vgxness 1.9.2 → 1.9.3

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 (39) hide show
  1. package/README.md +9 -4
  2. package/dist/agents/agent-resolver.js +33 -3
  3. package/dist/agents/canonical-agent-manifest.js +39 -18
  4. package/dist/agents/canonical-agent-projection.js +31 -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/memory-sdd-dispatcher.js +71 -5
  9. package/dist/cli/commands/status-dispatcher.js +130 -0
  10. package/dist/cli/commands/workflow-dispatcher.js +11 -5
  11. package/dist/cli/dispatcher.js +9 -1
  12. package/dist/cli/product-resume-renderer.js +32 -0
  13. package/dist/cli/product-status-renderer.js +74 -0
  14. package/dist/cli/sdd-renderer.js +80 -3
  15. package/dist/code/cli/code-command.js +7 -4
  16. package/dist/code/reporting/summary.js +4 -1
  17. package/dist/code/runtime/code-runtime.js +27 -4
  18. package/dist/code/runtime/sdd-context.js +18 -2
  19. package/dist/governance/governance-report-builder.js +18 -7
  20. package/dist/mcp/claude-code-agent-config.js +10 -4
  21. package/dist/mcp/control-plane.js +56 -0
  22. package/dist/mcp/provider-status.js +86 -81
  23. package/dist/mcp/schema.js +25 -7
  24. package/dist/mcp/stdio-server.js +4 -0
  25. package/dist/mcp/validation.js +39 -3
  26. package/dist/resume/product-resume.js +166 -0
  27. package/dist/runs/repositories/runs.js +12 -1
  28. package/dist/runs/run-service.js +62 -5
  29. package/dist/sdd/schema.js +8 -0
  30. package/dist/sdd/sdd-continuation-plan.js +81 -0
  31. package/dist/sdd/sdd-workflow-service.js +103 -16
  32. package/dist/skills/skill-resolver.js +21 -4
  33. package/dist/status/product-status.js +117 -0
  34. package/docs/architecture.md +1 -1
  35. package/docs/cli.md +33 -8
  36. package/docs/code-runtime.md +3 -0
  37. package/docs/glossary.md +1 -1
  38. package/docs/mcp.md +18 -4
  39. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import { getSddPhasePermissionMatrixForPhase, permissionCategories, sddPhasePermissionMatrixVersion } from '../permissions/policy-evaluator.js';
2
- import { isSddPhase, normalizeSddArtifact } from '../sdd/schema.js';
2
+ import { isSddPhase, normalizeSddArtifact, normalizeSddPhaseInput } from '../sdd/schema.js';
3
3
  import { fingerprintManagerProfileOverlay } from './overlay-fingerprint.js';
4
4
  export class GovernanceReportBuilder {
5
5
  services;
@@ -27,11 +27,13 @@ export class GovernanceReportBuilder {
27
27
  warnings.push('run-service-unavailable');
28
28
  }
29
29
  const workflow = input.workflow ?? run?.workflow;
30
- const phase = input.phase ?? run?.phase;
30
+ const rawPhase = input.phase ?? run?.phase;
31
+ const phase = canonicalReportPhase(workflow, rawPhase);
31
32
  const agent = buildAgentRef(input, run);
32
33
  const change = input.change;
33
34
  const payloadMode = input.payloadMode ?? 'compact';
34
35
  const resolvedRunId = input.runId ?? run?.id;
36
+ const permissionsPhase = phase === undefined ? undefined : (normalizeSddPhaseInput(phase) ?? phase);
35
37
  const report = {
36
38
  version: 1,
37
39
  kind: 'governance-report',
@@ -44,7 +46,7 @@ export class GovernanceReportBuilder {
44
46
  ...(agent === undefined ? {} : { agent }),
45
47
  sdd: {},
46
48
  permissions: {
47
- ...buildPermissionsMode(phase),
49
+ ...buildPermissionsMode(permissionsPhase),
48
50
  },
49
51
  openCode: {
50
52
  mode: 'audit-only',
@@ -72,7 +74,8 @@ export class GovernanceReportBuilder {
72
74
  tryBuildOptimizedSddSnapshot(report, project, change, phase, payloadMode, warnings) {
73
75
  if (this.services.sdd.getGovernanceSnapshot === undefined)
74
76
  return false;
75
- if (phase !== undefined && !isSddPhase(phase)) {
77
+ const canonicalPhase = phase === undefined ? undefined : normalizeSddPhaseInput(phase);
78
+ if (phase !== undefined && canonicalPhase === undefined) {
76
79
  warnings.push('sdd-readiness-skipped-non-sdd-phase');
77
80
  return false;
78
81
  }
@@ -80,7 +83,7 @@ export class GovernanceReportBuilder {
80
83
  project,
81
84
  change,
82
85
  payloadMode,
83
- ...(phase === undefined ? {} : { phase }),
86
+ ...(canonicalPhase === undefined ? {} : { phase: canonicalPhase }),
84
87
  });
85
88
  if (!snapshot.ok) {
86
89
  warnings.push(`sdd-snapshot-unavailable:${snapshot.error.code}`);
@@ -125,8 +128,9 @@ export class GovernanceReportBuilder {
125
128
  else {
126
129
  warnings.push(`sdd-artifacts-unavailable:${listed.error.code}`);
127
130
  }
128
- if (phase !== undefined && isSddPhase(phase)) {
129
- const readiness = this.services.sdd.getReady({ project, change, phase });
131
+ const canonicalPhase = phase === undefined ? undefined : normalizeSddPhaseInput(phase);
132
+ if (canonicalPhase !== undefined) {
133
+ const readiness = this.services.sdd.getReady({ project, change, phase: canonicalPhase });
130
134
  if (readiness.ok) {
131
135
  report.sdd.readiness = readiness.value;
132
136
  for (const blocker of readiness.value.blockedPrerequisites ?? [])
@@ -280,6 +284,13 @@ function buildAgentRef(input, run) {
280
284
  function dedupe(values) {
281
285
  return [...new Set(values)];
282
286
  }
287
+ function canonicalReportPhase(workflow, phase) {
288
+ if (phase === undefined)
289
+ return undefined;
290
+ if (workflow !== undefined && workflow.trim().toLowerCase() !== 'sdd')
291
+ return phase;
292
+ return normalizeSddPhaseInput(phase) ?? phase;
293
+ }
283
294
  function validationFailure(message) {
284
295
  return { ok: false, error: { code: 'validation_failed', message } };
285
296
  }
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
- import { join, resolve } from 'node:path';
3
+ import { join, parse, resolve } from 'node:path';
4
4
  import { projectCanonicalAgentManifestToClaudeCode } from '../agents/canonical-agent-projection.js';
5
5
  import { assertInsideWorkspace } from './claude-code-config.js';
6
6
  export const claudeCodeGeneratedMarker = 'VGXNESS-GENERATED';
@@ -29,10 +29,16 @@ export function resolveClaudeAgentTarget(input) {
29
29
  return { workspaceRoot: input.workspaceRoot, scope, directoryPath: join(home, '.claude', 'agents'), ...(input.env === undefined ? {} : { env: input.env }) };
30
30
  }
31
31
  export function safeHomeDirectory(env = process.env) {
32
- const candidate = env.HOME?.trim() || homedir();
33
- if (!candidate || candidate === '/' || candidate.includes('\0') || candidate.includes('~'))
32
+ const homeDrive = env.HOMEDRIVE?.trim();
33
+ const homePath = env.HOMEPATH?.trim();
34
+ const homeDrivePath = homeDrive && homePath ? `${homeDrive}${homePath}` : '';
35
+ const candidate = env.HOME?.trim() || env.USERPROFILE?.trim() || homeDrivePath || homedir();
36
+ if (!candidate || candidate.startsWith('~') || candidate.includes('\0'))
34
37
  throw new Error('Unable to resolve a safe home directory for Claude user agents.');
35
- return resolve(candidate);
38
+ const expanded = resolve(candidate);
39
+ if (parse(expanded).root === expanded)
40
+ throw new Error('Unable to resolve a safe home directory for Claude user agents.');
41
+ return expanded;
36
42
  }
37
43
  export function isVgxnessOwnedClaudeAgentMarkdown(contents) {
38
44
  return contents.includes(claudeCodeGeneratedMarker) && contents.includes('provider=claude') && contents.includes('artifact=claude-code-subagent');
@@ -8,6 +8,7 @@ import { openMemoryDatabase } from '../memory/sqlite/database.js';
8
8
  import { prepareMemoryDatabasePath, resolveMemoryDatabasePath } from '../memory/storage-paths.js';
9
9
  import { OpenCodeManagerPayloadService } from '../providers/opencode/manager-payload.js';
10
10
  import { RunService } from '../runs/run-service.js';
11
+ import { sddContinuationPlanFrom } from '../sdd/sdd-continuation-plan.js';
11
12
  import { SddWorkflowService } from '../sdd/sdd-workflow-service.js';
12
13
  import { SkillRegistryService } from '../skills/skill-registry-service.js';
13
14
  import { VerificationPlanService } from '../verification/index.js';
@@ -43,6 +44,8 @@ export function callVgxTool(call, services) {
43
44
  return toEnvelope(validated.tool, services.sdd.getNext(validated.input));
44
45
  case 'vgxness_sdd_cockpit':
45
46
  return toEnvelope(validated.tool, services.sdd.getCockpit(validated.input));
47
+ case 'vgxness_sdd_continue':
48
+ return sddContinueEnvelope(validated.input, services);
46
49
  case 'vgxness_governance_report':
47
50
  return auditedEnvelope(validated.tool, new GovernanceReportBuilder({
48
51
  sdd: governanceSddServices(services.sdd),
@@ -96,6 +99,8 @@ export function callVgxTool(call, services) {
96
99
  return toEnvelope(validated.tool, services.runs.appendCheckpoint(validated.input));
97
100
  case 'vgxness_run_finalize':
98
101
  return toEnvelope(validated.tool, services.runs.updateFinalStatus(validated.input));
102
+ case 'vgxness_run_resume_candidates':
103
+ return runResumeCandidatesEnvelope(validated.input, services);
99
104
  case 'vgxness_run_resume_inspect':
100
105
  return toEnvelope(validated.tool, services.runs.getRunOperatorResumePlan(validated.input.runId));
101
106
  case 'vgxness_run_resume_gate':
@@ -238,6 +243,23 @@ function governanceSddServices(services) {
238
243
  },
239
244
  };
240
245
  }
246
+ function sddContinueEnvelope(input, services) {
247
+ const next = services.sdd.getNext({ project: input.project, change: input.change });
248
+ if (!next.ok)
249
+ return errorEnvelope(next.error.code, next.error.message, 'vgxness_sdd_continue');
250
+ const cockpit = services.sdd.getCockpit({ project: input.project, change: input.change });
251
+ if (!cockpit.ok)
252
+ return errorEnvelope(cockpit.error.code, cockpit.error.message, 'vgxness_sdd_continue');
253
+ const relatedRun = services.runs.findRelatedInterruptedSddRun({ project: input.project, change: input.change });
254
+ if (!relatedRun.ok)
255
+ return errorEnvelope(relatedRun.error.code, relatedRun.error.message, 'vgxness_sdd_continue');
256
+ return successEnvelope('vgxness_sdd_continue', sddContinuationPlanFrom({
257
+ project: input.project,
258
+ next: next.value,
259
+ cockpit: cockpit.value,
260
+ ...(relatedRun.value === undefined ? {} : { relatedRunContext: relatedRun.value }),
261
+ }));
262
+ }
241
263
  export function createVgxMcpControlPlane(options = {}) {
242
264
  const databasePath = resolveControlPlaneDatabasePath(options);
243
265
  if (!databasePath.ok)
@@ -346,6 +368,40 @@ function listRunsEnvelope(input, services) {
346
368
  ? successEnvelope('vgxness_run_list', result.value.slice(0, limit))
347
369
  : errorEnvelope(result.error.code, result.error.message, 'vgxness_run_list');
348
370
  }
371
+ function runResumeCandidatesEnvelope(input, services) {
372
+ const limit = input.limit ?? 5;
373
+ const result = services.runs.listRecentInterruptedRuns({ project: input.project, limit });
374
+ if (!result.ok)
375
+ return errorEnvelope(result.error.code, result.error.message, 'vgxness_run_resume_candidates');
376
+ return successEnvelope('vgxness_run_resume_candidates', {
377
+ kind: 'run-resume-candidates',
378
+ version: 1,
379
+ project: input.project,
380
+ statuses: ['failed', 'blocked', 'needs-human'],
381
+ limit,
382
+ candidates: result.value.map((candidate) => ({
383
+ ...candidate,
384
+ recommendation: 'Inspect this interrupted run before deciding whether to continue manually.',
385
+ inspectTool: 'run_resume_inspect',
386
+ inspectInput: { runId: candidate.runId },
387
+ resumeCommand: `vgxness resume --project ${input.project} --run-id ${candidate.runId}`,
388
+ })),
389
+ nextStep: result.value.length === 0
390
+ ? 'No interrupted run candidates were found for this project. Start from the current project status or a known runId.'
391
+ : 'Call run_resume_inspect with a candidate runId, then use run_resume_gate only when inspect returns a relevant approvalId.',
392
+ safety: {
393
+ readOnly: true,
394
+ runMutation: false,
395
+ retryAdmitted: false,
396
+ providerInvoked: false,
397
+ writesProviderConfig: false,
398
+ writesArtifacts: false,
399
+ writesOpenSpec: false,
400
+ createsSandbox: false,
401
+ createsWorktree: false,
402
+ },
403
+ });
404
+ }
349
405
  function toEnvelope(tool, result) {
350
406
  return result.ok ? successEnvelope(tool, result.value) : errorEnvelope(result.error.code, result.error.message, tool);
351
407
  }
@@ -52,35 +52,36 @@ export class ProviderStatusService {
52
52
  const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
53
53
  const issueCount = [canonicalAgentManifest.status, configStatus, ...tools.map((tool) => (tool.present ? 'pass' : 'fail'))].filter((item) => item === 'fail' || item === 'not-configured').length;
54
54
  const warningCount = [configStatus, mcpEntry.status, ...paths.map((path) => path.status)].filter((item) => item === 'warn').length;
55
+ const reportBase = {
56
+ version: 1,
57
+ kind: 'provider-status',
58
+ project: normalized.project,
59
+ providerAdapter: normalized.providerAdapter,
60
+ scope: normalized.scope,
61
+ workspaceRoot: normalized.workspaceRoot,
62
+ status,
63
+ overallStatus: status,
64
+ inspectedPaths: checkedPaths,
65
+ issueCount,
66
+ warningCount,
67
+ summary: summarizeStatus(status, mcpEntry),
68
+ nextAction: nextActionFor(status, mcpEntry, sdd?.next),
69
+ checkedPaths,
70
+ canonicalAgentManifest,
71
+ ...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
72
+ mcpRequiredTools: tools,
73
+ originalBytes,
74
+ compactBytes,
75
+ verboseAvailable: normalized.payloadMode === 'compact',
76
+ fullContentRef: `provider-status:${normalized.providerAdapter}:${normalized.workspaceRoot}`,
77
+ generatedAt,
78
+ safety: PROVIDER_HEALTH_SAFETY,
79
+ };
55
80
  return {
56
81
  ok: true,
57
- value: {
58
- version: 1,
59
- kind: 'provider-status',
60
- project: normalized.project,
61
- providerAdapter: normalized.providerAdapter,
62
- scope: normalized.scope,
63
- workspaceRoot: normalized.workspaceRoot,
64
- status,
65
- payloadMode: normalized.payloadMode,
66
- overallStatus: status,
67
- inspectedPaths: checkedPaths,
68
- issueCount,
69
- warningCount,
70
- summary: summarizeStatus(status, mcpEntry),
71
- nextAction: nextActionFor(status, mcpEntry, sdd?.next),
72
- checkedPaths,
73
- canonicalAgentManifest,
74
- config: { status: configStatus, paths: compactPaths(paths, normalized.payloadMode), mcpEntry: compactMcpEntry(mcpEntry, normalized.payloadMode) },
75
- ...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
76
- mcpRequiredTools: tools,
77
- originalBytes,
78
- compactBytes,
79
- verboseAvailable: normalized.payloadMode === 'compact',
80
- fullContentRef: `provider-status:${normalized.providerAdapter}:${normalized.workspaceRoot}`,
81
- generatedAt,
82
- safety: PROVIDER_HEALTH_SAFETY,
83
- },
82
+ value: normalized.payloadMode === 'verbose'
83
+ ? { ...reportBase, payloadMode: 'verbose', config: { status: configStatus, paths: compactPaths(paths, 'verbose'), mcpEntry: compactMcpEntry(mcpEntry, 'verbose') } }
84
+ : { ...reportBase, payloadMode: 'compact', config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') } },
84
85
  };
85
86
  }
86
87
  readSdd(project, change) {
@@ -115,35 +116,36 @@ export class ProviderStatusService {
115
116
  const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
116
117
  const issueCount = [canonicalAgentManifest.status, configStatus, ...agentStatuses].filter((item) => item === 'fail' || item === 'not-configured').length;
117
118
  const warningCount = advisory.length + resolvedScope.value.warnings.length + paths.filter((path) => path.status === 'warn').length;
119
+ const reportBase = {
120
+ version: 1,
121
+ kind: 'provider-status',
122
+ project: normalized.project,
123
+ providerAdapter: 'claude',
124
+ scope: normalized.scope,
125
+ workspaceRoot: normalized.workspaceRoot,
126
+ status,
127
+ overallStatus: status,
128
+ inspectedPaths: checkedPaths,
129
+ issueCount,
130
+ warningCount,
131
+ summary: summarizeClaudeStatus(status, mcpEntry),
132
+ nextAction: nextActionFor(status, mcpEntry, sdd?.next),
133
+ checkedPaths,
134
+ canonicalAgentManifest,
135
+ ...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
136
+ mcpRequiredTools: tools,
137
+ originalBytes,
138
+ compactBytes,
139
+ verboseAvailable: normalized.payloadMode === 'compact',
140
+ fullContentRef: `provider-status:claude:${normalized.workspaceRoot}`,
141
+ generatedAt: 'read-only-snapshot',
142
+ safety: PROVIDER_HEALTH_SAFETY,
143
+ };
118
144
  return {
119
145
  ok: true,
120
- value: {
121
- version: 1,
122
- kind: 'provider-status',
123
- project: normalized.project,
124
- providerAdapter: 'claude',
125
- scope: normalized.scope,
126
- workspaceRoot: normalized.workspaceRoot,
127
- status,
128
- payloadMode: normalized.payloadMode,
129
- overallStatus: status,
130
- inspectedPaths: checkedPaths,
131
- issueCount,
132
- warningCount,
133
- summary: summarizeClaudeStatus(status, mcpEntry),
134
- nextAction: nextActionFor(status, mcpEntry, sdd?.next),
135
- checkedPaths,
136
- canonicalAgentManifest,
137
- config: { status: configStatus, paths: compactPaths(paths, normalized.payloadMode), mcpEntry: compactMcpEntry(mcpEntry, normalized.payloadMode) },
138
- ...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
139
- mcpRequiredTools: tools,
140
- originalBytes,
141
- compactBytes,
142
- verboseAvailable: normalized.payloadMode === 'compact',
143
- fullContentRef: `provider-status:claude:${normalized.workspaceRoot}`,
144
- generatedAt: 'read-only-snapshot',
145
- safety: PROVIDER_HEALTH_SAFETY,
146
- },
146
+ value: normalized.payloadMode === 'verbose'
147
+ ? { ...reportBase, payloadMode: 'verbose', config: { status: configStatus, paths: compactPaths(paths, 'verbose'), mcpEntry: compactMcpEntry(mcpEntry, 'verbose') } }
148
+ : { ...reportBase, payloadMode: 'compact', config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') } },
147
149
  };
148
150
  }
149
151
  getClaudeUserGlobalStatus(normalized, canonicalScope = 'user', scopeWarnings = []) {
@@ -162,35 +164,38 @@ export class ProviderStatusService {
162
164
  const checkedPaths = normalized.payloadMode === 'verbose' ? [mcpState.path, agents.directoryPath, ...agents.agents.map((agent) => agent.path), userMemory.path] : [mcpState.path, userMemory.path, ...agents.agents.filter((agent) => agent.exists || agent.status !== 'missing').map((agent) => agent.path)];
163
165
  const verboseShape = { config: { status: configStatus, paths, mcpEntry }, canonicalAgentManifest, agents, userMemory: { status: userMemory.status, action: userMemory.action }, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, scopeWarnings, sdd, mcpRequiredTools: tools };
164
166
  const compactShape = { config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') }, canonicalAgentManifest, agentSummary: summarizeClaudeAgents(agents), userMemory: { status: userMemory.status, action: userMemory.action }, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, sdd: sdd === undefined ? undefined : compactSdd(sdd, 'compact'), mcpRequiredTools: tools };
167
+ const originalBytes = Buffer.byteLength(JSON.stringify(verboseShape), 'utf8');
168
+ const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
169
+ const reportBase = {
170
+ version: 1,
171
+ kind: 'provider-status',
172
+ project: normalized.project,
173
+ providerAdapter: 'claude',
174
+ scope: normalized.scope,
175
+ workspaceRoot: normalized.workspaceRoot,
176
+ status,
177
+ overallStatus: status,
178
+ inspectedPaths: checkedPaths,
179
+ issueCount: [canonicalAgentManifest.status, configStatus, ...agentStatuses].filter((item) => item === 'fail' || item === 'not-configured').length,
180
+ warningCount: scopeWarnings.length + paths.filter((path) => path.status === 'warn').length + 1,
181
+ summary: summarizeClaudeStatus(status, mcpEntry),
182
+ nextAction: nextActionFor(status, mcpEntry, sdd?.next),
183
+ checkedPaths,
184
+ canonicalAgentManifest,
185
+ ...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
186
+ mcpRequiredTools: tools,
187
+ originalBytes,
188
+ compactBytes,
189
+ verboseAvailable: normalized.payloadMode === 'compact',
190
+ fullContentRef: `provider-status:claude:user-global:${normalized.workspaceRoot}`,
191
+ generatedAt: 'read-only-snapshot',
192
+ safety: { ...PROVIDER_HEALTH_SAFETY, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES },
193
+ };
165
194
  return {
166
195
  ok: true,
167
- value: {
168
- version: 1,
169
- kind: 'provider-status',
170
- project: normalized.project,
171
- providerAdapter: 'claude',
172
- scope: normalized.scope,
173
- workspaceRoot: normalized.workspaceRoot,
174
- status,
175
- payloadMode: normalized.payloadMode,
176
- overallStatus: status,
177
- inspectedPaths: checkedPaths,
178
- issueCount: [canonicalAgentManifest.status, configStatus, ...agentStatuses].filter((item) => item === 'fail' || item === 'not-configured').length,
179
- warningCount: scopeWarnings.length + paths.filter((path) => path.status === 'warn').length + 1,
180
- summary: summarizeClaudeStatus(status, mcpEntry),
181
- nextAction: nextActionFor(status, mcpEntry, sdd?.next),
182
- checkedPaths,
183
- canonicalAgentManifest,
184
- config: { status: configStatus, paths: compactPaths(paths, normalized.payloadMode), mcpEntry: compactMcpEntry(mcpEntry, normalized.payloadMode) },
185
- ...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
186
- mcpRequiredTools: tools,
187
- originalBytes: Buffer.byteLength(JSON.stringify(verboseShape), 'utf8'),
188
- compactBytes: Buffer.byteLength(JSON.stringify(compactShape), 'utf8'),
189
- verboseAvailable: normalized.payloadMode === 'compact',
190
- fullContentRef: `provider-status:claude:user-global:${normalized.workspaceRoot}`,
191
- generatedAt: 'read-only-snapshot',
192
- safety: { ...PROVIDER_HEALTH_SAFETY, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES },
193
- },
196
+ value: normalized.payloadMode === 'verbose'
197
+ ? { ...reportBase, payloadMode: 'verbose', config: { status: configStatus, paths: compactPaths(paths, 'verbose'), mcpEntry: compactMcpEntry(mcpEntry, 'verbose') } }
198
+ : { ...reportBase, payloadMode: 'compact', config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') } },
194
199
  };
195
200
  }
196
201
  }
@@ -14,6 +14,7 @@ export const SUPPORTED_VGX_MCP_TOOL_NAMES = [
14
14
  'vgxness_sdd_list_artifacts',
15
15
  'vgxness_sdd_next',
16
16
  'vgxness_sdd_cockpit',
17
+ 'vgxness_sdd_continue',
17
18
  'vgxness_governance_report',
18
19
  'vgxness_memory_save',
19
20
  'vgxness_memory_search',
@@ -37,6 +38,7 @@ export const SUPPORTED_VGX_MCP_TOOL_NAMES = [
37
38
  'vgxness_run_start',
38
39
  'vgxness_run_checkpoint',
39
40
  'vgxness_run_finalize',
41
+ 'vgxness_run_resume_candidates',
40
42
  'vgxness_run_resume_inspect',
41
43
  'vgxness_run_resume_gate',
42
44
  'vgxness_provider_status',
@@ -55,6 +57,7 @@ export const EXPOSED_VGX_MCP_TOOL_NAMES = [
55
57
  'sdd_list_artifacts',
56
58
  'sdd_next',
57
59
  'sdd_cockpit',
60
+ 'sdd_continue',
58
61
  'governance_report',
59
62
  'memory_save',
60
63
  'memory_search',
@@ -78,6 +81,7 @@ export const EXPOSED_VGX_MCP_TOOL_NAMES = [
78
81
  'run_start',
79
82
  'run_checkpoint',
80
83
  'run_finalize',
84
+ 'run_resume_candidates',
81
85
  'run_resume_inspect',
82
86
  'run_resume_gate',
83
87
  'provider_status',
@@ -104,6 +108,7 @@ const payloadModes = ['compact', 'verbose'];
104
108
  const contextCockpitLevels = ['compact', 'expanded', 'verbose'];
105
109
  const providerChangePlanProviders = ['opencode', 'claude', 'antigravity', 'custom'];
106
110
  const providerChangePlanTypes = ['opencode-mcp-install', 'claude-mcp-install', 'setup', 'install', 'config-preparation'];
111
+ const sddPhaseInputSchema = z.union([z.enum(sddPhases), z.literal('apply')]);
107
112
  const jsonValueSchema = z.lazy(() => z.union([z.string(), z.number().finite(), z.boolean(), z.null(), z.array(jsonValueSchema), z.record(z.string(), jsonValueSchema)]));
108
113
  export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
109
114
  vgxness_sdd_status: z
@@ -116,7 +121,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
116
121
  .object({
117
122
  project: z.string().min(1),
118
123
  change: z.string().min(1),
119
- phase: z.enum(sddPhases),
124
+ phase: sddPhaseInputSchema,
120
125
  runId: z.string().min(1).optional(),
121
126
  agentId: z.string().min(1).optional(),
122
127
  })
@@ -125,7 +130,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
125
130
  .object({
126
131
  project: z.string().min(1),
127
132
  change: z.string().min(1),
128
- phase: z.enum(sddPhases),
133
+ phase: sddPhaseInputSchema,
129
134
  runId: z.string().min(1).optional(),
130
135
  agentId: z.string().min(1).optional(),
131
136
  })
@@ -134,7 +139,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
134
139
  .object({
135
140
  project: z.string().min(1),
136
141
  change: z.string().min(1),
137
- phase: z.enum(sddPhases),
142
+ phase: sddPhaseInputSchema,
138
143
  content: z.string().min(1),
139
144
  })
140
145
  .passthrough(),
@@ -142,7 +147,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
142
147
  .object({
143
148
  project: z.string().min(1),
144
149
  change: z.string().min(1),
145
- phase: z.enum(sddPhases),
150
+ phase: sddPhaseInputSchema,
146
151
  acceptedBy: z.object({
147
152
  type: z.literal('human'),
148
153
  id: z.string().min(1),
@@ -158,7 +163,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
158
163
  .object({
159
164
  project: z.string().min(1),
160
165
  change: z.string().min(1),
161
- phase: z.enum(sddPhases),
166
+ phase: sddPhaseInputSchema,
162
167
  reopenedBy: z.object({
163
168
  type: z.literal('human'),
164
169
  id: z.string().min(1),
@@ -174,7 +179,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
174
179
  .object({
175
180
  project: z.string().min(1),
176
181
  change: z.string().min(1),
177
- phase: z.enum(sddPhases),
182
+ phase: sddPhaseInputSchema,
178
183
  payloadMode: z.enum(payloadModes).optional(),
179
184
  })
180
185
  .passthrough(),
@@ -197,6 +202,13 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
197
202
  change: z.string().min(1),
198
203
  })
199
204
  .passthrough(),
205
+ vgxness_sdd_continue: z
206
+ .object({
207
+ project: z.string().min(1),
208
+ change: z.string().min(1),
209
+ payloadMode: z.enum(payloadModes).optional(),
210
+ })
211
+ .passthrough(),
200
212
  vgxness_governance_report: z
201
213
  .object({
202
214
  project: z.string().min(1),
@@ -364,7 +376,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
364
376
  workspaceRoot: z.string().min(1).optional(),
365
377
  maxSourceBytes: z.number().int().positive().optional(),
366
378
  change: z.string().min(1).optional(),
367
- phase: z.enum(sddPhases).optional(),
379
+ phase: sddPhaseInputSchema.optional(),
368
380
  })
369
381
  .passthrough(),
370
382
  vgxness_run_list: z
@@ -431,6 +443,12 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
431
443
  outcomeReason: z.string().min(1).optional(),
432
444
  })
433
445
  .passthrough(),
446
+ vgxness_run_resume_candidates: z
447
+ .object({
448
+ project: z.string().min(1),
449
+ limit: z.number().int().min(1).max(100).optional(),
450
+ })
451
+ .passthrough(),
434
452
  vgxness_run_resume_inspect: z
435
453
  .object({
436
454
  runId: z.string().min(1),
@@ -57,6 +57,8 @@ function descriptionForTool(publicToolName) {
57
57
  return 'Read-only OpenCode handoff preview; returns context for manual continuation only, does not execute/control OpenCode, write .opencode/provider config, or create runs, checkpoints, events, sessions, or skill-usage records.';
58
58
  if (publicToolName === 'verification_plan')
59
59
  return 'Read-only verification plan recommendations only; does not execute commands, write provider config, persist results, create checkpoints, or infer SDD acceptance.';
60
+ if (publicToolName === 'run_resume_candidates')
61
+ return 'Read-only interrupted run resume candidate discovery by project; lists bounded failed, blocked, and needs-human runs without mutation, retry admission, provider execution, artifact/config/openspec writes, sandboxes, worktrees, or sessions.';
60
62
  if (publicToolName === 'run_resume_inspect')
61
63
  return 'Read-only run resume advisory inspect; plan-only and does not execute resume logic, invoke providers, write provider config, mutate retry/abandon/attempt state, or reconstruct sandboxes, worktrees, sessions, or transcripts.';
62
64
  if (publicToolName === 'run_resume_gate')
@@ -65,6 +67,8 @@ function descriptionForTool(publicToolName) {
65
67
  return 'Read-only context cockpit for start/resume/recovery; returns latest restorable session plus bounded memory previews without traces, provider config writes, repository writes, runs, artifacts, or session mutations.';
66
68
  if (publicToolName === 'sdd_cockpit')
67
69
  return 'Read-only SDD cockpit summary with next decision, explicit acceptance state, metadata-only artifact summaries, and aggregate blockers.';
70
+ if (publicToolName === 'sdd_continue')
71
+ return 'Read-only SDD continuation planner; returns blocker actions, safe suggested commands, related interrupted run context, and safety notes without provider execution, run creation, artifact mutation, provider config writes, or openspec writes.';
68
72
  const toolName = toInternalVgxMcpToolName(publicToolName);
69
73
  return `VGX control-plane tool ${toolName}`;
70
74
  }
@@ -1,6 +1,6 @@
1
1
  import { isRiskyPermissionCategory, permissionCategories } from '../permissions/schema.js';
2
2
  import { parseOperationRetryPolicy } from '../runs/operation-retry.js';
3
- import { isSddPhase, sddPhases } from '../sdd/schema.js';
3
+ import { normalizeSddPhaseInput, sddPhases } from '../sdd/schema.js';
4
4
  import { workflowIds } from '../workflows/schema.js';
5
5
  import { supportedTypeMessage, verificationChangeTypes } from '../verification/index.js';
6
6
  import { errorEnvelope, isVgxMcpToolName, } from './schema.js';
@@ -52,6 +52,8 @@ export function validateVgxMcpToolCall(call) {
52
52
  return validationSuccess(tool.value, validateSddNextInput(input, tool.value));
53
53
  case 'vgxness_sdd_cockpit':
54
54
  return validationSuccess(tool.value, validateSddCockpitInput(input, tool.value));
55
+ case 'vgxness_sdd_continue':
56
+ return validationSuccess(tool.value, validateSddContinueInput(input, tool.value));
55
57
  case 'vgxness_governance_report':
56
58
  return validationSuccess(tool.value, validateGovernanceReportInput(input, tool.value));
57
59
  case 'vgxness_memory_save':
@@ -98,6 +100,8 @@ export function validateVgxMcpToolCall(call) {
98
100
  return validationSuccess(tool.value, validateRunCheckpointInput(input, tool.value));
99
101
  case 'vgxness_run_finalize':
100
102
  return validationSuccess(tool.value, validateRunFinalizeInput(input, tool.value));
103
+ case 'vgxness_run_resume_candidates':
104
+ return validationSuccess(tool.value, validateRunResumeCandidatesInput(input, tool.value));
101
105
  case 'vgxness_run_resume_inspect':
102
106
  return validationSuccess(tool.value, validateRunResumeInspectInput(input, tool.value));
103
107
  case 'vgxness_run_resume_gate':
@@ -240,6 +244,21 @@ function validateSddCockpitInput(input, tool) {
240
244
  return record;
241
245
  return readProjectAndChange(record.value, tool);
242
246
  }
247
+ function validateSddContinueInput(input, tool) {
248
+ const record = inputRecord(input, tool, ['project', 'change', 'payloadMode']);
249
+ if (!record.ok)
250
+ return record;
251
+ const base = readProjectAndChange(record.value, tool);
252
+ if (!base.ok)
253
+ return base;
254
+ const payloadMode = readOptionalOneOf(record.value, 'payloadMode', payloadModes, tool);
255
+ if (!payloadMode.ok)
256
+ return payloadMode;
257
+ const result = { ...base.value };
258
+ if (payloadMode.value !== undefined)
259
+ result.payloadMode = payloadMode.value;
260
+ return { ok: true, value: result };
261
+ }
243
262
  function validateContextCockpitInput(input, tool) {
244
263
  const record = inputRecord(input, tool, ['project', 'change', 'directory', 'limit', 'level']);
245
264
  if (!record.ok)
@@ -971,6 +990,22 @@ function validateRunFinalizeInput(input, tool) {
971
990
  return copied;
972
991
  return { ok: true, value: result };
973
992
  }
993
+ function validateRunResumeCandidatesInput(input, tool) {
994
+ const record = inputRecord(input, tool, ['project', 'limit']);
995
+ if (!record.ok)
996
+ return record;
997
+ const project = readNonEmptyString(record.value, 'project', tool);
998
+ if (!project.ok)
999
+ return project;
1000
+ const result = { project: project.value };
1001
+ if (record.value.limit !== undefined) {
1002
+ const limit = readBoundedLimit(record.value, tool);
1003
+ if (!limit.ok)
1004
+ return limit;
1005
+ result.limit = limit.value;
1006
+ }
1007
+ return { ok: true, value: result };
1008
+ }
974
1009
  function validateRunResumeInspectInput(input, tool) {
975
1010
  const record = inputRecord(input, tool, ['runId']);
976
1011
  if (!record.ok)
@@ -1053,9 +1088,10 @@ function readPhase(record, tool) {
1053
1088
  const phase = readNonEmptyString(record, 'phase', tool);
1054
1089
  if (!phase.ok)
1055
1090
  return phase;
1056
- if (!isSddPhase(phase.value))
1091
+ const normalized = normalizeSddPhaseInput(phase.value);
1092
+ if (normalized === undefined)
1057
1093
  return validationFailure(`phase must be one of: ${sddPhases.join(', ')}`, tool);
1058
- return { ok: true, value: phase.value };
1094
+ return { ok: true, value: normalized };
1059
1095
  }
1060
1096
  function copyOptionalStrings(target, record, tool, fields) {
1061
1097
  for (const field of fields) {