vgxness 1.17.2 → 1.18.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/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/sqlite/migrations/018_skill_system_v2.sql +112 -0
- 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-status-service.js +4 -1
- package/package.json +1 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
ALTER TABLE skill_versions ADD COLUMN lifecycle_label TEXT;
|
|
2
|
+
ALTER TABLE skill_versions ADD COLUMN draft_parent_version_id TEXT REFERENCES skill_versions(id) ON DELETE SET NULL;
|
|
3
|
+
ALTER TABLE skill_versions ADD COLUMN draft_revision INTEGER;
|
|
4
|
+
ALTER TABLE skill_versions ADD COLUMN governance_json TEXT;
|
|
5
|
+
ALTER TABLE skill_versions ADD COLUMN published_at TEXT;
|
|
6
|
+
ALTER TABLE skill_versions ADD COLUMN published_by TEXT;
|
|
7
|
+
ALTER TABLE skill_versions ADD COLUMN deprecated_at TEXT;
|
|
8
|
+
ALTER TABLE skill_versions ADD COLUMN archived_at TEXT;
|
|
9
|
+
|
|
10
|
+
ALTER TABLE skill_usage_records ADD COLUMN workflow TEXT;
|
|
11
|
+
ALTER TABLE skill_usage_records ADD COLUMN phase TEXT;
|
|
12
|
+
ALTER TABLE skill_usage_records ADD COLUMN provider_adapter TEXT;
|
|
13
|
+
ALTER TABLE skill_usage_records ADD COLUMN agent_id TEXT;
|
|
14
|
+
ALTER TABLE skill_usage_records ADD COLUMN intent_json TEXT;
|
|
15
|
+
ALTER TABLE skill_usage_records ADD COLUMN evidence_json TEXT;
|
|
16
|
+
|
|
17
|
+
ALTER TABLE skill_improvement_proposals ADD COLUMN title TEXT;
|
|
18
|
+
ALTER TABLE skill_improvement_proposals ADD COLUMN problem TEXT;
|
|
19
|
+
ALTER TABLE skill_improvement_proposals ADD COLUMN suggested_change TEXT;
|
|
20
|
+
ALTER TABLE skill_improvement_proposals ADD COLUMN source_run_id TEXT;
|
|
21
|
+
ALTER TABLE skill_improvement_proposals ADD COLUMN source_agent_id TEXT;
|
|
22
|
+
ALTER TABLE skill_improvement_proposals ADD COLUMN evidence_json TEXT;
|
|
23
|
+
|
|
24
|
+
ALTER TABLE skill_evaluation_results ADD COLUMN request_id TEXT REFERENCES skill_evaluation_requests(id) ON DELETE SET NULL;
|
|
25
|
+
ALTER TABLE skill_evaluation_results ADD COLUMN score REAL;
|
|
26
|
+
ALTER TABLE skill_evaluation_results ADD COLUMN passed INTEGER CHECK (passed IN (0, 1));
|
|
27
|
+
ALTER TABLE skill_evaluation_results ADD COLUMN evidence_json TEXT;
|
|
28
|
+
ALTER TABLE skill_evaluation_results ADD COLUMN risk_json TEXT;
|
|
29
|
+
ALTER TABLE skill_evaluation_results ADD COLUMN preflight_json TEXT;
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS skill_activation_records (
|
|
32
|
+
id TEXT PRIMARY KEY,
|
|
33
|
+
project TEXT NOT NULL,
|
|
34
|
+
scope TEXT NOT NULL CHECK (scope IN ('project', 'personal')),
|
|
35
|
+
skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
|
|
36
|
+
version_id TEXT REFERENCES skill_versions(id) ON DELETE SET NULL,
|
|
37
|
+
run_id TEXT,
|
|
38
|
+
agent_id TEXT,
|
|
39
|
+
workflow TEXT,
|
|
40
|
+
phase TEXT,
|
|
41
|
+
provider_adapter TEXT,
|
|
42
|
+
intent_json TEXT,
|
|
43
|
+
reason TEXT,
|
|
44
|
+
payload_metadata_json TEXT,
|
|
45
|
+
created_at TEXT NOT NULL
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE TABLE IF NOT EXISTS skill_evaluation_requests (
|
|
49
|
+
id TEXT PRIMARY KEY,
|
|
50
|
+
project TEXT NOT NULL,
|
|
51
|
+
scope TEXT NOT NULL CHECK (scope IN ('project', 'personal')),
|
|
52
|
+
skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
|
|
53
|
+
version_id TEXT REFERENCES skill_versions(id) ON DELETE SET NULL,
|
|
54
|
+
scenario_id TEXT REFERENCES skill_evaluation_scenarios(id) ON DELETE SET NULL,
|
|
55
|
+
proposal_id TEXT REFERENCES skill_improvement_proposals(id) ON DELETE SET NULL,
|
|
56
|
+
status TEXT NOT NULL CHECK (status IN ('requested', 'planned', 'running', 'completed', 'failed', 'cancelled', 'skipped')),
|
|
57
|
+
requested_by TEXT NOT NULL,
|
|
58
|
+
run_id TEXT,
|
|
59
|
+
agent_id TEXT,
|
|
60
|
+
workflow TEXT,
|
|
61
|
+
phase TEXT,
|
|
62
|
+
provider_adapter TEXT,
|
|
63
|
+
risk_json TEXT,
|
|
64
|
+
preflight_json TEXT,
|
|
65
|
+
created_at TEXT NOT NULL,
|
|
66
|
+
updated_at TEXT NOT NULL
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE TABLE IF NOT EXISTS skill_lifecycle_events (
|
|
70
|
+
id TEXT PRIMARY KEY,
|
|
71
|
+
project TEXT NOT NULL,
|
|
72
|
+
scope TEXT NOT NULL CHECK (scope IN ('project', 'personal')),
|
|
73
|
+
skill_id TEXT NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
|
|
74
|
+
version_id TEXT REFERENCES skill_versions(id) ON DELETE SET NULL,
|
|
75
|
+
event_type TEXT NOT NULL,
|
|
76
|
+
actor_type TEXT,
|
|
77
|
+
actor_id TEXT,
|
|
78
|
+
run_id TEXT,
|
|
79
|
+
agent_id TEXT,
|
|
80
|
+
reason TEXT,
|
|
81
|
+
before_json TEXT,
|
|
82
|
+
after_json TEXT,
|
|
83
|
+
evidence_json TEXT,
|
|
84
|
+
created_at TEXT NOT NULL
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
CREATE INDEX IF NOT EXISTS skill_versions_status_idx
|
|
88
|
+
ON skill_versions(status, created_at DESC, id ASC);
|
|
89
|
+
|
|
90
|
+
CREATE INDEX IF NOT EXISTS skill_versions_lifecycle_label_idx
|
|
91
|
+
ON skill_versions(lifecycle_label, created_at DESC, id ASC);
|
|
92
|
+
|
|
93
|
+
CREATE INDEX IF NOT EXISTS skill_activation_records_context_idx
|
|
94
|
+
ON skill_activation_records(project, scope, skill_id, created_at DESC);
|
|
95
|
+
|
|
96
|
+
CREATE INDEX IF NOT EXISTS skill_activation_records_run_agent_idx
|
|
97
|
+
ON skill_activation_records(run_id, agent_id, created_at DESC);
|
|
98
|
+
|
|
99
|
+
CREATE INDEX IF NOT EXISTS skill_usage_records_v2_context_idx
|
|
100
|
+
ON skill_usage_records(skill_id, version_id, provider_adapter, created_at DESC);
|
|
101
|
+
|
|
102
|
+
CREATE INDEX IF NOT EXISTS skill_improvement_proposals_v2_context_idx
|
|
103
|
+
ON skill_improvement_proposals(skill_id, status, source_run_id, source_agent_id, created_at DESC);
|
|
104
|
+
|
|
105
|
+
CREATE INDEX IF NOT EXISTS skill_evaluation_requests_context_idx
|
|
106
|
+
ON skill_evaluation_requests(project, scope, skill_id, status, created_at DESC);
|
|
107
|
+
|
|
108
|
+
CREATE INDEX IF NOT EXISTS skill_evaluation_requests_run_agent_idx
|
|
109
|
+
ON skill_evaluation_requests(run_id, agent_id, created_at DESC);
|
|
110
|
+
|
|
111
|
+
CREATE INDEX IF NOT EXISTS skill_lifecycle_events_context_idx
|
|
112
|
+
ON skill_lifecycle_events(project, scope, skill_id, version_id, created_at DESC);
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
export class SkillEvaluationRequestRepository {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
create(input) {
|
|
8
|
+
const validation = validateCreateInput(input);
|
|
9
|
+
if (!validation.ok)
|
|
10
|
+
return validation;
|
|
11
|
+
const result = this.db.transaction(() => {
|
|
12
|
+
assertSkillInScope(this.db, input.skillId, input.project, input.scope);
|
|
13
|
+
if (input.versionId !== undefined)
|
|
14
|
+
assertVersionBelongsToSkill(this.db, input.versionId, input.skillId);
|
|
15
|
+
if (input.scenarioId !== undefined)
|
|
16
|
+
assertScenarioBelongsToSkill(this.db, input.scenarioId, input.skillId);
|
|
17
|
+
if (input.proposalId !== undefined)
|
|
18
|
+
assertProposalBelongsToSkill(this.db, input.proposalId, input.skillId);
|
|
19
|
+
const now = new Date().toISOString();
|
|
20
|
+
const id = randomUUID();
|
|
21
|
+
this.db.connection
|
|
22
|
+
.prepare(`
|
|
23
|
+
INSERT INTO skill_evaluation_requests(
|
|
24
|
+
id, project, scope, skill_id, version_id, scenario_id, proposal_id,
|
|
25
|
+
status, requested_by, run_id, agent_id, workflow, phase, provider_adapter,
|
|
26
|
+
risk_json, preflight_json, created_at, updated_at
|
|
27
|
+
) VALUES (
|
|
28
|
+
@id, @project, @scope, @skillId, @versionId, @scenarioId, @proposalId,
|
|
29
|
+
'requested', @requestedBy, @runId, @agentId, @workflow, @phase, @providerAdapter,
|
|
30
|
+
@riskJson, @preflightJson, @createdAt, @updatedAt
|
|
31
|
+
)
|
|
32
|
+
`)
|
|
33
|
+
.run({
|
|
34
|
+
id,
|
|
35
|
+
project: input.project,
|
|
36
|
+
scope: input.scope,
|
|
37
|
+
skillId: input.skillId,
|
|
38
|
+
versionId: input.versionId ?? null,
|
|
39
|
+
scenarioId: input.scenarioId ?? null,
|
|
40
|
+
proposalId: input.proposalId ?? null,
|
|
41
|
+
requestedBy: input.requestedBy.trim(),
|
|
42
|
+
runId: input.runId ?? null,
|
|
43
|
+
agentId: input.agentId ?? null,
|
|
44
|
+
workflow: input.workflow ?? null,
|
|
45
|
+
phase: input.phase ?? null,
|
|
46
|
+
providerAdapter: input.providerAdapter ?? null,
|
|
47
|
+
riskJson: input.risk === undefined ? null : JSON.stringify(input.risk),
|
|
48
|
+
preflightJson: input.preflight === undefined ? null : JSON.stringify(input.preflight),
|
|
49
|
+
createdAt: now,
|
|
50
|
+
updatedAt: now,
|
|
51
|
+
});
|
|
52
|
+
const read = this.getById(id);
|
|
53
|
+
if (!read.ok)
|
|
54
|
+
throw new Error(read.error.message);
|
|
55
|
+
return read.value;
|
|
56
|
+
});
|
|
57
|
+
if (!result.ok && result.error.cause instanceof SkillEvaluationRequestValidationError)
|
|
58
|
+
return validationFailure(result.error.cause.message);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
list(filters) {
|
|
62
|
+
try {
|
|
63
|
+
const where = ['project=@project', 'scope=@scope'];
|
|
64
|
+
const parameters = { project: filters.project, scope: filters.scope, limit: filters.limit ?? 25 };
|
|
65
|
+
addOptionalFilter(where, parameters, 'skill_id', 'skillId', filters.skillId);
|
|
66
|
+
addOptionalFilter(where, parameters, 'version_id', 'versionId', filters.versionId);
|
|
67
|
+
addOptionalFilter(where, parameters, 'scenario_id', 'scenarioId', filters.scenarioId);
|
|
68
|
+
addOptionalFilter(where, parameters, 'proposal_id', 'proposalId', filters.proposalId);
|
|
69
|
+
addOptionalFilter(where, parameters, 'status', 'status', filters.status);
|
|
70
|
+
addOptionalFilter(where, parameters, 'run_id', 'runId', filters.runId);
|
|
71
|
+
addOptionalFilter(where, parameters, 'agent_id', 'agentId', filters.agentId);
|
|
72
|
+
const rows = this.db.connection
|
|
73
|
+
.prepare(`
|
|
74
|
+
SELECT * FROM skill_evaluation_requests
|
|
75
|
+
WHERE ${where.join(' AND ')}
|
|
76
|
+
ORDER BY created_at DESC, rowid DESC
|
|
77
|
+
LIMIT @limit
|
|
78
|
+
`)
|
|
79
|
+
.all(parameters);
|
|
80
|
+
return ok(rows.map(mapRequest));
|
|
81
|
+
}
|
|
82
|
+
catch (cause) {
|
|
83
|
+
return fail('Failed to list skill evaluation requests', cause);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
getById(id) {
|
|
87
|
+
try {
|
|
88
|
+
const row = this.db.connection.prepare('SELECT * FROM skill_evaluation_requests WHERE id=?').get(id);
|
|
89
|
+
return row ? ok(mapRequest(row)) : missing(`Skill evaluation request not found: ${id}`);
|
|
90
|
+
}
|
|
91
|
+
catch (cause) {
|
|
92
|
+
return fail('Failed to read skill evaluation request', cause);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function validateCreateInput(input) {
|
|
97
|
+
if (!input.project.trim())
|
|
98
|
+
return validationFailure('Project is required');
|
|
99
|
+
if (input.scope !== 'project' && input.scope !== 'personal')
|
|
100
|
+
return validationFailure('Scope must be project or personal');
|
|
101
|
+
if (!input.skillId.trim())
|
|
102
|
+
return validationFailure('Skill id is required');
|
|
103
|
+
if (!input.requestedBy.trim())
|
|
104
|
+
return validationFailure('Skill evaluation requester is required');
|
|
105
|
+
return ok(undefined);
|
|
106
|
+
}
|
|
107
|
+
function addOptionalFilter(where, parameters, column, key, value) {
|
|
108
|
+
if (value === undefined)
|
|
109
|
+
return;
|
|
110
|
+
where.push(`${column}=@${key}`);
|
|
111
|
+
parameters[key] = value;
|
|
112
|
+
}
|
|
113
|
+
function assertSkillInScope(db, skillId, project, scope) {
|
|
114
|
+
const row = db.connection.prepare('SELECT project, scope FROM skills WHERE id=?').get(skillId);
|
|
115
|
+
if (row === undefined)
|
|
116
|
+
throw new SkillEvaluationRequestValidationError(`Skill not found: ${skillId}`);
|
|
117
|
+
if (row.project !== project || row.scope !== scope)
|
|
118
|
+
throw new SkillEvaluationRequestValidationError('Skill does not belong to the requested project/scope');
|
|
119
|
+
}
|
|
120
|
+
function assertVersionBelongsToSkill(db, versionId, skillId) {
|
|
121
|
+
const row = db.connection.prepare('SELECT skill_id FROM skill_versions WHERE id=?').get(versionId);
|
|
122
|
+
if (row === undefined)
|
|
123
|
+
throw new SkillEvaluationRequestValidationError(`Skill version not found: ${versionId}`);
|
|
124
|
+
if (row.skill_id !== skillId)
|
|
125
|
+
throw new SkillEvaluationRequestValidationError('Skill version must belong to the evaluated skill');
|
|
126
|
+
}
|
|
127
|
+
function assertScenarioBelongsToSkill(db, scenarioId, skillId) {
|
|
128
|
+
const row = db.connection.prepare('SELECT skill_id FROM skill_evaluation_scenarios WHERE id=?').get(scenarioId);
|
|
129
|
+
if (row === undefined)
|
|
130
|
+
throw new SkillEvaluationRequestValidationError(`Skill evaluation scenario not found: ${scenarioId}`);
|
|
131
|
+
if (row.skill_id !== skillId)
|
|
132
|
+
throw new SkillEvaluationRequestValidationError('Skill evaluation scenario must belong to the evaluated skill');
|
|
133
|
+
}
|
|
134
|
+
function assertProposalBelongsToSkill(db, proposalId, skillId) {
|
|
135
|
+
const row = db.connection.prepare('SELECT skill_id FROM skill_improvement_proposals WHERE id=?').get(proposalId);
|
|
136
|
+
if (row === undefined)
|
|
137
|
+
throw new SkillEvaluationRequestValidationError(`Skill improvement proposal not found: ${proposalId}`);
|
|
138
|
+
if (row.skill_id !== skillId)
|
|
139
|
+
throw new SkillEvaluationRequestValidationError('Skill improvement proposal must belong to the evaluated skill');
|
|
140
|
+
}
|
|
141
|
+
function mapRequest(row) {
|
|
142
|
+
const request = {
|
|
143
|
+
id: row.id,
|
|
144
|
+
project: row.project,
|
|
145
|
+
scope: row.scope,
|
|
146
|
+
skillId: row.skill_id,
|
|
147
|
+
status: row.status,
|
|
148
|
+
requestedBy: row.requested_by,
|
|
149
|
+
createdAt: row.created_at,
|
|
150
|
+
updatedAt: row.updated_at,
|
|
151
|
+
};
|
|
152
|
+
if (row.version_id !== null)
|
|
153
|
+
request.versionId = row.version_id;
|
|
154
|
+
if (row.scenario_id !== null)
|
|
155
|
+
request.scenarioId = row.scenario_id;
|
|
156
|
+
if (row.proposal_id !== null)
|
|
157
|
+
request.proposalId = row.proposal_id;
|
|
158
|
+
if (row.run_id !== null)
|
|
159
|
+
request.runId = row.run_id;
|
|
160
|
+
if (row.agent_id !== null)
|
|
161
|
+
request.agentId = row.agent_id;
|
|
162
|
+
if (row.workflow !== null)
|
|
163
|
+
request.workflow = row.workflow;
|
|
164
|
+
if (row.phase !== null)
|
|
165
|
+
request.phase = row.phase;
|
|
166
|
+
if (row.provider_adapter !== null)
|
|
167
|
+
request.providerAdapter = row.provider_adapter;
|
|
168
|
+
if (row.risk_json !== null)
|
|
169
|
+
request.risk = JSON.parse(row.risk_json);
|
|
170
|
+
if (row.preflight_json !== null)
|
|
171
|
+
request.preflight = JSON.parse(row.preflight_json);
|
|
172
|
+
return request;
|
|
173
|
+
}
|
|
174
|
+
function ok(value) {
|
|
175
|
+
return { ok: true, value };
|
|
176
|
+
}
|
|
177
|
+
function missing(message) {
|
|
178
|
+
return { ok: false, error: { code: 'not_found', message } };
|
|
179
|
+
}
|
|
180
|
+
function validationFailure(message) {
|
|
181
|
+
return { ok: false, error: { code: 'validation_failed', message } };
|
|
182
|
+
}
|
|
183
|
+
function fail(message, cause) {
|
|
184
|
+
const error = { code: 'validation_failed', message };
|
|
185
|
+
if (cause !== undefined)
|
|
186
|
+
error.cause = cause;
|
|
187
|
+
return { ok: false, error };
|
|
188
|
+
}
|
|
189
|
+
class SkillEvaluationRequestValidationError extends Error {
|
|
190
|
+
}
|
|
@@ -22,11 +22,13 @@ export class SkillImprovementProposalRepository {
|
|
|
22
22
|
id, skill_id, base_version_id, proposed_version,
|
|
23
23
|
proposed_source_kind, proposed_source_path, proposed_source_url, proposed_source_inline_metadata_json,
|
|
24
24
|
proposed_compatibility_json, rationale, source_signal_json, diff_summary_json,
|
|
25
|
+
title, problem, suggested_change, source_run_id, source_agent_id, evidence_json,
|
|
25
26
|
status, created_at, updated_at
|
|
26
27
|
) VALUES (
|
|
27
28
|
@id, @skillId, @baseVersionId, @proposedVersion,
|
|
28
29
|
@sourceKind, @sourcePath, @sourceUrl, @sourceInlineMetadataJson,
|
|
29
30
|
@compatibilityJson, @rationale, @sourceSignalJson, @diffSummaryJson,
|
|
31
|
+
@title, @problem, @suggestedChange, @sourceRunId, @sourceAgentId, @evidenceJson,
|
|
30
32
|
'draft', @createdAt, @updatedAt
|
|
31
33
|
)
|
|
32
34
|
`)
|
|
@@ -43,6 +45,12 @@ export class SkillImprovementProposalRepository {
|
|
|
43
45
|
rationale: input.rationale,
|
|
44
46
|
sourceSignalJson: JSON.stringify(input.sourceSignal ?? {}),
|
|
45
47
|
diffSummaryJson: JSON.stringify(diffSummary),
|
|
48
|
+
title: input.title ?? null,
|
|
49
|
+
problem: input.problem ?? null,
|
|
50
|
+
suggestedChange: input.suggestedChange ?? null,
|
|
51
|
+
sourceRunId: input.sourceRunId ?? null,
|
|
52
|
+
sourceAgentId: input.sourceAgentId ?? null,
|
|
53
|
+
evidenceJson: input.evidence === undefined ? null : JSON.stringify(input.evidence),
|
|
46
54
|
createdAt: now,
|
|
47
55
|
updatedAt: now,
|
|
48
56
|
});
|
|
@@ -59,19 +67,44 @@ export class SkillImprovementProposalRepository {
|
|
|
59
67
|
try {
|
|
60
68
|
const where = [];
|
|
61
69
|
const parameters = {};
|
|
70
|
+
if (filters.project !== undefined) {
|
|
71
|
+
where.push('s.project=@project');
|
|
72
|
+
parameters.project = filters.project;
|
|
73
|
+
}
|
|
74
|
+
if (filters.scope !== undefined) {
|
|
75
|
+
where.push('s.scope=@scope');
|
|
76
|
+
parameters.scope = filters.scope;
|
|
77
|
+
}
|
|
62
78
|
if (filters.skillId !== undefined) {
|
|
63
|
-
where.push('skill_id=@skillId');
|
|
79
|
+
where.push('p.skill_id=@skillId');
|
|
64
80
|
parameters.skillId = filters.skillId;
|
|
65
81
|
}
|
|
82
|
+
if (filters.baseVersionId !== undefined) {
|
|
83
|
+
where.push('p.base_version_id=@baseVersionId');
|
|
84
|
+
parameters.baseVersionId = filters.baseVersionId;
|
|
85
|
+
}
|
|
66
86
|
if (filters.status !== undefined) {
|
|
67
|
-
where.push('status=@status');
|
|
87
|
+
where.push('p.status=@status');
|
|
68
88
|
parameters.status = filters.status;
|
|
69
89
|
}
|
|
90
|
+
if (filters.runId !== undefined) {
|
|
91
|
+
where.push('p.source_run_id=@runId');
|
|
92
|
+
parameters.runId = filters.runId;
|
|
93
|
+
}
|
|
94
|
+
if (filters.agentId !== undefined) {
|
|
95
|
+
where.push('p.source_agent_id=@agentId');
|
|
96
|
+
parameters.agentId = filters.agentId;
|
|
97
|
+
}
|
|
98
|
+
const limitClause = filters.limit === undefined ? '' : 'LIMIT @limit';
|
|
99
|
+
if (filters.limit !== undefined)
|
|
100
|
+
parameters.limit = filters.limit;
|
|
70
101
|
const rows = this.db.connection
|
|
71
102
|
.prepare(`
|
|
72
|
-
SELECT
|
|
103
|
+
SELECT p.* FROM skill_improvement_proposals p
|
|
104
|
+
JOIN skills s ON s.id = p.skill_id
|
|
73
105
|
${where.length ? `WHERE ${where.join(' AND ')}` : ''}
|
|
74
|
-
ORDER BY created_at DESC, id ASC
|
|
106
|
+
ORDER BY p.created_at DESC, p.id ASC
|
|
107
|
+
${limitClause}
|
|
75
108
|
`)
|
|
76
109
|
.all(parameters);
|
|
77
110
|
return ok(rows.map(mapProposal));
|
|
@@ -211,6 +244,12 @@ function validateCreate(input) {
|
|
|
211
244
|
return validationFailure('Proposed version is required');
|
|
212
245
|
if (!input.rationale.trim())
|
|
213
246
|
return validationFailure('Proposal rationale is required');
|
|
247
|
+
if (input.title !== undefined && !input.title.trim())
|
|
248
|
+
return validationFailure('Proposal title must not be empty');
|
|
249
|
+
if (input.problem !== undefined && !input.problem.trim())
|
|
250
|
+
return validationFailure('Proposal problem must not be empty');
|
|
251
|
+
if (input.suggestedChange !== undefined && !input.suggestedChange.trim())
|
|
252
|
+
return validationFailure('Proposal suggested change must not be empty');
|
|
214
253
|
if (input.proposedSource.kind === 'path' && !input.proposedSource.path?.trim())
|
|
215
254
|
return validationFailure('Path proposal sources require proposedSource.path');
|
|
216
255
|
if (input.proposedSource.kind === 'url' && !input.proposedSource.url?.trim())
|
|
@@ -265,6 +304,18 @@ function mapProposal(row) {
|
|
|
265
304
|
createdAt: row.created_at,
|
|
266
305
|
updatedAt: row.updated_at,
|
|
267
306
|
};
|
|
307
|
+
if (row.title !== null && row.title !== undefined)
|
|
308
|
+
proposal.title = row.title;
|
|
309
|
+
if (row.problem !== null && row.problem !== undefined)
|
|
310
|
+
proposal.problem = row.problem;
|
|
311
|
+
if (row.suggested_change !== null && row.suggested_change !== undefined)
|
|
312
|
+
proposal.suggestedChange = row.suggested_change;
|
|
313
|
+
if (row.source_run_id !== null && row.source_run_id !== undefined)
|
|
314
|
+
proposal.sourceRunId = row.source_run_id;
|
|
315
|
+
if (row.source_agent_id !== null && row.source_agent_id !== undefined)
|
|
316
|
+
proposal.sourceAgentId = row.source_agent_id;
|
|
317
|
+
if (row.evidence_json !== null && row.evidence_json !== undefined)
|
|
318
|
+
proposal.evidence = JSON.parse(row.evidence_json);
|
|
268
319
|
if (row.submitted_at !== null)
|
|
269
320
|
proposal.submittedAt = row.submitted_at;
|
|
270
321
|
if (row.submitted_by !== null)
|