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.
Files changed (121) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +110 -0
  3. package/dist/agents/agent-activation-service.js +144 -0
  4. package/dist/agents/agent-registry-service.js +46 -0
  5. package/dist/agents/agent-resolver.js +249 -0
  6. package/dist/agents/agent-seed-service.js +146 -0
  7. package/dist/agents/manager-profile-overlay-service.js +34 -0
  8. package/dist/agents/profile-model-routing.js +26 -0
  9. package/dist/agents/renderers/claude-renderer.js +98 -0
  10. package/dist/agents/renderers/index.js +16 -0
  11. package/dist/agents/renderers/json-renderer.js +87 -0
  12. package/dist/agents/renderers/opencode-renderer.js +100 -0
  13. package/dist/agents/renderers/provider-adapter.js +6 -0
  14. package/dist/agents/repositories/agents.js +185 -0
  15. package/dist/agents/repositories/manager-profile-overlays.js +81 -0
  16. package/dist/agents/schema.js +1 -0
  17. package/dist/cli/dashboard-operational-read-models.js +153 -0
  18. package/dist/cli/dashboard-renderer.js +109 -0
  19. package/dist/cli/dashboard-screen-renderers.js +332 -0
  20. package/dist/cli/dashboard-tui-read-model.js +71 -0
  21. package/dist/cli/dashboard-tui-state.js +218 -0
  22. package/dist/cli/dispatcher.js +2880 -0
  23. package/dist/cli/index.js +27 -0
  24. package/dist/cli/interactive-dashboard.js +29 -0
  25. package/dist/cli/mcp-start-path.js +21 -0
  26. package/dist/cli/setup-status-renderer.js +29 -0
  27. package/dist/cli/setup-wizard-read-model.js +56 -0
  28. package/dist/cli/setup-wizard-renderer.js +148 -0
  29. package/dist/cli/setup-wizard-state.js +82 -0
  30. package/dist/cli/tui-render-helpers.js +192 -0
  31. package/dist/export/redaction.js +71 -0
  32. package/dist/harness/tools/agents.js +245 -0
  33. package/dist/harness/tools/memory.js +29 -0
  34. package/dist/mcp/client-install-opencode-contract.js +227 -0
  35. package/dist/mcp/client-install-opencode.js +194 -0
  36. package/dist/mcp/client-setup-preview.js +38 -0
  37. package/dist/mcp/control-plane.js +175 -0
  38. package/dist/mcp/doctor.js +193 -0
  39. package/dist/mcp/index.js +10 -0
  40. package/dist/mcp/opencode-default-agent-config.js +156 -0
  41. package/dist/mcp/opencode-visibility.js +102 -0
  42. package/dist/mcp/schema.js +234 -0
  43. package/dist/mcp/stdio-server.js +56 -0
  44. package/dist/mcp/validation.js +761 -0
  45. package/dist/memory/import/dry-run-planner.js +58 -0
  46. package/dist/memory/import/index.js +3 -0
  47. package/dist/memory/import/observation-writer.js +220 -0
  48. package/dist/memory/import/package.js +178 -0
  49. package/dist/memory/memory-service.js +126 -0
  50. package/dist/memory/repositories/artifacts.js +41 -0
  51. package/dist/memory/repositories/observations.js +133 -0
  52. package/dist/memory/repositories/sessions.js +105 -0
  53. package/dist/memory/repositories/traces.js +58 -0
  54. package/dist/memory/schema.js +1 -0
  55. package/dist/memory/search.js +11 -0
  56. package/dist/memory/sqlite/database.js +97 -0
  57. package/dist/memory/sqlite/migrations/001_initial.sql +128 -0
  58. package/dist/memory/sqlite/migrations/002_observation_revisions.sql +14 -0
  59. package/dist/memory/sqlite/migrations/003_agent_registry.sql +26 -0
  60. package/dist/memory/sqlite/migrations/004_run_runtime.sql +62 -0
  61. package/dist/memory/sqlite/migrations/005_run_approvals.sql +20 -0
  62. package/dist/memory/sqlite/migrations/006_run_operation_attempts.sql +32 -0
  63. package/dist/memory/sqlite/migrations/007_abandoned_operation_attempts.sql +46 -0
  64. package/dist/memory/sqlite/migrations/008_run_execution_plan_events.sql +105 -0
  65. package/dist/memory/sqlite/migrations/009_multiple_operation_attempts.sql +73 -0
  66. package/dist/memory/sqlite/migrations/010_skill_registry.sql +66 -0
  67. package/dist/memory/sqlite/migrations/011_skill_usage_resolution_outcomes.sql +21 -0
  68. package/dist/memory/sqlite/migrations/012_skill_improvement_proposals.sql +37 -0
  69. package/dist/memory/sqlite/migrations/013_skill_evaluation_scenarios.sql +43 -0
  70. package/dist/memory/sqlite/migrations/014_manager_profile_overlays.sql +14 -0
  71. package/dist/memory/storage-paths.js +72 -0
  72. package/dist/orchestrator/natural-language-planner.js +191 -0
  73. package/dist/orchestrator/schema.js +1 -0
  74. package/dist/permissions/index.js +2 -0
  75. package/dist/permissions/policy-evaluator.js +109 -0
  76. package/dist/permissions/schema.js +1 -0
  77. package/dist/providers/opencode/injection-preview.js +134 -0
  78. package/dist/providers/opencode/manager-payload.js +129 -0
  79. package/dist/runs/execution-planning.js +117 -0
  80. package/dist/runs/operation-execution.js +1 -0
  81. package/dist/runs/operation-retry.js +124 -0
  82. package/dist/runs/repositories/runs.js +611 -0
  83. package/dist/runs/run-insights.js +145 -0
  84. package/dist/runs/run-service.js +713 -0
  85. package/dist/runs/run-snapshot-export-service.js +31 -0
  86. package/dist/runs/sandbox-process-execution.js +218 -0
  87. package/dist/runs/sandbox-worktree-planning.js +59 -0
  88. package/dist/runs/schema.js +1 -0
  89. package/dist/sdd/artifact-portability-service.js +118 -0
  90. package/dist/sdd/schema.js +17 -0
  91. package/dist/sdd/sdd-workflow-service.js +217 -0
  92. package/dist/setup/backup-rollback-service.js +76 -0
  93. package/dist/setup/index.js +3 -0
  94. package/dist/setup/providers/antigravity-setup-adapter.js +18 -0
  95. package/dist/setup/providers/claude-setup-adapter.js +30 -0
  96. package/dist/setup/providers/custom-setup-adapter.js +18 -0
  97. package/dist/setup/providers/index.js +6 -0
  98. package/dist/setup/providers/opencode-setup-adapter.js +104 -0
  99. package/dist/setup/providers/provider-setup-adapter.js +15 -0
  100. package/dist/setup/providers/provider-setup-registry.js +11 -0
  101. package/dist/setup/schema.js +1 -0
  102. package/dist/setup/setup-defaults.js +11 -0
  103. package/dist/setup/setup-lifecycle-service.js +175 -0
  104. package/dist/setup/setup-plan.js +105 -0
  105. package/dist/skills/repositories/skill-evaluation-scenarios.js +289 -0
  106. package/dist/skills/repositories/skill-improvement-proposals.js +288 -0
  107. package/dist/skills/repositories/skills.js +430 -0
  108. package/dist/skills/schema.js +1 -0
  109. package/dist/skills/skill-payload.js +94 -0
  110. package/dist/skills/skill-registry-service.js +92 -0
  111. package/dist/skills/skill-resolver.js +191 -0
  112. package/dist/workflows/command-allowlist-adapter.js +70 -0
  113. package/dist/workflows/schema.js +4 -0
  114. package/dist/workflows/workflow-executor.js +345 -0
  115. package/dist/workflows/workflow-registry.js +66 -0
  116. package/docs/architecture.md +698 -0
  117. package/docs/cli.md +741 -0
  118. package/docs/funcionamiento-del-sistema.md +868 -0
  119. package/docs/harness-gap-analysis.md +229 -0
  120. package/docs/prd.md +372 -0
  121. package/package.json +57 -0
@@ -0,0 +1,289 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export class SkillEvaluationScenarioRepository {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ createScenario(input) {
8
+ const validation = validateScenarioInput(input);
9
+ if (!validation.ok)
10
+ return validation;
11
+ const result = this.db.transaction(() => {
12
+ assertSkillExists(this.db, input.skillId);
13
+ if (input.versionId !== undefined)
14
+ assertVersionBelongsToSkill(this.db, input.versionId, input.skillId);
15
+ if (input.proposalId !== undefined)
16
+ assertProposalBelongsToSkill(this.db, input.proposalId, input.skillId);
17
+ const normalizedName = normalizeScenarioName(input.name);
18
+ const proposalContextKey = input.proposalId ?? '';
19
+ const versionContextKey = input.versionId ?? '';
20
+ const duplicate = this.db.connection.prepare(`
21
+ SELECT id FROM skill_evaluation_scenarios
22
+ WHERE skill_id=? AND normalized_name=? AND proposal_context_key=? AND version_context_key=?
23
+ `).get(input.skillId, normalizedName, proposalContextKey, versionContextKey);
24
+ if (duplicate !== undefined)
25
+ throw new SkillEvaluationScenarioValidationError(`Skill evaluation scenario already exists: ${normalizedName}`);
26
+ const now = new Date().toISOString();
27
+ const id = randomUUID();
28
+ this.db.connection.prepare(`
29
+ INSERT INTO skill_evaluation_scenarios(
30
+ id, skill_id, name, normalized_name, criteria_json,
31
+ proposal_id, proposal_context_key, version_id, version_context_key,
32
+ created_by, created_at, updated_at
33
+ ) VALUES (
34
+ @id, @skillId, @name, @normalizedName, @criteriaJson,
35
+ @proposalId, @proposalContextKey, @versionId, @versionContextKey,
36
+ @createdBy, @createdAt, @updatedAt
37
+ )
38
+ `).run({
39
+ id,
40
+ skillId: input.skillId,
41
+ name: input.name.trim(),
42
+ normalizedName,
43
+ criteriaJson: JSON.stringify(input.criteria),
44
+ proposalId: input.proposalId ?? null,
45
+ proposalContextKey,
46
+ versionId: input.versionId ?? null,
47
+ versionContextKey,
48
+ createdBy: input.createdBy.trim(),
49
+ createdAt: now,
50
+ updatedAt: now,
51
+ });
52
+ const read = this.getScenario(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 SkillEvaluationScenarioValidationError)
58
+ return validationFailure(result.error.cause.message);
59
+ return result;
60
+ }
61
+ listScenarios(filters) {
62
+ if (!filters.skillId.trim())
63
+ return validationFailure('Skill id is required');
64
+ try {
65
+ const where = ['skill_id=@skillId'];
66
+ const parameters = { skillId: filters.skillId };
67
+ if (filters.proposalId !== undefined) {
68
+ where.push('proposal_id=@proposalId');
69
+ parameters.proposalId = filters.proposalId;
70
+ }
71
+ if (filters.versionId !== undefined) {
72
+ where.push('version_id=@versionId');
73
+ parameters.versionId = filters.versionId;
74
+ }
75
+ const rows = this.db.connection.prepare(`
76
+ SELECT * FROM skill_evaluation_scenarios
77
+ WHERE ${where.join(' AND ')}
78
+ ORDER BY normalized_name ASC, created_at ASC, id ASC
79
+ `).all(parameters);
80
+ return ok(rows.map(mapScenario));
81
+ }
82
+ catch (cause) {
83
+ return fail('Failed to list skill evaluation scenarios', cause);
84
+ }
85
+ }
86
+ recordResult(input) {
87
+ const validation = validateResultInput(input);
88
+ if (!validation.ok)
89
+ return validation;
90
+ const result = this.db.transaction(() => {
91
+ const scenario = assertScenarioExists(this.db, input.scenarioId);
92
+ if (input.versionId !== undefined)
93
+ assertVersionBelongsToSkill(this.db, input.versionId, scenario.skill_id);
94
+ if (input.proposalId !== undefined)
95
+ assertProposalBelongsToSkill(this.db, input.proposalId, scenario.skill_id);
96
+ const now = new Date().toISOString();
97
+ const id = randomUUID();
98
+ this.db.connection.prepare(`
99
+ INSERT INTO skill_evaluation_results(
100
+ id, scenario_id, status, result_json, observed_behavior,
101
+ evaluator, proposal_id, version_id, notes, created_at
102
+ ) VALUES (
103
+ @id, @scenarioId, @status, @resultJson, @observedBehavior,
104
+ @evaluator, @proposalId, @versionId, @notes, @createdAt
105
+ )
106
+ `).run({
107
+ id,
108
+ scenarioId: input.scenarioId,
109
+ status: input.status,
110
+ resultJson: JSON.stringify(input.result),
111
+ observedBehavior: input.observedBehavior.trim(),
112
+ evaluator: input.evaluator.trim(),
113
+ proposalId: input.proposalId ?? null,
114
+ versionId: input.versionId ?? null,
115
+ notes: input.notes?.trim() ? input.notes.trim() : null,
116
+ createdAt: now,
117
+ });
118
+ const read = this.getResult(id);
119
+ if (!read.ok)
120
+ throw new Error(read.error.message);
121
+ return read.value;
122
+ });
123
+ if (!result.ok && result.error.cause instanceof SkillEvaluationScenarioValidationError)
124
+ return validationFailure(result.error.cause.message);
125
+ return result;
126
+ }
127
+ listResults(filters = {}) {
128
+ try {
129
+ const where = [];
130
+ const parameters = {};
131
+ if (filters.scenarioId !== undefined) {
132
+ where.push('results.scenario_id=@scenarioId');
133
+ parameters.scenarioId = filters.scenarioId;
134
+ }
135
+ if (filters.skillId !== undefined) {
136
+ where.push('scenarios.skill_id=@skillId');
137
+ parameters.skillId = filters.skillId;
138
+ }
139
+ if (filters.proposalId !== undefined) {
140
+ where.push('results.proposal_id=@proposalId');
141
+ parameters.proposalId = filters.proposalId;
142
+ }
143
+ if (filters.versionId !== undefined) {
144
+ where.push('results.version_id=@versionId');
145
+ parameters.versionId = filters.versionId;
146
+ }
147
+ if (filters.evaluator !== undefined) {
148
+ where.push('results.evaluator=@evaluator');
149
+ parameters.evaluator = filters.evaluator;
150
+ }
151
+ if (filters.status !== undefined) {
152
+ where.push('results.status=@status');
153
+ parameters.status = filters.status;
154
+ }
155
+ const rows = this.db.connection.prepare(`
156
+ SELECT results.* FROM skill_evaluation_results results
157
+ JOIN skill_evaluation_scenarios scenarios ON scenarios.id = results.scenario_id
158
+ ${where.length ? `WHERE ${where.join(' AND ')}` : ''}
159
+ ORDER BY results.created_at ASC, results.rowid ASC
160
+ `).all(parameters);
161
+ return ok(rows.map(mapResult));
162
+ }
163
+ catch (cause) {
164
+ return fail('Failed to list skill evaluation results', cause);
165
+ }
166
+ }
167
+ getScenario(id) {
168
+ try {
169
+ const row = this.db.connection.prepare('SELECT * FROM skill_evaluation_scenarios WHERE id=?').get(id);
170
+ return row ? ok(mapScenario(row)) : missing(`Skill evaluation scenario not found: ${id}`);
171
+ }
172
+ catch (cause) {
173
+ return fail('Failed to read skill evaluation scenario', cause);
174
+ }
175
+ }
176
+ getResult(id) {
177
+ try {
178
+ const row = this.db.connection.prepare('SELECT * FROM skill_evaluation_results WHERE id=?').get(id);
179
+ return row ? ok(mapResult(row)) : missing(`Skill evaluation result not found: ${id}`);
180
+ }
181
+ catch (cause) {
182
+ return fail('Failed to read skill evaluation result', cause);
183
+ }
184
+ }
185
+ }
186
+ function validateScenarioInput(input) {
187
+ if (!input.skillId.trim())
188
+ return validationFailure('Skill id is required');
189
+ if (!input.name.trim())
190
+ return validationFailure('Skill evaluation scenario name is required');
191
+ if (!isJsonObject(input.criteria))
192
+ return validationFailure('Skill evaluation scenario criteria must be a JSON object');
193
+ if (!input.createdBy.trim())
194
+ return validationFailure('Skill evaluation scenario creator is required');
195
+ return ok(undefined);
196
+ }
197
+ function validateResultInput(input) {
198
+ if (!input.scenarioId.trim())
199
+ return validationFailure('Skill evaluation scenario id is required');
200
+ if (!isEvaluationResultStatus(input.status))
201
+ return validationFailure(`Invalid skill evaluation result status: ${input.status}`);
202
+ if (!isJsonObject(input.result))
203
+ return validationFailure('Skill evaluation result must be a JSON object');
204
+ if (!input.observedBehavior.trim())
205
+ return validationFailure('Skill evaluation observed behavior is required');
206
+ if (!input.evaluator.trim())
207
+ return validationFailure('Skill evaluation result evaluator is required');
208
+ return ok(undefined);
209
+ }
210
+ function normalizeScenarioName(name) {
211
+ return name.trim().toLowerCase().replace(/\s+/g, ' ');
212
+ }
213
+ function isJsonObject(value) {
214
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
215
+ }
216
+ function isEvaluationResultStatus(value) {
217
+ return value === 'passed' || value === 'failed' || value === 'needs-review' || value === 'not-applicable';
218
+ }
219
+ function assertScenarioExists(db, scenarioId) {
220
+ const row = db.connection.prepare('SELECT id, skill_id FROM skill_evaluation_scenarios WHERE id=?').get(scenarioId);
221
+ if (row === undefined)
222
+ throw new SkillEvaluationScenarioValidationError(`Skill evaluation scenario not found: ${scenarioId}`);
223
+ return row;
224
+ }
225
+ function assertSkillExists(db, skillId) {
226
+ const row = db.connection.prepare('SELECT 1 FROM skills WHERE id=?').get(skillId);
227
+ if (row === undefined)
228
+ throw new SkillEvaluationScenarioValidationError(`Skill not found: ${skillId}`);
229
+ }
230
+ function assertVersionBelongsToSkill(db, versionId, skillId) {
231
+ const row = db.connection.prepare('SELECT skill_id FROM skill_versions WHERE id=?').get(versionId);
232
+ if (row === undefined)
233
+ throw new SkillEvaluationScenarioValidationError(`Skill version not found: ${versionId}`);
234
+ if (row.skill_id !== skillId)
235
+ throw new SkillEvaluationScenarioValidationError('Skill version must belong to the evaluated skill');
236
+ }
237
+ function assertProposalBelongsToSkill(db, proposalId, skillId) {
238
+ const row = db.connection.prepare('SELECT skill_id FROM skill_improvement_proposals WHERE id=?').get(proposalId);
239
+ if (row === undefined)
240
+ throw new SkillEvaluationScenarioValidationError(`Skill improvement proposal not found: ${proposalId}`);
241
+ if (row.skill_id !== skillId)
242
+ throw new SkillEvaluationScenarioValidationError('Skill improvement proposal must belong to the evaluated skill');
243
+ }
244
+ function mapScenario(row) {
245
+ const scenario = {
246
+ id: row.id,
247
+ skillId: row.skill_id,
248
+ name: row.name,
249
+ normalizedName: row.normalized_name,
250
+ criteria: JSON.parse(row.criteria_json),
251
+ createdBy: row.created_by,
252
+ createdAt: row.created_at,
253
+ updatedAt: row.updated_at,
254
+ };
255
+ if (row.proposal_id !== null)
256
+ scenario.proposalId = row.proposal_id;
257
+ if (row.version_id !== null)
258
+ scenario.versionId = row.version_id;
259
+ return scenario;
260
+ }
261
+ function mapResult(row) {
262
+ const result = {
263
+ id: row.id,
264
+ scenarioId: row.scenario_id,
265
+ status: row.status,
266
+ result: JSON.parse(row.result_json),
267
+ observedBehavior: row.observed_behavior,
268
+ evaluator: row.evaluator,
269
+ createdAt: row.created_at,
270
+ };
271
+ if (row.proposal_id !== null)
272
+ result.proposalId = row.proposal_id;
273
+ if (row.version_id !== null)
274
+ result.versionId = row.version_id;
275
+ if (row.notes !== null)
276
+ result.notes = row.notes;
277
+ return result;
278
+ }
279
+ function ok(value) { return { ok: true, value }; }
280
+ function missing(message) { return { ok: false, error: { code: 'not_found', message } }; }
281
+ function validationFailure(message) { return { ok: false, error: { code: 'validation_failed', message } }; }
282
+ function fail(message, cause) {
283
+ const error = { code: 'validation_failed', message };
284
+ if (cause !== undefined)
285
+ error.cause = cause;
286
+ return { ok: false, error };
287
+ }
288
+ class SkillEvaluationScenarioValidationError extends Error {
289
+ }
@@ -0,0 +1,288 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { SkillRepository } from './skills.js';
3
+ export class SkillImprovementProposalRepository {
4
+ db;
5
+ constructor(db) {
6
+ this.db = db;
7
+ }
8
+ create(input) {
9
+ const validation = validateCreate(input);
10
+ if (!validation.ok)
11
+ return validation;
12
+ const result = this.db.transaction(() => {
13
+ const base = assertBaseVersion(this.db, input.skillId, input.baseVersionId);
14
+ assertProposedVersionIsNew(this.db, input.skillId, input.proposedVersion);
15
+ const now = new Date().toISOString();
16
+ const id = randomUUID();
17
+ const proposedCompatibility = input.proposedCompatibility ?? base.compatibility;
18
+ const diffSummary = buildDiffSummary(base, input.proposedVersion, input.proposedSource, proposedCompatibility);
19
+ this.db.connection.prepare(`
20
+ INSERT INTO skill_improvement_proposals(
21
+ id, skill_id, base_version_id, proposed_version,
22
+ proposed_source_kind, proposed_source_path, proposed_source_url, proposed_source_inline_metadata_json,
23
+ proposed_compatibility_json, rationale, source_signal_json, diff_summary_json,
24
+ status, created_at, updated_at
25
+ ) VALUES (
26
+ @id, @skillId, @baseVersionId, @proposedVersion,
27
+ @sourceKind, @sourcePath, @sourceUrl, @sourceInlineMetadataJson,
28
+ @compatibilityJson, @rationale, @sourceSignalJson, @diffSummaryJson,
29
+ 'draft', @createdAt, @updatedAt
30
+ )
31
+ `).run({
32
+ id,
33
+ skillId: input.skillId,
34
+ baseVersionId: input.baseVersionId,
35
+ proposedVersion: input.proposedVersion,
36
+ sourceKind: input.proposedSource.kind,
37
+ sourcePath: input.proposedSource.path ?? null,
38
+ sourceUrl: input.proposedSource.url ?? null,
39
+ sourceInlineMetadataJson: JSON.stringify(input.proposedSource.inlineMetadata ?? {}),
40
+ compatibilityJson: JSON.stringify(proposedCompatibility),
41
+ rationale: input.rationale,
42
+ sourceSignalJson: JSON.stringify(input.sourceSignal ?? {}),
43
+ diffSummaryJson: JSON.stringify(diffSummary),
44
+ createdAt: now,
45
+ updatedAt: now,
46
+ });
47
+ const read = this.getById(id);
48
+ if (!read.ok)
49
+ throw new Error(read.error.message);
50
+ return read.value;
51
+ });
52
+ if (!result.ok && result.error.cause instanceof SkillImprovementProposalValidationError)
53
+ return validationFailure(result.error.cause.message);
54
+ return result;
55
+ }
56
+ list(filters = {}) {
57
+ try {
58
+ const where = [];
59
+ const parameters = {};
60
+ if (filters.skillId !== undefined) {
61
+ where.push('skill_id=@skillId');
62
+ parameters.skillId = filters.skillId;
63
+ }
64
+ if (filters.status !== undefined) {
65
+ where.push('status=@status');
66
+ parameters.status = filters.status;
67
+ }
68
+ const rows = this.db.connection.prepare(`
69
+ SELECT * FROM skill_improvement_proposals
70
+ ${where.length ? `WHERE ${where.join(' AND ')}` : ''}
71
+ ORDER BY created_at DESC, id ASC
72
+ `).all(parameters);
73
+ return ok(rows.map(mapProposal));
74
+ }
75
+ catch (cause) {
76
+ return fail('Failed to list skill improvement proposals', cause);
77
+ }
78
+ }
79
+ getById(id) {
80
+ try {
81
+ const row = this.db.connection.prepare('SELECT * FROM skill_improvement_proposals WHERE id=?').get(id);
82
+ return row ? ok(mapProposal(row)) : missing(`Skill improvement proposal not found: ${id}`);
83
+ }
84
+ catch (cause) {
85
+ return fail('Failed to read skill improvement proposal', cause);
86
+ }
87
+ }
88
+ submitForApproval(input) {
89
+ return this.transition(input, 'draft', 'pending-approval', 'submitted_at', 'submitted_by');
90
+ }
91
+ approve(input) {
92
+ return this.resolve(input, 'approved');
93
+ }
94
+ reject(input) {
95
+ return this.resolve(input, 'rejected');
96
+ }
97
+ cancel(input) {
98
+ const proposal = this.getById(input.proposalId);
99
+ if (!proposal.ok)
100
+ return proposal;
101
+ if (proposal.value.status !== 'draft' && proposal.value.status !== 'pending-approval')
102
+ return validationFailure(`Cannot cancel proposal with status ${proposal.value.status}`);
103
+ return this.resolve(input, 'cancelled', proposal.value.status);
104
+ }
105
+ apply(input) {
106
+ if (!input.actor.trim())
107
+ return validationFailure('Proposal actor is required');
108
+ const result = this.db.transaction(() => {
109
+ const proposal = this.getById(input.proposalId);
110
+ if (!proposal.ok)
111
+ throw new SkillImprovementProposalValidationError(proposal.error.message);
112
+ if (proposal.value.status !== 'approved')
113
+ throw new SkillImprovementProposalValidationError(`Cannot apply proposal with status ${proposal.value.status}`);
114
+ assertProposedVersionIsNew(this.db, proposal.value.skillId, proposal.value.proposedVersion);
115
+ const skills = new SkillRepository(this.db);
116
+ const version = skills.addVersion({
117
+ skillId: proposal.value.skillId,
118
+ version: proposal.value.proposedVersion,
119
+ source: proposal.value.proposedSource,
120
+ compatibility: proposal.value.proposedCompatibility,
121
+ activate: true,
122
+ });
123
+ if (!version.ok)
124
+ throw new SkillImprovementProposalValidationError(version.error.message);
125
+ const now = new Date().toISOString();
126
+ this.db.connection.prepare(`
127
+ UPDATE skill_improvement_proposals SET
128
+ status='applied', updated_at=@updatedAt, applied_at=@appliedAt, applied_by=@appliedBy,
129
+ apply_reason=@applyReason, applied_version_id=@appliedVersionId
130
+ WHERE id=@id
131
+ `).run({
132
+ id: input.proposalId,
133
+ updatedAt: now,
134
+ appliedAt: now,
135
+ appliedBy: input.actor,
136
+ applyReason: input.reason ?? null,
137
+ appliedVersionId: version.value.id,
138
+ });
139
+ const read = this.getById(input.proposalId);
140
+ if (!read.ok)
141
+ throw new Error(read.error.message);
142
+ return read.value;
143
+ });
144
+ if (!result.ok && result.error.cause instanceof SkillImprovementProposalValidationError)
145
+ return validationFailure(result.error.cause.message);
146
+ return result;
147
+ }
148
+ transition(input, from, to, atColumn, byColumn) {
149
+ if (!input.actor.trim())
150
+ return validationFailure('Proposal actor is required');
151
+ const proposal = this.getById(input.proposalId);
152
+ if (!proposal.ok)
153
+ return proposal;
154
+ if (proposal.value.status !== from)
155
+ return validationFailure(`Cannot move proposal from ${proposal.value.status} to ${to}`);
156
+ const now = new Date().toISOString();
157
+ try {
158
+ this.db.connection.prepare(`UPDATE skill_improvement_proposals SET status=?, updated_at=?, ${atColumn}=?, ${byColumn}=? WHERE id=?`).run(to, now, now, input.actor, input.proposalId);
159
+ return this.getById(input.proposalId);
160
+ }
161
+ catch (cause) {
162
+ return fail('Failed to update skill improvement proposal', cause);
163
+ }
164
+ }
165
+ resolve(input, status, expectedStatus = 'pending-approval') {
166
+ if (!input.actor.trim())
167
+ return validationFailure('Proposal actor is required');
168
+ const proposal = this.getById(input.proposalId);
169
+ if (!proposal.ok)
170
+ return proposal;
171
+ if (proposal.value.status !== expectedStatus)
172
+ return validationFailure(`Cannot ${status} proposal with status ${proposal.value.status}`);
173
+ const now = new Date().toISOString();
174
+ try {
175
+ this.db.connection.prepare(`
176
+ UPDATE skill_improvement_proposals SET
177
+ status=@status, updated_at=@updatedAt, resolved_at=@resolvedAt,
178
+ resolved_by=@resolvedBy, resolution_reason=@resolutionReason
179
+ WHERE id=@id
180
+ `).run({
181
+ id: input.proposalId,
182
+ status,
183
+ updatedAt: now,
184
+ resolvedAt: now,
185
+ resolvedBy: input.actor,
186
+ resolutionReason: input.reason ?? null,
187
+ });
188
+ return this.getById(input.proposalId);
189
+ }
190
+ catch (cause) {
191
+ return fail('Failed to resolve skill improvement proposal', cause);
192
+ }
193
+ }
194
+ }
195
+ function validateCreate(input) {
196
+ if (!input.skillId.trim())
197
+ return validationFailure('Skill id is required');
198
+ if (!input.baseVersionId.trim())
199
+ return validationFailure('Base version id is required');
200
+ if (!input.proposedVersion.trim())
201
+ return validationFailure('Proposed version is required');
202
+ if (!input.rationale.trim())
203
+ return validationFailure('Proposal rationale is required');
204
+ if (input.proposedSource.kind === 'path' && !input.proposedSource.path?.trim())
205
+ return validationFailure('Path proposal sources require proposedSource.path');
206
+ if (input.proposedSource.kind === 'url' && !input.proposedSource.url?.trim())
207
+ return validationFailure('URL proposal sources require proposedSource.url');
208
+ if (input.proposedSource.kind === 'inline' && (input.proposedSource.path !== undefined || input.proposedSource.url !== undefined))
209
+ return validationFailure('Inline proposal sources cannot include path or url');
210
+ return ok(undefined);
211
+ }
212
+ function assertBaseVersion(db, skillId, baseVersionId) {
213
+ const base = new SkillRepository(db).getVersion(baseVersionId);
214
+ if (!base.ok)
215
+ throw new SkillImprovementProposalValidationError(base.error.message);
216
+ if (base.value.skillId !== skillId)
217
+ throw new SkillImprovementProposalValidationError('Base version must belong to the proposed skill');
218
+ return base.value;
219
+ }
220
+ function assertProposedVersionIsNew(db, skillId, proposedVersion) {
221
+ const existing = db.connection.prepare('SELECT 1 FROM skill_versions WHERE skill_id=? AND version=?').get(skillId, proposedVersion);
222
+ if (existing !== undefined)
223
+ throw new SkillImprovementProposalValidationError(`Proposed version already exists for skill: ${proposedVersion}`);
224
+ }
225
+ function buildDiffSummary(base, proposedVersion, proposedSource, proposedCompatibility) {
226
+ const changes = [];
227
+ if (base.version !== proposedVersion)
228
+ changes.push({ field: 'version', before: base.version, after: proposedVersion });
229
+ if (JSON.stringify(base.source) !== JSON.stringify(proposedSource))
230
+ changes.push({ field: 'source', before: base.source, after: proposedSource });
231
+ if (JSON.stringify(base.compatibility) !== JSON.stringify(proposedCompatibility))
232
+ changes.push({ field: 'compatibility', before: base.compatibility, after: proposedCompatibility });
233
+ return { baseVersionId: base.id, proposedVersion, changes };
234
+ }
235
+ function mapProposal(row) {
236
+ const proposedSource = { kind: row.proposed_source_kind };
237
+ if (row.proposed_source_path !== null)
238
+ proposedSource.path = row.proposed_source_path;
239
+ if (row.proposed_source_url !== null)
240
+ proposedSource.url = row.proposed_source_url;
241
+ const inlineMetadata = JSON.parse(row.proposed_source_inline_metadata_json ?? '{}');
242
+ if (Object.keys(inlineMetadata).length > 0)
243
+ proposedSource.inlineMetadata = inlineMetadata;
244
+ const proposal = {
245
+ id: row.id,
246
+ skillId: row.skill_id,
247
+ baseVersionId: row.base_version_id,
248
+ proposedVersion: row.proposed_version,
249
+ proposedSource,
250
+ proposedCompatibility: JSON.parse(row.proposed_compatibility_json),
251
+ rationale: row.rationale,
252
+ sourceSignal: JSON.parse(row.source_signal_json),
253
+ diffSummary: JSON.parse(row.diff_summary_json),
254
+ status: row.status,
255
+ createdAt: row.created_at,
256
+ updatedAt: row.updated_at,
257
+ };
258
+ if (row.submitted_at !== null)
259
+ proposal.submittedAt = row.submitted_at;
260
+ if (row.submitted_by !== null)
261
+ proposal.submittedBy = row.submitted_by;
262
+ if (row.resolved_at !== null)
263
+ proposal.resolvedAt = row.resolved_at;
264
+ if (row.resolved_by !== null)
265
+ proposal.resolvedBy = row.resolved_by;
266
+ if (row.resolution_reason !== null)
267
+ proposal.resolutionReason = row.resolution_reason;
268
+ if (row.applied_at !== null)
269
+ proposal.appliedAt = row.applied_at;
270
+ if (row.applied_by !== null)
271
+ proposal.appliedBy = row.applied_by;
272
+ if (row.apply_reason !== null)
273
+ proposal.applyReason = row.apply_reason;
274
+ if (row.applied_version_id !== null)
275
+ proposal.appliedVersionId = row.applied_version_id;
276
+ return proposal;
277
+ }
278
+ function ok(value) { return { ok: true, value }; }
279
+ function missing(message) { return { ok: false, error: { code: 'not_found', message } }; }
280
+ function validationFailure(message) { return { ok: false, error: { code: 'validation_failed', message } }; }
281
+ function fail(message, cause) {
282
+ const error = { code: 'validation_failed', message };
283
+ if (cause !== undefined)
284
+ error.cause = cause;
285
+ return { ok: false, error };
286
+ }
287
+ class SkillImprovementProposalValidationError extends Error {
288
+ }