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,194 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { createConfigBackupPath } from '../setup/backup-rollback-service.js';
4
+ import { findConflictingVgxnessAgents, planOpenCodeMcpInstall } from './client-install-opencode-contract.js';
5
+ import { createOpenCodeDefaultAgentConfig, createOpenCodeDefaultAgentInstallPlan } from './opencode-default-agent-config.js';
6
+ const opencodeConfigSchema = 'https://opencode.ai/config.json';
7
+ export { createInstalledVgxnessMcpServerCommand };
8
+ function createInstalledVgxnessMcpServerCommand(databasePath, source = 'flag') {
9
+ return source === 'global-default' ? ['vgxness', 'mcp', 'start'] : ['vgxness', 'mcp', 'start', '--db', databasePath];
10
+ }
11
+ function createVgxnessMcpDoctorCommand(databasePath, source) {
12
+ return source === 'global-default' ? ['vgxness', 'mcp', 'doctor'] : ['vgxness', 'mcp', 'doctor', '--db', databasePath];
13
+ }
14
+ export function createOpenCodeLocalMcpServerConfig(databasePath, source = 'flag') {
15
+ return { type: 'local', command: createInstalledVgxnessMcpServerCommand(databasePath, source), enabled: true };
16
+ }
17
+ export async function installOpenCodeMcpClient(input) {
18
+ const databasePathSource = input.databasePathSource ?? 'flag';
19
+ const server = createOpenCodeLocalMcpServerConfig(input.databasePath, databasePathSource);
20
+ const agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: input.mcpOnly });
21
+ const plan = planOpenCodeMcpInstall({
22
+ cwd: input.cwd,
23
+ databasePath: input.databasePath,
24
+ databasePathSource,
25
+ ...(input.scope !== undefined ? { scope: input.scope } : {}),
26
+ ...(input.env !== undefined ? { env: input.env } : {}),
27
+ ...(input.mcpOnly !== undefined ? { mcpOnly: input.mcpOnly } : {}),
28
+ });
29
+ if (!input.confirmed) {
30
+ return refusal('confirmation_required', confirmationRequiredMessage(plan.scope), input.databasePath, databasePathSource, server, confirmationRequiredSafety(plan), 'targetPath' in plan ? plan.targetPath : undefined, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
31
+ }
32
+ if (plan.status === 'refused')
33
+ return refusalFromPlan(plan, input.databasePath, databasePathSource, server);
34
+ if (plan.action === 'create') {
35
+ if (existsSync(plan.targetPath)) {
36
+ const reparsed = parseConfig(plan.targetPath);
37
+ if (!reparsed.ok)
38
+ return refusal(reparsed.reason, reparsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
39
+ const conflictingAgents = findConflictingVgxnessAgents(reparsed.value, agentPlan);
40
+ if (conflictingAgents.length > 0)
41
+ return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
42
+ return refusal('unsupported_config_shape', 'OpenCode config appeared after planning; rerun setup apply so it can be merged safely without overwriting user config.', input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
43
+ }
44
+ writeConfig(plan.targetPath, mergeVgxnessOpenCodeConfig({ $schema: opencodeConfigSchema }, plan.server, plan.installsAgents));
45
+ return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
46
+ }
47
+ const parsed = parseConfig(plan.targetPath);
48
+ if (!parsed.ok)
49
+ return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
50
+ const conflictingAgents = findConflictingVgxnessAgents(parsed.value, agentPlan);
51
+ if (conflictingAgents.length > 0)
52
+ return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
53
+ const backupPath = createBackup(plan.targetPath);
54
+ const config = parsed.value;
55
+ const mergedConfig = mergeVgxnessOpenCodeConfig({ ...config, $schema: plan.existingSchema ?? opencodeConfigSchema }, plan.server, plan.installsAgents);
56
+ writeConfig(plan.targetPath, mergedConfig);
57
+ return validateInstalledResult(plan.targetPath, backupPath, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
58
+ }
59
+ function mergeVgxnessOpenCodeConfig(config, server, installsAgents) {
60
+ const merged = {
61
+ ...config,
62
+ $schema: typeof config.$schema === 'string' ? config.$schema : opencodeConfigSchema,
63
+ mcp: { ...(isRecord(config.mcp) ? config.mcp : {}), vgxness: server },
64
+ };
65
+ if (installsAgents) {
66
+ const defaults = createOpenCodeDefaultAgentConfig();
67
+ merged.default_agent = defaults.defaultAgent;
68
+ merged.agent = { ...(isRecord(config.agent) ? config.agent : {}), ...defaults.agents };
69
+ }
70
+ return merged;
71
+ }
72
+ function parseConfig(path) {
73
+ try {
74
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
75
+ return isRecord(parsed)
76
+ ? { ok: true, value: parsed }
77
+ : { ok: false, reason: 'unsupported_config_shape', message: 'OpenCode config must be a JSON object.' };
78
+ }
79
+ catch {
80
+ return { ok: false, reason: 'malformed_json', message: 'OpenCode config must be valid JSON; malformed JSON and JSONC comments are refused.' };
81
+ }
82
+ }
83
+ function writeConfig(path, config) {
84
+ mkdirSync(dirname(path), { recursive: true });
85
+ writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
86
+ }
87
+ function createBackup(path) {
88
+ const backupPath = createConfigBackupPath(path);
89
+ copyFileSync(path, backupPath);
90
+ return backupPath;
91
+ }
92
+ function validateInstalledResult(targetPath, backupPath, server, databasePath, source, safety, verificationHints, warningMessages, manualTestGuidance, agentPlan) {
93
+ const parsed = parseConfig(targetPath);
94
+ if (!parsed.ok)
95
+ return refusal('post_write_validation_failed', 'OpenCode config could not be re-read after write.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan);
96
+ const mcp = parsed.value.mcp;
97
+ const installed = isRecord(mcp) ? mcp.vgxness : undefined;
98
+ if (!isOpenCodeLocalMcpServerConfig(installed)) {
99
+ return refusal('post_write_validation_failed', 'OpenCode config was written but mcp.vgxness did not validate.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan);
100
+ }
101
+ if (agentPlan.installsAgents) {
102
+ const agent = parsed.value.agent;
103
+ if (!isRecord(agent) || parsed.value.default_agent !== agentPlan.defaultAgent || agentPlan.agentNames.some((name) => !isRecord(agent[name]))) {
104
+ return refusal('post_write_validation_failed', 'OpenCode config was written but VGXNESS agent entries did not validate.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan);
105
+ }
106
+ }
107
+ return {
108
+ version: 1,
109
+ kind: 'mcp-client-install-opencode',
110
+ status: 'installed',
111
+ targetPath,
112
+ ...(backupPath !== undefined ? { backupPath } : {}),
113
+ safety,
114
+ server,
115
+ warnings: warningMessages,
116
+ verificationHints,
117
+ manualTest: manualTestGuidance,
118
+ installsAgents: agentPlan.installsAgents,
119
+ agentNames: agentPlan.agentNames,
120
+ ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
121
+ };
122
+ }
123
+ function isOpenCodeLocalMcpServerConfig(value) {
124
+ return isRecord(value)
125
+ && value.type === 'local'
126
+ && value.enabled === true
127
+ && Array.isArray(value.command)
128
+ && value.command.every((item) => typeof item === 'string');
129
+ }
130
+ function isRecord(value) {
131
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
132
+ }
133
+ function refusal(reason, message, databasePath, source, server, safety, targetPath, verificationHints = defaultVerificationHints(databasePath, source), warningMessages = warnings(), manualTestGuidance = manualTest(databasePath, source), agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true })) {
134
+ return {
135
+ version: 1,
136
+ kind: 'mcp-client-install-opencode',
137
+ status: 'refused',
138
+ reason,
139
+ message,
140
+ ...(targetPath !== undefined ? { targetPath } : {}),
141
+ safety,
142
+ server,
143
+ warnings: warningMessages,
144
+ verificationHints,
145
+ manualTest: manualTestGuidance,
146
+ installsAgents: agentPlan.installsAgents,
147
+ agentNames: agentPlan.agentNames,
148
+ ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
149
+ };
150
+ }
151
+ function refusalFromPlan(plan, databasePath, source, server) {
152
+ return refusal(plan.reason, plan.message, databasePath, source, server, plan.safety, plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, plan);
153
+ }
154
+ function agentConflictRefusal(conflictingAgents, databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan) {
155
+ return refusal('existing_vgxness_agent', `Existing OpenCode config contains custom VGXNESS agent entries that would be overwritten: ${conflictingAgents.join(', ')}. Remove, rename, or manually reconcile them before installing.`, databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan);
156
+ }
157
+ function applySafety(plan) {
158
+ return {
159
+ ...plan.safety,
160
+ operation: 'apply',
161
+ mutating: true,
162
+ writesProviderConfig: true,
163
+ };
164
+ }
165
+ function confirmationRequiredSafety(plan) {
166
+ return {
167
+ operation: 'apply',
168
+ mutating: false,
169
+ writesProviderConfig: false,
170
+ ...('targetPath' in plan && plan.targetPath !== undefined ? { targetPath: plan.targetPath } : {}),
171
+ backupRequired: false,
172
+ mergePolicy: 'refuse-no-clobber',
173
+ };
174
+ }
175
+ function confirmationRequiredMessage(scope) {
176
+ return `\`mcp install opencode\` requires explicit --yes before any ${scope} config write.`;
177
+ }
178
+ function warnings() {
179
+ return ['Restart OpenCode after installation so it reloads the project MCP config.'];
180
+ }
181
+ function manualTest(databasePath, source) {
182
+ return {
183
+ restart: 'Restart OpenCode after the config is installed.',
184
+ verify: 'Open the project in OpenCode and verify the vgxness MCP server is available.',
185
+ doctorCommand: createVgxnessMcpDoctorCommand(databasePath, source),
186
+ };
187
+ }
188
+ function defaultVerificationHints(databasePath, source) {
189
+ return [
190
+ { kind: 'restart-client', message: 'Restart OpenCode after the config is installed.' },
191
+ { kind: 'manual-check', message: 'Open the project in OpenCode and verify the vgxness MCP server is available.' },
192
+ { kind: 'command', message: 'Run the MCP doctor command after installation.', command: createVgxnessMcpDoctorCommand(databasePath, source) },
193
+ ];
194
+ }
@@ -0,0 +1,38 @@
1
+ export function createVgxnessMcpServerCommand(databasePath, source = 'flag') {
2
+ return source === 'global-default'
3
+ ? ['npm', 'run', 'cli', '--', 'mcp', 'start']
4
+ : ['npm', 'run', 'cli', '--', 'mcp', 'start', '--db', databasePath];
5
+ }
6
+ export function createMcpClientSetupPreview(input) {
7
+ const databasePathSource = input.databasePathSource ?? 'flag';
8
+ const server = {
9
+ name: 'vgxness',
10
+ command: 'npm',
11
+ args: createVgxnessMcpServerCommand(input.databasePath, databasePathSource).slice(1),
12
+ };
13
+ return {
14
+ version: 1,
15
+ kind: 'mcp-client-setup-preview',
16
+ provider: input.provider,
17
+ installable: false,
18
+ readOnly: true,
19
+ writesProviderConfig: false,
20
+ server,
21
+ snippets: snippetsFor(input.provider, server),
22
+ warnings: [
23
+ 'This is preview-only output; it does not install or write provider config.',
24
+ databasePathSource === 'global-default'
25
+ ? 'Copy the snippet manually only after reviewing that it uses the vgxness global default database path.'
26
+ : 'Copy the snippet manually only after reviewing the command and database path.',
27
+ ],
28
+ };
29
+ }
30
+ export function isMcpClientSetupProvider(value) {
31
+ return value === 'opencode' || value === 'claude';
32
+ }
33
+ function snippetsFor(provider, server) {
34
+ if (provider === 'opencode') {
35
+ return [{ label: 'OpenCode MCP server JSON', format: 'json', value: { mcp: { vgxness: server } } }];
36
+ }
37
+ return [{ label: 'Claude-style MCP server JSON', format: 'json', value: { mcpServers: { vgxness: server } } }];
38
+ }
@@ -0,0 +1,175 @@
1
+ import { AgentRegistryService } from '../agents/agent-registry-service.js';
2
+ import { AgentActivationService } from '../agents/agent-activation-service.js';
3
+ import { ManagerProfileOverlayService } from '../agents/manager-profile-overlay-service.js';
4
+ import { ManagerProfileOverlayRepository } from '../agents/repositories/manager-profile-overlays.js';
5
+ import { MemoryService } from '../memory/memory-service.js';
6
+ import { openMemoryDatabase } from '../memory/sqlite/database.js';
7
+ import { prepareMemoryDatabasePath, resolveMemoryDatabasePath } from '../memory/storage-paths.js';
8
+ import { SddWorkflowService } from '../sdd/sdd-workflow-service.js';
9
+ import { SkillRegistryService } from '../skills/skill-registry-service.js';
10
+ import { OpenCodeManagerPayloadService } from '../providers/opencode/manager-payload.js';
11
+ import { RunService } from '../runs/run-service.js';
12
+ import { errorEnvelope, successEnvelope } from './schema.js';
13
+ import { validateVgxMcpToolCall } from './validation.js';
14
+ export function callVgxTool(call, services) {
15
+ const validated = validateVgxMcpToolCall(call);
16
+ if (!validated.ok)
17
+ return validated;
18
+ switch (validated.tool) {
19
+ case 'vgxness_sdd_status':
20
+ return toEnvelope(validated.tool, services.sdd.getStatus(validated.input));
21
+ case 'vgxness_sdd_ready':
22
+ return toEnvelope(validated.tool, services.sdd.getReady(validated.input));
23
+ case 'vgxness_sdd_save_artifact':
24
+ return toEnvelope(validated.tool, services.sdd.saveArtifact(validated.input));
25
+ case 'vgxness_sdd_get_artifact':
26
+ return toEnvelope(validated.tool, services.sdd.getArtifact(validated.input));
27
+ case 'vgxness_sdd_list_artifacts':
28
+ return toEnvelope(validated.tool, services.sdd.listArtifacts(validated.input));
29
+ case 'vgxness_sdd_next':
30
+ return toEnvelope(validated.tool, services.sdd.getNext(validated.input));
31
+ case 'vgxness_memory_save':
32
+ return saveMemoryObservationEnvelope(validated.input, services);
33
+ case 'vgxness_memory_search':
34
+ return toEnvelope(validated.tool, services.memory.searchObservations(validated.input, memoryContext()));
35
+ case 'vgxness_memory_get':
36
+ return getMemoryObservationEnvelope(validated.input, services);
37
+ case 'vgxness_memory_update':
38
+ return updateMemoryObservationEnvelope(validated.input, services);
39
+ case 'vgxness_session_start':
40
+ return toEnvelope(validated.tool, services.memory.saveSession(validated.input, memoryContext(validated.input.id)));
41
+ case 'vgxness_session_append_activity':
42
+ return toEnvelope(validated.tool, services.memory.appendActivity(validated.input, memoryContext(validated.input.sessionId)));
43
+ case 'vgxness_session_close':
44
+ return toEnvelope(validated.tool, services.memory.closeSession(validated.input, memoryContext(validated.input.sessionId)));
45
+ case 'vgxness_session_restore':
46
+ return toEnvelope(validated.tool, services.memory.restoreSession(validated.input));
47
+ case 'vgxness_agent_resolve':
48
+ return toEnvelope(validated.tool, services.agents.resolveAgents(validated.input));
49
+ case 'vgxness_agent_activate':
50
+ return toEnvelope(validated.tool, services.activation.activate(validated.input));
51
+ case 'vgxness_manager_profile_get':
52
+ return getManagerProfileEnvelope(validated.input, services);
53
+ case 'vgxness_manager_profile_set':
54
+ return setManagerProfileEnvelope(validated.input, services);
55
+ case 'vgxness_skill_payload':
56
+ return buildSkillPayloadEnvelope(validated.input, services);
57
+ case 'vgxness_opencode_manager_payload':
58
+ return toEnvelope(validated.tool, services.opencodeManagerPayload.build(validated.input));
59
+ case 'vgxness_run_list':
60
+ return listRunsEnvelope(validated.input, services);
61
+ case 'vgxness_run_get':
62
+ return toEnvelope(validated.tool, services.runs.getRun(validated.input.id));
63
+ case 'vgxness_run_preflight':
64
+ return toEnvelope(validated.tool, services.runs.preflightOperation(validated.input));
65
+ case 'vgxness_run_start':
66
+ return toEnvelope(validated.tool, services.runs.createRun(validated.input));
67
+ case 'vgxness_run_checkpoint':
68
+ return toEnvelope(validated.tool, services.runs.appendCheckpoint(validated.input));
69
+ case 'vgxness_run_finalize':
70
+ return toEnvelope(validated.tool, services.runs.updateFinalStatus(validated.input));
71
+ }
72
+ return errorEnvelope('validation_failed', 'Tool dispatch is not implemented');
73
+ }
74
+ export function createVgxMcpControlPlane(options = {}) {
75
+ const databasePath = resolveControlPlaneDatabasePath(options);
76
+ if (!databasePath.ok)
77
+ throw new VgxMcpControlPlaneError(databasePath.error);
78
+ const prepared = prepareMemoryDatabasePath(databasePath.value);
79
+ if (!prepared.ok)
80
+ throw new VgxMcpControlPlaneError(prepared.error);
81
+ const opened = openMemoryDatabase({ path: databasePath.value });
82
+ if (!opened.ok)
83
+ throw new VgxMcpControlPlaneError(opened.error);
84
+ const database = opened.value;
85
+ const services = createServices(database);
86
+ let closed = false;
87
+ return {
88
+ callVgxTool: (tool, input) => callVgxTool({ tool, input }, services),
89
+ close: () => {
90
+ if (closed)
91
+ return;
92
+ closed = true;
93
+ database.close();
94
+ },
95
+ };
96
+ }
97
+ export class VgxMcpControlPlaneError extends Error {
98
+ code;
99
+ cause;
100
+ constructor(failure) {
101
+ super(failure.message);
102
+ this.name = 'VgxMcpControlPlaneError';
103
+ this.code = failure.code;
104
+ if (failure.cause !== undefined)
105
+ this.cause = failure.cause;
106
+ }
107
+ }
108
+ function createServices(database) {
109
+ const memory = new MemoryService(database);
110
+ const agents = new AgentRegistryService(database);
111
+ const skills = new SkillRegistryService(database);
112
+ const runs = new RunService(database);
113
+ const managerProfiles = new ManagerProfileOverlayService({ agents, overlays: new ManagerProfileOverlayRepository(database) });
114
+ const opencodeManagerPayload = new OpenCodeManagerPayloadService({ agents, managerProfiles, skills });
115
+ return {
116
+ sdd: new SddWorkflowService(memory, { actor: 'mcp-control-plane' }),
117
+ memory,
118
+ agents,
119
+ managerProfiles,
120
+ skills,
121
+ opencodeManagerPayload,
122
+ activation: new AgentActivationService({ agents, managerProfiles, runs, opencodeManagerPayload }),
123
+ runs,
124
+ };
125
+ }
126
+ function getManagerProfileEnvelope(input, services) {
127
+ if (services.managerProfiles === undefined)
128
+ return errorEnvelope('validation_failed', 'Manager profile service is not available', 'vgxness_manager_profile_get');
129
+ return toEnvelope('vgxness_manager_profile_get', services.managerProfiles.resolveEffectiveManager({ project: input.project, scope: input.scope ?? 'project', managerName: input.managerName ?? 'vgxness-manager' }));
130
+ }
131
+ function setManagerProfileEnvelope(input, services) {
132
+ if (services.managerProfiles === undefined)
133
+ return errorEnvelope('validation_failed', 'Manager profile service is not available', 'vgxness_manager_profile_set');
134
+ const baseline = services.managerProfiles.resolveEffectiveManager({ project: input.project, scope: input.scope, managerName: input.managerName });
135
+ if (!baseline.ok)
136
+ return errorEnvelope(baseline.error.code, baseline.error.message, 'vgxness_manager_profile_set');
137
+ const saved = services.managerProfiles.save(input);
138
+ if (!saved.ok)
139
+ return errorEnvelope(saved.error.code, saved.error.message, 'vgxness_manager_profile_set');
140
+ return toEnvelope('vgxness_manager_profile_set', services.managerProfiles.resolveEffectiveManager({ project: input.project, scope: input.scope, managerName: input.managerName }));
141
+ }
142
+ function saveMemoryObservationEnvelope(input, services) {
143
+ return toEnvelope('vgxness_memory_save', services.memory.saveObservation({ ...input, scope: input.scope ?? 'project' }, memoryContext()));
144
+ }
145
+ function getMemoryObservationEnvelope(input, services) {
146
+ return toEnvelope('vgxness_memory_get', services.memory.getObservation(input.id, memoryContext()));
147
+ }
148
+ function updateMemoryObservationEnvelope(input, services) {
149
+ return toEnvelope('vgxness_memory_update', services.memory.updateObservation(input, memoryContext()));
150
+ }
151
+ function memoryContext(sessionId) {
152
+ const context = { actor: 'mcp-control-plane' };
153
+ if (sessionId !== undefined)
154
+ context.sessionId = sessionId;
155
+ return context;
156
+ }
157
+ function buildSkillPayloadEnvelope(input, services) {
158
+ const { workspaceRoot, maxSourceBytes, ...resolverInput } = input;
159
+ const options = maxSourceBytes === undefined ? { workspaceRoot } : { workspaceRoot, maxSourceBytes };
160
+ return toEnvelope('vgxness_skill_payload', services.skills.buildSkillPayload(resolverInput, options));
161
+ }
162
+ function listRunsEnvelope(input, services) {
163
+ const { limit, ...filters } = input;
164
+ const result = services.runs.listRuns(filters);
165
+ return result.ok ? successEnvelope('vgxness_run_list', result.value.slice(0, limit)) : errorEnvelope(result.error.code, result.error.message, 'vgxness_run_list');
166
+ }
167
+ function toEnvelope(tool, result) {
168
+ return result.ok ? successEnvelope(tool, result.value) : errorEnvelope(result.error.code, result.error.message, tool);
169
+ }
170
+ function resolveControlPlaneDatabasePath(options) {
171
+ if (options.databasePath !== undefined)
172
+ return { ok: true, value: options.databasePath };
173
+ const resolved = resolveMemoryDatabasePath({ cwd: process.cwd(), env: process.env });
174
+ return resolved.ok ? { ok: true, value: resolved.value.path } : resolved;
175
+ }
@@ -0,0 +1,193 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import { existsSync, mkdirSync } from 'node:fs';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { openMemoryDatabase } from '../memory/sqlite/database.js';
7
+ import { resolveOpenCodeMcpInstallTarget } from './client-install-opencode-contract.js';
8
+ import { SUPPORTED_VGX_MCP_TOOL_NAMES } from './schema.js';
9
+ export async function createMcpDoctorReport(options) {
10
+ const cwd = options.cwd;
11
+ const databasePath = resolve(cwd, options.databasePath ?? join('.vgx', 'memory.sqlite'));
12
+ const project = options.project ?? 'vgxness';
13
+ const change = options.change ?? 'doctor-smoke';
14
+ const timeoutMs = options.timeoutMs ?? 5_000;
15
+ const providerTargets = resolveProviderConfigTargets(cwd, options.env);
16
+ const beforeProviderPaths = providerConfigSnapshot(providerTargets);
17
+ const checks = [];
18
+ checks.push(checkNodeVersion());
19
+ checks.push(checkDatabasePath(databasePath));
20
+ const sqlite = checkSqliteMigrations(databasePath);
21
+ checks.push(sqlite);
22
+ if (sqlite.status === 'pass') {
23
+ const smoke = await checkMcpSmoke({ cwd, databasePath, project, change, timeoutMs, smoke: options.smoke ?? runRealMcpSmoke });
24
+ checks.push(smoke.spawn, smoke.toolsList, smoke.safeStatusCall);
25
+ }
26
+ else {
27
+ checks.push(skipped('mcp-spawn', 'Skipped because SQLite/migrations readiness failed'));
28
+ checks.push(skipped('tools-list', 'Skipped because MCP stdio spawn did not pass'));
29
+ checks.push(skipped('safe-status-call', 'Skipped because MCP tools/list did not pass'));
30
+ }
31
+ checks.push(checkProviderConfigSafety(providerTargets, beforeProviderPaths));
32
+ return {
33
+ version: 1,
34
+ kind: 'mcp-doctor',
35
+ ready: checks.every((check) => check.status === 'pass'),
36
+ checks,
37
+ safety: { writesProviderConfig: false, checkedPaths: providerTargets.map((target) => target.label), checkedTargets: checkedTargets(providerTargets) },
38
+ };
39
+ }
40
+ async function checkMcpSmoke(options) {
41
+ try {
42
+ const result = await runWithTimeout(options.timeoutMs, (signal) => options.smoke({ cwd: options.cwd, databasePath: options.databasePath, project: options.project, change: options.change, signal }));
43
+ const expectedTools = [...SUPPORTED_VGX_MCP_TOOL_NAMES].sort();
44
+ const listedTools = [...result.tools].sort();
45
+ const toolsMatch = JSON.stringify(listedTools) === JSON.stringify(expectedTools);
46
+ return {
47
+ spawn: passed('mcp-spawn', 'MCP stdio server responded'),
48
+ toolsList: toolsMatch
49
+ ? passed('tools-list', `Listed expected tools: ${expectedTools.join(', ')}`)
50
+ : failed('tools-list', `Expected tools ${expectedTools.join(', ')}, got ${listedTools.join(', ')}`, 'Check the MCP server tool registration.'),
51
+ safeStatusCall: result.statusCallOk
52
+ ? passed('safe-status-call', `vgxness_sdd_status completed for ${options.project}/${options.change}`)
53
+ : failed('safe-status-call', 'vgxness_sdd_status did not complete successfully', 'Inspect the selected DB path and SDD status tool validation.'),
54
+ };
55
+ }
56
+ catch (cause) {
57
+ const message = cause instanceof Error ? cause.message : String(cause);
58
+ return {
59
+ spawn: failed('mcp-spawn', message.includes('timed out') ? message : `MCP stdio smoke failed: ${message}`, 'Run mcp start with the selected --db path and inspect stderr.'),
60
+ toolsList: skipped('tools-list', 'Skipped because MCP stdio spawn did not pass'),
61
+ safeStatusCall: skipped('safe-status-call', 'Skipped because MCP tools/list did not pass'),
62
+ };
63
+ }
64
+ }
65
+ async function runRealMcpSmoke(options) {
66
+ const client = new Client({ name: 'vgxness-doctor', version: '0.1.0' }, { capabilities: {} });
67
+ const invocation = resolveDoctorCliInvocation(options.databasePath);
68
+ const transport = new StdioClientTransport({
69
+ command: process.execPath,
70
+ args: invocation.args,
71
+ cwd: options.cwd,
72
+ stderr: 'pipe',
73
+ });
74
+ const closeOnAbort = () => {
75
+ void client.close().catch(() => undefined);
76
+ };
77
+ options.signal.addEventListener('abort', closeOnAbort, { once: true });
78
+ try {
79
+ await client.connect(transport);
80
+ const listed = await client.listTools();
81
+ const status = await client.callTool({ name: 'vgxness_sdd_status', arguments: { project: options.project, change: options.change } });
82
+ return { tools: listed.tools.map((tool) => tool.name), statusCallOk: parseMcpToolResult(status).ok === true };
83
+ }
84
+ finally {
85
+ options.signal.removeEventListener('abort', closeOnAbort);
86
+ await client.close().catch(() => undefined);
87
+ }
88
+ }
89
+ async function runWithTimeout(timeoutMs, operation) {
90
+ const controller = new AbortController();
91
+ let timeout;
92
+ try {
93
+ return await Promise.race([
94
+ operation(controller.signal),
95
+ new Promise((_, reject) => {
96
+ timeout = setTimeout(() => {
97
+ controller.abort();
98
+ reject(new Error(`MCP stdio smoke timed out after ${timeoutMs}ms`));
99
+ }, timeoutMs);
100
+ }),
101
+ ]);
102
+ }
103
+ finally {
104
+ if (timeout !== undefined)
105
+ clearTimeout(timeout);
106
+ }
107
+ }
108
+ function checkNodeVersion() {
109
+ const major = Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10);
110
+ return major >= 22
111
+ ? passed('node', `Node ${process.version} satisfies >=22`)
112
+ : failed('node', `Node ${process.version} does not satisfy >=22`, 'Install Node.js 22 or newer.');
113
+ }
114
+ function checkDatabasePath(databasePath) {
115
+ try {
116
+ mkdirSync(dirname(databasePath), { recursive: true });
117
+ return passed('db-path', `Selected DB path is ${databasePath}`);
118
+ }
119
+ catch (cause) {
120
+ const message = cause instanceof Error ? cause.message : String(cause);
121
+ return failed('db-path', `Unable to prepare selected DB path ${databasePath}: ${message}`, 'Choose a writable --db path.');
122
+ }
123
+ }
124
+ function checkSqliteMigrations(databasePath) {
125
+ const opened = openMemoryDatabase({ path: databasePath });
126
+ if (!opened.ok)
127
+ return failed('sqlite-migrations', opened.error.message, 'Check DB permissions and migration SQL files.');
128
+ try {
129
+ return passed('sqlite-migrations', 'SQLite opened and migrations are ready');
130
+ }
131
+ finally {
132
+ opened.value.close();
133
+ }
134
+ }
135
+ function resolveProviderConfigTargets(cwd, env) {
136
+ const targets = [
137
+ { label: '.opencode', path: join(cwd, '.opencode'), kind: 'project-opencode' },
138
+ { label: '.claude', path: join(cwd, '.claude'), kind: 'project-claude' },
139
+ ];
140
+ const userTarget = resolveOpenCodeMcpInstallTarget({ cwd, scope: 'user', env });
141
+ if (userTarget.ok)
142
+ targets.push({ label: '$HOME/.config/opencode/opencode.json', path: userTarget.path, kind: 'user-opencode' });
143
+ return targets;
144
+ }
145
+ function checkProviderConfigSafety(targets, before) {
146
+ const mutated = targets.filter((target) => before[target.label] !== existsSync(target.path));
147
+ const detail = providerSafetyDetail(targets);
148
+ return mutated.length === 0
149
+ ? passed('provider-config-safety', `No provider config paths were created or removed. ${detail}`)
150
+ : failed('provider-config-safety', `Provider config paths changed: ${mutated.map((target) => target.label).join(', ')}. ${detail}`, 'Doctor must not write provider config; inspect the command implementation.');
151
+ }
152
+ function providerConfigSnapshot(targets) {
153
+ return Object.fromEntries(targets.map((target) => [target.label, existsSync(target.path)]));
154
+ }
155
+ function providerSafetyDetail(targets) {
156
+ const projectOpenCode = projectOpenCodeConfigPath(targets);
157
+ const userOpenCode = targets.find((target) => target.kind === 'user-opencode')?.path ?? 'unresolved because HOME is not set';
158
+ return `Project OpenCode target: ${projectOpenCode}; User OpenCode target: ${userOpenCode}. Project OpenCode config can override user config.`;
159
+ }
160
+ function checkedTargets(targets) {
161
+ const projectOpenCode = projectOpenCodeConfigPath(targets);
162
+ const userOpenCode = targets.find((target) => target.kind === 'user-opencode')?.path;
163
+ return { projectOpenCode, ...(userOpenCode !== undefined ? { userOpenCode } : {}) };
164
+ }
165
+ function projectOpenCodeConfigPath(targets) {
166
+ const projectDirectory = targets.find((target) => target.kind === 'project-opencode')?.path;
167
+ return projectDirectory === undefined ? 'unresolved' : join(projectDirectory, 'opencode.json');
168
+ }
169
+ function parseMcpToolResult(result) {
170
+ const envelope = result;
171
+ const text = envelope.content?.[0]?.text;
172
+ if (typeof text !== 'string')
173
+ return { ok: false, error: { code: 'validation_failed', message: 'MCP response did not contain text JSON' } };
174
+ return JSON.parse(text);
175
+ }
176
+ export function resolveDoctorCliInvocation(databasePath, moduleUrl = import.meta.url) {
177
+ const builtCliPath = fileURLToPath(new URL('../cli/index.js', moduleUrl));
178
+ if (existsSync(builtCliPath))
179
+ return { args: [builtCliPath, 'mcp', 'start', '--db', databasePath] };
180
+ return { args: ['--import', import.meta.resolve('tsx'), cliSourcePath(moduleUrl), 'mcp', 'start', '--db', databasePath] };
181
+ }
182
+ function cliSourcePath(moduleUrl) {
183
+ return fileURLToPath(new URL('../cli/index.ts', moduleUrl));
184
+ }
185
+ function passed(id, detail) {
186
+ return { id, status: 'pass', detail };
187
+ }
188
+ function failed(id, detail, remediation) {
189
+ return { id, status: 'fail', detail, remediation };
190
+ }
191
+ function skipped(id, detail) {
192
+ return { id, status: 'skip', detail };
193
+ }
@@ -0,0 +1,10 @@
1
+ export * from './schema.js';
2
+ export * from './validation.js';
3
+ export * from './control-plane.js';
4
+ export * from './stdio-server.js';
5
+ export * from './client-setup-preview.js';
6
+ export * from './client-install-opencode-contract.js';
7
+ export * from './client-install-opencode.js';
8
+ export * from './doctor.js';
9
+ export * from './opencode-visibility.js';
10
+ export * from '../providers/opencode/manager-payload.js';