vgxness 1.9.2 → 1.9.4

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 (46) hide show
  1. package/README.md +12 -6
  2. package/dist/agents/agent-resolver.js +33 -3
  3. package/dist/agents/canonical-agent-manifest.js +68 -21
  4. package/dist/agents/canonical-agent-projection.js +46 -4
  5. package/dist/cli/cli-help.js +14 -3
  6. package/dist/cli/commands/index.js +1 -0
  7. package/dist/cli/commands/interactive-entrypoint-dispatcher.js +8 -0
  8. package/dist/cli/commands/mcp-dispatcher.js +49 -18
  9. package/dist/cli/commands/memory-sdd-dispatcher.js +71 -5
  10. package/dist/cli/commands/setup-dispatcher.js +22 -10
  11. package/dist/cli/commands/status-dispatcher.js +130 -0
  12. package/dist/cli/commands/workflow-dispatcher.js +11 -5
  13. package/dist/cli/dispatcher.js +9 -1
  14. package/dist/cli/product-resume-renderer.js +32 -0
  15. package/dist/cli/product-status-renderer.js +81 -0
  16. package/dist/cli/sdd-renderer.js +90 -7
  17. package/dist/cli/tui/main-menu/main-menu-read-model.js +8 -8
  18. package/dist/cli/tui/setup/setup-tui-services.js +27 -10
  19. package/dist/code/cli/code-command.js +7 -4
  20. package/dist/code/reporting/summary.js +4 -1
  21. package/dist/code/runtime/code-runtime.js +27 -4
  22. package/dist/code/runtime/sdd-context.js +18 -2
  23. package/dist/governance/governance-report-builder.js +18 -7
  24. package/dist/mcp/claude-code-agent-config.js +10 -4
  25. package/dist/mcp/client-install-opencode-contract.js +2 -2
  26. package/dist/mcp/client-install-opencode.js +10 -6
  27. package/dist/mcp/control-plane.js +56 -0
  28. package/dist/mcp/opencode-default-agent-config.js +7 -4
  29. package/dist/mcp/provider-status.js +86 -81
  30. package/dist/mcp/schema.js +25 -7
  31. package/dist/mcp/stdio-server.js +4 -0
  32. package/dist/mcp/validation.js +39 -3
  33. package/dist/resume/product-resume.js +166 -0
  34. package/dist/runs/repositories/runs.js +12 -1
  35. package/dist/runs/run-service.js +62 -5
  36. package/dist/sdd/schema.js +8 -0
  37. package/dist/sdd/sdd-continuation-plan.js +81 -0
  38. package/dist/sdd/sdd-workflow-service.js +103 -16
  39. package/dist/skills/skill-resolver.js +21 -4
  40. package/dist/status/product-status.js +121 -0
  41. package/docs/architecture.md +1 -1
  42. package/docs/cli.md +40 -12
  43. package/docs/code-runtime.md +3 -0
  44. package/docs/glossary.md +1 -1
  45. package/docs/mcp.md +18 -4
  46. package/package.json +1 -1
@@ -6,14 +6,15 @@ import { planMemoryImportDryRun, validateMemoryImportPackage, writeMemoryImportO
6
6
  import { MemoryService } from '../../memory/memory-service.js';
7
7
  import { createNaturalLanguagePlan } from '../../orchestrator/natural-language-planner.js';
8
8
  import { OpenCodeInjectionPreviewService } from '../../providers/opencode/injection-preview.js';
9
+ import { RunService } from '../../runs/run-service.js';
9
10
  import { ArtifactPortabilityService } from '../../sdd/artifact-portability-service.js';
10
- import { isSddPhase, sddPhases } from '../../sdd/schema.js';
11
+ import { normalizeSddArtifact, normalizeSddPhaseInput, sddPhases } from '../../sdd/schema.js';
11
12
  import { SddWorkflowService } from '../../sdd/sdd-workflow-service.js';
12
13
  import { SkillRegistryService } from '../../skills/skill-registry-service.js';
13
14
  import { acceptedAtFlag, databasePathSelectionFor, optionalNumberFlag, optionalScopeFlag, optionalStringFlag, optionalTrimmedFlag, requiredFlag, requiredTrimmedFlag, scopeFlag, } from '../cli-flags.js';
14
15
  import { okText, usageFailure, validationFailure } from '../cli-help.js';
15
16
  import { formatMemoryImportValidationErrors, jsonResult, openCliDatabase, readJsonFile, resultFailure, validateMemoryImportMode, writeJsonFile, } from '../cli-helpers.js';
16
- import { renderSddArtifactAccepted, renderSddCockpit, renderSddArtifactDetail, renderSddArtifactList, renderSddNext, renderSddStatus, } from '../sdd-renderer.js';
17
+ import { renderSddArtifactAccepted, renderSddArtifactReopened, renderSddCockpit, renderSddArtifactDetail, renderSddArtifactList, renderSddContinuationPlan, renderSddNext, renderSddStatus, sddContinuationPlanFrom, } from '../sdd-renderer.js';
17
18
  export function runMemoryCommand(command, parsed, database) {
18
19
  const handlers = createMemoryToolHandlers({ service: new MemoryService(database), config: { localMemoryEnabled: true } });
19
20
  const context = { actor: 'cli' };
@@ -122,6 +123,26 @@ export function runSddCommand(command, parsed, database, environment) {
122
123
  return jsonResult(next);
123
124
  return okText(renderSddNext({ project: project.value, decision: next.value }));
124
125
  }
126
+ if (command === 'continue') {
127
+ const next = service.getNext({ project: project.value, change: change.value });
128
+ if (!next.ok)
129
+ return jsonResult(next);
130
+ const cockpit = service.getCockpit({ project: project.value, change: change.value });
131
+ if (!cockpit.ok)
132
+ return jsonResult(cockpit);
133
+ const explicitDatabasePath = optionalStringFlag(parsed.flags, 'db');
134
+ const relatedRun = new RunService(database).findRelatedInterruptedSddRun({ project: project.value, change: change.value });
135
+ if (!relatedRun.ok)
136
+ return jsonResult(relatedRun);
137
+ const plan = sddContinuationPlanFrom({
138
+ project: project.value,
139
+ next: next.value,
140
+ cockpit: cockpit.value,
141
+ ...(relatedRun.value === undefined ? {} : { relatedRunContext: relatedRun.value }),
142
+ ...(explicitDatabasePath === undefined ? {} : { explicitDatabasePath }),
143
+ });
144
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: plan }) : okText(renderSddContinuationPlan(plan));
145
+ }
125
146
  if (command === 'cockpit') {
126
147
  const cockpit = service.getCockpit({ project: project.value, change: change.value });
127
148
  if (!cockpit.ok || parsed.flags.json === true)
@@ -159,7 +180,8 @@ export function runSddCommand(command, parsed, database, environment) {
159
180
  return resultFailure(note);
160
181
  if (!acceptedAt.ok)
161
182
  return resultFailure(acceptedAt);
162
- if (!isSddPhase(phase.value))
183
+ const canonicalPhase = normalizeSddPhaseInput(phase.value);
184
+ if (canonicalPhase === undefined)
163
185
  return resultFailure(validationFailure(`Unknown SDD phase: ${phase.value}. Expected one of: ${sddPhases.join(', ')}`));
164
186
  const acceptedBy = {
165
187
  type: 'human',
@@ -169,7 +191,7 @@ export function runSddCommand(command, parsed, database, environment) {
169
191
  const accepted = service.acceptArtifact({
170
192
  project: project.value,
171
193
  change: change.value,
172
- phase: phase.value,
194
+ phase: canonicalPhase,
173
195
  acceptedBy,
174
196
  acceptedAt: acceptedAt.value,
175
197
  ...(note.value === undefined ? {} : { note: note.value }),
@@ -179,7 +201,7 @@ export function runSddCommand(command, parsed, database, environment) {
179
201
  const view = {
180
202
  project: accepted.value.project,
181
203
  change: change.value,
182
- phase: phase.value,
204
+ phase: canonicalPhase,
183
205
  topicKey: accepted.value.topicKey,
184
206
  artifactId: accepted.value.id,
185
207
  status: 'accepted',
@@ -189,6 +211,50 @@ export function runSddCommand(command, parsed, database, environment) {
189
211
  };
190
212
  return parsed.flags.json === true ? jsonResult({ ok: true, value: view }) : okText(renderSddArtifactAccepted(view));
191
213
  }
214
+ if (command === 'reopen-artifact') {
215
+ const phase = requiredTrimmedFlag(parsed.flags, 'phase');
216
+ const actor = requiredTrimmedFlag(parsed.flags, 'actor');
217
+ const displayName = optionalTrimmedFlag(parsed.flags, 'display-name');
218
+ const note = optionalTrimmedFlag(parsed.flags, 'note');
219
+ if (!phase.ok)
220
+ return resultFailure(phase);
221
+ if (!actor.ok)
222
+ return resultFailure(actor);
223
+ if (!displayName.ok)
224
+ return resultFailure(displayName);
225
+ if (!note.ok)
226
+ return resultFailure(note);
227
+ const canonicalPhase = normalizeSddPhaseInput(phase.value);
228
+ if (canonicalPhase === undefined)
229
+ return resultFailure(validationFailure(`Unknown SDD phase: ${phase.value}. Expected one of: ${sddPhases.join(', ')}`));
230
+ const reopenedBy = {
231
+ type: 'human',
232
+ id: actor.value,
233
+ displayName: displayName.value ?? actor.value,
234
+ };
235
+ const reopened = service.reopenArtifact({
236
+ project: project.value,
237
+ change: change.value,
238
+ phase: canonicalPhase,
239
+ reopenedBy,
240
+ ...(note.value === undefined ? {} : { note: note.value }),
241
+ });
242
+ if (!reopened.ok)
243
+ return resultFailure(reopened);
244
+ const normalized = normalizeSddArtifact(reopened.value);
245
+ const view = {
246
+ project: reopened.value.project,
247
+ change: change.value,
248
+ phase: canonicalPhase,
249
+ topicKey: reopened.value.topicKey,
250
+ artifactId: reopened.value.id,
251
+ status: normalized.metadata.status,
252
+ reopenedBy,
253
+ reopenedAt: normalized.metadata.reopen?.reopenedAt ?? normalized.metadata.updatedAt ?? reopened.value.updatedAt,
254
+ ...(note.value === undefined ? {} : { note: note.value }),
255
+ };
256
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: view }) : okText(renderSddArtifactReopened(view));
257
+ }
192
258
  if (command === 'get-artifact') {
193
259
  const phase = requiredFlag(parsed.flags, 'phase');
194
260
  if (!phase.ok)
@@ -1,5 +1,6 @@
1
1
  import { resolve } from 'node:path';
2
2
  import { AgentRegistryService } from '../../agents/agent-registry-service.js';
3
+ import { computeEffectiveManagerInstructions } from '../../agents/manager-profile-overlay-service.js';
3
4
  import { resolveAgentProfileModel } from '../../agents/profile-model-routing.js';
4
5
  import { installOpenCodeMcpClient } from '../../mcp/client-install-opencode.js';
5
6
  import { createMcpClientSetupPreview } from '../../mcp/client-setup-preview.js';
@@ -144,16 +145,27 @@ export async function applySetupPlanInput(input, environment) {
144
145
  plan: plan.value,
145
146
  },
146
147
  };
147
- const result = await installOpenCodeMcpClient({
148
- cwd: environment.cwd,
149
- databasePath: plan.value.db.path,
150
- databasePathSource: plan.value.db.source === 'global-default' || plan.value.db.source === 'environment' ? plan.value.db.source : 'flag',
151
- scope: input.scope ?? vgxnessSetupDefaults.defaultOpenCodeScope,
152
- env: environment.env,
153
- confirmed: true,
154
- mcpOnly: input.installMode === 'mcp-only',
155
- ...(input.overwriteVgxness === undefined ? {} : { overwriteVgxness: input.overwriteVgxness }),
156
- });
148
+ const opened = openCliDatabase(plan.value.db.path);
149
+ if (!opened.ok)
150
+ return opened;
151
+ let result;
152
+ try {
153
+ const effectiveManagerInstructions = computeEffectiveManagerInstructions(opened.value, input.project);
154
+ result = await installOpenCodeMcpClient({
155
+ cwd: environment.cwd,
156
+ databasePath: plan.value.db.path,
157
+ databasePathSource: plan.value.db.source === 'global-default' || plan.value.db.source === 'environment' ? plan.value.db.source : 'flag',
158
+ scope: input.scope ?? vgxnessSetupDefaults.defaultOpenCodeScope,
159
+ env: environment.env,
160
+ confirmed: true,
161
+ mcpOnly: input.installMode === 'mcp-only',
162
+ ...(input.overwriteVgxness === undefined ? {} : { overwriteVgxness: input.overwriteVgxness }),
163
+ ...(effectiveManagerInstructions === undefined ? {} : { effectiveManagerInstructions }),
164
+ });
165
+ }
166
+ finally {
167
+ opened.value.close();
168
+ }
157
169
  return result.status === 'installed'
158
170
  ? {
159
171
  ok: true,
@@ -0,0 +1,130 @@
1
+ import { MemoryService } from '../../memory/memory-service.js';
2
+ import { openMemoryDatabase } from '../../memory/sqlite/database.js';
3
+ import { SddWorkflowService } from '../../sdd/sdd-workflow-service.js';
4
+ import { buildProductStatus } from '../../status/product-status.js';
5
+ import { RunService } from '../../runs/run-service.js';
6
+ import { buildProductResume } from '../../resume/product-resume.js';
7
+ import { databasePathFor, optionalStringFlag, requiredFlag } from '../cli-flags.js';
8
+ import { jsonResult, resultFailure } from '../cli-helpers.js';
9
+ import { okText, usageFailure } from '../cli-help.js';
10
+ import { renderProductResume } from '../product-resume-renderer.js';
11
+ import { buildProductNext, renderProductNext, renderProductStatus } from '../product-status-renderer.js';
12
+ export function runProductStatusCommand(parsed, environment) {
13
+ const extraPositionals = rejectExtraPositionals(parsed, 'status');
14
+ if (extraPositionals !== undefined)
15
+ return extraPositionals;
16
+ const status = buildProductStatusForCli(parsed, environment);
17
+ if (!status.ok)
18
+ return status.value;
19
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: status.value }) : okText(renderProductStatus(status.value));
20
+ }
21
+ export function runProductNextCommand(parsed, environment) {
22
+ const extraPositionals = rejectExtraPositionals(parsed, 'next');
23
+ if (extraPositionals !== undefined)
24
+ return extraPositionals;
25
+ const status = buildProductStatusForCli(parsed, environment);
26
+ if (!status.ok)
27
+ return status.value;
28
+ const next = buildProductNext(status.value);
29
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: next }) : okText(renderProductNext(next));
30
+ }
31
+ export function runProductResumeCommand(parsed, environment) {
32
+ const extraPositionals = rejectExtraPositionals(parsed, 'resume');
33
+ if (extraPositionals !== undefined)
34
+ return extraPositionals;
35
+ const resume = buildProductResumeForCli(parsed, environment);
36
+ if (!resume.ok)
37
+ return resume.value;
38
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: resume.value }) : okText(renderProductResume(resume.value));
39
+ }
40
+ function rejectExtraPositionals(parsed, command) {
41
+ const extra = parsed.positionals.slice(1);
42
+ return extra.length === 0 ? undefined : usageFailure(`${command} does not accept positional arguments: ${extra.join(' ')}`);
43
+ }
44
+ function buildProductStatusForCli(parsed, environment) {
45
+ const projectFlag = parsed.flags.project === undefined ? { ok: true, value: undefined } : requiredFlag(parsed.flags, 'project');
46
+ if (!projectFlag.ok)
47
+ return { ok: false, value: resultFailure(projectFlag) };
48
+ const changeFlag = parsed.flags.change === undefined ? { ok: true, value: undefined } : requiredFlag(parsed.flags, 'change');
49
+ if (!changeFlag.ok)
50
+ return { ok: false, value: resultFailure(changeFlag) };
51
+ const project = projectFlag.value;
52
+ const change = changeFlag.value;
53
+ if (change === undefined) {
54
+ const status = buildProductStatus({ cwd: environment.cwd, ...(project === undefined ? {} : { project }) });
55
+ return { ok: true, value: status };
56
+ }
57
+ const selectedDatabasePath = databasePathFor(parsed.flags, environment);
58
+ if (!selectedDatabasePath.ok)
59
+ return { ok: false, value: resultFailure(selectedDatabasePath) };
60
+ const explicitDatabasePath = optionalStringFlag(parsed.flags, 'db');
61
+ const opened = openMemoryDatabase({ path: selectedDatabasePath.value, readonly: true });
62
+ if (!opened.ok) {
63
+ const status = buildProductStatus({
64
+ cwd: environment.cwd,
65
+ ...(project === undefined ? {} : { project }),
66
+ change,
67
+ databasePath: selectedDatabasePath.value,
68
+ databaseError: opened.error.message,
69
+ });
70
+ return { ok: true, value: status };
71
+ }
72
+ try {
73
+ const status = buildProductStatus({
74
+ cwd: environment.cwd,
75
+ ...(project === undefined ? {} : { project }),
76
+ change,
77
+ databasePath: selectedDatabasePath.value,
78
+ ...(explicitDatabasePath === undefined ? {} : { explicitDatabasePath }),
79
+ sdd: new SddWorkflowService(new MemoryService(opened.value)),
80
+ runs: new RunService(opened.value),
81
+ });
82
+ return { ok: true, value: status };
83
+ }
84
+ finally {
85
+ opened.value.close();
86
+ }
87
+ }
88
+ function buildProductResumeForCli(parsed, environment) {
89
+ const projectFlag = parsed.flags.project === undefined ? { ok: true, value: undefined } : requiredFlag(parsed.flags, 'project');
90
+ if (!projectFlag.ok)
91
+ return { ok: false, value: resultFailure(projectFlag) };
92
+ const runIdFlag = parsed.flags['run-id'] === undefined ? { ok: true, value: undefined } : requiredFlag(parsed.flags, 'run-id');
93
+ if (!runIdFlag.ok)
94
+ return { ok: false, value: resultFailure(runIdFlag) };
95
+ const project = projectFlag.value;
96
+ const runId = runIdFlag.value;
97
+ if (runId === undefined && project === undefined) {
98
+ const resume = buildProductResume({ cwd: environment.cwd, ...(project === undefined ? {} : { project }) });
99
+ return { ok: true, value: resume };
100
+ }
101
+ const selectedDatabasePath = databasePathFor(parsed.flags, environment);
102
+ if (!selectedDatabasePath.ok)
103
+ return { ok: false, value: resultFailure(selectedDatabasePath) };
104
+ const opened = openMemoryDatabase({ path: selectedDatabasePath.value, readonly: true });
105
+ if (!opened.ok) {
106
+ const visibleDatabasePath = runId === undefined && parsed.flags.db === undefined ? undefined : selectedDatabasePath.value;
107
+ const resume = buildProductResume({
108
+ cwd: environment.cwd,
109
+ ...(project === undefined ? {} : { project }),
110
+ ...(runId === undefined ? {} : { runId }),
111
+ ...(visibleDatabasePath === undefined ? {} : { databasePath: visibleDatabasePath }),
112
+ databaseError: opened.error.message,
113
+ });
114
+ return { ok: true, value: resume };
115
+ }
116
+ try {
117
+ const flaggedDatabasePath = parsed.flags.db === undefined ? undefined : selectedDatabasePath.value;
118
+ const resume = buildProductResume({
119
+ cwd: environment.cwd,
120
+ ...(project === undefined ? {} : { project }),
121
+ ...(runId === undefined ? {} : { runId }),
122
+ ...(flaggedDatabasePath === undefined ? {} : { databasePath: flaggedDatabasePath }),
123
+ runs: new RunService(opened.value),
124
+ });
125
+ return { ok: true, value: resume };
126
+ }
127
+ finally {
128
+ opened.value.close();
129
+ }
130
+ }
@@ -1,7 +1,8 @@
1
1
  import { AgentRegistryService } from '../../agents/agent-registry-service.js';
2
+ import { canonicalOpenCodeDefaultModel } from '../../agents/canonical-agent-manifest.js';
2
3
  import { createNaturalLanguagePlan } from '../../orchestrator/natural-language-planner.js';
3
4
  import { RunService } from '../../runs/run-service.js';
4
- import { isSddPhase, sddPhases } from '../../sdd/schema.js';
5
+ import { normalizeSddPhaseInput, sddPhases } from '../../sdd/schema.js';
5
6
  import { CommandAllowlistAdapter, commandAllowlistIds } from '../../workflows/command-allowlist-adapter.js';
6
7
  import { GuardedProviderWorkflowExecutor, operationMetadataForWorkflowExecution, SafeNonDispatchingWorkflowExecutor, workflowExecutorSafety, } from '../../workflows/workflow-executor.js';
7
8
  import { getWorkflowDefinition, listWorkflows } from '../../workflows/workflow-registry.js';
@@ -248,11 +249,16 @@ export function runWorkflowRunCommand(workflow, parsed, database) {
248
249
  const selected = getWorkflowDefinition(workflow);
249
250
  const planner = createNaturalLanguagePlan({ project, intent: intent.value });
250
251
  const recommended = getWorkflowDefinition(planner.workflow);
251
- const phase = optionalStringFlag(parsed.flags, 'phase') ?? selected.defaultPhase;
252
- if (selected.id === 'sdd' && !isSddPhase(phase))
253
- return resultFailure(validationFailure(`Unknown SDD phase: ${phase}. Expected one of: ${sddPhases.join(', ')}`));
252
+ const phaseInput = optionalStringFlag(parsed.flags, 'phase') ?? selected.defaultPhase;
253
+ let phase = phaseInput;
254
+ if (selected.id === 'sdd') {
255
+ const canonicalPhase = normalizeSddPhaseInput(phaseInput);
256
+ if (canonicalPhase === undefined)
257
+ return resultFailure(validationFailure(`Unknown SDD phase: ${phaseInput}. Expected one of: ${sddPhases.join(', ')}`));
258
+ phase = canonicalPhase;
259
+ }
254
260
  const providerAdapter = optionalStringFlag(parsed.flags, 'provider-adapter') ?? 'opencode';
255
- const model = optionalStringFlag(parsed.flags, 'model') ?? 'openai/gpt-5.5';
261
+ const model = optionalStringFlag(parsed.flags, 'model') ?? canonicalOpenCodeDefaultModel;
256
262
  const registry = new AgentRegistryService(database);
257
263
  const explicitAgentId = optionalStringFlag(parsed.flags, 'agent-id');
258
264
  const selectedAgent = resolveWorkflowRunAgent({
@@ -5,7 +5,7 @@ import { databasePathFor, parseArgs, requiredFlag } from './cli-flags.js';
5
5
  import { okText, usageFailure, visibleHelpText } from './cli-help.js';
6
6
  import { openCliDatabase, resultFailure } from './cli-helpers.js';
7
7
  import { runBootAgentSeedUpgrade } from '../agents/boot-upgrade.js';
8
- import { runAgentCommand, runApprovalsCommand, runCodeCliCommand, runDefaultInteractiveEntrypoint, runDoctorAliasCommand, runInitCommand, runMcpDoctorCommand, runMcpInstallCommand, runMcpSetupCommand, runMemoryCommand, runMemoryImportCommand, runOpenCodeCommand, runOrchestratorCommand, runPermissionsCommand, runRunsCommand, runSddCommand, runSetupApplyCommand, runSetupLifecycleCommand, runSetupPlanCommand, runSetupRollbackCommand, runSkillCommand, runSubagentCommand, runVerificationPlanCommand, runVerificationReportCommand, runWorkflowExecuteCommand, runWorkflowPreviewCommand, runWorkflowRunCommand, } from './commands/index.js';
8
+ import { runAgentCommand, runApprovalsCommand, runCodeCliCommand, runDefaultInteractiveEntrypoint, runDoctorAliasCommand, runInitCommand, runMcpDoctorCommand, runMcpInstallCommand, runMcpSetupCommand, runMemoryCommand, runMemoryImportCommand, runOpenCodeCommand, runOrchestratorCommand, runPermissionsCommand, runProductNextCommand, runProductResumeCommand, runRunsCommand, runSddCommand, runSetupApplyCommand, runSetupLifecycleCommand, runSetupPlanCommand, runSetupRollbackCommand, runProductStatusCommand, runSkillCommand, runSubagentCommand, runVerificationPlanCommand, runVerificationReportCommand, runWorkflowExecuteCommand, runWorkflowPreviewCommand, runWorkflowRunCommand, } from './commands/index.js';
9
9
  const _promptBuffers = new WeakMap();
10
10
  const require = createRequire(import.meta.url);
11
11
  const packageJson = require('../../package.json');
@@ -17,6 +17,12 @@ export function dispatchCli(argv, environment) {
17
17
  const [area, command] = parsed.positionals;
18
18
  if (!area || area === 'help' || area === '--help' || area === '-h')
19
19
  return okText(visibleHelpText());
20
+ if (area === 'status')
21
+ return runProductStatusCommand(parsed, environment);
22
+ if (area === 'next')
23
+ return runProductNextCommand(parsed, environment);
24
+ if (area === 'resume')
25
+ return runProductResumeCommand(parsed, environment);
20
26
  if (area === 'init')
21
27
  return runSetupPlanCommand(parsed, environment);
22
28
  if (area === 'doctor')
@@ -192,10 +198,12 @@ function validateCommand(area, command) {
192
198
  command === 'execute' ||
193
199
  command === 'status' ||
194
200
  command === 'next' ||
201
+ command === 'continue' ||
195
202
  command === 'cockpit' ||
196
203
  command === 'ready' ||
197
204
  command === 'save-artifact' ||
198
205
  command === 'accept-artifact' ||
206
+ command === 'reopen-artifact' ||
199
207
  command === 'get-artifact' ||
200
208
  command === 'list-artifacts' ||
201
209
  command === 'export' ||
@@ -0,0 +1,32 @@
1
+ export function renderProductResume(resume) {
2
+ const lines = [
3
+ 'Resume',
4
+ ...resume.resume.map((line) => `- ${line}`),
5
+ '',
6
+ 'Why',
7
+ ...resume.why.map((line) => `- ${line}`),
8
+ '',
9
+ 'Blockers',
10
+ ...(resume.blockers.length === 0 ? ['- none'] : resume.blockers.map((line) => `- ${line}`)),
11
+ ...(resume.candidateRuns.length === 0
12
+ ? []
13
+ : [
14
+ '',
15
+ 'Candidate runs',
16
+ ...resume.candidateRuns.flatMap((run) => [
17
+ `- ${run.runId} [${run.status}] ${run.workflow}/${run.phase}`,
18
+ ...(run.userIntent === undefined ? [] : [` Intent: ${run.userIntent}`]),
19
+ ` Latest checkpoint: ${run.latestCheckpointLabel ?? 'none'}`,
20
+ ` Resume command: ${run.command}`,
21
+ ]),
22
+ ]),
23
+ '',
24
+ 'Command',
25
+ `- ${resume.command}`,
26
+ ...(resume.manualNextCommands.length === 0 ? [] : ['', 'Related commands', ...resume.manualNextCommands.map((line) => `- ${line}`)]),
27
+ '',
28
+ 'Safety',
29
+ ...resume.safety.map((line) => `- ${line}`),
30
+ ];
31
+ return `${lines.join('\n')}\n`;
32
+ }
@@ -0,0 +1,81 @@
1
+ export function renderProductStatus(status) {
2
+ const commandHeading = productCommandHeading(status);
3
+ const lines = [
4
+ 'Status',
5
+ ...status.status.map((line) => `- ${line}`),
6
+ '',
7
+ 'Blockers',
8
+ ...status.blockers.map((line) => `- ${line}`),
9
+ '',
10
+ 'Next',
11
+ ...status.next.map((line) => `- ${line}`),
12
+ '',
13
+ commandHeading,
14
+ `- ${status.command}`,
15
+ '',
16
+ ...(status.relatedRunContext === undefined ? [] : relatedRunContextLines(status.relatedRunContext, true)),
17
+ 'Safety',
18
+ ...status.safety.map((line) => `- ${line}`),
19
+ ];
20
+ return `${lines.join('\n')}\n`;
21
+ }
22
+ export function buildProductNext(status) {
23
+ const blockers = status.blockers.filter((line) => line !== 'none');
24
+ const blocked = status.sddNextStatus === undefined ? blockers.length > 0 : status.sddNextStatus === 'blocked';
25
+ const visibleBlockers = blocked ? blockers : [];
26
+ const command = blocked ? buildBlockedNextCommand(status) : status.command;
27
+ return {
28
+ version: 1,
29
+ kind: 'product-next',
30
+ project: status.project,
31
+ ...(status.change === undefined ? {} : { change: status.change }),
32
+ blocked,
33
+ next: status.next,
34
+ why: status.status,
35
+ blockers: visibleBlockers,
36
+ command,
37
+ ...(status.relatedRunContext === undefined ? {} : { relatedRunContext: status.relatedRunContext }),
38
+ safety: status.safety,
39
+ };
40
+ }
41
+ function buildBlockedNextCommand(status) {
42
+ const change = status.change === undefined ? '<change>' : status.change;
43
+ const dbHint = status.command.includes('--db <path>') ? ' --db <path>' : '';
44
+ return `vgxness next --project ${status.project.value} --change ${change}${dbHint}`;
45
+ }
46
+ export function renderProductNext(next) {
47
+ const commandHeading = next.blocked ? 'Recovery/diagnostic command' : 'Manual fallback command';
48
+ const lines = [
49
+ 'Next',
50
+ ...next.next.map((line) => `- ${line}`),
51
+ '',
52
+ 'Why',
53
+ ...next.why.map((line) => `- ${line}`),
54
+ ...(next.blockers.length === 0 ? [] : ['', 'Blockers', ...next.blockers.map((line) => `- ${line}`)]),
55
+ '',
56
+ commandHeading,
57
+ `- ${next.command}`,
58
+ '',
59
+ ...(next.relatedRunContext === undefined ? [] : relatedRunContextLines(next.relatedRunContext, true)),
60
+ 'Safety',
61
+ ...next.safety.map((line) => `- ${line}`),
62
+ ];
63
+ return `${lines.join('\n')}\n`;
64
+ }
65
+ function productCommandHeading(status) {
66
+ if (status.sddNextStatus === 'runnable')
67
+ return 'Manual fallback command';
68
+ return 'Recovery/diagnostic command';
69
+ }
70
+ function relatedRunContextLines(relatedRunContext, includeTrailingBlank) {
71
+ return [
72
+ 'Related interrupted run:',
73
+ `- Run ID: ${relatedRunContext.runId}`,
74
+ `- Status: ${relatedRunContext.status}`,
75
+ `- Workflow/phase: ${relatedRunContext.workflow}/${relatedRunContext.phase}`,
76
+ `- Latest checkpoint: ${relatedRunContext.latestCheckpointLabel ?? 'none'}`,
77
+ `- Recommendation: ${relatedRunContext.recommendation}`,
78
+ `- Resume command: ${relatedRunContext.resumeCommand}`,
79
+ ...(includeTrailingBlank ? [''] : []),
80
+ ];
81
+ }
@@ -1,4 +1,5 @@
1
- import { normalizeSddArtifact, sddPhases } from '../sdd/schema.js';
1
+ import { normalizeSddArtifact, sddPhases, } from '../sdd/schema.js';
2
+ export { sddContinuationPlanFrom } from '../sdd/sdd-continuation-plan.js';
2
3
  export function renderSddStatus(input) {
3
4
  const missing = input.status.phases.filter((phase) => !phase.present).map((phase) => phase.topicKey);
4
5
  const blockers = input.status.phases.filter((phase) => phase.present && phase.accepted !== true);
@@ -9,7 +10,8 @@ export function renderSddStatus(input) {
9
10
  ? 'No next SDD phase remains for this change.'
10
11
  : input.status.nextReadyPhase === undefined
11
12
  ? 'Review blockers or accept present draft artifacts before continuing.'
12
- : `Run or draft the ${input.status.nextReadyPhase} phase with ${directPhaseCommand}.`;
13
+ : `Continue the ${input.status.nextReadyPhase} phase in OpenCode using VGXNESS MCP and hidden SDD subagents.`;
14
+ const commandLabel = directPhaseCommand === undefined ? 'Diagnostic command' : 'Manual fallback command';
13
15
  const lines = [
14
16
  'SDD Status',
15
17
  `Project: ${input.project}`,
@@ -27,17 +29,22 @@ export function renderSddStatus(input) {
27
29
  ...(missing.length === 0 ? ['- none'] : missing.map((topicKey) => `- ${topicKey}`)),
28
30
  '',
29
31
  `Recommended action: ${recommendedAction}`,
30
- `Command: ${directPhaseCommand ?? `vgxness sdd next --project ${input.project} --change ${input.status.change}`}`,
32
+ `${commandLabel}: ${directPhaseCommand ?? `vgxness sdd next --project ${input.project} --change ${input.status.change}`}`,
31
33
  `JSON: vgxness sdd status --project ${input.project} --change ${input.status.change} --json`,
32
34
  ];
33
35
  return `${lines.join('\n')}\n`;
34
36
  }
35
37
  export function renderSddNext(input) {
36
38
  const blockers = input.decision.blockedPrerequisites ?? [];
39
+ const guidance = input.decision.blockerGuidance ?? [];
37
40
  const phase = input.decision.nextPhase ?? 'none';
38
41
  const commandGuidance = input.decision.status === 'runnable' && input.decision.nextPhase !== undefined
39
42
  ? `vgxness code sdd ${input.decision.change} ${input.decision.nextPhase} --save-artifact`
40
43
  : `vgxness sdd status --project ${input.project} --change ${input.decision.change}`;
44
+ const commandLabel = input.decision.status === 'runnable' && input.decision.nextPhase !== undefined ? 'Manual fallback command' : 'Diagnostic command';
45
+ const primaryAction = input.decision.status === 'runnable' && input.decision.nextPhase !== undefined
46
+ ? `Continue the ${input.decision.nextPhase} phase in OpenCode using VGXNESS MCP and hidden SDD subagents.`
47
+ : input.decision.recommendedAction;
41
48
  const lines = [
42
49
  'SDD Next',
43
50
  `Project: ${input.project}`,
@@ -47,17 +54,71 @@ export function renderSddNext(input) {
47
54
  `Reason: ${input.decision.reason}`,
48
55
  '',
49
56
  'Blockers:',
50
- ...(blockers.length === 0 ? ['- none'] : blockers.map((blocker) => `- ${blocker.phase}: ${blocker.reason} at ${blocker.topicKey}`)),
57
+ ...(blockers.length === 0
58
+ ? ['- none']
59
+ : blockers.map((blocker) => `- ${blocker.phase}: ${blocker.reason} at ${blocker.topicKey}${blocker.action === undefined ? '' : `; action=${blocker.action}`}`)),
51
60
  '',
52
61
  'Missing topic keys:',
53
62
  ...(input.decision.missingArtifactTopicKeys.length === 0 ? ['- none'] : input.decision.missingArtifactTopicKeys.map((topicKey) => `- ${topicKey}`)),
54
63
  '',
55
- `Recommended action: ${input.decision.recommendedAction}`,
56
- `Command: ${commandGuidance}`,
64
+ 'Missing prerequisite topic keys:',
65
+ ...((input.decision.missingPrerequisiteTopicKeys ?? []).length === 0
66
+ ? ['- none']
67
+ : (input.decision.missingPrerequisiteTopicKeys ?? []).map((topicKey) => `- ${topicKey}`)),
68
+ '',
69
+ 'Next actions:',
70
+ ...(guidance.length === 0 ? ['- none'] : guidance.map((item) => `- ${item.phase}: ${item.action}`)),
71
+ '',
72
+ `Recommended action: ${primaryAction}`,
73
+ `${commandLabel}: ${commandGuidance}`,
57
74
  `JSON: vgxness sdd next --project ${input.project} --change ${input.decision.change} --json`,
58
75
  ];
59
76
  return `${lines.join('\n')}\n`;
60
77
  }
78
+ export function renderSddContinuationPlan(plan) {
79
+ const suggestedCommandLabel = plan.status === 'runnable' ? 'Manual fallback command' : 'Diagnostic command';
80
+ const lines = [
81
+ 'SDD Continue (read-only)',
82
+ `Project: ${plan.project}`,
83
+ `Change: ${plan.change}`,
84
+ `Status: ${plan.status}`,
85
+ `Next phase: ${plan.nextPhase ?? 'none'}`,
86
+ `Actionable phase: ${plan.actionablePhase ?? 'none'}`,
87
+ `Reason: ${plan.reason}`,
88
+ '',
89
+ 'Recommended plan:',
90
+ `- ${plan.status === 'runnable' && plan.nextPhase !== undefined ? `Continue the ${plan.nextPhase} phase in OpenCode using VGXNESS MCP and hidden SDD subagents.` : plan.recommendedAction}`,
91
+ `- ${suggestedCommandLabel}: ${plan.suggestedCommand}`,
92
+ `- Diagnostic command: ${plan.inspectCommand}`,
93
+ '',
94
+ 'Blocker-specific next actions:',
95
+ ...(plan.blockerActions.length === 0
96
+ ? ['- none']
97
+ : plan.blockerActions.map((item) => {
98
+ const draftRun = item.draftRunCommand === undefined ? '' : `; manualFallbackCommand=${item.draftRunCommand}`;
99
+ const warning = item.warning === undefined ? '' : `; warning=${item.warning}`;
100
+ return `- ${item.phase}: ${item.action}; recoveryCommand=${item.command}${draftRun}${warning}`;
101
+ })),
102
+ '',
103
+ ...(plan.relatedRunContext === undefined
104
+ ? []
105
+ : [
106
+ 'Related interrupted run:',
107
+ `- Run ID: ${plan.relatedRunContext.runId}`,
108
+ `- Status: ${plan.relatedRunContext.status}`,
109
+ `- Workflow/phase: ${plan.relatedRunContext.workflow}/${plan.relatedRunContext.phase}`,
110
+ `- Latest checkpoint: ${plan.relatedRunContext.latestCheckpointLabel ?? 'none'}`,
111
+ `- Recommendation: ${plan.relatedRunContext.recommendation}`,
112
+ `- Resume command: ${plan.relatedRunContext.resumeCommand}`,
113
+ '',
114
+ ]),
115
+ 'Safety:',
116
+ ...plan.safety.map((line) => `- ${line}`),
117
+ '',
118
+ `JSON: vgxness sdd continue --project ${plan.project} --change ${plan.change} --json${continuationDbFlag(plan.explicitDatabasePath)}`,
119
+ ];
120
+ return `${lines.join('\n')}\n`;
121
+ }
61
122
  export function renderSddCockpit(cockpit) {
62
123
  const blockers = cockpit.aggregateBlockers;
63
124
  const lines = [
@@ -71,13 +132,18 @@ export function renderSddCockpit(cockpit) {
71
132
  `Legacy artifacts: ${cockpit.legacyCount}`,
72
133
  '',
73
134
  'Aggregate blockers:',
74
- ...(blockers.length === 0 ? ['- none'] : blockers.map((blocker) => `- ${blocker.kind}: ${blocker.phase} at ${blocker.topicKey} — ${blocker.reason}`)),
135
+ ...(blockers.length === 0
136
+ ? ['- none']
137
+ : blockers.map((blocker) => `- ${blocker.kind}: ${blocker.phase} at ${blocker.topicKey} - ${blocker.reason}${blocker.action === undefined ? '' : `; action=${blocker.action}`}`)),
75
138
  '',
76
139
  `Inspect: ${cockpit.inspectCommand}`,
77
140
  `Cockpit JSON: ${cockpit.inspectCommand}`,
78
141
  ];
79
142
  return `${lines.join('\n')}\n`;
80
143
  }
144
+ function continuationDbFlag(explicitDatabasePath) {
145
+ return explicitDatabasePath === undefined ? '' : ` --db ${explicitDatabasePath}`;
146
+ }
81
147
  export function renderSddArtifactAccepted(input) {
82
148
  const lines = [
83
149
  'SDD Artifact Accepted',
@@ -93,6 +159,23 @@ export function renderSddArtifactAccepted(input) {
93
159
  ];
94
160
  return `${lines.join('\n')}\n`;
95
161
  }
162
+ export function renderSddArtifactReopened(input) {
163
+ const lines = [
164
+ 'SDD Artifact Reopened',
165
+ `- Project: ${input.project}`,
166
+ `- Change: ${input.change}`,
167
+ `- Phase: ${input.phase}`,
168
+ `- Status: ${input.status}`,
169
+ `- Topic key: ${input.topicKey}`,
170
+ `- Artifact ID: ${input.artifactId}`,
171
+ `- Reopened by: ${input.reopenedBy.displayName} (${input.reopenedBy.id})`,
172
+ `- Reopened at: ${input.reopenedAt}`,
173
+ ...(input.note === undefined ? [] : [`- Note: ${input.note}`]),
174
+ `Next step: update the ${input.phase} artifact, then request human acceptance again.`,
175
+ `JSON: vgxness sdd reopen-artifact --project ${input.project} --change ${input.change} --phase ${input.phase} --actor ${input.reopenedBy.id} --json`,
176
+ ];
177
+ return `${lines.join('\n')}\n`;
178
+ }
96
179
  export function renderSddArtifactList(input) {
97
180
  const artifactsByPhase = new Map(input.artifacts.map((artifact) => [artifact.phase, artifact]));
98
181
  const hasArtifacts = input.artifacts.length > 0;