vgxness 1.21.0 → 1.21.2

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.
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { canonicalCapabilitySubagentLegacyAliases } from '../../../domain/agents/canonical-agent-manifest.js';
4
4
  import { createOpenCodeDefaultAgentConfig, createOpenCodeDefaultAgentInstallPlan, } from './opencode-default-agent-config.js';
5
+ import { planVgxnessPathProfileUpdate, resolveVgxnessPathEnvironment, } from './vgxness-path-environment.js';
5
6
  const opencodeConfigSchema = 'https://opencode.ai/config.json';
6
7
  const opencodeUserGlobalOnlyMessage = 'VGX-managed OpenCode configuration is user-global only. Project/local OpenCode files are treated as external/manual diagnostics and will not be written by VGXNESS. Re-run without the project/local install scope.';
7
8
  export function resolveOpenCodeMcpInstallTarget(input) {
@@ -40,7 +41,7 @@ export function planOpenCodeMcpInstall(input) {
40
41
  if (scope === 'user') {
41
42
  return planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness, bashPermissionPolicy);
42
43
  }
43
- return refusal('unsupported_config_shape', opencodeUserGlobalOnlyMessage, input.databasePath, databasePathSource, scope, undefined, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
44
+ return refusal('unsupported_config_shape', opencodeUserGlobalOnlyMessage, input.databasePath, databasePathSource, scope, undefined, [], agentPlan, overwriteVgxness, bashPermissionPolicy, input.env);
44
45
  }
45
46
  function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan, overwriteVgxness, bashPermissionPolicy) {
46
47
  const target = resolveOpenCodeMcpInstallTarget({
@@ -49,15 +50,15 @@ function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan
49
50
  env: input.env,
50
51
  });
51
52
  if (!target.ok) {
52
- return refusal('unsupported_config_shape', target.message, input.databasePath, databasePathSource, 'user', undefined, [], undefined, overwriteVgxness, bashPermissionPolicy);
53
+ return refusal('unsupported_config_shape', target.message, input.databasePath, databasePathSource, 'user', undefined, [], undefined, overwriteVgxness, bashPermissionPolicy, input.env);
53
54
  }
54
55
  const jsoncPath = `${target.path}c`;
55
56
  if (existsSync(jsoncPath)) {
56
- return refusal('unsupported_jsonc', 'OpenCode user JSONC config opencode.jsonc is not supported yet; use JSON or remove comments first.', input.databasePath, databasePathSource, 'user', jsoncPath, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
57
+ return refusal('unsupported_jsonc', 'OpenCode user JSONC config opencode.jsonc is not supported yet; use JSON or remove comments first.', input.databasePath, databasePathSource, 'user', jsoncPath, [], agentPlan, overwriteVgxness, bashPermissionPolicy, input.env);
57
58
  }
58
59
  if (!existsSync(target.path)) {
59
60
  return {
60
- ...baseContract(input.databasePath, databasePathSource, 'user', target.path, false, 'create', agentPlan, overwriteVgxness, bashPermissionPolicy),
61
+ ...baseContract(input.databasePath, databasePathSource, 'user', target.path, false, 'create', agentPlan, overwriteVgxness, bashPermissionPolicy, input.env),
61
62
  status: 'would_install',
62
63
  action: 'create',
63
64
  targetPath: target.path,
@@ -69,13 +70,13 @@ function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan
69
70
  }
70
71
  const parsed = parseConfig(target.path);
71
72
  if (!parsed.ok)
72
- return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
73
+ return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy, input.env);
73
74
  const config = parsed.value;
74
75
  if (config.mcp !== undefined && !isRecord(config.mcp)) {
75
- return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
76
+ return refusal('invalid_mcp_shape', 'Existing top-level mcp must be a JSON object before vgxness can be merged.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy, input.env);
76
77
  }
77
78
  if (isRecord(config.mcp) && Object.hasOwn(config.mcp, 'vgxness') && !overwriteVgxness) {
78
- return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
79
+ return refusal('existing_vgxness_mcp', 'Existing OpenCode config already contains mcp.vgxness; overwrite is refused by default.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy, input.env);
79
80
  }
80
81
  const agentConflict = findConflictingVgxnessAgents(config, agentPlan);
81
82
  if (agentConflict.length > 0 && !overwriteVgxness) {
@@ -84,16 +85,16 @@ function planUserOpenCodeMcpInstall(input, databasePathSource, server, agentPlan
84
85
  kind: 'manual-check',
85
86
  message: `Manually reconcile conflicting VGXNESS agent entries: ${agentConflict.join(', ')}.`,
86
87
  },
87
- ], agentPlan, overwriteVgxness, bashPermissionPolicy);
88
+ ], agentPlan, overwriteVgxness, bashPermissionPolicy, input.env);
88
89
  }
89
90
  if (agentPlan.installsAgents && config.agent !== undefined && !isRecord(config.agent)) {
90
- return refusal('unsupported_config_shape', 'Existing top-level agent must be a JSON object before VGXNESS agent entries can be merged or overwritten.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
91
+ return refusal('unsupported_config_shape', 'Existing top-level agent must be a JSON object before VGXNESS agent entries can be merged or overwritten.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy, input.env);
91
92
  }
92
93
  if (config.permission !== undefined && !isRecord(config.permission)) {
93
- return refusal('unsupported_config_shape', 'Existing top-level permission must be a JSON object before VGXNESS can set top-level permission.bash to ask.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy);
94
+ return refusal('unsupported_config_shape', 'Existing top-level permission must be a JSON object before VGXNESS can set top-level permission.bash to ask.', input.databasePath, databasePathSource, 'user', target.path, [], agentPlan, overwriteVgxness, bashPermissionPolicy, input.env);
94
95
  }
95
96
  return {
96
- ...baseContract(input.databasePath, databasePathSource, 'user', target.path, true, 'merge-preserve-existing', agentPlan, overwriteVgxness, bashPermissionPolicy),
97
+ ...baseContract(input.databasePath, databasePathSource, 'user', target.path, true, 'merge-preserve-existing', agentPlan, overwriteVgxness, bashPermissionPolicy, input.env),
97
98
  status: 'would_install',
98
99
  action: 'merge',
99
100
  targetPath: target.path,
@@ -157,7 +158,9 @@ function deepEqual(left, right) {
157
158
  const rightKeys = Object.keys(right).sort();
158
159
  return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && deepEqual(left[key], right[key]));
159
160
  }
160
- function baseContract(databasePath, source, scope, targetPath, backupRequired, mergePolicy, agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan)) {
161
+ function baseContract(databasePath, source, scope, targetPath, backupRequired, mergePolicy, agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan), env) {
162
+ const pathEnvironment = resolveVgxnessPathEnvironment({ env });
163
+ const pathProfileUpdate = planVgxnessPathProfileUpdate({ env });
161
164
  return {
162
165
  version: 1,
163
166
  kind: 'mcp-client-install-opencode',
@@ -172,18 +175,20 @@ function baseContract(databasePath, source, scope, targetPath, backupRequired, m
172
175
  mergePolicy,
173
176
  },
174
177
  scope,
175
- warnings: warningsForScope(scope, overwriteVgxness, agentPlan, bashPermissionPolicy),
176
- verificationHints: verificationHints(databasePath, source),
178
+ warnings: warningsForScope(scope, overwriteVgxness, agentPlan, bashPermissionPolicy, pathEnvironment),
179
+ verificationHints: verificationHints(databasePath, source, pathEnvironment),
177
180
  manualTest: manualTestForScope(scope, databasePath, source),
178
181
  installsAgents: agentPlan.installsAgents,
179
182
  agentNames: agentPlan.agentNames,
180
183
  overwriteVgxness,
181
184
  bashPermissionPolicy,
185
+ pathEnvironment,
186
+ ...(pathProfileUpdate.supported && pathProfileUpdate.changed ? { pathProfileUpdate } : {}),
182
187
  ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
183
188
  };
184
189
  }
185
- function refusal(reason, message, databasePath, source, scope, targetPath, extraVerificationHints = [], agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan)) {
186
- const contract = baseContract(databasePath, source, scope, targetPath, false, 'refuse-no-clobber', agentPlan, overwriteVgxness, bashPermissionPolicy);
190
+ function refusal(reason, message, databasePath, source, scope, targetPath, extraVerificationHints = [], agentPlan = createOpenCodeDefaultAgentInstallPlan({ mcpOnly: true }), overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan), env) {
191
+ const contract = baseContract(databasePath, source, scope, targetPath, false, 'refuse-no-clobber', agentPlan, overwriteVgxness, bashPermissionPolicy, env);
187
192
  return {
188
193
  ...contract,
189
194
  verificationHints: [...extraVerificationHints, ...contract.verificationHints],
@@ -193,7 +198,7 @@ function refusal(reason, message, databasePath, source, scope, targetPath, extra
193
198
  ...(targetPath !== undefined ? { targetPath } : {}),
194
199
  };
195
200
  }
196
- function warningsForScope(_scope, overwriteVgxness, agentPlan, bashPermissionPolicy) {
201
+ function warningsForScope(_scope, overwriteVgxness, agentPlan, bashPermissionPolicy, pathEnvironment) {
197
202
  const overwriteWarnings = overwriteVgxness
198
203
  ? [
199
204
  `Reinstall/overwrite is enabled: existing VGXNESS-managed OpenCode entries will be replaced (${agentPlan.installsAgents ? 'mcp.vgxness, default_agent, instructions AGENTS.md, and known VGXNESS agents' : 'mcp.vgxness only'}); unrelated OpenCode config is preserved.`,
@@ -207,6 +212,7 @@ function warningsForScope(_scope, overwriteVgxness, agentPlan, bashPermissionPol
207
212
  return [
208
213
  'Restart OpenCode after installation so it reloads the user MCP config.',
209
214
  'OpenCode project config may override user config for a workspace; check project-level config if vgxness is not visible.',
215
+ ...pathEnvironment.warnings,
210
216
  ...overwriteWarnings,
211
217
  ...bashWarnings,
212
218
  ];
@@ -225,7 +231,7 @@ function manualTestForScope(_scope, databasePath, source) {
225
231
  doctorCommand: createVgxnessMcpDoctorCommand(databasePath, source),
226
232
  };
227
233
  }
228
- function verificationHints(databasePath, source) {
234
+ function verificationHints(databasePath, source, pathEnvironment) {
229
235
  return [
230
236
  {
231
237
  kind: 'restart-client',
@@ -240,5 +246,6 @@ function verificationHints(databasePath, source) {
240
246
  message: 'Run the MCP doctor command after installation.',
241
247
  command: createVgxnessMcpDoctorCommand(databasePath, source),
242
248
  },
249
+ ...(pathEnvironment.vgxness.onPath ? [] : pathEnvironment.remediation.map((message) => ({ kind: 'manual-check', message }))),
243
250
  ];
244
251
  }
@@ -1,9 +1,10 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
- import { dirname } from 'node:path';
2
+ import { dirname, join } from 'node:path';
3
3
  import { canonicalCapabilitySubagentLegacyAliases } from '../../../domain/agents/canonical-agent-manifest.js';
4
4
  import { createManagedProviderConfigBackup } from '../../../setup/backup-rollback-service.js';
5
5
  import { findConflictingVgxnessAgents, planOpenCodeMcpInstall, } from './client-install-opencode-contract.js';
6
- import { createOpenCodeDefaultAgentConfig, createOpenCodeDefaultAgentInstallPlan, vgxnessOpenCodeInstructionsPath, } from './opencode-default-agent-config.js';
6
+ import { applyVgxnessPathProfileUpdate } from './vgxness-path-environment.js';
7
+ import { createOpenCodeDefaultAgentConfig, createOpenCodeDefaultAgentInstallPlan, createOpenCodeGlobalInstructionsContent, createOpenCodeGlobalInstructionsManagedBlock, vgxnessOpenCodeGlobalInstructionsBlockEnd, vgxnessOpenCodeGlobalInstructionsBlockStart, vgxnessOpenCodeInstructionsPath, } from './opencode-default-agent-config.js';
7
8
  const opencodeConfigSchema = 'https://opencode.ai/config.json';
8
9
  export { createInstalledVgxnessMcpServerCommand };
9
10
  function createInstalledVgxnessMcpServerCommand(databasePath, source = 'flag') {
@@ -45,8 +46,13 @@ export async function installOpenCodeMcpClient(input) {
45
46
  return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.bashPermissionPolicy);
46
47
  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, plan.overwriteVgxness, plan.bashPermissionPolicy);
47
48
  }
49
+ const globalInstructions = prepareOpenCodeGlobalInstructionsWrite(plan.targetPath, plan.scope, input.env?.HOME, agentPlan);
50
+ if (!globalInstructions.ok)
51
+ return refusal('post_write_validation_failed', globalInstructions.error.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
48
52
  writeConfig(plan.targetPath, mergeVgxnessOpenCodeConfig({ $schema: opencodeConfigSchema }, plan.server, plan.installsAgents, input.effectiveManagerInstructions));
49
- return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
53
+ writeOpenCodeGlobalInstructions(globalInstructions.value);
54
+ const pathProfileUpdate = applyVgxnessPathProfileUpdate({ env: input.env });
55
+ return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy, plan.pathEnvironment, pathProfileUpdate);
50
56
  }
51
57
  const parsed = parseConfig(plan.targetPath);
52
58
  if (!parsed.ok)
@@ -59,10 +65,15 @@ export async function installOpenCodeMcpClient(input) {
59
65
  const backup = createBackup(plan.targetPath, plan.scope, input.env?.HOME);
60
66
  if (!backup.ok)
61
67
  return refusal('post_write_validation_failed', backup.error.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
68
+ const globalInstructions = prepareOpenCodeGlobalInstructionsWrite(plan.targetPath, plan.scope, input.env?.HOME, agentPlan);
69
+ if (!globalInstructions.ok)
70
+ return refusal('post_write_validation_failed', globalInstructions.error.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
62
71
  const config = parsed.value;
63
72
  const mergedConfig = mergeVgxnessOpenCodeConfig({ ...config, $schema: plan.existingSchema ?? opencodeConfigSchema }, plan.server, plan.installsAgents, input.effectiveManagerInstructions);
64
73
  writeConfig(plan.targetPath, mergedConfig);
65
- return validateInstalledResult(plan.targetPath, backup.value, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy);
74
+ writeOpenCodeGlobalInstructions(globalInstructions.value);
75
+ const pathProfileUpdate = applyVgxnessPathProfileUpdate({ env: input.env });
76
+ return validateInstalledResult(plan.targetPath, backup.value, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness, plan.bashPermissionPolicy, plan.pathEnvironment, pathProfileUpdate);
66
77
  }
67
78
  function mergeVgxnessOpenCodeConfig(config, server, installsAgents, effectiveManagerInstructions) {
68
79
  const merged = {
@@ -101,18 +112,72 @@ function writeConfig(path, config) {
101
112
  mkdirSync(dirname(path), { recursive: true });
102
113
  writeFileSync(path, `${JSON.stringify(config, null, 2)}\n`);
103
114
  }
104
- function createBackup(path, scope, allowedManagedHome) {
115
+ function prepareOpenCodeGlobalInstructionsWrite(opencodeConfigPath, scope, allowedManagedHome, agentPlan) {
116
+ const instructionsPath = join(dirname(opencodeConfigPath), vgxnessOpenCodeInstructionsPath);
117
+ if (!agentPlan.installsAgents)
118
+ return { ok: true, value: { path: instructionsPath, content: '', changed: false } };
119
+ const existing = existsSync(instructionsPath) ? readFileSync(instructionsPath, 'utf8') : undefined;
120
+ const nextContent = reconcileOpenCodeGlobalInstructions(existing);
121
+ if (existing === nextContent)
122
+ return { ok: true, value: { path: instructionsPath, content: nextContent, changed: false } };
123
+ if (existing !== undefined) {
124
+ const backup = createBackup(instructionsPath, scope, allowedManagedHome, {
125
+ reason: 'pre-global-instructions-merge',
126
+ description: 'Backup existing OpenCode global AGENTS.md before aligning VGXNESS global instructions.',
127
+ });
128
+ if (!backup.ok)
129
+ return backup;
130
+ }
131
+ return { ok: true, value: { path: instructionsPath, content: nextContent, changed: true } };
132
+ }
133
+ function writeOpenCodeGlobalInstructions(plan) {
134
+ if (!plan.changed)
135
+ return;
136
+ mkdirSync(dirname(plan.path), { recursive: true });
137
+ writeFileSync(plan.path, plan.content);
138
+ }
139
+ function reconcileOpenCodeGlobalInstructions(existing) {
140
+ const canonical = createOpenCodeGlobalInstructionsContent();
141
+ if (existing === undefined || existing.trim().length === 0)
142
+ return canonical;
143
+ if (looksLikeLegacyVgxnessGlobalInstructions(existing) || looksLikeUnmarkedVgxnessGlobalInstructions(existing))
144
+ return canonical;
145
+ if (existing.includes(vgxnessOpenCodeGlobalInstructionsBlockStart) || existing.includes(vgxnessOpenCodeGlobalInstructionsBlockEnd)) {
146
+ return replaceOpenCodeGlobalInstructionsManagedBlock(existing);
147
+ }
148
+ return `${existing.trimEnd()}\n\n${createOpenCodeGlobalInstructionsManagedBlock()}\n`;
149
+ }
150
+ function replaceOpenCodeGlobalInstructionsManagedBlock(existing) {
151
+ const start = existing.indexOf(vgxnessOpenCodeGlobalInstructionsBlockStart);
152
+ const end = existing.indexOf(vgxnessOpenCodeGlobalInstructionsBlockEnd);
153
+ const block = createOpenCodeGlobalInstructionsManagedBlock();
154
+ if (start < 0 || end < start)
155
+ return `${existing.trimEnd()}\n\n${block}\n`;
156
+ const afterEnd = end + vgxnessOpenCodeGlobalInstructionsBlockEnd.length;
157
+ return `${existing.slice(0, start).trimEnd()}\n\n${block}\n${existing.slice(afterEnd).trimStart()}`;
158
+ }
159
+ function looksLikeLegacyVgxnessGlobalInstructions(content) {
160
+ return /^# VGXNESS Persona\b/m.test(content) || content.includes('gentle-ai:codegraph-guidance');
161
+ }
162
+ function looksLikeUnmarkedVgxnessGlobalInstructions(content) {
163
+ return (/^# Global OpenCode Instructions\b/m.test(content) &&
164
+ content.includes('## Instruction Layering') &&
165
+ content.includes('## Memory and Context Continuity') &&
166
+ content.includes('## VGXNESS Workflow Expectations') &&
167
+ content.includes('## Code Context Providers'));
168
+ }
169
+ function createBackup(path, scope, allowedManagedHome, override) {
105
170
  return createManagedProviderConfigBackup({
106
171
  targetPath: path,
107
172
  provider: 'opencode',
108
173
  scope,
109
174
  createdByOperation: 'mcp-client-install-opencode',
110
- reason: 'pre-merge-safety',
111
- description: 'Backup existing OpenCode config before merging VGXNESS MCP configuration.',
175
+ reason: override?.reason ?? 'pre-merge-safety',
176
+ description: override?.description ?? 'Backup existing OpenCode config before merging VGXNESS MCP configuration.',
112
177
  ...(allowedManagedHome !== undefined ? { allowedManagedHome } : {}),
113
178
  });
114
179
  }
115
- function validateInstalledResult(targetPath, backup, server, databasePath, source, safety, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan)) {
180
+ function validateInstalledResult(targetPath, backup, server, databasePath, source, safety, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness = false, bashPermissionPolicy = bashPermissionPolicyFor(agentPlan), pathEnvironment, pathProfileUpdate) {
116
181
  const parsed = parseConfig(targetPath);
117
182
  if (!parsed.ok)
118
183
  return refusal('post_write_validation_failed', 'OpenCode config could not be re-read after write.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, bashPermissionPolicy);
@@ -126,6 +191,10 @@ function validateInstalledResult(targetPath, backup, server, databasePath, sourc
126
191
  if (!isRecord(agent) || parsed.value.default_agent !== agentPlan.defaultAgent || agentPlan.agentNames.some((name) => !isRecord(agent[name]))) {
127
192
  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, overwriteVgxness, bashPermissionPolicy);
128
193
  }
194
+ const instructionsPath = join(dirname(targetPath), vgxnessOpenCodeInstructionsPath);
195
+ if (!existsSync(instructionsPath) || !readFileSync(instructionsPath, 'utf8').includes('## Memory and Context Continuity')) {
196
+ return refusal('post_write_validation_failed', 'OpenCode config was written but global AGENTS.md instructions did not validate.', databasePath, source, server, safety, targetPath, verificationHints, warningMessages, manualTestGuidance, agentPlan, overwriteVgxness, bashPermissionPolicy);
197
+ }
129
198
  }
130
199
  const permission = parsed.value.permission;
131
200
  if (!isRecord(permission) || permission.bash !== 'ask') {
@@ -146,6 +215,8 @@ function validateInstalledResult(targetPath, backup, server, databasePath, sourc
146
215
  agentNames: agentPlan.agentNames,
147
216
  overwriteVgxness,
148
217
  bashPermissionPolicy,
218
+ pathEnvironment,
219
+ ...(pathProfileUpdate.supported ? { pathProfileUpdate } : {}),
149
220
  ...(agentPlan.defaultAgent !== undefined ? { defaultAgent: agentPlan.defaultAgent } : {}),
150
221
  };
151
222
  }
@@ -3,6 +3,8 @@ import { projectCanonicalAgentManifestToOpenCode, withEffectiveOpenCodeManagerIn
3
3
  export const vgxnessOpenCodeDefaultAgent = canonicalDefaultAgentName;
4
4
  export const vgxnessOpenCodePromptContractVersion = canonicalPromptContractVersion;
5
5
  export const vgxnessOpenCodeInstructionsPath = 'AGENTS.md';
6
+ export const vgxnessOpenCodeGlobalInstructionsBlockStart = '<!-- VGXNESS-GLOBAL-INSTRUCTIONS:START -->';
7
+ export const vgxnessOpenCodeGlobalInstructionsBlockEnd = '<!-- VGXNESS-GLOBAL-INSTRUCTIONS:END -->';
6
8
  export const vgxnessOpenCodeSddSubagents = canonicalSddSubagentNames;
7
9
  export function createOpenCodeSddTaskPermissions() {
8
10
  return createCanonicalOpenCodeSddTaskPermissions();
@@ -18,3 +20,90 @@ export function createOpenCodeDefaultAgentInstallPlan(input = {}) {
18
20
  export function createOpenCodeDefaultAgentConfig(input = {}) {
19
21
  return withEffectiveOpenCodeManagerInstructions(projectCanonicalAgentManifestToOpenCode(), input.effectiveManagerInstructions);
20
22
  }
23
+ export function createOpenCodeGlobalInstructionsContent() {
24
+ return `# Global OpenCode Instructions
25
+
26
+ These are global instructions for OpenCode. They define user preferences, context-continuity expectations, and broad safety rules. They do not replace project \`AGENTS.md\` / \`CLAUDE.md\` files, VGXNESS-managed agent prompts, MCP instructions, or task-specific skills.
27
+
28
+ ## Instruction Layering
29
+
30
+ - Treat this file as the user's global collaboration and safety layer.
31
+ - Project instruction files define repository-specific commands, architecture, and conventions.
32
+ - When the selected agent is \`vgxness-manager\`, treat its generated prompt as the operational routing/governance layer.
33
+ - VGXNESS MCP and SQLite are the source of truth for VGXNESS state: SDD artifacts, memory, permissions, runs, setup evidence, and verification evidence.
34
+ - OpenCode and Claude execute provider-native. VGXNESS coordinates and governs; it is not a provider runtime.
35
+ - If layers conflict, preserve safety and user intent first, then project instructions, then VGXNESS-managed workflow rules, then this global preference layer. Report the conflict instead of guessing.
36
+
37
+ ## Rules
38
+
39
+ - Never add \`Co-Authored-By\` or AI attribution to commits.
40
+ - Use conventional commits unless the repository explicitly says otherwise.
41
+ - Ask at most one focused question at a time. After asking, stop and wait.
42
+ - Never agree with technical claims without verification. Check code, docs, config, live state, or evidence first.
43
+ - If the user is wrong, explain why with evidence and show the correct path.
44
+ - If you were wrong, acknowledge it clearly and explain the correction.
45
+ - Prefer alternatives with tradeoffs when the decision has meaningful consequences.
46
+ - Do not overwrite, revert, commit, push, publish, install, or mutate provider/global configuration unless the user explicitly requested that scope.
47
+ - Protect user work and unrelated changes. Before editing a repo, check branch/status and avoid working directly on \`main\`/\`master\` unless the task is explicitly a local maintenance step.
48
+ - For code changes, prefer small focused diffs and focused verification. Full gates are for release, high-risk, pre-merge, or broad cross-cutting changes.
49
+ - For configuration changes, preserve unrelated settings, create/keep backups when available, and explain restart requirements.
50
+
51
+ ## Language and Tone
52
+
53
+ - Respond in the same language the user uses.
54
+ - Use a warm, professional, direct tone.
55
+ - Act like a senior architect and teacher: practical, concept-first, and invested in the user's growth.
56
+ - Push back when a request is risky, vague, or technically weak. Be precise, not rude.
57
+
58
+ ## Philosophy
59
+
60
+ - CONCEPTS > CODE.
61
+ - AI is a tool; the human leads.
62
+ - Solid foundations before shortcuts.
63
+ - Architecture and correctness matter more than immediacy.
64
+ - Prefer safe, testable, reversible steps.
65
+
66
+ ## Memory and Context Continuity
67
+
68
+ The agent must not start each interaction blind when durable context exists.
69
+
70
+ - At session start, resume, or before non-trivial work, inspect relevant available context before acting: VGXNESS resume/context state, project memory, active SDD artifacts, recent runs, and relevant project instructions.
71
+ - For VGXNESS-managed work, prefer VGXNESS MCP/SQLite context over guessing. Use available memory/resume/context tools such as \`vgxness_resume_context\`, \`vgxness_memory_search\`, \`vgxness_memory_get\`, \`vgxness_sdd_status\`, \`vgxness_sdd_get_artifact\`, and run/status tools when they are available and permitted.
72
+ - Memory is advisory context, not proof. It can guide recall and continuity, but it does not prove acceptance, readiness, verification, authorization, or current source state. Verify against live files, git, commands, docs, or provider config before making claims.
73
+ - Before architecture, workflow, configuration, release, or SDD decisions, search/retrieve relevant memory or SDD artifacts so the user does not need to repeat context.
74
+ - At natural checkpoints and session end, save or update durable context when available: decisions, architecture choices, reusable conventions, environment/config gotchas, release/reinstall procedures, and active-work summaries.
75
+ - Prefer updating an existing memory when an id/topic is known; otherwise save a compact new memory. Avoid duplicate memory spam.
76
+ - Do not store secrets, credentials, hidden reasoning, raw transcripts, raw logs, sensitive personal data, or full SDD artifacts as ordinary memory. SDD artifacts belong in the SDD artifact store.
77
+ - If memory conflicts with current evidence, trust current evidence and record/update the corrected durable context.
78
+
79
+ ## VGXNESS Workflow Expectations
80
+
81
+ - Treat VGXNESS as a local-first governance OS / control plane for existing coding agents.
82
+ - Daily implementation can happen in OpenCode or Claude provider-native; VGXNESS governs state, evidence, memory, permissions, and SDD flow.
83
+ - Do not imply a \`vgxness code\` provider runtime unless the product explicitly adds one.
84
+ - Human acceptance is explicit. Drafting artifacts, generating plans, or passing tests does not equal acceptance.
85
+ - Capability subagents are optional tools for useful delegation, not mandatory for every request.
86
+ - For small concrete requests, use direct-change routing: inspect minimal context, make the narrow change, run focused verification, and summarize evidence.
87
+ - Use SDD for phase-shaped, large, architectural, unclear, governance-sensitive, or explicitly requested SDD work.
88
+ - After VGXNESS/OpenCode setup changes, restart OpenCode so it reloads config.
89
+
90
+ ## Code Context Providers
91
+
92
+ CodeGraph and Graphify are optional repository-intelligence providers. They provide advisory evidence; they are not required for every task.
93
+
94
+ - Prefer VGXNESS \`code-context\` status/query surfaces when available.
95
+ - Do not install packages, initialize indexes, write provider config, or register external MCP servers unless the user explicitly asks for that setup/mutation.
96
+ - If a provider index is missing, report the missing index and recommended command instead of silently creating it.
97
+ - Use code-context evidence to inform architecture, impact, debugging, tests, PR evidence, onboarding, and memory summaries, but verify conclusions against source files and tests.
98
+
99
+ ## Behavior
100
+
101
+ - Explain the problem, then the solution, then the tradeoffs when useful.
102
+ - Use examples when they clarify the concept.
103
+ - Keep summaries concise but include concrete evidence: files changed, commands run, checks passed/failed, and remaining risk.
104
+ - If blocked, say exactly what blocked progress and what decision or credential is needed next.
105
+ `;
106
+ }
107
+ export function createOpenCodeGlobalInstructionsManagedBlock() {
108
+ return `${vgxnessOpenCodeGlobalInstructionsBlockStart}\n${createOpenCodeGlobalInstructionsContent().trimEnd()}\n${vgxnessOpenCodeGlobalInstructionsBlockEnd}`;
109
+ }
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { dirname, join, relative, resolve } from 'node:path';
3
3
  import { SUPPORTED_VGX_MCP_TOOL_NAMES } from '../../../mcp/schema.js';
4
4
  import { resolveOpenCodeMcpInstallTarget } from './client-install-opencode-contract.js';
5
+ import { resolveVgxnessPathEnvironment } from './vgxness-path-environment.js';
5
6
  const serverName = 'vgxness';
6
7
  const expectedToolCount = SUPPORTED_VGX_MCP_TOOL_NAMES.length;
7
8
  export function createOpenCodeMcpVisibilityReport(options) {
@@ -17,6 +18,7 @@ export function createOpenCodeMcpVisibilityReport(options) {
17
18
  const userReport = userTarget.ok ? userVisibilityReport(userTarget.path, projectVisibleFromCurrentCwd) : undefined;
18
19
  const selectedTargetPath = userTarget.ok ? userTarget.path : '$HOME/.config/opencode/opencode.json';
19
20
  const selectedReady = targetScope === 'user' ? userReport?.configExists === true && userReport.hasVgxnessServer : projectVisibleFromCurrentCwd;
21
+ const pathEnvironment = resolveVgxnessPathEnvironment({ env: options.env });
20
22
  return {
21
23
  version: 1,
22
24
  kind: 'mcp-doctor-opencode-visibility',
@@ -36,6 +38,7 @@ export function createOpenCodeMcpVisibilityReport(options) {
36
38
  },
37
39
  visibility: { cwdInsideProject, configExists: projectConfigExists, hasVgxnessServer: projectHasVgxnessServer },
38
40
  expected: { serverName, toolCount: expectedToolCount, visibleFromCurrentCwd: selectedReady },
41
+ pathEnvironment,
39
42
  guidance: buildGuidance({
40
43
  cwdInsideProject,
41
44
  configExists: projectConfigExists,
@@ -0,0 +1,186 @@
1
+ import { accessSync, constants, copyFileSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { delimiter, dirname, extname, join, resolve } from 'node:path';
3
+ export const vgxnessPathProfileBlockStart = '# >>> VGXNESS managed PATH >>>';
4
+ export const vgxnessPathProfileBlockEnd = '# <<< VGXNESS managed PATH <<<';
5
+ export function resolveVgxnessPathEnvironment(input) {
6
+ const env = input.env ?? process.env;
7
+ const home = homeFromEnv(env);
8
+ const pathEntries = splitPath(Object.hasOwn(env, 'PATH') ? env.PATH : process.env.PATH, home);
9
+ const bunGlobalBinPath = bunGlobalBin(env, home);
10
+ const vgxness = resolveCommandOnPath('vgxness', pathEntries);
11
+ const opencodeCli = resolveCommandOnPath('opencode', pathEntries);
12
+ const bunGlobalBinStatus = {
13
+ path: bunGlobalBinPath,
14
+ exists: existsSync(bunGlobalBinPath),
15
+ onPath: pathEntries.some((entry) => samePath(entry, bunGlobalBinPath)),
16
+ hasVgxnessExecutable: resolveCommandInDirectory('vgxness', bunGlobalBinPath).onPath,
17
+ };
18
+ const warnings = pathWarnings(vgxness, bunGlobalBinStatus);
19
+ return {
20
+ command: 'vgxness',
21
+ pathEntries,
22
+ vgxness,
23
+ opencodeCli,
24
+ bunGlobalBin: bunGlobalBinStatus,
25
+ warnings,
26
+ remediation: pathRemediation(vgxness, bunGlobalBinStatus, opencodeCli),
27
+ };
28
+ }
29
+ export function planVgxnessPathProfileUpdate(input) {
30
+ const env = input.env ?? process.env;
31
+ const home = homeFromEnv(env);
32
+ const targetBinDir = bunGlobalBin(env, home);
33
+ const managedBlock = createManagedPathBlock(targetBinDir, home);
34
+ if (home === undefined) {
35
+ return {
36
+ supported: false,
37
+ changed: false,
38
+ backupRequired: false,
39
+ managedBlock,
40
+ reason: 'HOME is not set; VGXNESS cannot safely update a shell profile PATH block.',
41
+ targetBinDir,
42
+ };
43
+ }
44
+ const profilePath = shellProfilePath(env, home);
45
+ const existing = existsSync(profilePath) ? readFileSync(profilePath, 'utf8') : undefined;
46
+ const nextContent = reconcileManagedPathBlock(existing, managedBlock);
47
+ const pathEnvironment = resolveVgxnessPathEnvironment({ env });
48
+ const blockExists = existing?.includes(vgxnessPathProfileBlockStart) === true || existing?.includes(vgxnessPathProfileBlockEnd) === true;
49
+ const shouldEnsureProfile = blockExists || (!pathEnvironment.vgxness.onPath && !pathEnvironment.bunGlobalBin.onPath);
50
+ const changed = shouldEnsureProfile && existing !== nextContent;
51
+ return {
52
+ supported: true,
53
+ changed,
54
+ backupRequired: changed && existing !== undefined,
55
+ managedBlock,
56
+ reason: changed
57
+ ? 'A VGXNESS-managed shell PATH block is planned so path-based OpenCode MCP command `vgxness mcp start` can resolve after restarting terminal/OpenCode.'
58
+ : 'No shell profile PATH update is needed for the current environment.',
59
+ profilePath,
60
+ targetBinDir,
61
+ };
62
+ }
63
+ export function applyVgxnessPathProfileUpdate(input) {
64
+ const plan = planVgxnessPathProfileUpdate(input);
65
+ if (!plan.supported || !plan.changed || plan.profilePath === undefined)
66
+ return plan;
67
+ const existing = existsSync(plan.profilePath) ? readFileSync(plan.profilePath, 'utf8') : undefined;
68
+ const backupPath = existing === undefined ? undefined : createProfileBackup(plan.profilePath);
69
+ if (backupPath !== undefined)
70
+ copyFileSync(plan.profilePath, backupPath);
71
+ mkdirSync(dirname(plan.profilePath), { recursive: true });
72
+ writeFileSync(plan.profilePath, reconcileManagedPathBlock(existing, plan.managedBlock));
73
+ return { ...plan, ...(backupPath === undefined ? {} : { backupPath }) };
74
+ }
75
+ function pathWarnings(vgxness, bunGlobalBinStatus) {
76
+ if (vgxness.onPath)
77
+ return [];
78
+ return [
79
+ `PATH-based OpenCode MCP command \`vgxness mcp start\` may fail because \`vgxness\` was not found on the current PATH. OpenCode may report "Executable not found in $PATH" until ${bunGlobalBinStatus.path} is on PATH.`,
80
+ ];
81
+ }
82
+ function pathRemediation(vgxness, bunGlobalBinStatus, opencodeCli) {
83
+ const steps = vgxness.onPath
84
+ ? ['`vgxness` resolves from the current PATH; keep OpenCode config path-based as `vgxness mcp start`.']
85
+ : [
86
+ 'Keep OpenCode config portable as `mcp.vgxness.command: ["vgxness", "mcp", "start"]`; do not replace it with an absolute binary path by default.',
87
+ `Add Bun global binaries to your shell PATH, for example: export PATH="$HOME/.bun/bin:$PATH" (${bunGlobalBinStatus.path}).`,
88
+ 'Restart terminal/OpenCode after PATH profile changes so the provider process inherits the updated PATH.',
89
+ ];
90
+ return opencodeCli.onPath
91
+ ? [...steps, 'After restarting, verify with `opencode mcp list` and confirm the `vgxness` server is listed.']
92
+ : [...steps, 'Skipped `opencode mcp list` verification because the OpenCode CLI was not found on the current PATH.'];
93
+ }
94
+ function createManagedPathBlock(targetBinDir, home) {
95
+ const pathExpression = home !== undefined && samePath(targetBinDir, join(home, '.bun', 'bin')) ? '$HOME/.bun/bin' : targetBinDir;
96
+ return [
97
+ vgxnessPathProfileBlockStart,
98
+ '# Ensures OpenCode can resolve the path-based VGXNESS MCP command.',
99
+ `export PATH="${pathExpression}:$PATH"`,
100
+ vgxnessPathProfileBlockEnd,
101
+ ].join('\n');
102
+ }
103
+ function reconcileManagedPathBlock(existing, managedBlock) {
104
+ if (existing === undefined || existing.length === 0)
105
+ return `${managedBlock}\n`;
106
+ const start = existing.indexOf(vgxnessPathProfileBlockStart);
107
+ const end = existing.indexOf(vgxnessPathProfileBlockEnd);
108
+ if (start >= 0 && end >= start) {
109
+ const afterEnd = end + vgxnessPathProfileBlockEnd.length;
110
+ return `${existing.slice(0, start).trimEnd()}\n\n${managedBlock}\n${existing.slice(afterEnd).trimStart()}`;
111
+ }
112
+ return `${existing.trimEnd()}\n\n${managedBlock}\n`;
113
+ }
114
+ function shellProfilePath(env, home) {
115
+ const explicit = env.VGXNESS_SHELL_PROFILE;
116
+ if (explicit !== undefined && explicit.trim().length > 0)
117
+ return resolve(home, explicit);
118
+ const shell = env.SHELL ?? '';
119
+ if (shell.endsWith('/zsh') || shell === 'zsh')
120
+ return join(home, '.zshrc');
121
+ if (shell.endsWith('/bash') || shell === 'bash')
122
+ return join(home, '.bashrc');
123
+ return join(home, '.profile');
124
+ }
125
+ function createProfileBackup(profilePath) {
126
+ const stamp = new Date().toISOString().replaceAll('-', '').replaceAll(':', '').replace(/\.\d{3}Z$/, 'Z');
127
+ return `${profilePath}.backup-${stamp}-vgxness-path`;
128
+ }
129
+ function bunGlobalBin(env, home) {
130
+ const bunInstall = env.BUN_INSTALL;
131
+ if (bunInstall !== undefined && bunInstall.trim().length > 0)
132
+ return resolve(expandHome(bunInstall, home), 'bin');
133
+ return home === undefined ? resolve('.bun', 'bin') : join(home, '.bun', 'bin');
134
+ }
135
+ function homeFromEnv(env) {
136
+ const home = env.HOME;
137
+ return home === undefined || home.trim().length === 0 ? undefined : home;
138
+ }
139
+ function splitPath(pathValue, home) {
140
+ if (pathValue === undefined || pathValue.length === 0)
141
+ return [];
142
+ return pathValue
143
+ .split(delimiter)
144
+ .filter((entry) => entry.length > 0)
145
+ .map((entry) => resolve(expandHome(entry, home)));
146
+ }
147
+ function expandHome(pathValue, home) {
148
+ if (home !== undefined && (pathValue === '~' || pathValue.startsWith('~/')))
149
+ return join(home, pathValue.slice(2));
150
+ return pathValue;
151
+ }
152
+ function resolveCommandOnPath(command, pathEntries) {
153
+ for (const pathEntry of pathEntries) {
154
+ const resolved = resolveCommandInDirectory(command, pathEntry);
155
+ if (resolved.onPath)
156
+ return resolved;
157
+ }
158
+ return { onPath: false };
159
+ }
160
+ function resolveCommandInDirectory(command, directory) {
161
+ for (const candidateName of commandCandidateNames(command)) {
162
+ const candidate = join(directory, candidateName);
163
+ if (isExecutable(candidate))
164
+ return { onPath: true, resolvedPath: candidate };
165
+ }
166
+ return { onPath: false };
167
+ }
168
+ function commandCandidateNames(command) {
169
+ if (process.platform !== 'win32' || extname(command).length > 0)
170
+ return [command];
171
+ return [command, `${command}.cmd`, `${command}.exe`, `${command}.bat`, `${command}.ps1`];
172
+ }
173
+ function isExecutable(path) {
174
+ try {
175
+ accessSync(path, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
176
+ return statSync(path).isFile();
177
+ }
178
+ catch {
179
+ return false;
180
+ }
181
+ }
182
+ function samePath(left, right) {
183
+ const resolvedLeft = resolve(left);
184
+ const resolvedRight = resolve(right);
185
+ return process.platform === 'win32' ? resolvedLeft.toLowerCase() === resolvedRight.toLowerCase() : resolvedLeft === resolvedRight;
186
+ }
@@ -22,6 +22,11 @@ function renderOpenCode(plan) {
22
22
  field('Installs agents', yesNo(plan.opencode.installsAgents)),
23
23
  field('Reinstall entries', yesNo(plan.opencode.overwriteVgxness === true)),
24
24
  field('Bash policy', formatBashPermissionPolicy(plan.opencode.bashPermissionPolicy)),
25
+ field('VGXNESS on PATH', yesNo(plan.opencode.pathEnvironment.vgxness.onPath)),
26
+ field('Bun bin on PATH', yesNo(plan.opencode.pathEnvironment.bunGlobalBin.onPath)),
27
+ ...(plan.opencode.pathProfileUpdate === undefined
28
+ ? []
29
+ : [field('PATH profile', `${plan.opencode.pathProfileUpdate.changed ? 'planned' : 'unchanged'} — ${plan.opencode.pathProfileUpdate.profilePath ?? 'not supported'}`)]),
25
30
  field('Agent names', plan.opencode.agentNames.length === 0 ? 'none' : plan.opencode.agentNames.join(', ')),
26
31
  ], { status });
27
32
  }
@@ -263,6 +263,8 @@ function isManagedUserGlobalProviderTarget(targetPath, allowedManagedHome) {
263
263
  const home = resolveManagedHome(allowedManagedHome);
264
264
  if (targetPath === resolve(home, '.config', 'opencode', 'opencode.json'))
265
265
  return true;
266
+ if (targetPath === resolve(home, '.config', 'opencode', 'AGENTS.md'))
267
+ return true;
266
268
  if (targetPath === resolve(home, '.claude.json'))
267
269
  return true;
268
270
  if (targetPath === resolve(home, '.claude', 'CLAUDE.md'))
@@ -141,6 +141,8 @@ function setupPlanFromOpenCode(input) {
141
141
  agentNames: input.opencode.agentNames,
142
142
  ...(input.opencode.overwriteVgxness ? { overwriteVgxness: true } : {}),
143
143
  bashPermissionPolicy: input.opencode.bashPermissionPolicy,
144
+ pathEnvironment: input.opencode.pathEnvironment,
145
+ ...(input.opencode.pathProfileUpdate === undefined ? {} : { pathProfileUpdate: input.opencode.pathProfileUpdate }),
144
146
  },
145
147
  actions: [],
146
148
  conflicts: [
@@ -171,6 +173,8 @@ function setupPlanFromOpenCode(input) {
171
173
  agentNames: input.opencode.agentNames,
172
174
  ...(input.opencode.overwriteVgxness ? { overwriteVgxness: true } : {}),
173
175
  bashPermissionPolicy: input.opencode.bashPermissionPolicy,
176
+ pathEnvironment: input.opencode.pathEnvironment,
177
+ ...(input.opencode.pathProfileUpdate === undefined ? {} : { pathProfileUpdate: input.opencode.pathProfileUpdate }),
174
178
  },
175
179
  actions: [
176
180
  {
@@ -180,18 +184,22 @@ function setupPlanFromOpenCode(input) {
180
184
  targetPath: input.opencode.targetPath,
181
185
  backupRequired: input.opencode.backupRequired,
182
186
  },
187
+ ...pathProfileUpdateActions(input.opencode.pathProfileUpdate),
183
188
  ],
184
189
  conflicts: [],
185
- backupsPlanned: input.opencode.backupRequired
186
- ? [
187
- {
188
- targetPath: input.opencode.targetPath,
189
- reason: input.opencode.overwriteVgxness
190
- ? 'Existing OpenCode config would receive a managed VGXNESS backup with rollback metadata before VGXNESS entries are overwritten/reinstalled.'
191
- : 'Existing OpenCode config would receive a managed VGXNESS backup with rollback metadata before merge.',
192
- },
193
- ]
194
- : [],
190
+ backupsPlanned: [
191
+ ...(input.opencode.backupRequired
192
+ ? [
193
+ {
194
+ targetPath: input.opencode.targetPath,
195
+ reason: input.opencode.overwriteVgxness
196
+ ? 'Existing OpenCode config would receive a managed VGXNESS backup with rollback metadata before VGXNESS entries are overwritten/reinstalled.'
197
+ : 'Existing OpenCode config would receive a managed VGXNESS backup with rollback metadata before merge.',
198
+ },
199
+ ]
200
+ : []),
201
+ ...pathProfileUpdateBackups(input.opencode.pathProfileUpdate),
202
+ ],
195
203
  nextCommands: [
196
204
  input.opencode.overwriteVgxness ? 'vgxness setup reinstall --yes' : 'vgxness setup apply --yes',
197
205
  'vgxness doctor',
@@ -202,6 +210,29 @@ function setupPlanFromOpenCode(input) {
202
210
  function bashPermissionDescription(policy) {
203
211
  return policy.manager === 'deny' ? '; sets top-level permission.bash to ask and manager bash to deny' : '; sets top-level permission.bash to ask';
204
212
  }
213
+ function pathProfileUpdateActions(update) {
214
+ if (update === undefined || !update.supported || !update.changed || update.profilePath === undefined)
215
+ return [];
216
+ return [
217
+ {
218
+ id: 'opencode-path-profile-update',
219
+ description: `${update.reason} Target shell profile: ${update.profilePath}`,
220
+ mutating: false,
221
+ targetPath: update.profilePath,
222
+ backupRequired: update.backupRequired,
223
+ },
224
+ ];
225
+ }
226
+ function pathProfileUpdateBackups(update) {
227
+ if (update === undefined || !update.supported || !update.changed || !update.backupRequired || update.profilePath === undefined)
228
+ return [];
229
+ return [
230
+ {
231
+ targetPath: update.profilePath,
232
+ reason: 'Existing shell profile would receive a managed VGXNESS backup before updating the PATH block used by the path-based OpenCode MCP command.',
233
+ },
234
+ ];
235
+ }
205
236
  function resolveSetupDatabase(input) {
206
237
  if (input.mode === 'global') {
207
238
  const resolved = resolveMemoryDatabasePath({ cwd: input.workspaceRoot, env: input.env });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgxness",
3
- "version": "1.21.0",
3
+ "version": "1.21.2",
4
4
  "description": "Local-first governance OS, CLI and MCP control plane for existing coding agents, SDD, memory, runs, permissions, and OpenCode setup.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {