vgxness 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +110 -0
  3. package/dist/agents/agent-activation-service.js +144 -0
  4. package/dist/agents/agent-registry-service.js +46 -0
  5. package/dist/agents/agent-resolver.js +249 -0
  6. package/dist/agents/agent-seed-service.js +146 -0
  7. package/dist/agents/manager-profile-overlay-service.js +34 -0
  8. package/dist/agents/profile-model-routing.js +26 -0
  9. package/dist/agents/renderers/claude-renderer.js +98 -0
  10. package/dist/agents/renderers/index.js +16 -0
  11. package/dist/agents/renderers/json-renderer.js +87 -0
  12. package/dist/agents/renderers/opencode-renderer.js +100 -0
  13. package/dist/agents/renderers/provider-adapter.js +6 -0
  14. package/dist/agents/repositories/agents.js +185 -0
  15. package/dist/agents/repositories/manager-profile-overlays.js +81 -0
  16. package/dist/agents/schema.js +1 -0
  17. package/dist/cli/dashboard-operational-read-models.js +153 -0
  18. package/dist/cli/dashboard-renderer.js +109 -0
  19. package/dist/cli/dashboard-screen-renderers.js +332 -0
  20. package/dist/cli/dashboard-tui-read-model.js +71 -0
  21. package/dist/cli/dashboard-tui-state.js +218 -0
  22. package/dist/cli/dispatcher.js +2880 -0
  23. package/dist/cli/index.js +27 -0
  24. package/dist/cli/interactive-dashboard.js +29 -0
  25. package/dist/cli/mcp-start-path.js +21 -0
  26. package/dist/cli/setup-status-renderer.js +29 -0
  27. package/dist/cli/setup-wizard-read-model.js +56 -0
  28. package/dist/cli/setup-wizard-renderer.js +148 -0
  29. package/dist/cli/setup-wizard-state.js +82 -0
  30. package/dist/cli/tui-render-helpers.js +192 -0
  31. package/dist/export/redaction.js +71 -0
  32. package/dist/harness/tools/agents.js +245 -0
  33. package/dist/harness/tools/memory.js +29 -0
  34. package/dist/mcp/client-install-opencode-contract.js +227 -0
  35. package/dist/mcp/client-install-opencode.js +194 -0
  36. package/dist/mcp/client-setup-preview.js +38 -0
  37. package/dist/mcp/control-plane.js +175 -0
  38. package/dist/mcp/doctor.js +193 -0
  39. package/dist/mcp/index.js +10 -0
  40. package/dist/mcp/opencode-default-agent-config.js +156 -0
  41. package/dist/mcp/opencode-visibility.js +102 -0
  42. package/dist/mcp/schema.js +234 -0
  43. package/dist/mcp/stdio-server.js +56 -0
  44. package/dist/mcp/validation.js +761 -0
  45. package/dist/memory/import/dry-run-planner.js +58 -0
  46. package/dist/memory/import/index.js +3 -0
  47. package/dist/memory/import/observation-writer.js +220 -0
  48. package/dist/memory/import/package.js +178 -0
  49. package/dist/memory/memory-service.js +126 -0
  50. package/dist/memory/repositories/artifacts.js +41 -0
  51. package/dist/memory/repositories/observations.js +133 -0
  52. package/dist/memory/repositories/sessions.js +105 -0
  53. package/dist/memory/repositories/traces.js +58 -0
  54. package/dist/memory/schema.js +1 -0
  55. package/dist/memory/search.js +11 -0
  56. package/dist/memory/sqlite/database.js +97 -0
  57. package/dist/memory/sqlite/migrations/001_initial.sql +128 -0
  58. package/dist/memory/sqlite/migrations/002_observation_revisions.sql +14 -0
  59. package/dist/memory/sqlite/migrations/003_agent_registry.sql +26 -0
  60. package/dist/memory/sqlite/migrations/004_run_runtime.sql +62 -0
  61. package/dist/memory/sqlite/migrations/005_run_approvals.sql +20 -0
  62. package/dist/memory/sqlite/migrations/006_run_operation_attempts.sql +32 -0
  63. package/dist/memory/sqlite/migrations/007_abandoned_operation_attempts.sql +46 -0
  64. package/dist/memory/sqlite/migrations/008_run_execution_plan_events.sql +105 -0
  65. package/dist/memory/sqlite/migrations/009_multiple_operation_attempts.sql +73 -0
  66. package/dist/memory/sqlite/migrations/010_skill_registry.sql +66 -0
  67. package/dist/memory/sqlite/migrations/011_skill_usage_resolution_outcomes.sql +21 -0
  68. package/dist/memory/sqlite/migrations/012_skill_improvement_proposals.sql +37 -0
  69. package/dist/memory/sqlite/migrations/013_skill_evaluation_scenarios.sql +43 -0
  70. package/dist/memory/sqlite/migrations/014_manager_profile_overlays.sql +14 -0
  71. package/dist/memory/storage-paths.js +72 -0
  72. package/dist/orchestrator/natural-language-planner.js +191 -0
  73. package/dist/orchestrator/schema.js +1 -0
  74. package/dist/permissions/index.js +2 -0
  75. package/dist/permissions/policy-evaluator.js +109 -0
  76. package/dist/permissions/schema.js +1 -0
  77. package/dist/providers/opencode/injection-preview.js +134 -0
  78. package/dist/providers/opencode/manager-payload.js +129 -0
  79. package/dist/runs/execution-planning.js +117 -0
  80. package/dist/runs/operation-execution.js +1 -0
  81. package/dist/runs/operation-retry.js +124 -0
  82. package/dist/runs/repositories/runs.js +611 -0
  83. package/dist/runs/run-insights.js +145 -0
  84. package/dist/runs/run-service.js +713 -0
  85. package/dist/runs/run-snapshot-export-service.js +31 -0
  86. package/dist/runs/sandbox-process-execution.js +218 -0
  87. package/dist/runs/sandbox-worktree-planning.js +59 -0
  88. package/dist/runs/schema.js +1 -0
  89. package/dist/sdd/artifact-portability-service.js +118 -0
  90. package/dist/sdd/schema.js +17 -0
  91. package/dist/sdd/sdd-workflow-service.js +217 -0
  92. package/dist/setup/backup-rollback-service.js +76 -0
  93. package/dist/setup/index.js +3 -0
  94. package/dist/setup/providers/antigravity-setup-adapter.js +18 -0
  95. package/dist/setup/providers/claude-setup-adapter.js +30 -0
  96. package/dist/setup/providers/custom-setup-adapter.js +18 -0
  97. package/dist/setup/providers/index.js +6 -0
  98. package/dist/setup/providers/opencode-setup-adapter.js +104 -0
  99. package/dist/setup/providers/provider-setup-adapter.js +15 -0
  100. package/dist/setup/providers/provider-setup-registry.js +11 -0
  101. package/dist/setup/schema.js +1 -0
  102. package/dist/setup/setup-defaults.js +11 -0
  103. package/dist/setup/setup-lifecycle-service.js +175 -0
  104. package/dist/setup/setup-plan.js +105 -0
  105. package/dist/skills/repositories/skill-evaluation-scenarios.js +289 -0
  106. package/dist/skills/repositories/skill-improvement-proposals.js +288 -0
  107. package/dist/skills/repositories/skills.js +430 -0
  108. package/dist/skills/schema.js +1 -0
  109. package/dist/skills/skill-payload.js +94 -0
  110. package/dist/skills/skill-registry-service.js +92 -0
  111. package/dist/skills/skill-resolver.js +191 -0
  112. package/dist/workflows/command-allowlist-adapter.js +70 -0
  113. package/dist/workflows/schema.js +4 -0
  114. package/dist/workflows/workflow-executor.js +345 -0
  115. package/dist/workflows/workflow-registry.js +66 -0
  116. package/docs/architecture.md +698 -0
  117. package/docs/cli.md +741 -0
  118. package/docs/funcionamiento-del-sistema.md +868 -0
  119. package/docs/harness-gap-analysis.md +229 -0
  120. package/docs/prd.md +372 -0
  121. package/package.json +57 -0
@@ -0,0 +1,332 @@
1
+ import { renderSetupStatus } from './setup-status-renderer.js';
2
+ import { canonicalSddPhasePresence, classifyAgentsSkills, classifyDoctor, classifyProjectScoped, classifySdd, classifySetupForProject, copyOnly, isSetupReady } from './dashboard-tui-read-model.js';
3
+ import { dashboardScreens } from './dashboard-tui-state.js';
4
+ import { renderSetupWizard } from './setup-wizard-renderer.js';
5
+ import { joinSections, normalizeRenderWidth, renderFooterGroups, renderPanel, renderTabRow, styleFocusedRow } from './tui-render-helpers.js';
6
+ const screenLabels = dashboardScreens;
7
+ const menuLabelColumnWidth = Math.max(...screenLabels.map((screen) => screen.label.length));
8
+ const menuFocusColumnWidth = '[focused]'.length;
9
+ const menuDescriptionColumnWidth = Math.max(...screenLabels.map((screen) => menuPurpose(screen.id).length));
10
+ export function renderDashboardFrame(state, options = {}) {
11
+ if (state.viewMode === 'menu') {
12
+ return joinSections([
13
+ renderDashboardHeader(state, options),
14
+ renderErrorsPanel(state, options),
15
+ renderDashboardMenuView(state, options),
16
+ renderDashboardFooter(state, options),
17
+ ]);
18
+ }
19
+ return joinSections([
20
+ renderDashboardHeader(state, options),
21
+ renderTabRow(screenLabels.map((screen) => ({ key: screen.key, label: screen.label, selected: screen.id === state.activeScreen, focused: screen.id === state.focusedScreen })), options),
22
+ renderErrorsPanel(state, options),
23
+ renderBackControl(options),
24
+ renderPanel(`Content · ${activeLabel(state.activeScreen)}`, renderActiveScreen(state, options).split('\n'), options),
25
+ renderDashboardFooter(state, options),
26
+ ]);
27
+ }
28
+ function renderDashboardHeader(state, options) {
29
+ if (state.viewMode === 'menu') {
30
+ return renderPanel('Header', [
31
+ 'VGXNESS Interactive Dashboard',
32
+ `Project: ${state.project ?? 'none'} · Change: ${state.change ?? 'none'}`,
33
+ ], options);
34
+ }
35
+ return renderPanel('Header', [
36
+ 'VGXNESS Interactive Dashboard',
37
+ `Context: Project: ${state.project ?? 'none'} · Change: ${state.change ?? 'none'} · View: ${state.viewMode} · Screen: ${activeLabel(state.activeScreen)} · Menu focus: ${activeLabel(state.focusedScreen)}`,
38
+ `Menu order: ${screenLabels.map((screen) => screen.label).join(', ')}`,
39
+ ], options);
40
+ }
41
+ function renderDashboardMenuView(state, options) {
42
+ return renderPanel('Menu', [
43
+ 'Choose a section to open:',
44
+ ...screenLabels.map((screen) => renderMenuRow(screen, screen.id === state.focusedScreen, options)),
45
+ ], options);
46
+ }
47
+ function renderMenuRow(screen, focused, options) {
48
+ const marker = focused ? '›' : ' ';
49
+ const label = screen.label.padEnd(menuLabelColumnWidth);
50
+ const focusMarker = (focused ? '[focused]' : '').padEnd(menuFocusColumnWidth);
51
+ const description = menuPurpose(screen.id).padEnd(menuDescriptionColumnWidth);
52
+ const row = `${marker} [${screen.key}] ${label} ${focusMarker} ${description}`;
53
+ return focused ? styleFocusedRow(row, options) : row;
54
+ }
55
+ function menuPurpose(id) {
56
+ if (id === 'setup')
57
+ return 'Setup readiness';
58
+ if (id === 'workflows')
59
+ return 'Workflow previews';
60
+ if (id === 'runs')
61
+ return 'Run status';
62
+ if (id === 'approvals')
63
+ return 'Approval queue';
64
+ if (id === 'agents')
65
+ return 'Agents and skills';
66
+ if (id === 'sdd')
67
+ return 'Artifact status';
68
+ if (id === 'doctor')
69
+ return 'Diagnostics';
70
+ return 'Workspace context';
71
+ }
72
+ function renderBackControl(options) {
73
+ return renderPanel('Back', ['[ b / Esc ] Back to menu', 'Direct shortcuts 1-8 open another section.'], options);
74
+ }
75
+ function renderErrorsPanel(state, options) {
76
+ const errors = [state.error === undefined ? undefined : `Error: ${state.error}`].filter((value) => value !== undefined);
77
+ if (errors.length === 0)
78
+ return undefined;
79
+ return renderPanel('Recovery', [...errors, 'Recovery: press r to refresh.'], { ...options, status: 'error' });
80
+ }
81
+ function renderDashboardFooter(state, options) {
82
+ const modeGroups = state.viewMode === 'menu'
83
+ ? [
84
+ { label: 'Menu', items: ['↑/↓ or j/k move', 'Enter open', '1-8 open'] },
85
+ ]
86
+ : [
87
+ { label: 'Section', items: ['b/Esc back', '1-8 open', 'j/k or ↑/↓ move'] },
88
+ ];
89
+ return renderPanel('Footer/help', [
90
+ ...renderFooterGroups([
91
+ ...modeGroups,
92
+ ...(state.viewMode === 'screen' && state.activeScreen === 'setup' ? [{ label: 'Setup wizard', items: ['n next step', 'Space toggle', 'd details'] }] : []),
93
+ { label: 'Global', items: ['r refresh', '? help', 'q quit'] },
94
+ ], options),
95
+ 'Safety: read-only TUI; provider/apply/install stay external-only.',
96
+ ], options);
97
+ }
98
+ function renderActiveScreen(state, options) {
99
+ if (state.activeScreen === 'setup')
100
+ return renderSetupScreen(state, options);
101
+ if (state.activeScreen === 'workflows')
102
+ return renderWorkflowsScreen(state);
103
+ if (state.activeScreen === 'runs')
104
+ return renderRunsScreen(state);
105
+ if (state.activeScreen === 'approvals')
106
+ return renderApprovalsScreen(state);
107
+ if (state.activeScreen === 'agents')
108
+ return renderAgentsScreen(state);
109
+ if (state.activeScreen === 'sdd')
110
+ return renderSddScreen(state);
111
+ if (state.activeScreen === 'doctor')
112
+ return renderDoctorScreen(state);
113
+ return renderSettingsScreen(state);
114
+ }
115
+ function renderSetupScreen(state, options) {
116
+ const nestedOptions = { ...options, width: Math.max(40, normalizeRenderWidth(options.width) - 4) };
117
+ const status = classifySetupForProject(state.project, state.setup, state.sectionErrors.setup);
118
+ if (status === 'ready-without-project')
119
+ return ['Setup', 'State: ready-without-project', 'Environment and provider setup can continue without a selected project; project-scoped checks are deferred.', renderSetupWizard(state.setupWizardReadModel, state.setupWizard, nestedOptions), copyOnly('Preview setup', 'npm run cli -- mcp setup --provider opencode --preview')].join('\n');
120
+ if (status === 'loading')
121
+ return ['Setup', 'State: loading', 'Checking setup status from the shared setup lifecycle.', renderSetupWizard(state.setupWizardReadModel, state.setupWizard, nestedOptions), copyOnly('Preview setup', 'npm run cli -- mcp setup --provider opencode --preview')].join('\n');
122
+ if (status === 'error')
123
+ return ['Setup', 'State: error', `Setup status unavailable: ${state.sectionErrors.setup ?? state.error ?? 'unknown error'}`, renderSetupWizard(state.setupWizardReadModel, state.setupWizard, nestedOptions), copyOnly('Inspect setup status', 'npm run cli -- setup status')].join('\n');
124
+ if (state.setup === undefined)
125
+ return ['Setup', 'State: empty', 'No setup status data is available yet.', renderSetupWizard(state.setupWizardReadModel, state.setupWizard, nestedOptions), copyOnly('Inspect setup status', 'npm run cli -- setup status')].join('\n');
126
+ const setupReady = isSetupReady(state.setup);
127
+ return [
128
+ 'Setup',
129
+ `State: ${setupReady ? 'ready' : 'blocked'}`,
130
+ `Next action: ${setupReady ? 'Continue to Runs with key 3.' : state.setup.nextAction.reason}`,
131
+ renderSetupWizard(state.setupWizardReadModel, state.setupWizard, nestedOptions),
132
+ 'Readiness checklist',
133
+ checklist('Project store', state.setup.store.status, state.setup.store.blocker),
134
+ checklist('Default manager/profile', state.setup.defaults.status, state.setup.defaults.blocker),
135
+ checklist('MCP visibility/setup', state.setup.mcp.status, state.setup.mcp.nextAction),
136
+ checklist('Doctor guidance', setupReady ? 'ready' : 'unknown', setupReady ? undefined : 'Run diagnostics outside the TUI.'),
137
+ renderSetupStatus(state.setup),
138
+ 'PRD install flow: choose AI tool integration, preview MCP setup, configure memory/profile through CLI, run doctor outside TUI, then press r to refresh.',
139
+ copyOnly('Preview setup', 'npm run cli -- mcp setup --provider opencode --preview'),
140
+ copyOnly('Plan install', 'npm run cli -- mcp install opencode --plan'),
141
+ copyOnly('Apply install explicitly', 'npm run cli -- mcp install opencode --yes'),
142
+ copyOnly('Run doctor', 'npm run cli -- mcp doctor opencode'),
143
+ ].join('\n');
144
+ }
145
+ function renderWorkflowsScreen(state) {
146
+ const status = classifyProjectScoped(state.project, state.project === undefined ? undefined : state.workflows === undefined || state.workflows.items.length === 0 ? 'empty' : 'ready', state.sectionErrors.workflows);
147
+ const lines = ['Workflows', `State: ${status}`];
148
+ if (status === 'project-required')
149
+ lines.push('Select or pass --project to inspect workflow guidance for a project.');
150
+ else if (state.workflows?.errorState !== undefined)
151
+ lines.push(`Error: ${state.workflows.errorState.message}`, ...renderCopyActions(state.workflows.errorState.nextActions));
152
+ else if (state.workflows === undefined)
153
+ lines.push('Workflow registry read model is unavailable; press r to refresh.', copyOnly('Inspect help', 'npm run cli -- --help'));
154
+ else if (state.workflows.items.length === 0)
155
+ lines.push(state.workflows.emptyState?.message ?? 'No workflows are available.', ...renderCopyActions(state.workflows.emptyState?.nextActions ?? state.workflows.copyActions));
156
+ else {
157
+ lines.push(`Source: ${state.workflows.meta.source}`, state.workflows.summary);
158
+ for (const workflow of state.workflows.items) {
159
+ lines.push(`- ${workflow.label} (${workflow.id}) [${workflow.availability}]`, ` ${workflow.description}`, ` phase=${workflow.phase} · mutability=${workflow.mutability} · safety=${workflow.safetyProfile} · sddArtifacts=${workflow.requiresSddArtifacts}`, ` inputs=${workflow.inputsSummary}`, ` lastRun=${workflow.lastKnownRun === undefined ? 'none' : `${workflow.lastKnownRun.id} [${workflow.lastKnownRun.status}] ${workflow.lastKnownRun.phase} updated ${workflow.lastKnownRun.updatedAt}`}`, ...renderCopyActions(workflow.nextActions).map((line) => ` ${line}`));
160
+ }
161
+ }
162
+ return lines.join('\n');
163
+ }
164
+ function renderRunsScreen(state) {
165
+ if (state.project === undefined)
166
+ return ['Runs', 'State: project-required', 'Pass --project <name> to list project runs.'].join('\n');
167
+ const homeError = state.sectionErrors.runs ?? state.error;
168
+ const status = homeError !== undefined ? state.runs.length > 0 ? 'ready' : 'error' : state.runs.length > 0 ? 'ready' : 'empty';
169
+ const lines = ['Runs', `State: ${status}`];
170
+ if (state.setup !== undefined)
171
+ lines.push(`Setup: ${isSetupReady(state.setup) ? 'ready' : `blocked — ${state.setup.nextAction.reason}`}`);
172
+ if (status === 'empty')
173
+ lines.push('No runs or supplied change are available yet.', copyOnly('Inspect runs', `npm run cli -- runs list --project ${state.project}`));
174
+ else
175
+ lines.push(renderRuns(state), renderSelectedRunOperationalTrace(state), renderSelectedRunInsights(state));
176
+ if (state.sdd !== undefined)
177
+ lines.push(`SDD change: ${state.sdd.change} · next ready phase: ${state.sdd.status.nextReadyPhase ?? 'none'}`);
178
+ lines.push(copyOnly('Inspect dashboard status', `npm run cli -- dashboard status --project ${state.project}${state.change === undefined ? '' : ` --change ${state.change}`}`));
179
+ return lines.filter((line) => line.length > 0).join('\n');
180
+ }
181
+ function renderApprovalsScreen(state) {
182
+ const status = classifyProjectScoped(state.project, state.project === undefined ? undefined : state.approvals === undefined || state.approvals.items.length === 0 ? 'empty' : 'ready', state.sectionErrors.approvals);
183
+ const lines = ['Approvals', `State: ${status}`];
184
+ if (status === 'project-required')
185
+ lines.push('Pass --project <name> to inspect approvals.');
186
+ else if (state.approvals?.errorState !== undefined)
187
+ lines.push(`Error: ${state.approvals.errorState.message}`, ...renderCopyActions(state.approvals.errorState.nextActions));
188
+ else if (state.approvals === undefined)
189
+ lines.push('Approval/preflight read model is unavailable; press r to refresh.', copyOnly('Inspect approvals', `npm run cli -- approvals list --project ${state.project ?? '<project>'}`));
190
+ else if (state.approvals.items.length === 0)
191
+ lines.push(state.approvals.emptyState?.message ?? 'No pending or recent approvals/preflights are visible.', ...renderCopyActions(state.approvals.emptyState?.nextActions ?? state.approvals.copyActions));
192
+ else {
193
+ lines.push(`Source: ${state.approvals.meta.source}`, state.approvals.summary, 'Pending items are listed before recent/resolved items.');
194
+ for (const item of state.approvals.items) {
195
+ lines.push(`- ${item.kind} ${item.id} [${item.status}] run=${item.runId ?? 'unknown'}`, ` category=${item.category} · operation=${item.operation} · risk=${item.riskFlags.join(', ') || 'none'}`, ` reason=${item.reason}`, ` agent=${item.agent ?? 'unknown'} · updated=${item.updatedAt ?? item.createdAt ?? 'unknown'}`, ...renderCopyActions(item.nextActions).map((line) => ` ${line}`));
196
+ }
197
+ }
198
+ return lines.join('\n');
199
+ }
200
+ function renderSddScreen(state) {
201
+ if (state.project === undefined)
202
+ return ['SDD', 'State: project-required', 'Pass --project <name> and optionally --change <id> to inspect SDD artifacts.'].join('\n');
203
+ const status = classifySdd(state.sdd, state.change, state.sectionErrors.sdd);
204
+ const lines = ['SDD', `State: ${status}`];
205
+ if (state.change === undefined) {
206
+ lines.push('No change supplied. Change discovery/indexing is deferred for this MVP.', copyOnly('Relaunch with a change', `npm run cli -- dashboard interactive --project ${state.project ?? '<project>'} --change <change>`));
207
+ return lines.join('\n');
208
+ }
209
+ lines.push(`Change: ${state.change}`);
210
+ lines.push(...canonicalSddPhasePresence(state.sdd).map((phase) => `${phase.present ? '[x]' : '[ ]'} ${phase.phase} — ${phase.topicKey}`));
211
+ if (state.sdd?.status.nextReadyPhase !== undefined)
212
+ lines.push(`Next ready phase: ${state.sdd.status.nextReadyPhase}`);
213
+ if (state.sdd?.readiness !== undefined && !state.sdd.readiness.ready)
214
+ lines.push(`Missing prerequisites: ${state.sdd.readiness.missingArtifactTopicKeys.join(', ') || 'none reported'}`);
215
+ if (state.sectionErrors.sdd !== undefined)
216
+ lines.push(`Readiness/status error: ${state.sectionErrors.sdd}`);
217
+ lines.push(copyOnly('Inspect SDD status', `npm run cli -- sdd status --project ${state.project} --change ${state.change}`));
218
+ lines.push(copyOnly('Continue SDD outside TUI', `npm run cli -- sdd next --project ${state.project} --change ${state.change}`));
219
+ return lines.join('\n');
220
+ }
221
+ function renderAgentsScreen(state) {
222
+ if (state.project === undefined)
223
+ return ['Agents', 'State: project-required', 'Pass --project <name> to inspect project-scoped agents and skills.'].join('\n');
224
+ const profileError = state.sectionErrors.agents;
225
+ const status = classifyAgentsSkills(state.agentsSkills, profileError);
226
+ const lines = ['Agents', `State: ${status}`];
227
+ if (state.profiles === undefined)
228
+ lines.push('No effective manager profile data is available.');
229
+ else {
230
+ lines.push(`Manager: ${state.profiles.managerName}`, `Scope: ${state.profiles.scope}`, `Agent: ${state.profiles.agentId ?? 'unavailable'}`, `Provider: ${state.profiles.providerAdapter ?? 'unavailable'}`, `Model: ${state.profiles.model ?? 'unavailable'}`, `Profile: ${state.profiles.profile ?? 'unavailable'}`, `Overlay: ${state.profiles.overlayPresent === true ? 'present' : 'missing'}`);
231
+ if (state.profiles.error !== undefined)
232
+ lines.push(`Partial error: ${state.profiles.error}`);
233
+ }
234
+ if (profileError !== undefined)
235
+ lines.push(`Profile error: ${profileError}`);
236
+ lines.push(copyOnly('Inspect manager profile', `npm run cli -- agents manager-profile get --project ${state.project} --scope project --manager vgxness-manager`));
237
+ lines.push(copyOnly('Change manager profile', `npm run cli -- agents manager-profile set --project ${state.project} --scope project --manager vgxness-manager --instructions <text>`));
238
+ const agents = state.agentsSkills?.agents ?? [];
239
+ const skills = state.agentsSkills?.skills ?? [];
240
+ if (agents.length === 0)
241
+ lines.push('Agents: none registered for this project scope.');
242
+ else
243
+ lines.push('Agents:', ...agents.map((agent) => `- ${agent.name} (${agent.id}) [${agent.mode}/${agent.scope}]${agent.providerAdapter === undefined ? '' : ` via ${agent.providerAdapter}`}${agent.model === undefined ? '' : ` ${agent.model}`} — ${truncate(agent.description ?? 'no description', 96)}`));
244
+ if (skills.length === 0)
245
+ lines.push('Skills: none registered for this project scope.');
246
+ else
247
+ lines.push('Skills:', ...skills.map((skill) => `- ${skill.name} (${skill.id}) [${skill.scope}] — ${truncate(skill.description ?? 'no description', 96)}`));
248
+ if (state.agentsSkills?.truncated === true)
249
+ lines.push('List truncated by --limit.');
250
+ if (profileError !== undefined)
251
+ lines.push(`Registry error: ${profileError}`);
252
+ lines.push(copyOnly('Inspect agents', `npm run cli -- agents list --project ${state.project}`));
253
+ lines.push(copyOnly('Inspect skills', `npm run cli -- skills list --project ${state.project}`));
254
+ return lines.join('\n');
255
+ }
256
+ function renderDoctorScreen(state) {
257
+ const status = state.project === undefined && state.doctor === undefined ? 'ready-without-project' : classifyDoctor(state.doctor, state.sectionErrors.doctor);
258
+ const lines = ['Doctor', `State: ${status}`, 'Warning: doctor commands may prepare or initialize local state; run them outside the TUI only.'];
259
+ if (state.doctor === undefined)
260
+ lines.push('No doctor report was executed by the TUI. Guidance is based on setup evidence only.');
261
+ else {
262
+ lines.push(state.doctor.summary, ...state.doctor.warnings.map((warning) => `Warning: ${warning}`), ...state.doctor.commands.map((command) => copyOnly('Doctor command', command)));
263
+ }
264
+ if (state.setup !== undefined && !isSetupReady(state.setup))
265
+ lines.push(`Setup blocker: ${state.setup.nextAction.reason}`);
266
+ if (state.sectionErrors.doctor !== undefined)
267
+ lines.push(`Doctor guidance error: ${state.sectionErrors.doctor}`);
268
+ lines.push(copyOnly('Run full doctor', 'npm run cli -- mcp doctor'));
269
+ lines.push(copyOnly('Run OpenCode doctor', 'npm run cli -- mcp doctor opencode'));
270
+ return lines.join('\n');
271
+ }
272
+ function renderSettingsScreen(state) {
273
+ return ['Settings', `State: ${state.project === undefined ? 'ready-without-project' : 'ready'}`, `Workspace: ${state.context.workspaceRoot}`, `Database: ${state.context.databasePath ?? 'default'}`, 'Safety: provider config writes are external/copy-only and require explicit confirmation.'].join('\n');
274
+ }
275
+ function renderRuns(state) {
276
+ const notice = state.navigationNotice === undefined ? [] : [`Notice: ${state.navigationNotice}`];
277
+ if (state.runs.length === 0)
278
+ return ['Latest Runs', ...notice, '- No runs found.'].join('\n');
279
+ return ['Latest Runs', ...notice, ...state.runs.map((run, index) => renderRun(run, index === state.selectedIndex))].join('\n');
280
+ }
281
+ function renderRun(run, selected) {
282
+ return `${selected ? '›' : ' '} ${run.id} [${run.status}] ${run.workflow}/${run.phase} — ${run.userIntent}`;
283
+ }
284
+ function renderSelectedRunInsights(state) {
285
+ const selectedRun = state.runs[state.selectedIndex];
286
+ if (selectedRun === undefined)
287
+ return '';
288
+ const insights = state.runInsights?.[selectedRun.id];
289
+ if (insights === undefined)
290
+ return '';
291
+ const lines = ['Selected Run Insights', `Debug: ${insights.debug.cause} — ${insights.debug.summary}`, `Recommended action: ${insights.debug.recommendedAction}`, `Resume plan: ${renderResumePlan(insights.resumePlan)}`];
292
+ if (insights.resumePlan.blockers.length > 0)
293
+ lines.push(`Blockers: ${insights.resumePlan.blockers.map((blocker) => `${blocker.code} — ${blocker.message}`).join('; ')}`);
294
+ const latestTimeline = insights.timeline.slice(-3).map((entry) => `${entry.type}: ${entry.label}`);
295
+ if (latestTimeline.length > 0)
296
+ lines.push(`Latest timeline: ${latestTimeline.join(' · ')}`);
297
+ return lines.join('\n');
298
+ }
299
+ function renderSelectedRunOperationalTrace(state) {
300
+ const selectedRun = state.runs[state.selectedIndex];
301
+ if (selectedRun === undefined)
302
+ return '';
303
+ const item = state.operationalRuns?.items.find((candidate) => candidate.runId === selectedRun.id);
304
+ if (item === undefined)
305
+ return ['Selected Run Trace', 'Trace: sin vínculo disponible'].join('\n');
306
+ const lines = ['Selected Run Trace', `Run: ${item.runId} [${item.status}] ${item.workflow}/${item.phase}`, `Agent/model: ${item.selectedAgentId} · ${item.model}`, `Updated: ${item.updatedAt}${item.outcome === undefined ? '' : ` · outcome=${item.outcome}`}`, `Last checkpoint: ${item.lastCheckpoint === undefined ? 'sin vínculo disponible' : `${item.lastCheckpoint.label} (${item.lastCheckpoint.id}) — ${item.lastCheckpoint.stateSummary}`}`, `Checkpoints: ${item.checkpointsSummary}`];
307
+ lines.push(`Approvals: ${item.relatedApprovals.length === 0 ? 'sin vínculo disponible' : item.relatedApprovals.map((approval) => `${approval.id} [${approval.status}] ${approval.category ?? 'unknown'}/${approval.operation ?? 'unknown'}`).join('; ')}`);
308
+ lines.push(`Preflights/attempts: ${item.relatedPreflights.length === 0 ? 'sin vínculo disponible' : item.relatedPreflights.map((attempt) => `${attempt.id} [${attempt.status}] ${attempt.category}/${attempt.operation} risk=${attempt.riskFlags.join(',') || 'none'}`).join('; ')}`);
309
+ if (item.blockers.length > 0)
310
+ lines.push(`Blockers: ${item.blockers.join('; ')}`);
311
+ if (item.latestTimeline.length > 0)
312
+ lines.push(`Latest timeline: ${item.latestTimeline.join(' · ')}`);
313
+ lines.push(...renderCopyActions(item.nextActions));
314
+ return lines.join('\n');
315
+ }
316
+ function renderCopyActions(actions) {
317
+ return actions.map((action) => copyOnly(action.label, action.command));
318
+ }
319
+ function renderResumePlan(plan) {
320
+ if (plan.resumable && plan.latestCheckpoint !== undefined)
321
+ return `manual review eligible from checkpoint ${plan.latestCheckpoint.id}`;
322
+ return 'blocked or unavailable';
323
+ }
324
+ function checklist(label, status, detail) {
325
+ return `${status === 'ready' ? '[x]' : '[ ]'} ${label} — ${status}${detail === undefined ? '' : ` — ${detail}`}`;
326
+ }
327
+ function activeLabel(id) {
328
+ return screenLabels.find((screen) => screen.id === id)?.label ?? id;
329
+ }
330
+ function truncate(value, limit) {
331
+ return value.length <= limit ? value : `${value.slice(0, Math.max(0, limit - 1))}…`;
332
+ }
@@ -0,0 +1,71 @@
1
+ import { sddPhases } from '../sdd/schema.js';
2
+ export function sanitizeDashboardError(error) {
3
+ const message = error instanceof Error ? error.message : typeof error === 'string' ? error : 'Unavailable';
4
+ return message.replace(/\s+/g, ' ').trim().slice(0, 180) || 'Unavailable';
5
+ }
6
+ export function isSetupReady(setup) {
7
+ return setup?.store.status === 'ready' && setup.verification.status === 'ready' && setup.agents.deferred !== true && setup.defaults.status === 'ready';
8
+ }
9
+ export function classifySetup(setup, error) {
10
+ if (error !== undefined)
11
+ return 'error';
12
+ if (setup === undefined)
13
+ return 'loading';
14
+ return isSetupReady(setup) ? 'ready' : 'blocked';
15
+ }
16
+ export function classifySetupForProject(project, setup, error) {
17
+ if (project === undefined && error === undefined && setup === undefined)
18
+ return 'ready-without-project';
19
+ if (project === undefined && error === undefined && setup !== undefined)
20
+ return setup.store.status === 'ready' ? 'ready-without-project' : 'blocked';
21
+ return classifySetup(setup, error);
22
+ }
23
+ export function classifySdd(sdd, change, error) {
24
+ if (error !== undefined)
25
+ return sdd === undefined ? 'error' : 'blocked';
26
+ if (change === undefined)
27
+ return 'empty';
28
+ if (sdd === undefined || sdd.status.phases.every((phase) => !phase.present))
29
+ return 'empty';
30
+ if (sdd.readiness !== undefined && !sdd.readiness.ready)
31
+ return 'blocked';
32
+ return 'ready';
33
+ }
34
+ export function classifyProjectScoped(project, status, error) {
35
+ if (error !== undefined)
36
+ return 'error';
37
+ if (project === undefined)
38
+ return 'project-required';
39
+ return status ?? 'empty';
40
+ }
41
+ export function createNoProjectDashboardSections() {
42
+ return {
43
+ setup: 'ready-without-project',
44
+ workflows: 'project-required',
45
+ runs: 'project-required',
46
+ approvals: 'project-required',
47
+ agents: 'project-required',
48
+ sdd: 'project-required',
49
+ doctor: 'ready-without-project',
50
+ settings: 'ready-without-project',
51
+ };
52
+ }
53
+ export function classifyAgentsSkills(agentsSkills, error) {
54
+ if (error !== undefined)
55
+ return agentsSkills === undefined ? 'error' : agentsSkills.status;
56
+ return agentsSkills?.status ?? 'empty';
57
+ }
58
+ export function classifyDoctor(doctor, error) {
59
+ if (error !== undefined)
60
+ return doctor === undefined ? 'error' : doctor.status;
61
+ return doctor?.status ?? 'empty';
62
+ }
63
+ export function canonicalSddPhasePresence(sdd) {
64
+ return sddPhases.map((phase) => {
65
+ const status = sdd?.status.phases.find((item) => item.phase === phase);
66
+ return { phase, present: status?.present ?? false, topicKey: status?.topicKey ?? `sdd/${sdd?.change ?? '<change>'}/${phase}` };
67
+ });
68
+ }
69
+ export function copyOnly(label, command) {
70
+ return `${label} (copy-only, Run outside TUI): ${command}`;
71
+ }
@@ -0,0 +1,218 @@
1
+ import { classifySetup } from './dashboard-tui-read-model.js';
2
+ import { buildSetupWizardReadModel } from './setup-wizard-read-model.js';
3
+ import { createSetupWizardState, reduceSetupWizardKey, setupWizardKeyFromInput } from './setup-wizard-state.js';
4
+ export const dashboardScreens = [
5
+ { id: 'setup', key: '1', label: 'Setup' },
6
+ { id: 'workflows', key: '2', label: 'Workflows' },
7
+ { id: 'runs', key: '3', label: 'Runs' },
8
+ { id: 'approvals', key: '4', label: 'Approvals' },
9
+ { id: 'agents', key: '5', label: 'Agents' },
10
+ { id: 'sdd', key: '6', label: 'SDD' },
11
+ { id: 'doctor', key: '7', label: 'Doctor' },
12
+ { id: 'settings', key: '8', label: 'Settings' },
13
+ ];
14
+ export function createDashboardState(input) {
15
+ void input.setupOnboardingDismissed;
16
+ const sectionErrors = input.sectionErrors ?? {};
17
+ const activeScreen = input.activeScreen ?? initialDashboardScreen(input.setup, sectionErrors.setup);
18
+ const focusedScreen = input.focusedScreen ?? activeScreen;
19
+ const project = input.project ?? input.context?.project;
20
+ const change = input.change ?? input.context?.change;
21
+ const databasePath = input.databasePath ?? input.context?.databasePath;
22
+ const context = {
23
+ workspaceRoot: input.workspaceRoot ?? input.context?.workspaceRoot ?? process.cwd(),
24
+ ...(project === undefined ? {} : { project }),
25
+ ...(change === undefined ? {} : { change }),
26
+ ...(databasePath === undefined ? {} : { databasePath }),
27
+ };
28
+ const state = {
29
+ context,
30
+ ...(project === undefined ? {} : { project }),
31
+ activeScreen,
32
+ focusedScreen,
33
+ viewMode: input.viewMode ?? 'menu',
34
+ runs: input.runs,
35
+ selectedIndex: clampSelection(input.selectedIndex ?? 0, input.runs),
36
+ shouldExit: input.shouldExit ?? false,
37
+ helpVisible: false,
38
+ setupWizard: input.setupWizard ?? createSetupWizardState({ projectMode: project === undefined ? 'none' : 'current', ...(project === undefined ? {} : { selectedProject: project }) }),
39
+ sectionErrors,
40
+ };
41
+ if (change !== undefined)
42
+ state.change = change;
43
+ if (input.sdd !== undefined)
44
+ state.sdd = input.sdd;
45
+ if (input.setup !== undefined)
46
+ state.setup = input.setup;
47
+ if (input.setupWizardReadModel !== undefined)
48
+ state.setupWizardReadModel = input.setupWizardReadModel;
49
+ else if (input.setup !== undefined)
50
+ state.setupWizardReadModel = buildSetupWizardReadModel(input.setup);
51
+ if (input.profiles !== undefined)
52
+ state.profiles = input.profiles;
53
+ if (input.agentsSkills !== undefined)
54
+ state.agentsSkills = input.agentsSkills;
55
+ if (input.workflows !== undefined)
56
+ state.workflows = input.workflows;
57
+ if (input.operationalRuns !== undefined)
58
+ state.operationalRuns = input.operationalRuns;
59
+ if (input.approvals !== undefined)
60
+ state.approvals = input.approvals;
61
+ if (input.doctor !== undefined)
62
+ state.doctor = input.doctor;
63
+ if (input.runInsights !== undefined)
64
+ state.runInsights = input.runInsights;
65
+ if (input.navigationNotice !== undefined)
66
+ state.navigationNotice = input.navigationNotice;
67
+ if (input.error !== undefined)
68
+ state.error = input.error;
69
+ return state;
70
+ }
71
+ export function initialDashboardScreen(setup, setupError) {
72
+ return classifySetup(setup, setupError) === 'ready' || setupError !== undefined ? 'runs' : 'setup';
73
+ }
74
+ export function reduceDashboardKey(state, key) {
75
+ const screen = screenForKey(key);
76
+ if (screen !== undefined) {
77
+ const { navigationNotice: _navigationNotice, ...rest } = state;
78
+ return { ...rest, viewMode: 'screen', activeScreen: screen, focusedScreen: screen };
79
+ }
80
+ if (key === 'menu-back')
81
+ return state.viewMode === 'screen' ? { ...state, viewMode: 'menu' } : state;
82
+ if (key === 'menu-left')
83
+ return moveMenuFocus(state, -1);
84
+ if (key === 'menu-right')
85
+ return moveMenuFocus(state, 1);
86
+ if (key === 'menu-open') {
87
+ const { navigationNotice: _navigationNotice, ...rest } = state;
88
+ return { ...rest, viewMode: 'screen', activeScreen: state.focusedScreen };
89
+ }
90
+ if (isSetupKey(key) && state.viewMode === 'screen' && state.activeScreen === 'setup')
91
+ return { ...state, setupWizard: reduceSetupWizardKey(state.setupWizard, toSetupWizardKey(key)) };
92
+ if (key === 'up')
93
+ return state.viewMode === 'menu' ? moveMenuFocus(state, -1) : state.activeScreen === 'runs' ? navigateRuns(state, -1) : state.activeScreen === 'setup' ? { ...state, setupWizard: reduceSetupWizardKey(state.setupWizard, 'up') } : state;
94
+ if (key === 'down')
95
+ return state.viewMode === 'menu' ? moveMenuFocus(state, 1) : state.activeScreen === 'runs' ? navigateRuns(state, 1) : state.activeScreen === 'setup' ? { ...state, setupWizard: reduceSetupWizardKey(state.setupWizard, 'down') } : state;
96
+ if (key === 'help')
97
+ return { ...state, helpVisible: !state.helpVisible };
98
+ if (key === 'quit')
99
+ return { ...state, shouldExit: true };
100
+ return state;
101
+ }
102
+ export function dashboardKeyFromInput(input) {
103
+ if (input === '1')
104
+ return 'screen-setup';
105
+ if (input === '2')
106
+ return 'screen-workflows';
107
+ if (input === '3')
108
+ return 'screen-runs';
109
+ if (input === '4')
110
+ return 'screen-approvals';
111
+ if (input === '5')
112
+ return 'screen-agents';
113
+ if (input === '6')
114
+ return 'screen-sdd';
115
+ if (input === '7')
116
+ return 'screen-doctor';
117
+ if (input === '8')
118
+ return 'screen-settings';
119
+ if (input === '' || input === 'h')
120
+ return 'menu-left';
121
+ if (input === '' || input === 'l')
122
+ return 'menu-right';
123
+ if (input === '\r' || input === '\n')
124
+ return 'menu-open';
125
+ if (input === 'b' || input === '\u001B')
126
+ return 'menu-back';
127
+ if (input === 'd')
128
+ return 'setup-details';
129
+ if (input === '?')
130
+ return 'help';
131
+ const setupKey = setupWizardKeyFromInput(input);
132
+ if (setupKey === 'next')
133
+ return 'setup-next';
134
+ if (setupKey === 'back')
135
+ return 'setup-back';
136
+ if (setupKey === 'toggle')
137
+ return 'setup-toggle';
138
+ if (setupKey === 'details')
139
+ return 'setup-details';
140
+ if (setupKey === 'escape')
141
+ return 'setup-escape';
142
+ if (input === 'k' || input === '\u001B[A')
143
+ return 'up';
144
+ if (input === 'j' || input === '\u001B[B')
145
+ return 'down';
146
+ if (input === 'r')
147
+ return 'refresh';
148
+ if (input === 'q' || input === '\u0003')
149
+ return 'quit';
150
+ return undefined;
151
+ }
152
+ export function refreshDashboardState(previous, loaded) {
153
+ return createDashboardState({
154
+ ...(previous.project === undefined ? {} : { project: previous.project }),
155
+ context: previous.context,
156
+ ...(previous.change === undefined ? {} : { change: previous.change }),
157
+ ...loaded,
158
+ activeScreen: previous.activeScreen,
159
+ focusedScreen: previous.focusedScreen,
160
+ viewMode: previous.viewMode,
161
+ selectedIndex: previous.selectedIndex,
162
+ shouldExit: previous.shouldExit,
163
+ setupWizard: previous.setupWizard,
164
+ });
165
+ }
166
+ function moveMenuFocus(state, delta) {
167
+ const currentIndex = Math.max(0, dashboardScreens.findIndex((screen) => screen.id === state.focusedScreen));
168
+ const nextIndex = (currentIndex + delta + dashboardScreens.length) % dashboardScreens.length;
169
+ const focusedScreen = dashboardScreens[nextIndex]?.id ?? state.focusedScreen;
170
+ return focusedScreen === state.focusedScreen ? state : { ...state, focusedScreen };
171
+ }
172
+ function screenForKey(key) {
173
+ if (key === 'screen-setup')
174
+ return 'setup';
175
+ if (key === 'screen-workflows')
176
+ return 'workflows';
177
+ if (key === 'screen-runs')
178
+ return 'runs';
179
+ if (key === 'screen-approvals')
180
+ return 'approvals';
181
+ if (key === 'screen-agents')
182
+ return 'agents';
183
+ if (key === 'screen-sdd')
184
+ return 'sdd';
185
+ if (key === 'screen-doctor')
186
+ return 'doctor';
187
+ if (key === 'screen-settings')
188
+ return 'settings';
189
+ return undefined;
190
+ }
191
+ function isSetupKey(key) {
192
+ return key.startsWith('setup-');
193
+ }
194
+ function toSetupWizardKey(key) {
195
+ if (key === 'setup-next')
196
+ return 'next';
197
+ if (key === 'setup-back')
198
+ return 'back';
199
+ if (key === 'setup-toggle')
200
+ return 'toggle';
201
+ if (key === 'setup-details')
202
+ return 'details';
203
+ return 'escape';
204
+ }
205
+ function clampSelection(selectedIndex, runs) {
206
+ if (runs.length === 0)
207
+ return 0;
208
+ return Math.max(0, Math.min(selectedIndex, runs.length - 1));
209
+ }
210
+ function navigateRuns(state, delta) {
211
+ if (state.runs.length === 0)
212
+ return { ...state, selectedIndex: 0, navigationNotice: 'No runs are available to select yet.' };
213
+ if (state.runs.length === 1)
214
+ return { ...state, selectedIndex: 0, navigationNotice: 'Only one run is available; selection stayed on the only run.' };
215
+ const selectedIndex = clampSelection(state.selectedIndex + delta, state.runs);
216
+ const { navigationNotice: _navigationNotice, ...rest } = state;
217
+ return { ...rest, selectedIndex };
218
+ }