vgxness 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -58,7 +58,7 @@ npm run probe:bun
58
58
  npm run probe:bun -- --yes # mutates dependencies only after explicit consent
59
59
  ```
60
60
 
61
- Runtime SQLite readiness is proven with `bun:sqlite`. `bun run verify:bun-sqlite` copies the source migrations into a temporary directory, applies them against a temporary database, checks foreign keys, busy timeout, FTS/search, transaction rollback, integrity, and cleanup. `bun run verify:node-sqlite` remains optional Node native-binding tooling coverage only.
61
+ Runtime SQLite readiness is proven only with `bun:sqlite`. `bun run verify:bun-sqlite` copies the source migrations into a temporary directory, applies them against a temporary database, checks foreign keys, busy timeout, FTS/search, transaction rollback, integrity, and cleanup. Node.js remains development/build/test tooling, but real local SQLite storage is supported through the Bun runtime path.
62
62
 
63
63
  #### Bun package evidence
64
64
 
@@ -6,6 +6,7 @@ import { InMemoryRunGateway } from '../../code/runtime/gateways.js';
6
6
  import { MemoryServiceCodeGateway } from '../../code/runtime/memory-service-gateway.js';
7
7
  import { RunsCodeRunGateway } from '../../code/runtime/runs-code-run-gateway.js';
8
8
  import { SddWorkflowGateway } from '../../code/runtime/sdd-workflow-gateway.js';
9
+ import React from 'react';
9
10
  import { ProviderChangePlanService } from '../../mcp/provider-change-plan.js';
10
11
  import { ProviderDoctorService } from '../../mcp/provider-doctor.js';
11
12
  import { ProviderStatusService } from '../../mcp/provider-status.js';
@@ -20,14 +21,47 @@ import { buildDashboardApprovalsReadModel, buildDashboardInstallationReadModel,
20
21
  import { renderDashboard as renderStatusDashboard } from '../dashboard-renderer.js';
21
22
  import { sanitizeDashboardError, } from '../dashboard-tui-read-model.js';
22
23
  import { dashboardKeyFromInput, loadInitialDashboardState, reduceDashboardKey, refreshDashboard, renderDashboard as renderInteractiveDashboard, resolveDashboardRenderStyle, } from '../interactive-dashboard.js';
24
+ import { MainMenuApp } from '../tui/main-menu/index.js';
25
+ import { renderInkApp } from '../tui/render-ink-app.js';
23
26
  import { canRunInteractiveTui } from '../tui/terminal-capabilities.js';
24
- import { collectRunDetails, collectRunInsights, createSetupLifecycleService, readSetupStatus } from './setup-dispatcher.js';
27
+ import { collectRunDetails, collectRunInsights, createSetupLifecycleService, readSetupStatus, runSetupTuiCommand } from './setup-dispatcher.js';
25
28
  function defaultNoTtyGuidance() {
26
29
  return ([
27
- 'VGXNESS interactive dashboard requires a TTY; no provider config was written.',
28
- 'Next: rerun `vgx` in an interactive terminal, use `vgx dashboard interactive` for the same dashboard, or run `vgx dashboard status --project <name>` for scriptable read-only output.',
30
+ 'VGXNESS main menu requires a TTY; no provider config was written.',
31
+ 'Next: rerun `vgx` in an interactive terminal, run `vgx init --plan` or `vgx setup plan` for read-only installation guidance, use `vgx dashboard interactive` for the dashboard, or run `vgx dashboard status --project <name>` for scriptable read-only output.',
29
32
  ].join('\n') + '\n');
30
33
  }
34
+ function guidanceForMainMenuResult(result) {
35
+ if (result.type === 'show-doctor-guidance')
36
+ return ['Doctor / recovery guidance:', '- Read-only status: `vgx setup status`', '- Provider doctor: `vgx mcp doctor opencode`', '- No provider config was written.'].join('\n') + '\n';
37
+ if (result.type === 'show-sdd-guidance')
38
+ return ['SDD / workflow guidance:', '- Use VGXNESS MCP tools from OpenCode for normal SDD progression.', '- Manual status: `vgx sdd status --project <name> --change <change>`', '- No provider config was written.'].join('\n') + '\n';
39
+ if (result.type === 'show-advanced-cli')
40
+ return ['Advanced CLI guidance:', '- Installation preview: `vgx init --plan` or `vgx setup plan`', '- Dashboard: `vgx dashboard interactive`', '- Scriptable status: `vgx dashboard status --project <name>`', '- No provider config was written.'].join('\n') + '\n';
41
+ if (result.type === 'exit')
42
+ return 'Exited main menu; no provider config was written.\n';
43
+ return undefined;
44
+ }
45
+ async function renderDefaultMainMenu(environment, onResult) {
46
+ await renderInkApp(React.createElement(MainMenuApp, { onResult }), {
47
+ stdin: environment.stdin,
48
+ stdout: environment.stdout,
49
+ });
50
+ }
51
+ export async function runDefaultInteractiveEntrypointWithMainMenu(environment, input = {}) {
52
+ if (!canRunInteractiveTui(environment.stdin, environment.stdout))
53
+ return okText(defaultNoTtyGuidance());
54
+ let selected;
55
+ await (input.renderMainMenu ?? ((onResult) => renderDefaultMainMenu(environment, onResult)))((result) => {
56
+ selected = result;
57
+ });
58
+ const result = selected ?? { type: 'exit' };
59
+ if (result.type === 'open-setup')
60
+ return (input.setupTui ?? runSetupTuiCommand)(environment);
61
+ if (result.type === 'open-dashboard')
62
+ return (input.dashboardInteractive ?? runDashboardInteractiveCommand)({ positionals: ['dashboard', 'interactive'], flags: {} }, environment);
63
+ return okText(guidanceForMainMenuResult(result) ?? 'No provider config was written.\n');
64
+ }
31
65
  function buildDashboardProfiles(managerProfiles, project, setupStatus, sectionErrors) {
32
66
  const resolved = managerProfiles.resolveEffectiveManager({ project, scope: 'project', managerName: 'vgxness-manager' });
33
67
  if (!resolved.ok) {
@@ -431,9 +465,7 @@ export async function runDashboardInteractiveCommand(parsed, environment) {
431
465
  }
432
466
  }
433
467
  export async function runDefaultInteractiveEntrypoint(environment) {
434
- if (!canRunInteractiveTui(environment.stdin, environment.stdout))
435
- return okText(defaultNoTtyGuidance());
436
- return runDashboardInteractiveCommand({ positionals: ['dashboard', 'interactive'], flags: {} }, environment);
468
+ return runDefaultInteractiveEntrypointWithMainMenu(environment);
437
469
  }
438
470
  export async function runCodeCliCommand(parsed, environment) {
439
471
  const [, command] = parsed.positionals;
@@ -2,12 +2,12 @@ import { formatTuiFooter, tuiBadges } from '../visual/index.js';
2
2
  import { mainMenuOptionIds } from './main-menu-state.js';
3
3
  const optionCopy = {
4
4
  setup: {
5
- label: 'Setup / Guided installation',
6
- description: 'Review local storage and OpenCode integration safely.',
5
+ label: 'Installation',
6
+ description: 'Start guided installation for memory and OpenCode integration safely.',
7
7
  badges: [tuiBadges.requiresConfirmation],
8
- detailTitle: 'Setup assistant',
8
+ detailTitle: 'Installation',
9
9
  detailLines: [
10
- 'Opens the existing Setup TUI.',
10
+ 'Opens the guided Installation TUI.',
11
11
  'Plan/review screens are read-only.',
12
12
  'Provider writes require final confirmation; OpenCode is the only automatic provider with explicit consent.',
13
13
  ],
@@ -60,12 +60,12 @@ export function buildMainMenuViewModel(state) {
60
60
  return {
61
61
  title: 'VGXNESS Main Menu',
62
62
  subtitle: 'Keyboard-first local control plane',
63
- contextLines: ['Choose where to go next. The menu itself is read-only and does not write provider config.'],
63
+ contextLines: ['Choose where to go next. Installation is first; the menu itself is read-only and does not write provider config.'],
64
64
  options,
65
65
  detail: { title: focused.detailTitle, lines: focused.detailLines, badges: focused.badges },
66
66
  safetyLines: [
67
67
  'Previews are read-only.',
68
- 'Setup/provider writes require final confirmation.',
68
+ 'Installation/provider writes require final confirmation.',
69
69
  'OpenCode is the only automatic provider path and still requires explicit consent.',
70
70
  ],
71
71
  helpLines: state.helpVisible
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Text } from 'ink';
3
3
  import { badgeLabels, Panel, SetupScreenFrame, setupFooterHints } from './screen-components.js';
4
4
  export function CancellationScreen() {
5
- return (_jsx(SetupScreenFrame, { title: "Setup cancelled", tone: "cancelled", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Cancelled", badge: { label: badgeLabels.cancelled }, children: [_jsx(Text, { children: "Setup was not completed." }), _jsx(Text, { children: "No provider config was written." })] }) }));
5
+ return (_jsx(SetupScreenFrame, { title: "Setup cancelled", tone: "cancelled", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Cancelled", badge: { label: badgeLabels.cancelled }, children: [_jsx(Text, { children: "Setup was not completed." }), _jsx(Text, { children: "No provider config was written." }), _jsx(Text, { children: "No agent seeding was performed." })] }) }));
6
6
  }
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Text } from 'ink';
3
3
  import { Panel, PlanSummary, SetupWorkspace, setupFooterHints } from './screen-components.js';
4
4
  export function FinalConfirmationScreen(props) {
5
- return (_jsx(SetupWorkspace, { title: "Final confirmation", viewModel: props.viewModel, footer: [setupFooterHints.confirmApply, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Confirm OpenCode setup", children: [_jsx(PlanSummary, { viewModel: props.viewModel }), _jsx(Text, { children: "[warning]" }), _jsx(Text, { color: "yellow", children: "WARNING: OpenCode provider config may be modified at the target path above. Existing config is backed up when planned." }), _jsx(Text, { children: "Press Enter to confirm and apply OpenCode setup, or Esc/q to cancel without writing." })] }) }));
5
+ return (_jsx(SetupWorkspace, { title: "Final confirmation", viewModel: props.viewModel, footer: [setupFooterHints.confirmApply, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Confirm OpenCode setup", children: [_jsx(PlanSummary, { viewModel: props.viewModel }), _jsx(Text, { children: "[warning]" }), _jsx(Text, { color: "yellow", children: "WARNING: OpenCode provider config may be modified at the target path above. Existing config is backed up when planned." }), _jsx(Text, { children: "After confirmation: run doctor, then restart OpenCode to reload MCP configuration." }), _jsx(Text, { children: "Press Enter to confirm and apply OpenCode setup, or Esc/q to cancel without writing." })] }) }));
6
6
  }
@@ -6,5 +6,5 @@ export function OpenCodeDetailsScreen(props) {
6
6
  if (vm.providerLabel !== 'OpenCode') {
7
7
  return (_jsx(SetupWorkspace, { title: "OpenCode details", viewModel: vm, footer: [setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsx(ManualNoProviderNotice, {}) }));
8
8
  }
9
- return (_jsx(SetupWorkspace, { title: "OpenCode details", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "OpenCode plan", badge: { label: badgeLabels.writeAfterConfirm }, children: [_jsx(ChoiceList, { choices: vm.scopeChoices }), _jsx(ChoiceList, { choices: vm.installModeChoices }), _jsx(KeyValue, { label: "Scope", value: vm.scopeLabel }), _jsx(KeyValue, { label: "Install mode", value: vm.installModeLabel }), _jsx(KeyValue, { label: "Target config", value: vm.targetPathLabel }), _jsx(KeyValue, { label: "Action", value: vm.opencodeActionLabel }), _jsx(Text, { children: "Details are preview-only here; writes require final confirmation." })] }) }));
9
+ return (_jsx(SetupWorkspace, { title: "OpenCode details", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "OpenCode plan", badge: { label: badgeLabels.writeAfterConfirm }, children: [_jsx(ChoiceList, { choices: vm.scopeChoices }), _jsx(ChoiceList, { choices: vm.installModeChoices }), _jsx(KeyValue, { label: "Scope", value: vm.scopeLabel }), _jsx(KeyValue, { label: "Install mode", value: vm.installModeLabel }), _jsx(KeyValue, { label: "Target config", value: vm.targetPathLabel }), _jsx(KeyValue, { label: "Action", value: vm.opencodeActionLabel }), _jsx(KeyValue, { label: "Agent readiness", value: vm.agentReadinessLabel }), _jsx(Text, { children: vm.agentReadinessDetail }), _jsx(Text, { children: "Details are preview-only here; writes require final confirmation." })] }) }));
10
10
  }
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Text } from 'ink';
3
3
  import { badgeLabels, Panel, PlanSummary, SetupWorkspace, setupFooterHints } from './screen-components.js';
4
4
  export function PlanReviewScreen(props) {
5
- return (_jsx(SetupWorkspace, { title: "Read-only plan review", viewModel: props.viewModel, footer: [setupFooterHints.finalConfirmation, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Plan summary", badge: { label: badgeLabels.readOnly }, children: [_jsx(PlanSummary, { viewModel: props.viewModel }), _jsx(Text, { children: "Review only: this screen does not write provider config." }), _jsx(Text, { children: props.viewModel.canAutoApply ? 'Press Enter to continue to final confirmation.' : 'Manual setup required; no automatic apply is available.' })] }) }));
5
+ return (_jsx(SetupWorkspace, { title: "Read-only plan review", viewModel: props.viewModel, footer: [setupFooterHints.finalConfirmation, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Plan summary", badge: { label: badgeLabels.readOnly }, children: [_jsx(PlanSummary, { viewModel: props.viewModel }), _jsxs(Text, { children: ["Doctor next step: ", props.viewModel.nextCommands.find((command) => /\bdoctor\b/.test(command)) ?? 'vgx doctor'] }), _jsx(Text, { children: "Restart next step: Restart OpenCode after confirmed setup so it reloads MCP configuration." }), _jsx(Text, { children: "Review only: this screen does not write provider config." }), _jsx(Text, { children: props.viewModel.canAutoApply ? 'Press Enter to continue to final confirmation.' : 'Manual setup required; no automatic apply is available.' })] }) }));
6
6
  }
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { ChoiceList, KeyValue, Panel, SetupWorkspace, setupFooterHints } from './screen-components.js';
3
3
  export function ProjectDatabaseScreen(props) {
4
4
  const vm = props.viewModel;
5
- return (_jsxs(SetupWorkspace, { title: "Project and database", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: [_jsxs(Panel, { title: "Project", children: [_jsx(KeyValue, { label: "Project", value: vm.projectLabel }), _jsx(KeyValue, { label: "Workspace", value: vm.workspaceRootLabel })] }), _jsxs(Panel, { title: "Database", children: [_jsx(ChoiceList, { choices: vm.databaseChoices }), _jsx(KeyValue, { label: "Mode", value: vm.databaseLabel }), _jsx(KeyValue, { label: "Path", value: vm.databasePathLabel }), _jsx(KeyValue, { label: "Source", value: vm.databaseSourceLabel })] })] }));
5
+ return (_jsxs(SetupWorkspace, { title: "Project and database", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: [_jsxs(Panel, { title: "Project", children: [_jsx(KeyValue, { label: "Project", value: vm.projectLabel }), _jsx(KeyValue, { label: "Workspace", value: vm.workspaceRootLabel })] }), _jsxs(Panel, { title: "Database", children: [_jsx(ChoiceList, { choices: vm.databaseChoices }), _jsx(KeyValue, { label: "Mode", value: vm.databaseLabel }), _jsx(KeyValue, { label: "Path", value: vm.databasePathLabel }), _jsx(KeyValue, { label: "Source", value: vm.databaseSourceLabel }), _jsx(KeyValue, { label: "Memory path explanation", value: vm.memoryPathExplanation })] })] }));
6
6
  }
@@ -3,5 +3,5 @@ import { Text } from 'ink';
3
3
  import { ChoiceList, Panel, SetupWorkspace, setupFooterHints } from './screen-components.js';
4
4
  export function ProviderScreen(props) {
5
5
  const vm = props.viewModel;
6
- return (_jsx(SetupWorkspace, { title: "Provider", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Provider selection", children: [_jsx(ChoiceList, { choices: vm.providerChoices }), _jsx(Text, { children: "OpenCode setup remains gated by final confirmation. Non-OpenCode/manual has no auto-apply." })] }) }));
6
+ return (_jsx(SetupWorkspace, { title: "Provider", viewModel: vm, footer: [setupFooterHints.select, setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Provider selection", children: [_jsx(ChoiceList, { choices: vm.providerChoices }), _jsx(Text, { children: vm.providerInstallabilityLabel }), _jsx(Text, { children: "OpenCode setup remains gated by final confirmation. Non-OpenCode/manual has no auto-apply." })] }) }));
7
7
  }
@@ -4,8 +4,13 @@ import { badgeLabels, KeyValue, Panel, SetupScreenFrame, setupFooterHints } from
4
4
  export function ResultScreen(props) {
5
5
  const result = props.result;
6
6
  if (result?.status === 'installed')
7
- return (_jsx(SetupScreenFrame, { title: "Setup complete", tone: "success", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Installed", badge: { label: badgeLabels.success }, children: [_jsx(KeyValue, { label: "Target config", value: result.targetPath }), result.backupPath === undefined ? _jsx(Text, { children: "Backup: none created" }) : _jsx(KeyValue, { label: "Backup", value: result.backupPath }), result.warnings.map((warning) => (_jsxs(Text, { children: ["Warning: ", warning] }, warning)))] }) }));
7
+ return (_jsx(SetupScreenFrame, { title: "Setup complete", tone: "success", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Installed", badge: { label: badgeLabels.success }, children: [_jsx(KeyValue, { label: "Target config", value: result.targetPath }), result.backupPath === undefined ? _jsx(Text, { children: "Backup: none created" }) : _jsx(KeyValue, { label: "Backup", value: result.backupPath }), result.warnings.map((warning) => (_jsxs(Text, { children: ["Warning: ", warning] }, warning))), resultNextCommands(props.viewModel).map((command) => (_jsxs(Text, { children: ["Next: ", command] }, command)))] }) }));
8
8
  if (result?.status === 'manual-required')
9
9
  return (_jsx(SetupScreenFrame, { title: "Manual setup", tone: "warning", footer: [setupFooterHints.close], children: _jsxs(Panel, { title: "Manual follow-up", badge: { label: badgeLabels.warning }, children: [_jsx(Text, { children: result.message }), props.viewModel.nextCommands.map((command) => (_jsxs(Text, { children: ["Next: ", command] }, command)))] }) }));
10
10
  return (_jsx(SetupScreenFrame, { title: "Setup result", footer: [setupFooterHints.close], children: _jsx(Panel, { title: "No write", children: _jsx(Text, { children: "No provider config write occurred." }) }) }));
11
11
  }
12
+ function resultNextCommands(viewModel) {
13
+ const filtered = viewModel.nextCommands.filter((command) => !/^vgx(?:ness)? setup apply\b/.test(command));
14
+ const withDoctor = filtered.some((command) => /\bdoctor\b/.test(command)) ? filtered : [...filtered, 'vgx doctor'];
15
+ return withDoctor.some((command) => /Restart OpenCode/i.test(command)) ? withDoctor : [...withDoctor, 'Restart OpenCode and verify the vgxness MCP server is visible.'];
16
+ }
@@ -64,10 +64,10 @@ export function KeyValue(props) {
64
64
  }
65
65
  export function PlanSummary(props) {
66
66
  const vm = props.viewModel;
67
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(KeyValue, { label: "Readiness", value: `${vm.readinessBadge} ${vm.readinessLabel}` }), _jsx(KeyValue, { label: "Provider", value: vm.providerLabel }), _jsx(KeyValue, { label: "Database", value: vm.databaseLabel }), _jsx(KeyValue, { label: "Database path", value: vm.databasePathLabel }), _jsx(KeyValue, { label: "OpenCode scope", value: vm.scopeLabel }), _jsx(KeyValue, { label: "Install mode", value: vm.installModeLabel }), _jsx(KeyValue, { label: "Target config", value: vm.targetPathLabel }), _jsx(Text, { children: vm.backupLabel }), vm.plannedActions.map((action) => (_jsxs(Text, { children: ["Action: ", action.safetyBadge, " ", action.label, action.targetPathLabel === undefined ? '' : ` (${action.targetPathLabel})`] }, `action:${action.id}`))), vm.warnings.map((warning) => (_jsxs(Text, { children: ["Warning: ", warning] }, `warning:${warning}`))), vm.blockers.map((blocker) => (_jsxs(Text, { children: ["Blocked: ", blocker] }, `blocker:${blocker}`)))] }));
67
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(KeyValue, { label: "Readiness", value: `${vm.readinessBadge} ${vm.readinessLabel}` }), _jsx(KeyValue, { label: "Provider", value: vm.providerLabel }), _jsx(KeyValue, { label: "Database", value: vm.databaseLabel }), _jsx(KeyValue, { label: "Database path", value: vm.databasePathLabel }), _jsx(KeyValue, { label: "OpenCode scope", value: vm.scopeLabel }), _jsx(KeyValue, { label: "Install mode", value: vm.installModeLabel }), _jsx(KeyValue, { label: "Provider installability", value: vm.providerInstallabilityLabel }), _jsx(KeyValue, { label: "Agent readiness", value: vm.agentReadinessLabel }), _jsx(KeyValue, { label: "Agent detail", value: vm.agentReadinessDetail }), _jsx(KeyValue, { label: "Target config", value: vm.targetPathLabel }), _jsx(Text, { children: vm.backupLabel }), vm.plannedActions.map((action) => (_jsxs(Text, { children: ["Action: ", action.safetyBadge, " ", action.label, action.targetPathLabel === undefined ? '' : ` (${action.targetPathLabel})`] }, `action:${action.id}`))), vm.warnings.map((warning) => (_jsxs(Text, { children: ["Warning: ", warning] }, `warning:${warning}`))), vm.blockers.map((blocker) => (_jsxs(Text, { children: ["Blocked: ", blocker] }, `blocker:${blocker}`)))] }));
68
68
  }
69
69
  export function ManualNoProviderNotice() {
70
- return (_jsxs(Panel, { title: "Manual / no-provider-write mode", badge: { label: badgeLabels.manual }, children: [_jsx(Text, { children: "OpenCode-only scope and install controls are disabled because provider is Manual / none." }), _jsx(Text, { children: "No provider config will be written; use the manual next commands from the plan." })] }));
70
+ return (_jsxs(Panel, { title: "Manual / no-provider-write mode", badge: { label: badgeLabels.manual }, children: [_jsx(Text, { children: "OpenCode-only scope and install controls are disabled because provider is Manual / none." }), _jsx(Text, { children: "Claude remains deferred/not installable in guided setup." }), _jsx(Text, { children: "No provider config will be written; use the manual next commands from the plan." })] }));
71
71
  }
72
72
  export function compactPath(path, width) {
73
73
  if (width <= 0 || path.length <= width)
@@ -81,7 +81,7 @@ export function compactPath(path, width) {
81
81
  export function wrapLabel(value, width) {
82
82
  if (width <= 0 || value.length <= width)
83
83
  return value;
84
- const protectedPhrases = ['final confirmation', 'confirm and apply', 'read-only', 'requires confirmation', 'will write after confirm', 'error', 'cancelled'];
84
+ const protectedPhrases = ['final confirmation', 'confirm and apply', 'not installable', 'read-only', 'requires confirmation', 'will write after confirm', 'error', 'cancelled'];
85
85
  const phrase = protectedPhrases.find((candidate) => value.includes(candidate));
86
86
  if (phrase !== undefined)
87
87
  return `${value.slice(0, Math.max(0, width - phrase.length - 5)).trim()} ... ${phrase}`.trim();
@@ -2,5 +2,5 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Text } from 'ink';
3
3
  import { Panel, SetupWorkspace, setupFooterHints } from './screen-components.js';
4
4
  export function WelcomeScreen(props) {
5
- return (_jsx(SetupWorkspace, { title: "Detect", viewModel: props.viewModel, footer: [setupFooterHints.continue, setupFooterHints.help, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Safe local setup", children: [_jsx(Text, { children: "Setup welcome" }), _jsx(Text, { children: "VGXNESS Setup Assistant" }), _jsx(Text, { children: "Configure local storage and OpenCode integration with read-only previews first." }), _jsx(Text, { children: "No provider config is written until the final confirmation." })] }) }));
5
+ return (_jsx(SetupWorkspace, { title: "Detect", viewModel: props.viewModel, footer: [setupFooterHints.continue, setupFooterHints.help, setupFooterHints.cancel], children: _jsxs(Panel, { title: "Guided installation sequence", children: [_jsx(Text, { children: "Setup welcome: global memory \u2192 provider \u2192 OpenCode details \u2192 agent readiness \u2192 preview \u2192 final confirmation \u2192 doctor/restart." }), _jsx(Text, { children: "VGXNESS Setup Assistant" }), _jsx(Text, { children: "Configure local storage and OpenCode integration with read-only previews first." }), _jsx(Text, { children: "No provider config is written until the final confirmation." })] }) }));
6
6
  }
@@ -12,6 +12,8 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
12
12
  const workspaceRoot = plan?.workspaceRoot ?? status?.environment.workspaceRoot ?? 'pending workspace';
13
13
  const databasePath = plan?.db.path ?? status?.store.path ?? 'pending database path';
14
14
  const databaseSource = plan?.db.source ?? 'pending';
15
+ const memoryExplanation = memoryPathExplanation(plan, databasePath, databaseSource);
16
+ const agentReadiness = agentReadinessLabels(plan, status);
15
17
  const blockers = plan?.conflicts
16
18
  .filter((conflict) => conflict.severity === 'blocking')
17
19
  .map((conflict) => `${conflict.message}${conflict.recovery === undefined ? '' : ` Recovery: ${conflict.recovery}`}`) ?? [];
@@ -40,6 +42,12 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
40
42
  : 'Manual / none (no OpenCode install)',
41
43
  targetPathLabel: isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : 'No provider config target; manual/no-provider-write mode',
42
44
  opencodeActionLabel: isOpenCode ? opencodeActionLabel(opencode?.action) : 'No automatic provider write; use manual setup guidance.',
45
+ memoryPathExplanation: memoryExplanation,
46
+ providerInstallabilityLabel: isOpenCode
47
+ ? 'OpenCode is the only installable provider in this guided setup. Claude is deferred guidance only.'
48
+ : 'Manual / none is read-only guidance; no provider is installable from this selection. Claude is deferred guidance only.',
49
+ agentReadinessLabel: agentReadiness.label,
50
+ agentReadinessDetail: agentReadiness.detail,
43
51
  plannedActions: plan?.actions.map((action) => ({
44
52
  id: action.id,
45
53
  label: action.description,
@@ -65,7 +73,8 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
65
73
  choice('database:custom', 'Custom database path', 'Deferred/read-only in the TUI unless already supplied by flags or environment.', selections?.databaseMode === 'custom' || (selections === undefined && plan?.db.mode === 'custom'), state?.focusedChoiceId, [badgeLabels.deferred, badgeLabels.readOnly]),
66
74
  ],
67
75
  providerChoices: [
68
- choice('provider:opencode', 'OpenCode', 'Primary supported provider for managed MCP setup.', (selections?.provider ?? plan?.provider ?? 'opencode') === 'opencode', state?.focusedChoiceId, [badgeLabels.recommended]),
76
+ choice('provider:opencode', 'OpenCode', 'Only installable provider in this guided setup; writes still require final confirmation.', (selections?.provider ?? plan?.provider ?? 'opencode') === 'opencode', state?.focusedChoiceId, [badgeLabels.recommended]),
77
+ choice('provider:claude-deferred', 'Claude (deferred)', 'Guidance only for now; not selectable and not installable in this setup flow.', false, state?.focusedChoiceId, [badgeLabels.deferred, badgeLabels.readOnly]),
69
78
  choice('provider:none', 'Manual / none', 'No automatic provider config write; follow manual setup guidance.', (selections?.provider ?? plan?.provider) === 'none', state?.focusedChoiceId, [badgeLabels.manual, badgeLabels.readOnly]),
70
79
  ],
71
80
  scopeChoices: isOpenCode
@@ -76,7 +85,7 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
76
85
  : [],
77
86
  installModeChoices: isOpenCode
78
87
  ? [
79
- choice('install:mcp-plus-agents', 'MCP plus agents', 'Install VGXNESS MCP and bundled agent metadata after confirmation.', (selections?.installMode ?? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents')) === 'mcp-plus-agents', state?.focusedChoiceId, [badgeLabels.recommended, badgeLabels.writeAfterConfirm]),
88
+ choice('install:mcp-plus-agents', 'MCP plus agents', 'Install OpenCode MCP/agent entries after confirmation; local VGXNESS agent seeding is not done by preview screens.', (selections?.installMode ?? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents')) === 'mcp-plus-agents', state?.focusedChoiceId, [badgeLabels.recommended, badgeLabels.writeAfterConfirm]),
80
89
  choice('install:mcp-only', 'MCP only', 'Install only the MCP server entry after confirmation.', (selections?.installMode ?? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents')) === 'mcp-only', state?.focusedChoiceId, [badgeLabels.writeAfterConfirm]),
81
90
  ]
82
91
  : [],
@@ -88,9 +97,12 @@ function previewDetailLines(input) {
88
97
  const actions = plan?.actions.map((action) => `Planned action: ${action.description}${action.targetPath === undefined ? '' : ` Target: ${compactPath(action.targetPath, 72)}`}${action.backupRequired ? ' Backup required.' : ''}`) ?? ['Planned action: create read-only preview plan.'];
89
98
  return [
90
99
  `Provider: ${input.provider === 'opencode' ? 'OpenCode [recommended]' : 'Manual / none [manual] [read-only]'}`,
91
- `Database: ${plan?.db.mode ?? 'pending'} at ${compactPath(input.databasePath, 72)} (source: ${String(input.databaseSource)})`,
100
+ `Memory path: ${plan?.db.mode ?? 'pending'} at ${compactPath(input.databasePath, 72)} (source: ${String(input.databaseSource)})`,
101
+ `Memory guidance: ${memoryPathExplanation(plan, input.databasePath, input.databaseSource)}`,
92
102
  `Scope: ${input.isOpenCode ? (opencode?.scope ?? 'user') : 'disabled for manual/none provider'}`,
93
103
  `Install mode: ${input.isOpenCode ? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents') : 'disabled for manual/none provider'}`,
104
+ `Provider installability: ${input.isOpenCode ? 'OpenCode installable after final confirmation; Claude deferred/not installable.' : 'No provider installable; OpenCode controls disabled and Claude deferred.'}`,
105
+ `Agent readiness: ${agentReadinessFromPlan(plan)}`,
94
106
  `Target config: ${input.isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : 'none; no provider config will be written'}`,
95
107
  `Safety: ${input.isOpenCode ? '[will write after confirm] only on final confirmation' : '[read-only] manual/no-provider-write mode'}`,
96
108
  ...actions,
@@ -109,10 +121,38 @@ function helpLines(screen) {
109
121
  'Next/back: Tab continues; Shift+Tab goes back. Enter continues on review and confirms only on final confirmation.',
110
122
  'Cancel/close: q or Esc cancels setup; when help is open, ?/h toggles it closed.',
111
123
  reviewLine,
112
- 'Provider support: OpenCode is [recommended]; Manual / none is [manual] [read-only] and writes no provider config.',
124
+ 'Provider support: OpenCode is the only installable provider; Claude is [deferred] [read-only] guidance and Manual / none writes no provider config.',
125
+ 'Agent readiness: the preview checks vgxness-manager/SDD readiness guidance; preview screens never seed agents.',
113
126
  'No-write guarantee: no provider config is written before explicit final confirmation.',
114
127
  ];
115
128
  }
129
+ function memoryPathExplanation(plan, path, source) {
130
+ if (plan?.db.mode === 'project-local')
131
+ return `Project-local memory would use ${compactPath(path, 72)} for this workspace only; this preview creates no file.`;
132
+ if (plan?.db.mode === 'custom')
133
+ return `Custom memory path ${compactPath(path, 72)} is used only when supplied by flags or environment; this preview is read-only.`;
134
+ return `Global memory is the default local store at ${compactPath(path, 72)} (source: ${String(source)}), shared across VGXNESS projects unless a project/custom path is chosen.`;
135
+ }
136
+ function agentReadinessLabels(plan, status) {
137
+ const names = plan?.opencode?.agentNames.length === 0 ? ['vgxness-manager', 'SDD agents'] : (plan?.opencode?.agentNames ?? ['vgxness-manager', 'SDD agents']);
138
+ if (status?.agents.deferred === true)
139
+ return {
140
+ label: 'Agent readiness deferred',
141
+ detail: status.agents.nextAction ?? 'Select a project to check vgxness-manager and SDD agent readiness. No agent seeding occurs in preview.',
142
+ };
143
+ if (status?.agents.status === 'ready')
144
+ return { label: 'Agents ready', detail: `Required local agent context is ready for ${status.agents.agentName}; preview target agents: ${names.join(', ')}.` };
145
+ if (status?.agents.status === 'blocked')
146
+ return {
147
+ label: 'Agents need attention',
148
+ detail: `${status.agents.blocker ?? 'Required agents are not ready.'} Next: ${status.agents.nextAction ?? 'vgx agents seed --scope project'}`,
149
+ };
150
+ return { label: 'Agent readiness preview', detail: `Required agents: ${names.join(', ')}. Preview/status screens do not seed agents.` };
151
+ }
152
+ function agentReadinessFromPlan(plan) {
153
+ const names = plan?.opencode?.agentNames.length === 0 ? ['vgxness-manager', 'SDD agents'] : (plan?.opencode?.agentNames ?? ['vgxness-manager', 'SDD agents']);
154
+ return `preview required agents (${names.join(', ')}); no agent seeding on read-only screens`;
155
+ }
116
156
  function choice(id, label, description, selected, focusedChoiceId, badges) {
117
157
  return { id, label, description, selected, focused: focusedChoiceId === undefined ? selected : focusedChoiceId === id, badges };
118
158
  }
@@ -9,8 +9,8 @@ function screenLines(input, width) {
9
9
  switch (input.screen) {
10
10
  case 'welcome':
11
11
  return workspaceLines(vm, [
12
- 'Safe local setup',
13
- 'Setup welcome',
12
+ 'Guided installation sequence',
13
+ 'Setup welcome: global memory → provider → OpenCode details → agent readiness → preview → final confirmation → doctor/restart.',
14
14
  'VGXNESS Setup Assistant',
15
15
  'Configure local storage and OpenCode integration with read-only previews first.',
16
16
  'No provider config is written until the final confirmation.',
@@ -25,11 +25,13 @@ function screenLines(input, width) {
25
25
  `Mode: ${vm.databaseLabel}`,
26
26
  `Path: ${compactPath(vm.databasePathLabel, width - 7)}`,
27
27
  `Source: ${vm.databaseSourceLabel}`,
28
+ `Memory path explanation: ${vm.memoryPathExplanation}`,
28
29
  ], width, choiceFooter());
29
30
  case 'provider':
30
31
  return workspaceLines(vm, [
31
32
  'Provider selection',
32
33
  ...choiceLines(vm.providerChoices),
34
+ vm.providerInstallabilityLabel,
33
35
  'OpenCode setup remains gated by final confirmation. Manual / none has no automatic install and no provider config will be written.',
34
36
  ], width, choiceFooter());
35
37
  case 'opencode-details':
@@ -42,11 +44,14 @@ function screenLines(input, width) {
42
44
  `Install mode: ${vm.installModeLabel}`,
43
45
  `Target config: ${compactPath(vm.targetPathLabel, width - 15)}`,
44
46
  `Action: ${vm.opencodeActionLabel}`,
47
+ `Agent readiness: ${vm.agentReadinessLabel}`,
48
+ vm.agentReadinessDetail,
45
49
  'Details are preview-only here; writes require final confirmation.',
46
50
  ]
47
51
  : [
48
52
  'Manual / no-provider-write mode ' + badgeLabels.manual,
49
53
  'OpenCode-only scope and install controls are disabled.',
54
+ 'Claude remains deferred/not installable; no provider config will be written.',
50
55
  'No provider config will be written; use manual setup guidance.',
51
56
  ], width, vm.providerLabel === 'OpenCode' ? choiceFooter() : [setupFooterHints.continue, setupFooterHints.back, setupFooterHints.cancel]);
52
57
  case 'plan-review':
@@ -54,6 +59,8 @@ function screenLines(input, width) {
54
59
  'Read-only plan review',
55
60
  'Plan summary ' + badgeLabels.readOnly,
56
61
  ...planLines(vm, width),
62
+ `Doctor next step: ${doctorCommand(vm)}`,
63
+ 'Restart next step: Restart OpenCode after confirmed setup so it reloads MCP configuration.',
57
64
  'Review only: this screen does not write provider config.',
58
65
  vm.canAutoApply ? 'Press Enter to continue to final confirmation.' : 'Manual setup required; no automatic apply is available.',
59
66
  ], width, [setupFooterHints.finalConfirmation, setupFooterHints.back, setupFooterHints.cancel]);
@@ -64,6 +71,7 @@ function screenLines(input, width) {
64
71
  'Confirm OpenCode setup',
65
72
  ...planLines(vm, width),
66
73
  'WARNING: OpenCode provider config may be modified at the target path above. Existing config is backed up when planned.',
74
+ 'After confirmation: run doctor, then restart OpenCode to reload MCP configuration.',
67
75
  'Press Enter to confirm and apply OpenCode setup, or Esc/q to cancel without writing.',
68
76
  ], width, [setupFooterHints.confirmApply, setupFooterHints.back, setupFooterHints.cancel]);
69
77
  case 'applying':
@@ -80,7 +88,7 @@ function screenLines(input, width) {
80
88
  footer([setupFooterHints.close], width),
81
89
  ];
82
90
  case 'cancelled':
83
- return ['Setup cancelled', badgeLabels.cancelled, 'Setup was not completed.', 'No provider config was written.', footer([setupFooterHints.close], width)];
91
+ return ['Setup cancelled', badgeLabels.cancelled, 'Setup was not completed.', 'No provider config was written.', 'No agent seeding was performed.', footer([setupFooterHints.close], width)];
84
92
  }
85
93
  }
86
94
  function planLines(vm, width) {
@@ -91,6 +99,9 @@ function planLines(vm, width) {
91
99
  `Database path: ${compactPath(vm.databasePathLabel, width - 15)}`,
92
100
  `OpenCode scope: ${vm.scopeLabel}`,
93
101
  `Install mode: ${vm.installModeLabel}`,
102
+ `Provider installability: ${vm.providerInstallabilityLabel}`,
103
+ `Agent readiness: ${vm.agentReadinessLabel}`,
104
+ `Agent detail: ${vm.agentReadinessDetail}`,
94
105
  `Target config: ${compactPath(vm.targetPathLabel, width - 15)}`,
95
106
  vm.backupLabel,
96
107
  ...vm.plannedActions.map((action) => `Action: ${action.safetyBadge} ${action.label}${action.targetPathLabel === undefined ? '' : ` (${compactPath(action.targetPathLabel, width - 12)})`}`),
@@ -129,6 +140,7 @@ function resultLines(result, vm, width) {
129
140
  `Target config: ${compactPath(result.targetPath, width - 15)}`,
130
141
  result.backupPath === undefined ? 'Backup: none created' : `Backup: ${compactPath(result.backupPath, width - 8)}`,
131
142
  ...result.warnings.map((warning) => `Warning: ${warning}`),
143
+ ...resultNextCommands(vm).map((command) => `Next: ${command}`),
132
144
  footer([setupFooterHints.close], width),
133
145
  ];
134
146
  if (result?.status === 'manual-required')
@@ -141,6 +153,14 @@ function resultLines(result, vm, width) {
141
153
  ];
142
154
  return ['Setup result', 'No provider config write occurred.', footer([setupFooterHints.close], width)];
143
155
  }
156
+ function resultNextCommands(vm) {
157
+ const filtered = vm.nextCommands.filter((command) => !/^vgx(?:ness)? setup apply\b/.test(command));
158
+ const withDoctor = filtered.some((command) => /\bdoctor\b/.test(command)) ? filtered : [...filtered, 'vgx doctor'];
159
+ return withDoctor.some((command) => /Restart OpenCode/i.test(command)) ? withDoctor : [...withDoctor, 'Restart OpenCode and verify the vgxness MCP server is visible.'];
160
+ }
161
+ function doctorCommand(vm) {
162
+ return vm.nextCommands.find((command) => /\bdoctor\b/.test(command)) ?? 'vgx doctor';
163
+ }
144
164
  function footer(hints, width) {
145
165
  return formatFooter(hints, width);
146
166
  }
@@ -66,7 +66,7 @@ async function runRealMcpSmoke(options) {
66
66
  const client = new Client({ name: 'vgxness-doctor', version: '0.1.0' }, { capabilities: {} });
67
67
  const invocation = resolveDoctorCliInvocation(options.databasePath);
68
68
  const transport = new StdioClientTransport({
69
- command: process.execPath,
69
+ command: invocation.command,
70
70
  args: invocation.args,
71
71
  cwd: options.cwd,
72
72
  stderr: 'pipe',
@@ -173,11 +173,17 @@ function parseMcpToolResult(result) {
173
173
  return { ok: false, error: { code: 'validation_failed', message: 'MCP response did not contain text JSON' } };
174
174
  return JSON.parse(text);
175
175
  }
176
- export function resolveDoctorCliInvocation(databasePath, moduleUrl = import.meta.url) {
176
+ export function resolveDoctorCliInvocation(databasePath, moduleUrl = import.meta.url, options = {}) {
177
+ const runtime = options.runtime ?? currentRuntime();
177
178
  const builtCliPath = fileURLToPath(new URL('../cli/index.js', moduleUrl));
178
179
  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] };
180
+ return { command: process.execPath, args: [builtCliPath, 'mcp', 'start', '--db', databasePath] };
181
+ if (runtime === 'bun')
182
+ return { command: process.execPath, args: [cliSourcePath(moduleUrl), 'mcp', 'start', '--db', databasePath] };
183
+ return { command: process.execPath, args: ['--import', import.meta.resolve('tsx'), cliSourcePath(moduleUrl), 'mcp', 'start', '--db', databasePath] };
184
+ }
185
+ function currentRuntime() {
186
+ return typeof globalThis.Bun === 'undefined' ? 'node' : 'bun';
181
187
  }
182
188
  function cliSourcePath(moduleUrl) {
183
189
  return fileURLToPath(new URL('../cli/index.ts', moduleUrl));
@@ -1,17 +1,10 @@
1
1
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
2
3
  import { dirname, join } from 'node:path';
3
4
  import { fileURLToPath } from 'node:url';
4
5
  const migrationsDirectory = join(dirname(fileURLToPath(import.meta.url)), 'migrations');
5
6
  export const sqliteBusyTimeoutMs = 5000;
6
- const nativeBindingDiagnosticGuidance = 'Detected a likely better-sqlite3 native binding problem. Ensure npm install scripts are enabled, then rebuild or reinstall dependencies for the active Node.js version (for example: npm rebuild better-sqlite3 --ignore-scripts=false).';
7
- const nativeBindingFailurePatterns = [
8
- /better_sqlite3\.node/i,
9
- /better-sqlite3/i,
10
- /NODE_MODULE_VERSION/i,
11
- /ERR_DLOPEN_FAILED/i,
12
- /Could not locate the bindings file/i,
13
- /compiled against a different Node\.js version/i,
14
- ];
7
+ const bunRequiredGuidance = 'Real local SQLite storage requires Bun. Use the installed Bun-backed vgxness/vgx runtime, or run Bun storage verification with bun run verify:bun-sqlite.';
15
8
  export class MemoryDatabase {
16
9
  connection;
17
10
  constructor(connection) {
@@ -99,39 +92,37 @@ export function openMemoryDatabase(options) {
99
92
  }
100
93
  export function buildMemoryStoreUnavailableMessage(path, cause) {
101
94
  const message = `Unable to open local memory store at ${path}`;
102
- if (!isLikelyBetterSqlite3NativeBindingFailure(cause))
95
+ if (!isUnsupportedSqliteRuntimeError(cause))
103
96
  return message;
104
- return `${message}. ${nativeBindingDiagnosticGuidance}`;
97
+ return `${message}. ${bunRequiredGuidance}`;
105
98
  }
106
- export function isLikelyBetterSqlite3NativeBindingFailure(cause) {
107
- const values = collectDiagnosticValues(cause);
108
- return values.some((value) => nativeBindingFailurePatterns.some((pattern) => pattern.test(value)));
109
- }
110
- const sqliteDriver = await resolveSqliteDriver();
111
99
  function openSqliteConnection(options) {
112
- return sqliteDriver(options);
100
+ return resolveSqliteDriver()(options);
113
101
  }
114
- async function resolveSqliteDriver() {
102
+ function resolveSqliteDriver() {
115
103
  if (isBunRuntime())
116
104
  return resolveBunSqliteDriver();
117
- return resolveBetterSqlite3Driver();
105
+ throw createUnsupportedSqliteRuntimeError();
118
106
  }
119
107
  function isBunRuntime() {
120
108
  return typeof globalThis.Bun !== 'undefined';
121
109
  }
122
- async function resolveBetterSqlite3Driver() {
123
- const module = await import('better-sqlite3');
124
- const BetterSqlite3Database = module.default;
125
- return (options) => new BetterSqlite3Database(options.path, { readonly: options.readonly ?? false });
126
- }
127
- async function resolveBunSqliteDriver() {
128
- const bunSqliteSpecifier = 'bun:sqlite';
129
- const module = (await import(bunSqliteSpecifier));
110
+ function resolveBunSqliteDriver() {
111
+ const require = createRequire(import.meta.url);
112
+ const module = require('bun:sqlite');
130
113
  const BunDatabase = module.Database;
131
114
  if (typeof BunDatabase !== 'function')
132
115
  throw new Error('bun:sqlite Database constructor is unavailable');
133
116
  return (options) => createBunSqliteConnection(new BunDatabase(options.path, { readonly: options.readonly ?? false, strict: true }));
134
117
  }
118
+ function createUnsupportedSqliteRuntimeError() {
119
+ const error = new Error(bunRequiredGuidance);
120
+ error.name = 'UnsupportedSqliteRuntimeError';
121
+ return error;
122
+ }
123
+ function isUnsupportedSqliteRuntimeError(cause) {
124
+ return cause instanceof Error && cause.name === 'UnsupportedSqliteRuntimeError';
125
+ }
135
126
  function createBunSqliteConnection(database) {
136
127
  const record = database;
137
128
  return {
@@ -139,7 +130,7 @@ function createBunSqliteConnection(database) {
139
130
  const prepare = record.prepare;
140
131
  if (typeof prepare !== 'function')
141
132
  throw new Error('bun:sqlite prepare() is unavailable');
142
- return prepare.call(database, sql);
133
+ return createBunSqliteStatement(prepare.call(database, sql));
143
134
  },
144
135
  exec(sql) {
145
136
  const exec = record.exec;
@@ -174,6 +165,30 @@ function createBunSqliteConnection(database) {
174
165
  },
175
166
  };
176
167
  }
168
+ function createBunSqliteStatement(statement) {
169
+ const record = statement;
170
+ return {
171
+ get(...params) {
172
+ const get = record.get;
173
+ if (typeof get !== 'function')
174
+ throw new Error('bun:sqlite statement.get() is unavailable');
175
+ const row = get.call(statement, ...params);
176
+ return row === null ? undefined : row;
177
+ },
178
+ all(...params) {
179
+ const all = record.all;
180
+ if (typeof all !== 'function')
181
+ throw new Error('bun:sqlite statement.all() is unavailable');
182
+ return all.call(statement, ...params);
183
+ },
184
+ run(...params) {
185
+ const run = record.run;
186
+ if (typeof run !== 'function')
187
+ throw new Error('bun:sqlite statement.run() is unavailable');
188
+ return run.call(statement, ...params);
189
+ },
190
+ };
191
+ }
177
192
  function runBunPragma(database, statement) {
178
193
  const normalizedStatement = statement.trim();
179
194
  if (isPragmaSetter(normalizedStatement)) {
@@ -211,26 +226,3 @@ function migrationFailure(message, cause) {
211
226
  error.cause = cause;
212
227
  return { ok: false, error };
213
228
  }
214
- function collectDiagnosticValues(value, seen = new Set()) {
215
- if (value === undefined || value === null)
216
- return [];
217
- if (typeof value === 'string')
218
- return [value];
219
- if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint' || typeof value === 'symbol')
220
- return [String(value)];
221
- if (typeof value !== 'object')
222
- return [];
223
- if (seen.has(value))
224
- return [];
225
- seen.add(value);
226
- const diagnosticValues = [];
227
- const record = value;
228
- for (const key of ['code', 'message', 'stack', 'name']) {
229
- const property = record[key];
230
- if (property !== undefined)
231
- diagnosticValues.push(...collectDiagnosticValues(property, seen));
232
- }
233
- if (record.cause !== undefined)
234
- diagnosticValues.push(...collectDiagnosticValues(record.cause, seen));
235
- return diagnosticValues;
236
- }
package/docs/cli.md CHANGED
@@ -77,12 +77,13 @@ bun install --frozen-lockfile
77
77
  bun run check:bun-lock
78
78
  bun run verify:typecheck
79
79
  bun run verify:test
80
+ bun run verify:test:bun-storage
80
81
  bun run verify:bun-sqlite
81
82
  bun run verify:package
82
83
  bun run package:bun:evidence -- --require-pass
83
84
  ```
84
85
 
85
- `bun run verify:bun-sqlite` is the SQLite runtime release gate. It uses `bun:sqlite` against a temporary database, copies/applies the source migrations, and checks foreign keys, busy timeout, FTS/search, transaction rollback, integrity, and cleanup. `bun run verify:node-sqlite` is optional Node native-binding tooling coverage only.
86
+ `bun run verify:bun-sqlite` is the SQLite runtime release gate. It uses `bun:sqlite` against a temporary database, copies/applies the source migrations, and checks foreign keys, busy timeout, FTS/search, transaction rollback, integrity, and cleanup. `bun run verify:test:bun-storage` covers storage-backed tests under Bun. Node.js remains development/build/test tooling, but real local SQLite storage is supported through the Bun runtime path.
86
87
 
87
88
  Keep lockfile changes intentional: when `package.json` dependency specifiers change, refresh and review the root `bun.lock` diff. The read-only drift check does not run `bun install` and does not mutate `node_modules`:
88
89
 
@@ -43,13 +43,12 @@ Esa capa no reemplaza a OpenCode, Claude Code o futuros adapters. Los coordina.
43
43
 
44
44
  ## Como esta construido
45
45
 
46
- El proyecto esta escrito en TypeScript y corre sobre Node.js 22 o superior.
46
+ El proyecto esta escrito en TypeScript. Node.js 22 o superior sigue siendo tooling de desarrollo/build/tests; la persistencia local real en SQLite corre por el runtime Bun soportado.
47
47
 
48
48
  Dependencias principales:
49
49
 
50
50
  | Dependencia | Uso |
51
51
  |---|---|
52
- | `better-sqlite3` | Persistencia local en SQLite. |
53
52
  | `@modelcontextprotocol/sdk` | Servidor MCP por stdio. |
54
53
  | `zod` | Validacion de schemas de entrada. |
55
54
  | `tsx` | Ejecucion TypeScript en desarrollo. |
@@ -135,9 +134,9 @@ Archivo clave:
135
134
  src/memory/sqlite/database.ts
136
135
  ```
137
136
 
138
- Al abrir la base, el sistema:
137
+ Al abrir la base con el runtime Bun, el sistema:
139
138
 
140
- 1. Crea una conexion con `better-sqlite3`.
139
+ 1. Crea una conexion SQLite usando `bun:sqlite`.
141
140
  2. Activa `foreign_keys`.
142
141
  3. Configura `busy_timeout`.
143
142
  4. Aplica migraciones SQL si la base no esta en modo readonly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vgxness",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "CLI and MCP control plane for guided AI-agent workflows, SDD, memory, and OpenCode setup.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
@@ -22,12 +22,12 @@
22
22
  "scripts": {
23
23
  "cli": "tsx src/cli/index.ts",
24
24
  "build": "tsc -p tsconfig.build.json && node scripts/copy-migrations.mjs",
25
- "verify": "bun run verify:typecheck && bun run verify:test && bun run verify:bun-sqlite && bun run verify:package",
25
+ "verify": "bun run verify:typecheck && bun run verify:test && bun run verify:test:bun-storage && bun run verify:bun-sqlite && bun run verify:package",
26
26
  "verify:typecheck": "tsc --noEmit",
27
- "verify:test": "node --import tsx --test \"test/**/*.test.ts\"",
27
+ "verify:test": "node scripts/run-node-tests.mjs",
28
+ "verify:test:bun-storage": "node scripts/run-bun-storage-tests.mjs",
28
29
  "verify:package": "bun run package:bun:evidence",
29
30
  "verify:bun-sqlite": "bun scripts/probe-bun-sqlite.mjs",
30
- "verify:node-sqlite": "node scripts/probe-native-sqlite.mjs",
31
31
  "ci:bun": "bun run check:bun-lock && bun run verify",
32
32
  "check:bun-lock": "node scripts/check-bun-lock.mjs",
33
33
  "check:vgxcode:bun-lock": "node scripts/check-bun-lock.mjs --package-json packages/vgxcode/package.json --bun-lock packages/vgxcode/bun.lock",
@@ -36,7 +36,7 @@
36
36
  "probe:bun-runtime": "node scripts/probe-bun-runtime-cli-mcp.mjs",
37
37
  "verify:bun-runtime": "node scripts/probe-bun-runtime-cli-mcp.mjs --require-pass",
38
38
  "package:bun:evidence": "node scripts/probe-bun-package-evidence.mjs",
39
- "test": "node --import tsx --test \"test/**/*.test.ts\"",
39
+ "test": "node scripts/run-node-tests.mjs",
40
40
  "typecheck": "tsc --noEmit"
41
41
  },
42
42
  "bin": {
@@ -58,10 +58,8 @@
58
58
  "zod": "^4.4.3"
59
59
  },
60
60
  "devDependencies": {
61
- "@types/better-sqlite3": "^7.6.13",
62
61
  "@types/node": "^22.15.18",
63
62
  "@types/react": "^19.2.15",
64
- "better-sqlite3": "^12.10.0",
65
63
  "tsx": "^4.19.4",
66
64
  "typescript": "^5.8.3"
67
65
  },