vgxness 1.15.0 → 1.17.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
@@ -8,7 +8,7 @@ This package is proprietary software. The npm package ships inspectable JavaScri
8
8
 
9
9
  OpenCode is the primary supported provider. Claude setup support is secondary. VGX-managed OpenCode and Claude provider configuration is user-global only; provider config writes require explicit CLI confirmation.
10
10
 
11
- VGXNESS v1.14.x has a current project health audit describing the implemented CLI/MCP/SDD control plane, OpenCode-first workflow, release-readiness checks, and the remaining execution/recovery gaps. See [Project health audit v1.14.x](./docs/project-health-audit-v1.14.x.md) for the current system snapshot; [v1.10.x](./docs/project-health-audit-v1.10.x.md) and [v1.9.1](./docs/project-health-audit-v1.9.1.md) remain historical validation evidence for those releases.
11
+ VGXNESS is currently versioned from `package.json` (`1.16.0`). The latest full project health audit is the historical [v1.14.x snapshot](./docs/project-health-audit-v1.14.x.md), which documents the implemented CLI/MCP/SDD control plane, OpenCode-first workflow, release-readiness checks, and the remaining execution/recovery gaps at that point in time. [v1.10.x](./docs/project-health-audit-v1.10.x.md) and [v1.9.1](./docs/project-health-audit-v1.9.1.md) remain historical validation evidence for those releases.
12
12
 
13
13
  ## Requirements
14
14
 
@@ -256,7 +256,7 @@ Remove any user-global OpenCode/Claude config entries and local/global VGXNESS d
256
256
  ## More docs
257
257
 
258
258
  - [CLI reference](./docs/cli.md)
259
- - [Project health audit v1.14.x](./docs/project-health-audit-v1.14.x.md)
259
+ - [Project health audit v1.14.x](./docs/project-health-audit-v1.14.x.md) — historical snapshot
260
260
  - [Project health audit v1.10.x](./docs/project-health-audit-v1.10.x.md)
261
261
  - [Project health audit v1.9.1](./docs/project-health-audit-v1.9.1.md)
262
262
  - [Architecture](./docs/architecture.md)
@@ -2,7 +2,7 @@ import { canonicalBehaviorContractVersion } from '../behavior/behavior-contract-
2
2
  export const canonicalDefaultAgentName = 'vgxness-manager';
3
3
  export const canonicalOpenCodeDefaultModel = 'openai/gpt-5.5';
4
4
  export const canonicalOpenCodeManagerReasoningEffort = 'high';
5
- export const canonicalPromptContractVersion = 11;
5
+ export const canonicalPromptContractVersion = 12;
6
6
  export const canonicalSddSubagentNames = [
7
7
  'vgxness-sdd-explore',
8
8
  'vgxness-sdd-propose',
@@ -131,8 +131,15 @@ export function createCanonicalOpenCodeSddMcpToolPermissions() {
131
131
  vgxness_sdd_get_artifact: 'allow',
132
132
  vgxness_sdd_list_artifacts: 'allow',
133
133
  vgxness_sdd_cockpit: 'allow',
134
+ vgxness_context_cockpit: 'allow',
134
135
  vgxness_agent_resolve: 'allow',
135
136
  vgxness_sdd_save_artifact: 'allow',
137
+ vgxness_memory_search: 'allow',
138
+ vgxness_memory_get: 'allow',
139
+ vgxness_memory_save: 'allow',
140
+ vgxness_memory_update: 'allow',
141
+ vgxness_skill_payload: 'allow',
142
+ vgxness_run_preflight: 'allow',
136
143
  };
137
144
  }
138
145
  export function createCanonicalOpenCodeSddSubagentPrompt(name) {
@@ -146,50 +153,52 @@ export function createCanonicalOpenCodeSddSubagentPrompt(name) {
146
153
  * This feeds the OpenCode default config and Claude generated agent files; it is
147
154
  * intentionally separate from registry/seed instructions below.
148
155
  */
149
- export const canonicalOpenCodeManagerPrompt = `# VGXNESS Manager - compact SDD orchestrator
156
+ export const canonicalOpenCodeManagerPrompt = `# VGXNESS Manager
150
157
 
151
- Bind only to the primary \`vgxness-manager\` agent. Executor agents use their own prompts.
158
+ Bind to \`vgxness-manager\` only. Executors use their own prompts.
152
159
 
153
160
  ## Role
154
- Coordinate SDD; keep chat thin, use VGXNESS MCP as durable state, delegate to smallest exact hidden SDD subagent, synthesize evidence. Coach while coordinating: teach briefly; explain practical tradeoffs, risk/effort/unknowns; challenge weak assumptions; keep user in control; avoid lectures.
161
+ Coordinate SDD; keep chat thin, use VGXNESS MCP state, delegate to exact SDD subagent. Coach while coordinating: teach briefly; explain practical tradeoffs, risk/effort/unknowns; challenge weak assumptions; keep user in control; avoid lectures.
155
162
 
156
163
  ## Non-negotiable governance
157
- - SDD artifact acceptance is human-only. Never infer or fabricate acceptance from generated output, subagent/model output, file presence, confidence, or legacy artifacts. Treat draft/rejected/superseded/stale/unaccepted artifacts as not accepted until a human acceptance record exists.
158
- - Before phase advancement, call readiness/status tools: \`vgxness_sdd_status\`/\`vgxness_sdd_next\` and \`vgxness_sdd_ready\` or \`vgxness_sdd_get_readiness\`.
159
- - Before risky VGX-managed side effects (edit, shell/tests, git, network, provider-tool, secrets, external-directory, destructive, privileged, ambiguous), call \`vgxness_run_preflight\` with runId/workflow/phase/agent context when available. If approval/block is required, stop; do not invent approval.
160
- - Direct human acceptance of an exact SDD artifact via \`vgxness_sdd_accept_artifact\` is not a generic SDD write for manager routing: do not call \`vgxness_run_preflight\` solely for that acceptance when the user explicitly accepted the exact project/change/phase artifact, \`acceptedBy.type\` is \`"human"\`, \`acceptedBy.id\` is non-empty, and status/readiness confirms the artifact is eligible. This shortcut applies only to \`vgxness_sdd_accept_artifact\` and the trusted draft autorun chain described below; it does not apply to edits, shell/tests, git, provider config, memory writes, external paths, secrets, destructive/privileged/ambiguous operations.
164
+ - VGXNESS uses SQLite artifacts/control-plane; OpenCode as primary provider; artifacts are not openspec files. Do not write to \`openspec/\`.
165
+ - SDD artifact acceptance is human-only. Artifact presence is not acceptance. Never infer or fabricate acceptance from generated output, subagent/model output, file presence, confidence, or legacy artifacts; draft/rejected/superseded/stale/unaccepted artifacts are not accepted until a human acceptance record exists.
166
+ - Before phase advancement, call \`vgxness_sdd_status\`/\`vgxness_sdd_next\` plus \`vgxness_sdd_ready\` or \`vgxness_sdd_get_readiness\`.
167
+ - Before risky VGX-managed side effects (edit, shell/tests, git, network, provider-tool, secrets, external-directory, destructive, privileged, ambiguous), call \`vgxness_run_preflight\` with runId/workflow/phase/agent when available. Stop on approval/block; never invent approval.
168
+ - Direct human acceptance of an exact SDD artifact via \`vgxness_sdd_accept_artifact\` is not a generic SDD write for manager routing: do not call \`vgxness_run_preflight\` solely for that acceptance; require the user explicitly accepted the exact project/change/phase artifact, \`acceptedBy.type\` is \`"human"\`, \`acceptedBy.id\` is non-empty, and status/readiness confirms the artifact is eligible. This shortcut applies only to \`vgxness_sdd_accept_artifact\` and the trusted draft autorun chain described below; excludes edits, shell/tests, git, provider config, memory writes, external paths, secrets, destructive/privileged/ambiguous operations.
161
169
  - OpenCode native/provider tools are governance-v1 audit-only/non-hard-blocking. Report warnings; do not say native OpenCode tools are hard-blocked by config.
162
- - Do not mutate provider/global OpenCode config. Do not write to \`openspec/\`. Do not publish packages unless explicitly requested. Never revert/overwrite unrelated user work. Preserve \`permission.task\` deny-by-default with only exact known SDD subagents allowed.
170
+ - Do not mutate provider/global OpenCode config. Do not publish packages unless explicitly requested. Never revert/overwrite unrelated user work. Preserve \`permission.task\` deny-by-default with only exact known SDD subagents.
163
171
  - Do not change provider model/reasoning config unless explicitly requested.
164
172
 
165
173
  ## Flexible governance routing
166
- Use the lightest safe path: Tier 0-2 direct; Tier 3 preflight; Tier 4 formal SDD for governance, permissions, acceptance, architecture/security, or cross-surface behavior. Provider status/doctor/preview/handoff are read-only/audit-only; writes stay gated.
174
+ Use lightest safe path: T0-2 direct, T3 preflight, T4 formal SDD for governance, permissions, acceptance, architecture/security, or cross-surface behavior. Provider status/doctor/preview/handoff read-only/audit-only; writes gated.
167
175
 
168
176
  ## Provider-native daily flow
169
- SDD happens in OpenCode via conversation, VGXNESS MCP, and SDD subagents. No terminal SDD phase commands for normal flow. CLI is an escape hatch for bootstrap, doctor, recovery, setup gaps, or explicit request.
177
+ SDD happens in OpenCode via conversation, VGXNESS MCP, and SDD subagents. No terminal SDD phase commands. CLI is an escape hatch for bootstrap, doctor, recovery, setup gaps, or explicit request.
170
178
 
171
179
  ## MCP playbook
172
- - For starting, resuming, or recovering context: prefer \`vgxness_context_cockpit\`; treat \`vgxness_session_restore\` as one signal, not truth. For ending, pausing, handing off, or compacting: \`vgxness_session_close\` with current session id and actor \`manager\`; if no id, do not invent one and summarize.
173
- - SDD artifacts: guide state with \`vgxness_sdd_next\`/\`vgxness_sdd_cockpit\`; list/read with \`vgxness_sdd_get_artifact\`/\`vgxness_sdd_list_artifacts\`; prefer \`payloadMode: "compact"\` so the primary context stays clean; use verbose only when required, preferably inside the delegated phase subagent. Save with \`vgxness_sdd_save_artifact\` only after the appropriate flow. Use \`vgxness_sdd_reopen_artifact\` only for rejected artifacts returning to draft, with explicit human actor/audit context.
174
- - Trusted draft autorun: when the exact \`proposal\` artifact is already accepted, you may run exactly \`spec -> design -> tasks\` without extra human confirmation via exact hidden subagents and save drafts with \`vgxness_sdd_save_artifact\`. This is draft-only planning, not acceptance or completion. Do not use for explore/proposal/apply-progress/verify/archive, rejected/superseded artifacts, accepted overwrites, or risky side effects (provider config, edits, shell/tests, git, secrets, external/destructive/privileged/ambiguous). Re-check status/readiness before and after; generated spec/design drafts may feed downstream design/tasks but remain unaccepted.
175
- - Acceptance/readiness: check SDD status/readiness for the exact project/change/phase, confirm explicit human acceptance of that exact artifact, call \`vgxness_sdd_accept_artifact\` directly with audit context (\`acceptedBy.type: "human"\`, non-empty \`acceptedBy.id\`, runId, agentId), then re-check status/readiness before reporting state. Do not add \`vgxness_run_preflight\` solely for that exact direct acceptance call. Ambiguous replies count only when tied to an immediate exact acceptance prompt. Use \`vgxness_governance_report\` for readiness, artifact states, preflight posture, and audit warnings.
176
- - Memory: call \`vgxness_memory_search\`/\`vgxness_memory_get\` for prior work or unclear context; call \`vgxness_memory_save\` for durable discoveries, decisions, bug fixes, config, patterns, or preferences; use \`vgxness_memory_update\` only to correct/evolve a known id. Do not duplicate full SDD artifacts as memory.
177
- - Agents/profile/payloads: resolve phase with \`vgxness_agent_resolve\`. Preview mode does not execute a provider or write provider config for \`vgxness_agent_activate\`, \`vgxness_opencode_manager_payload\`, or \`vgxness_skill_payload\`. Use \`vgxness_manager_profile_get\` before changes; \`vgxness_manager_profile_set\` requires explicit human authorization.
178
- - Runs/recovery: use \`vgxness_run_start\`/\`vgxness_run_list\`/\`vgxness_run_get\` for run work; checkpoint with \`vgxness_run_checkpoint\`, preflight with \`vgxness_run_preflight\`, and close with \`vgxness_run_finalize\`. Unknown runId: \`vgxness_run_resume_candidates\`. For interrupted runs, inspect with \`vgxness_run_resume_inspect\`, then call \`vgxness_run_resume_gate\` with approvalId from pendingApprovals/inspect before advice; never pass runId as approvalId. Follow safe \`recommendedActions[]\`.
179
- - Provider diagnostics: \`vgxness_provider_status\`/\`vgxness_provider_doctor\` read-only; report \`providerEvidence.evidenceLevel\`/\`hostToolPresenceVerified\` limits.
180
+ - For starting, resuming, or recovering context: prefer \`vgxness_context_cockpit\`; treat \`vgxness_session_restore\` as one signal, not truth. For ending, pausing, handing off, or compacting: \`vgxness_session_close\` with session id and actor \`manager\`; if no id, do not invent one and summarize.
181
+ - Proposal clarity: if product/business clarity is missing, run a proposal question round.
182
+ - SDD artifacts: guide with \`vgxness_sdd_next\`/\`vgxness_sdd_cockpit\`; list/read with \`vgxness_sdd_get_artifact\`/\`vgxness_sdd_list_artifacts\`; prefer \`payloadMode: "compact"\` so primary context stays clean; use verbose only when required, preferably inside the delegated phase subagent. Save with \`vgxness_sdd_save_artifact\` after the right flow. Use \`vgxness_sdd_reopen_artifact\` only for rejected artifacts returning to draft, with explicit human actor/audit context.
183
+ - Trusted draft autorun: when the exact \`proposal\` artifact is already accepted, run exactly \`spec -> design -> tasks\` without extra human confirmation via exact subagents; save drafts with \`vgxness_sdd_save_artifact\`. This is draft-only planning, not acceptance or completion. Not for explore/proposal/apply-progress/verify/archive, rejected/superseded artifacts, accepted overwrites, or risky side effects (provider config, edits, shell/tests, git, secrets, external/destructive/privileged/ambiguous). Re-check status/readiness before/after; generated spec/design drafts can feed design/tasks but remain unaccepted.
184
+ - Acceptance/readiness: check SDD status/readiness for the exact project/change/phase; confirm explicit human acceptance; call \`vgxness_sdd_accept_artifact\` directly with audit context (\`acceptedBy.type: "human"\`, non-empty \`acceptedBy.id\`, runId, agentId); re-check status/readiness before reporting state. Do not add \`vgxness_run_preflight\` solely for that exact direct acceptance call. Ambiguous replies count only when tied to an immediate exact acceptance prompt. Use \`vgxness_governance_report\` for readiness, artifact states, preflight posture, audit warnings.
185
+ - Memory: \`vgxness_memory_search\`/\`vgxness_memory_get\` for prior work/unclear context; \`vgxness_memory_save\` for durable discoveries, decisions, bug fixes, config, patterns, or preferences; \`vgxness_memory_update\` only to correct/evolve a known id. Do not duplicate full SDD artifacts as memory.
186
+ - Agents/skills: Skill registry index-first; resolve skills by registry, exact path, and task context; read exact paths; do not invent summaries. Resolve phase with \`vgxness_agent_resolve\`. Preview mode does not execute a provider or write provider config for \`vgxness_agent_activate\`, \`vgxness_opencode_manager_payload\`, or \`vgxness_skill_payload\`. \`vgxness_manager_profile_get\` before changes; \`vgxness_manager_profile_set\` needs explicit human authorization.
187
+ - Runs/recovery: \`vgxness_run_start\`/\`vgxness_run_list\`/\`vgxness_run_get\`; checkpoint with \`vgxness_run_checkpoint\`, preflight with \`vgxness_run_preflight\`, close with \`vgxness_run_finalize\`. Unknown runId: \`vgxness_run_resume_candidates\`. For interrupted runs, inspect with \`vgxness_run_resume_inspect\`, then call \`vgxness_run_resume_gate\` with approvalId from pendingApprovals/inspect; never pass runId as approvalId. Follow safe \`recommendedActions[]\`.
188
+ - Provider diagnostics: \`vgxness_provider_status\`/\`vgxness_provider_doctor\` read-only; report \`providerEvidence.evidenceLevel\` and \`hostToolPresenceVerified\` limits.
180
189
 
181
190
  ## Minimum flows
182
- - Simple answer: inline; memory only for prior context; no run by default.
183
- - Proposal/spec/design/tasks: status/next -> readiness -> prerequisites -> exact subagent -> delegate -> persist by governance; if proposal is accepted, spec/design/tasks may run as the trusted draft autorun chain without extra confirmation, but drafts remain unaccepted.
184
- - Apply/verify: require prerequisites -> artifacts -> exact subagent -> start/recover run -> preflight writes/shell/git/tests -> delegate -> checkpoint -> save -> finalize.
185
- - Config/provider/prompt change: inspect manager/profile or payload first; persistent changes need explicit human authorization.
191
+ - Simple answer: inline; no run by default.
192
+ - Proposal/spec/design/tasks: status/next -> readiness -> prereqs -> exact subagent -> delegate -> persist; if proposal is accepted, spec/design/tasks may use trusted draft autorun without extra confirmation; drafts remain unaccepted.
193
+ - Apply/verify: prereqs -> artifacts -> exact subagent -> start/recover run -> preflight writes/shell/git/tests -> delegate -> checkpoint -> save -> finalize. Before apply/PR, review workload guard: estimate size/risk/reviewer load; recommend slicing into work units or chained PRs when large.
194
+ - Config/provider/prompt change: inspect manager/profile/payload first; persistence needs explicit human authorization.
186
195
  - Recovery: MCP first. Continue: use \`vgxness_sdd_continue\`; read-only/advisory: no provider execution, run creation, artifact mutation, provider config/openspec writes, or acceptance/apply-progress bypass. CLI \`vgxness sdd continue\` is human fallback only. \`vgxness resume --project\` is for candidate runs. The removed experimental \`vgxness code\` runtime is not an SDD fallback.
187
196
 
188
197
  ## Delegation thresholds
189
- Default to delegation for SDD phase-shaped work, repository exploration beyond one narrow lookup, implementation, verification, incident recovery, or multi-step analysis. Inline only conversational guidance, single-fact lookup, MCP/status/readiness checks, and trivial mechanical edits when safe. When uncertain between inline work and subagent work, delegate to the exact smallest SDD subagent. Never delegate to unknown agents; use exact SDD phase mapping.
198
+ Default to delegation for SDD phase-shaped work, repository exploration beyond one narrow lookup, implementation, verification, incident recovery, or multi-step analysis. Inline only conversational guidance, single-fact lookup, MCP/status/readiness checks, and trivial mechanical edits when safe. When uncertain between inline work and subagent work, delegate to the exact smallest SDD subagent. Never delegate to unknown agents; use exact phase mapping.
190
199
 
191
200
  ## Output
192
- Be concise: delegated work, results, files/decisions, evidence/tests, risks, next step.`;
201
+ Be concise.`;
193
202
  /**
194
203
  * Registry/seed instructions for the manager agent definition.
195
204
  * These feed the canonical manifest, checked-in seed, boot upgrade, registry
@@ -144,6 +144,7 @@ Areas:
144
144
  runs finish --run-id <id> --status completed|failed|blocked|cancelled --outcome <outcome> [--reason <text>]
145
145
  runs permission-check --run-id <id> --category <category> --operation <name> [--path <path>] [--agent-id <id>] [--destructive] [--external] [--privileged] [--ambiguous]
146
146
  runs preflight --run-id <id> --category <category> --operation <name> [--workspace <path>] [--path <path>] [--provider-tool <name>] [--agent-id <id>] [--destructive] [--external] [--privileged] [--ambiguous]
147
+ runs preflight output includes workspaceStrategy recommendation JSON and does not create branches, worktrees, or PRs.
147
148
  runs approvals --run <run-id>
148
149
  runs approve|reject|cancel-approval --approval <id> --actor <name> [--reason <text>]
149
150
  runs retry-check --approval <id> [--policy <json>]
@@ -1,17 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box } from 'ink';
3
+ import { renderSddAdviceLines } from './sdd-renderer.js';
3
4
  import { TuiCard, TuiFooter, TuiHeader, TuiSummaryBar, TuiTabs } from './tui/ink/components.js';
4
5
  import { inkTuiTheme, normalizeInkTuiWidth } from './tui/ink/theme.js';
5
6
  export const homeTuiTabs = ['setup', 'status', 'runs', 'sdd', 'skills', 'provider'];
6
7
  const homeTuiTabItems = homeTuiTabs.map((tab) => ({ key: tab, label: labelForHomeTab(tab) }));
7
- export function HomeTuiApp({ plan, width, selectedTab, view = 'overview', runsReadModel = { state: 'not-loaded' }, skillsReadModel = { state: 'not-loaded' }, sddInput = { change: '' } }) {
8
+ export function HomeTuiApp({ plan, width, selectedTab, view = 'overview', runsReadModel = { state: 'not-loaded' }, skillsReadModel = { state: 'not-loaded' }, sddInput = { change: '' }, sddReadModel = { state: 'not-loaded' } }) {
8
9
  const cardWidth = normalizeInkTuiWidth(width);
9
10
  if (view === 'status-focus')
10
11
  return _jsx(FocusedStatusView, { plan: plan, width: cardWidth });
11
12
  if (view === 'runs-focus')
12
13
  return _jsx(FocusedRunsView, { plan: plan, width: cardWidth, runs: runsReadModel });
13
14
  if (view === 'sdd-focus')
14
- return _jsx(FocusedSddView, { plan: plan, width: cardWidth, sddInput: sddInput });
15
+ return _jsx(FocusedSddView, { plan: plan, width: cardWidth, sddInput: sddInput, sdd: sddReadModel });
15
16
  if (view === 'skills-focus')
16
17
  return _jsx(FocusedSkillsView, { plan: plan, width: cardWidth, skills: skillsReadModel });
17
18
  if (view === 'provider-focus')
@@ -26,10 +27,13 @@ function FocusedSkillsView({ plan, width, skills }) {
26
27
  function FocusedRunsView({ plan, width, runs }) {
27
28
  const badge = runs.state === 'ready' ? 'read-only' : runs.state === 'unavailable' ? 'unavailable' : 'loading';
28
29
  const tone = runs.state === 'unavailable' ? 'warning' : 'info';
29
- return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS Runs", subtitle: "Run recovery cockpit \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: "Runs", badge: badge, tone: tone, lines: focusedRunsLines(plan, runs) }) }), _jsx(TuiFooter, { text: "Keys: Esc/b back to Home \u00B7 q exits \u00B7 read-only \u00B7 no writes" })] }));
30
+ const title = runs.state === 'ready' && runs.selected !== undefined ? 'Run detail' : 'Runs';
31
+ return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS Runs", subtitle: "Run recovery cockpit \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: title, badge: badge, tone: tone, lines: focusedRunsLines(plan, runs) }) }), _jsx(TuiFooter, { text: "Keys: Enter inspects first run detail \u00B7 Esc/b back to Home \u00B7 q exits \u00B7 read-only \u00B7 no writes \u00B7 no retry/approval/execution" })] }));
30
32
  }
31
- function FocusedSddView({ plan, width, sddInput }) {
32
- return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS SDD", subtitle: "Spec-driven development cockpit \u00B7 change required \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: "SDD change gate", badge: sddInput.change.length === 0 ? 'change required' : 'change selected', tone: "info", lines: focusedSddLines(plan, sddInput) }) }), _jsx(TuiFooter, { text: "Keys: type change id \u00B7 Backspace edits \u00B7 Esc back \u00B7 Ctrl-C exits \u00B7 local SDD state not opened" })] }));
33
+ function FocusedSddView({ plan, width, sddInput, sdd }) {
34
+ const badge = sddInput.change.length === 0 ? 'change required' : sdd.state === 'ready' ? sdd.next.status : sdd.state === 'unavailable' ? 'unavailable' : 'change selected';
35
+ const tone = sdd.state === 'unavailable' ? 'warning' : 'info';
36
+ return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS SDD", subtitle: "Spec-driven development cockpit \u00B7 change required \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: "SDD change gate", badge: badge, tone: tone, lines: focusedSddLines(plan, sddInput, sdd) }) }), _jsx(TuiFooter, { text: `Keys: type change id · Backspace edits · Esc back · Ctrl-C exits · ${sdd.state === 'ready' ? 'SDD state read-only' : 'local SDD state not opened'}` })] }));
33
37
  }
34
38
  function FocusedStatusView({ plan, width }) {
35
39
  return (_jsxs(Box, { flexDirection: "column", width: width, paddingX: 1, paddingY: 1, children: [_jsx(TuiHeader, { title: "VGXNESS Status", subtitle: "Focused setup readiness \u00B7 read-only", project: plan.project }), _jsx(TuiSummaryBar, { items: summaryItems(plan) }), _jsx(Box, { marginTop: 1, children: _jsx(TuiCard, { title: "Current setup status", badge: plan.status === 'ready' ? 'ready' : statusTitle(plan.status), tone: plan.status === 'ready' ? 'success' : plan.status === 'conflict' ? 'danger' : 'warning', lines: focusedStatusLines(plan) }) }), _jsx(TuiFooter, { text: "Keys: Esc/b back to Home \u00B7 q exits \u00B7 read-only \u00B7 no provider config writes" })] }));
@@ -103,11 +107,14 @@ function focusedRunsLines(plan, runs) {
103
107
  '',
104
108
  runs.message,
105
109
  '',
106
- 'Use `vgxness resume --project <name>` to inspect interrupted runs from a Bun-backed runtime.',
110
+ 'Interrupted runs can be inspected with `vgxness runs list --project <name> --status needs-human` from a Bun-backed runtime.',
107
111
  'Use `vgxness runs list --project <name> --status needs-human` for actionable blockers.',
112
+ 'Safety: read-only/no writes/no retry/approval/execution.',
108
113
  ];
109
114
  }
110
115
  if (runs.state === 'ready') {
116
+ if (runs.selected !== undefined)
117
+ return focusedRunDetailLines(plan, runs.databasePath, runs.selected);
111
118
  return [
112
119
  field('Project', plan.project),
113
120
  field('Store', 'opened read-only'),
@@ -115,11 +122,13 @@ function focusedRunsLines(plan, runs) {
115
122
  field('Recent runs', String(runs.recent.length)),
116
123
  field('Interrupted', String(runs.interrupted.length)),
117
124
  '',
118
- ...runSummarySection('Interrupted runs', runs.interrupted),
125
+ ...runSummarySection('Interrupted runs', runs.interrupted, runs.details),
119
126
  '',
120
- ...runSummarySection('Recent runs', runs.recent),
127
+ ...runSummarySection('Recent runs', runs.recent, runs.details),
121
128
  '',
122
- 'Inspect one run: `vgxness runs get --id <run-id>`.',
129
+ runs.details.length === 0 ? 'No run is available for detail inspection.' : 'Press Enter to inspect the first interrupted/recent run here.',
130
+ 'CLI fallback: `vgxness runs get --id <run-id>`.',
131
+ 'Safety: read-only/no writes/no retry/approval/execution.',
123
132
  ];
124
133
  }
125
134
  return [
@@ -128,22 +137,108 @@ function focusedRunsLines(plan, runs) {
128
137
  field('Safety', 'read-only placeholder'),
129
138
  '',
130
139
  'This panel does not open the local SQLite store yet.',
131
- 'Use `vgxness resume --project <name>` to inspect interrupted runs.',
140
+ 'Use `vgxness runs list --project <name> --status needs-human` to inspect interrupted runs.',
132
141
  'Use `vgxness runs list --project <name> --status needs-human` for actionable blockers.',
133
142
  '',
134
- 'Next implementation slice can wire this panel to the run read model.',
143
+ 'This panel is read-only/no writes/no retry/approval/execution.',
135
144
  ];
136
145
  }
137
- function runSummarySection(title, runs) {
146
+ export function renderHomeRunsReadOnlyLines(plan, runs) {
147
+ return focusedRunsLines(plan, runs);
148
+ }
149
+ function focusedRunDetailLines(plan, databasePath, run) {
150
+ return [
151
+ field('Project', plan.project),
152
+ field('Store', 'opened read-only'),
153
+ field('Database', databasePath),
154
+ field('Run', shortRunId(run.id)),
155
+ field('Status', run.status),
156
+ field('Workflow/phase', `${run.workflow}/${run.phase}`),
157
+ field('Events', String(run.events)),
158
+ field('Checkpoints', String(run.checkpoints)),
159
+ field('Approvals', `${run.approvals} (${formatApprovalStatusCounts(run.approvalStatusCounts)})`),
160
+ field('Attempts', `${run.attempts} (${formatAttemptStatusCounts(run.attemptStatusCounts)})`),
161
+ ...(run.latestCheckpointLabel === undefined ? [] : [field('Latest checkpoint', run.latestCheckpointLabel)]),
162
+ ...(run.latestEventTitle === undefined ? [] : [field('Latest event', run.latestEventTitle)]),
163
+ ...(run.outcome === undefined ? [] : [field('Outcome', run.outcome)]),
164
+ ...(run.outcomeReason === undefined ? [] : [field('Outcome reason', run.outcomeReason)]),
165
+ ...(run.userIntent === undefined ? [] : ['', `Intent: ${run.userIntent}`]),
166
+ '',
167
+ `Inspect JSON: vgxness runs get --id ${run.id}`,
168
+ 'Safety: read-only/no writes/no retry/approval/execution from this TUI detail.',
169
+ ];
170
+ }
171
+ function runSummarySection(title, runs, details = []) {
138
172
  if (runs.length === 0)
139
173
  return [title, '- none'];
140
- return [title, ...runs.map((run) => `- ${shortRunId(run.id)} ${run.status} ${run.workflow}/${run.phase}${run.userIntent === undefined ? '' : ` — ${run.userIntent}`}`)];
174
+ return [title, ...runs.map((run) => runSummaryLine(run, details.find((detail) => detail.id === run.id)))];
175
+ }
176
+ function runSummaryLine(run, detail) {
177
+ const base = `- ${shortRunId(run.id)} ${run.status} ${run.workflow}/${run.phase}${run.userIntent === undefined ? '' : ` — ${run.userIntent}`}`;
178
+ if (detail === undefined)
179
+ return base;
180
+ return [
181
+ base,
182
+ `approvals ${detail.approvals} (${formatApprovalStatusCounts(detail.approvalStatusCounts)})`,
183
+ `attempts ${detail.attempts} (${formatAttemptStatusCounts(detail.attemptStatusCounts)})`,
184
+ `latest checkpoint ${detail.latestCheckpointLabel ?? 'none'}`,
185
+ `latest event ${detail.latestEventTitle ?? 'none'}`,
186
+ ].join(' · ');
187
+ }
188
+ function formatApprovalStatusCounts(counts) {
189
+ return [`pending=${counts.pending}`, `approved=${counts.approved}`, `rejected=${counts.rejected}`, `cancelled=${counts.cancelled}`].join(' ');
190
+ }
191
+ function formatAttemptStatusCounts(counts) {
192
+ return [`reserved=${counts.reserved}`, `succeeded=${counts.succeeded}`, `failed=${counts.failed}`, `abandoned=${counts.abandoned}`].join(' ');
141
193
  }
142
194
  function shortRunId(id) {
143
195
  return id.length <= 8 ? id : id.slice(0, 8);
144
196
  }
145
- function focusedSddLines(plan, input) {
197
+ function focusedSddLines(plan, input, sdd) {
146
198
  const change = input.change.length === 0 ? '<id>' : input.change;
199
+ if (input.change.length > 0 && sdd.state === 'unavailable') {
200
+ return [
201
+ field('Project', plan.project),
202
+ field('Change', input.change),
203
+ field('Input', `${input.change}_`),
204
+ field('Store', 'unavailable'),
205
+ field('Database', sdd.databasePath),
206
+ field('Acceptance', 'human-only'),
207
+ field('Safety', 'read-only'),
208
+ '',
209
+ sdd.message,
210
+ '',
211
+ `Continue: vgxness sdd continue --project ${plan.project} --change ${change}`,
212
+ `Status: vgxness sdd status --project ${plan.project} --change ${change}`,
213
+ 'Human acceptance remains explicit; artifact presence is not acceptance.',
214
+ ];
215
+ }
216
+ if (input.change.length > 0 && sdd.state === 'ready') {
217
+ return [
218
+ field('Project', plan.project),
219
+ field('Change', input.change),
220
+ field('Input', `${input.change}_`),
221
+ field('Store', 'opened read-only'),
222
+ field('Database', sdd.databasePath),
223
+ field('Status', sdd.next.status),
224
+ field('Next phase', sdd.next.nextPhase ?? 'none'),
225
+ field('Accepted', `${sdd.cockpit.acceptedCount}/${sdd.cockpit.phases.length}`),
226
+ field('Artifacts', String(sdd.cockpit.artifacts.length)),
227
+ field('Blockers', String(sdd.cockpit.aggregateBlockers.length)),
228
+ field('Recommended', sdd.cockpit.recommendedAction),
229
+ field('Acceptance', sdd.cockpit.gates?.requiresHumanAcceptance === true ? 'human review needed' : 'human-only'),
230
+ field('Safety', 'read-only'),
231
+ '',
232
+ sdd.next.reason,
233
+ '',
234
+ ...sddBlockerLines(sdd.cockpit.aggregateBlockers),
235
+ ...(sdd.continuation.advice.length === 0 ? [] : ['', ...renderSddAdviceLines(sdd.continuation.advice)]),
236
+ '',
237
+ `Inspect: ${sdd.continuation.inspectCommand}`,
238
+ `Continue: vgxness sdd continue --project ${plan.project} --change ${change}`,
239
+ 'Human acceptance remains explicit; artifact presence is not acceptance.',
240
+ ];
241
+ }
147
242
  return [
148
243
  field('Project', plan.project),
149
244
  field('Change', input.change.length === 0 ? 'not selected' : input.change),
@@ -163,6 +258,11 @@ function focusedSddLines(plan, input) {
163
258
  'Next implementation slice can ask for/select a change id before loading SDD state.',
164
259
  ];
165
260
  }
261
+ function sddBlockerLines(blockers) {
262
+ if (blockers.length === 0)
263
+ return ['Blockers', '- none'];
264
+ return ['Blockers', ...blockers.slice(0, 4).map((blocker) => `- ${blocker.phase}: ${blocker.reason}`), ...(blockers.length > 4 ? [`- +${blockers.length - 4} more`] : [])];
265
+ }
166
266
  function focusedSkillsLines(plan, skills) {
167
267
  if (skills.state === 'unavailable') {
168
268
  return [
@@ -210,7 +310,7 @@ function skillSummarySection(skills) {
210
310
  return ['Indexed skills', ...skills.slice(0, 8).map((skill) => `- ${skill.name} ${skill.status}${skill.activeVersion === null ? '' : ` @ ${skill.activeVersion}`}`), ...(skills.length > 8 ? [`- +${skills.length - 8} more`] : [])];
211
311
  }
212
312
  function runsLines() {
213
- return ['Run dashboard is not loaded in this first Home TUI slice.', '', 'Use `vgxness status` or `vgxness resume --project <name>` for current CLI diagnostics.', 'Next slice can wire this panel to the run read model.'];
313
+ return ['Run dashboard is not loaded in this Home TUI overview.', '', 'Use `vgxness status` or `vgxness runs list --project <name>` for current CLI diagnostics.', 'Focused Runs is read-only/no writes/no retry/approval/execution.'];
214
314
  }
215
315
  function sddLines() {
216
316
  return [
@@ -1,6 +1,10 @@
1
1
  import { render as renderInk } from 'ink';
2
2
  import React from 'react';
3
+ import { MemoryService } from '../memory/memory-service.js';
4
+ import { openMemoryDatabase } from '../memory/sqlite/database.js';
3
5
  import { RunService } from '../runs/run-service.js';
6
+ import { SddWorkflowService } from '../sdd/sdd-workflow-service.js';
7
+ import { sddContinuationPlanFrom } from '../sdd/sdd-continuation-plan.js';
4
8
  import { SkillRegistryService } from '../skills/skill-registry-service.js';
5
9
  import { SkillIndexService } from '../skills/skill-index-service.js';
6
10
  import { okText } from './cli-help.js';
@@ -9,7 +13,6 @@ import { renderSetupPlan } from './setup-plan-renderer.js';
9
13
  import { runSetupTuiController } from './setup-tui-controller.js';
10
14
  import { navigationIntentFromInput } from './tui/keymap.js';
11
15
  import { nextItem } from './tui/navigation.js';
12
- import { openMemoryDatabase } from '../memory/sqlite/database.js';
13
16
  const enter = '\r';
14
17
  const escape = '\u001B';
15
18
  const ctrlC = '\u0003';
@@ -26,7 +29,8 @@ export async function runHomeTuiController(input) {
26
29
  let runsReadModel = { state: 'not-loaded' };
27
30
  let skillsReadModel = { state: 'not-loaded' };
28
31
  let sddInput = { change: '' };
29
- const app = renderInk(React.createElement(HomeTuiApp, { plan: input.plan, width, selectedTab, view, runsReadModel, skillsReadModel, sddInput }), {
32
+ let sddReadModel = { state: 'not-loaded' };
33
+ const app = renderInk(React.createElement(HomeTuiApp, { plan: input.plan, width, selectedTab, view, runsReadModel, skillsReadModel, sddInput, sddReadModel }), {
30
34
  stdin: stdin,
31
35
  stdout: stdout,
32
36
  interactive: true,
@@ -34,7 +38,7 @@ export async function runHomeTuiController(input) {
34
38
  exitOnCtrlC: false,
35
39
  });
36
40
  const rerender = () => {
37
- app.rerender(React.createElement(HomeTuiApp, { plan: input.plan, width, selectedTab, view, runsReadModel, skillsReadModel, sddInput }));
41
+ app.rerender(React.createElement(HomeTuiApp, { plan: input.plan, width, selectedTab, view, runsReadModel, skillsReadModel, sddInput, sddReadModel }));
38
42
  };
39
43
  stdin.setRawMode?.(true);
40
44
  stdin.resume?.();
@@ -54,12 +58,14 @@ export async function runHomeTuiController(input) {
54
58
  }
55
59
  if (value === backspace || value === ctrlH) {
56
60
  sddInput = { change: sddInput.change.slice(0, -1) };
61
+ sddReadModel = readHomeSdd(input.plan, sddInput.change);
57
62
  rerender();
58
63
  return;
59
64
  }
60
65
  const nextSddChange = appendSddChangeInput(sddInput.change, value);
61
66
  if (nextSddChange !== sddInput.change) {
62
67
  sddInput = { change: nextSddChange };
68
+ sddReadModel = readHomeSdd(input.plan, nextSddChange);
63
69
  rerender();
64
70
  return;
65
71
  }
@@ -77,6 +83,11 @@ export async function runHomeTuiController(input) {
77
83
  rerender();
78
84
  return;
79
85
  }
86
+ if (view === 'runs-focus' && (value === enter || value === '\n' || intent === 'select')) {
87
+ runsReadModel = selectFirstHomeRunDetail(runsReadModel);
88
+ rerender();
89
+ return;
90
+ }
80
91
  if (intent === 'cancel') {
81
92
  cleanup();
82
93
  resolve('quit');
@@ -99,6 +110,7 @@ export async function runHomeTuiController(input) {
99
110
  return;
100
111
  }
101
112
  if ((value === enter || value === '\n' || intent === 'select') && selectedTab === 'sdd') {
113
+ sddReadModel = readHomeSdd(input.plan, sddInput.change);
102
114
  view = 'sdd-focus';
103
115
  rerender();
104
116
  return;
@@ -179,12 +191,73 @@ function readHomeRuns(plan) {
179
191
  phase: run.phase,
180
192
  ...(run.userIntent === undefined ? {} : { userIntent: run.userIntent }),
181
193
  })),
194
+ details: collectRunDetails(service, [...interrupted.value.map((run) => run.runId), ...recent.value.map((run) => run.id)]),
182
195
  };
183
196
  }
184
197
  finally {
185
198
  opened.value.close();
186
199
  }
187
200
  }
201
+ function collectRunDetails(service, runIds) {
202
+ const uniqueRunIds = [...new Set(runIds)];
203
+ const details = [];
204
+ for (const runId of uniqueRunIds) {
205
+ const run = service.getRun(runId);
206
+ if (!run.ok)
207
+ continue;
208
+ details.push(runDetailSummary(run.value));
209
+ }
210
+ return details;
211
+ }
212
+ function runDetailSummary(run) {
213
+ const userIntent = run.userIntent.trim();
214
+ const latestCheckpoint = run.checkpoints.at(-1);
215
+ const latestEvent = run.events.at(-1);
216
+ return {
217
+ id: run.id,
218
+ status: run.status,
219
+ workflow: run.workflow,
220
+ phase: run.phase,
221
+ ...(userIntent.length === 0 ? {} : { userIntent }),
222
+ ...(run.outcome === undefined ? {} : { outcome: run.outcome }),
223
+ ...(run.outcomeReason === undefined ? {} : { outcomeReason: run.outcomeReason }),
224
+ events: run.events.length,
225
+ checkpoints: run.checkpoints.length,
226
+ approvals: run.approvals.length,
227
+ approvalStatusCounts: countApprovalStatuses(run.approvals.map((approval) => approval.status)),
228
+ attempts: run.operationAttempts.length,
229
+ attemptStatusCounts: countAttemptStatuses(run.operationAttempts.map((attempt) => attempt.status)),
230
+ ...(latestCheckpoint === undefined ? {} : { latestCheckpointLabel: latestCheckpoint.label }),
231
+ ...(latestEvent === undefined ? {} : { latestEventTitle: latestEvent.title }),
232
+ };
233
+ }
234
+ function countApprovalStatuses(statuses) {
235
+ return {
236
+ pending: statuses.filter((status) => status === 'pending').length,
237
+ approved: statuses.filter((status) => status === 'approved').length,
238
+ rejected: statuses.filter((status) => status === 'rejected').length,
239
+ cancelled: statuses.filter((status) => status === 'cancelled').length,
240
+ };
241
+ }
242
+ function countAttemptStatuses(statuses) {
243
+ return {
244
+ reserved: statuses.filter((status) => status === 'reserved').length,
245
+ succeeded: statuses.filter((status) => status === 'succeeded').length,
246
+ failed: statuses.filter((status) => status === 'failed').length,
247
+ abandoned: statuses.filter((status) => status === 'abandoned').length,
248
+ };
249
+ }
250
+ function selectFirstHomeRunDetail(runs) {
251
+ if (runs.state !== 'ready' || runs.selected !== undefined)
252
+ return runs;
253
+ const firstInterruptedId = runs.interrupted[0]?.id;
254
+ const firstRecentId = runs.recent[0]?.id;
255
+ const selectedId = firstInterruptedId ?? firstRecentId;
256
+ if (selectedId === undefined)
257
+ return runs;
258
+ const selected = runs.details.find((detail) => detail.id === selectedId);
259
+ return selected === undefined ? runs : { ...runs, selected };
260
+ }
188
261
  function runRecordSummary(run) {
189
262
  const userIntent = run.userIntent.trim();
190
263
  return {
@@ -210,6 +283,30 @@ function readHomeSkills(plan) {
210
283
  opened.value.close();
211
284
  }
212
285
  }
286
+ function readHomeSdd(plan, change) {
287
+ if (change.length === 0)
288
+ return { state: 'not-loaded' };
289
+ const opened = openMemoryDatabase({ path: plan.db.path, readonly: true });
290
+ if (!opened.ok)
291
+ return { state: 'unavailable', databasePath: plan.db.path, message: opened.error.message };
292
+ try {
293
+ const sdd = new SddWorkflowService(new MemoryService(opened.value));
294
+ const status = sdd.getStatus({ project: plan.project, change });
295
+ if (!status.ok)
296
+ return { state: 'unavailable', databasePath: plan.db.path, message: status.error.message };
297
+ const next = sdd.getNext({ project: plan.project, change });
298
+ if (!next.ok)
299
+ return { state: 'unavailable', databasePath: plan.db.path, message: next.error.message };
300
+ const cockpit = sdd.getCockpit({ project: plan.project, change });
301
+ if (!cockpit.ok)
302
+ return { state: 'unavailable', databasePath: plan.db.path, message: cockpit.error.message };
303
+ const continuation = sddContinuationPlanFrom({ project: plan.project, next: next.value, cockpit: cockpit.value });
304
+ return { state: 'ready', databasePath: plan.db.path, status: status.value, next: next.value, cockpit: cockpit.value, continuation };
305
+ }
306
+ finally {
307
+ opened.value.close();
308
+ }
309
+ }
213
310
  function tuiWidth(stdout) {
214
311
  const columns = stdout.columns;
215
312
  const width = typeof columns === 'number' && Number.isFinite(columns) ? Math.floor(columns) : 88;
@@ -91,6 +91,8 @@ export function renderSddContinuationPlan(plan) {
91
91
  `- ${suggestedCommandLabel}: ${plan.suggestedCommand}`,
92
92
  `- Diagnostic command: ${plan.inspectCommand}`,
93
93
  '',
94
+ ...renderSddAdviceLines(plan.advice),
95
+ ...(plan.advice.length === 0 ? [] : ['']),
94
96
  'Blocker-specific next actions:',
95
97
  ...(plan.blockerActions.length === 0
96
98
  ? ['- none']
@@ -119,6 +121,11 @@ export function renderSddContinuationPlan(plan) {
119
121
  ];
120
122
  return `${lines.join('\n')}\n`;
121
123
  }
124
+ export function renderSddAdviceLines(advice) {
125
+ if (advice.length === 0)
126
+ return [];
127
+ return ['Advice:', ...advice.map((item) => `- ${item.title}: ${item.message}`)];
128
+ }
122
129
  export function renderSddCockpit(cockpit) {
123
130
  const readModel = cockpit.readModel;
124
131
  const blockers = readModel.blockers;
@@ -46,7 +46,7 @@ export function callVgxTool(call, services) {
46
46
  case 'vgxness_sdd_next':
47
47
  return toEnvelope(validated.tool, services.sdd.getNext(validated.input));
48
48
  case 'vgxness_sdd_cockpit':
49
- return toEnvelope(validated.tool, mapMemoryResult(services.sdd.getCockpit(validated.input), buildSddCockpitSurfaceResponse));
49
+ return toEnvelope(validated.tool, sddCockpitSurfaceResponse(validated.input, services));
50
50
  case 'vgxness_sdd_continue':
51
51
  return sddContinueEnvelope(validated.input, services);
52
52
  case 'vgxness_governance_report':
@@ -267,6 +267,15 @@ function sddContinueEnvelope(input, services) {
267
267
  ...(relatedRun.value === undefined ? {} : { relatedRunContext: relatedRun.value }),
268
268
  }));
269
269
  }
270
+ function sddCockpitSurfaceResponse(input, services) {
271
+ const cockpit = services.sdd.getCockpit(input);
272
+ if (!cockpit.ok)
273
+ return cockpit;
274
+ const operationalEvidence = services.runs.getSddOperationalEvidence({ project: input.project, change: input.change });
275
+ if (!operationalEvidence.ok)
276
+ return { ok: true, value: buildSddCockpitSurfaceResponse(cockpit.value) };
277
+ return { ok: true, value: buildSddCockpitSurfaceResponse(cockpit.value, { operationalEvidenceByPhase: operationalEvidence.value }) };
278
+ }
270
279
  export function createVgxMcpControlPlane(options = {}) {
271
280
  const databasePath = resolveControlPlaneDatabasePath(options);
272
281
  if (!databasePath.ok)
@@ -223,6 +223,22 @@ const sddRecommendedActionOutputSchema = z
223
223
  .passthrough()),
224
224
  })
225
225
  .passthrough();
226
+ const sddContinuationAdviceOutputSchema = z
227
+ .object({
228
+ id: z.string(),
229
+ kind: z.enum(['proposal-question-round', 'review-workload-guard']),
230
+ title: z.string(),
231
+ message: z.string(),
232
+ readOnly: z.literal(true),
233
+ advisoryOnly: z.literal(true),
234
+ mutating: z.literal(false),
235
+ providerExecution: z.literal(false),
236
+ artifactMutation: z.literal(false),
237
+ runCreation: z.literal(false),
238
+ openspecWrite: z.literal(false),
239
+ reason: z.string(),
240
+ })
241
+ .passthrough();
226
242
  const mcpSuccessOutputSchema = (value) => z
227
243
  .object({
228
244
  ok: z.literal(true),
@@ -672,6 +688,7 @@ export const VGX_MCP_TOOL_OUTPUT_SCHEMAS = {
672
688
  kind: z.literal('sdd-continuation-plan'),
673
689
  project: z.string(),
674
690
  change: z.string(),
691
+ advice: z.array(sddContinuationAdviceOutputSchema),
675
692
  recommendedActions: z.array(sddRecommendedActionOutputSchema),
676
693
  })
677
694
  .passthrough()),