vgxness 1.12.0 → 1.13.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/dist/mcp/provider-doctor.js +10 -6
- package/dist/mcp/provider-health-types.js +20 -0
- package/dist/mcp/provider-status.js +18 -6
- package/dist/mcp/schema.js +176 -0
- package/dist/mcp/stdio-server.js +20 -5
- package/dist/sdd/cockpit-read-model.js +2 -0
- package/dist/sdd/sdd-continuation-plan.js +149 -0
- package/dist/sdd/sdd-workflow-service.js +127 -4
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ import { inspectClaudeCodeUserMcpConfig } from './claude-code-user-config.js';
|
|
|
7
7
|
import { inspectClaudeUserMemory } from './claude-code-user-memory.js';
|
|
8
8
|
import { vgxnessOpenCodeDefaultAgent, vgxnessOpenCodeSddSubagents } from './opencode-default-agent-config.js';
|
|
9
9
|
import { buildCanonicalAgentManifestDiagnostic } from './provider-canonical-agent-manifest.js';
|
|
10
|
-
import { normalizeProviderHealthInput, PROVIDER_HEALTH_SAFETY, CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, classifyProviderConfigPaths, providerHealthFailure, providerRuntimeContext, isUserGlobalScope, REQUIRED_PROVIDER_NATIVE_MCP_TOOLS, rollupProviderDoctor, } from './provider-health-types.js';
|
|
10
|
+
import { normalizeProviderHealthInput, PROVIDER_HEALTH_SAFETY, CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, classifyProviderConfigPaths, providerEvidence, providerHealthFailure, providerRuntimeContext, isUserGlobalScope, REQUIRED_PROVIDER_NATIVE_MCP_TOOLS, rollupProviderDoctor, } from './provider-health-types.js';
|
|
11
11
|
import { inspectOpenCodeConfigPaths } from './provider-status.js';
|
|
12
12
|
export class ProviderDoctorService {
|
|
13
13
|
deps;
|
|
@@ -28,6 +28,7 @@ export class ProviderDoctorService {
|
|
|
28
28
|
const before = snapshotPaths(paths.map((path) => path.path), normalized.workspaceRoot);
|
|
29
29
|
const readableJson = paths.filter((path) => path.parsed);
|
|
30
30
|
const config = readFirstConfig(readableJson[0]?.path);
|
|
31
|
+
const evidence = providerEvidence({ staticManifestKnown: true, renderedConfigExpected: true, configPathInspected: paths.length > 0 });
|
|
31
32
|
const checks = [
|
|
32
33
|
{
|
|
33
34
|
id: 'workspace-root',
|
|
@@ -42,7 +43,7 @@ export class ProviderDoctorService {
|
|
|
42
43
|
subagentsCheck(config),
|
|
43
44
|
delegationCheck(config),
|
|
44
45
|
{ id: 'mcp-current-call', status: 'pass', detail: 'Current MCP call reached the VGXNESS control-plane.' },
|
|
45
|
-
{ id: 'mcp-required-tools', status: 'pass', detail: `
|
|
46
|
+
{ id: 'mcp-required-tools', status: 'pass', detail: `Static VGXNESS manifest expects provider-native tools: ${REQUIRED_PROVIDER_NATIVE_MCP_TOOLS.join(', ')}. MCP host tool presence was not verified.` },
|
|
46
47
|
promptContractCheck(config, normalized.expectedPromptContractVersion),
|
|
47
48
|
readonlySafetyCheck(before, snapshotPaths(paths.map((path) => path.path), normalized.workspaceRoot)),
|
|
48
49
|
];
|
|
@@ -54,8 +55,8 @@ export class ProviderDoctorService {
|
|
|
54
55
|
: paths.filter((path) => path.exists || path.status !== 'not-configured').map((path) => path.path);
|
|
55
56
|
const failedChecks = checks.filter((check) => check.status === 'fail').map((check) => check.id);
|
|
56
57
|
const recommendations = checks.flatMap((check) => (check.remediation === undefined ? [] : [check.remediation]));
|
|
57
|
-
const originalBytes = Buffer.byteLength(JSON.stringify({ checks, checkedPaths: paths.map((path) => path.path) }), 'utf8');
|
|
58
|
-
const compactBytes = Buffer.byteLength(JSON.stringify({ checks: compactChecksValue, checkedPaths }), 'utf8');
|
|
58
|
+
const originalBytes = Buffer.byteLength(JSON.stringify({ checks, checkedPaths: paths.map((path) => path.path), providerEvidence: evidence }), 'utf8');
|
|
59
|
+
const compactBytes = Buffer.byteLength(JSON.stringify({ checks: compactChecksValue, checkedPaths, providerEvidence: evidence }), 'utf8');
|
|
59
60
|
return {
|
|
60
61
|
ok: true,
|
|
61
62
|
value: {
|
|
@@ -80,6 +81,7 @@ export class ProviderDoctorService {
|
|
|
80
81
|
recommendations,
|
|
81
82
|
checks: compactChecksValue,
|
|
82
83
|
checkedPaths,
|
|
84
|
+
providerEvidence: evidence,
|
|
83
85
|
bytes: { originalBytes, compactBytes },
|
|
84
86
|
verboseAvailable: normalized.payloadMode === 'compact',
|
|
85
87
|
fullContentRef: `provider-doctor:${normalized.providerAdapter}:${normalized.workspaceRoot}`,
|
|
@@ -99,6 +101,7 @@ export class ProviderDoctorService {
|
|
|
99
101
|
const projectMemory = inspectClaudeProjectMemory(normalized.workspaceRoot);
|
|
100
102
|
const advisoryPaths = claudeAdvisoryPaths(normalized.workspaceRoot);
|
|
101
103
|
const checkedPathList = [mcp.path, agents.directoryPath, ...agents.agents.map((agent) => agent.path), projectMemory.path, ...advisoryPaths];
|
|
104
|
+
const evidence = providerEvidence({ staticManifestKnown: true, renderedConfigExpected: true, configPathInspected: checkedPathList.length > 0 });
|
|
102
105
|
const before = snapshotPaths(checkedPathList, normalized.workspaceRoot);
|
|
103
106
|
const missingAgents = agents.agents.filter((agent) => agent.status === 'missing');
|
|
104
107
|
const blockingAgents = agents.agents.filter((agent) => agent.status === 'conflicting' || agent.status === 'invalid');
|
|
@@ -133,13 +136,14 @@ export class ProviderDoctorService {
|
|
|
133
136
|
const checkedPaths = normalized.payloadMode === 'verbose' ? checkedPathList : checkedPathList.filter((path) => existsSync(path) || path === mcp.path);
|
|
134
137
|
const failedChecks = checks.filter((check) => check.status === 'fail').map((check) => check.id);
|
|
135
138
|
const recommendations = checks.flatMap((check) => (check.remediation === undefined ? [] : [check.remediation]));
|
|
136
|
-
return { ok: true, value: { version: 1, kind: 'provider-doctor', project: normalized.project, providerAdapter: 'claude', scope: normalized.scope, workspaceRoot: normalized.workspaceRoot, runtimeContext: providerRuntimeContext(normalized), configClassification: externalProjectClassification(checkedPathList), status, payloadMode: normalized.payloadMode, overallStatus: status, checkCount: checks.length, passedCount: checks.filter((check) => check.status === 'pass').length, warningCount: checks.filter((check) => check.status === 'warn' || check.status === 'not-configured').length, errorCount: failedChecks.length, skippedCount: checks.filter((check) => check.status === 'skip').length, failedChecks, summary: summarizeDoctor(status, failedChecks.length, recommendations.length), recommendations, checks: compactChecksValue, checkedPaths, bytes: { originalBytes: Buffer.byteLength(JSON.stringify({ checks, checkedPaths: checkedPathList }), 'utf8'), compactBytes: Buffer.byteLength(JSON.stringify({ checks: compactChecksValue, checkedPaths }), 'utf8') }, verboseAvailable: normalized.payloadMode === 'compact', fullContentRef: `provider-doctor:claude:${normalized.workspaceRoot}`, generatedAt: 'read-only-snapshot', safety: PROVIDER_HEALTH_SAFETY } };
|
|
139
|
+
return { ok: true, value: { version: 1, kind: 'provider-doctor', project: normalized.project, providerAdapter: 'claude', scope: normalized.scope, workspaceRoot: normalized.workspaceRoot, runtimeContext: providerRuntimeContext(normalized), configClassification: externalProjectClassification(checkedPathList), status, payloadMode: normalized.payloadMode, overallStatus: status, checkCount: checks.length, passedCount: checks.filter((check) => check.status === 'pass').length, warningCount: checks.filter((check) => check.status === 'warn' || check.status === 'not-configured').length, errorCount: failedChecks.length, skippedCount: checks.filter((check) => check.status === 'skip').length, failedChecks, summary: summarizeDoctor(status, failedChecks.length, recommendations.length), recommendations, checks: compactChecksValue, checkedPaths, providerEvidence: evidence, bytes: { originalBytes: Buffer.byteLength(JSON.stringify({ checks, checkedPaths: checkedPathList, providerEvidence: evidence }), 'utf8'), compactBytes: Buffer.byteLength(JSON.stringify({ checks: compactChecksValue, checkedPaths, providerEvidence: evidence }), 'utf8') }, verboseAvailable: normalized.payloadMode === 'compact', fullContentRef: `provider-doctor:claude:${normalized.workspaceRoot}`, generatedAt: 'read-only-snapshot', safety: PROVIDER_HEALTH_SAFETY } };
|
|
137
140
|
}
|
|
138
141
|
getClaudeUserGlobalDoctor(normalized, canonicalScope = 'user', scopeWarnings = []) {
|
|
139
142
|
const mcp = inspectClaudeCodeUserMcpConfig(normalized.env);
|
|
140
143
|
const agents = inspectClaudeCodeAgents({ workspaceRoot: normalized.workspaceRoot, scope: 'user', env: normalized.env });
|
|
141
144
|
const userMemory = inspectClaudeUserMemory(normalized.env);
|
|
142
145
|
const checkedPathList = [mcp.path, agents.directoryPath, ...agents.agents.map((agent) => agent.path), userMemory.path];
|
|
146
|
+
const evidence = providerEvidence({ staticManifestKnown: true, renderedConfigExpected: true, configPathInspected: checkedPathList.length > 0 });
|
|
143
147
|
const before = snapshotPaths(checkedPathList, normalized.workspaceRoot);
|
|
144
148
|
const missingAgents = agents.agents.filter((agent) => agent.status === 'missing');
|
|
145
149
|
const blockingAgents = agents.agents.filter((agent) => agent.status === 'conflicting' || agent.status === 'invalid');
|
|
@@ -164,7 +168,7 @@ export class ProviderDoctorService {
|
|
|
164
168
|
const failedChecks = checks.filter((check) => check.status === 'fail').map((check) => check.id);
|
|
165
169
|
const recommendations = checks.flatMap((check) => (check.remediation === undefined ? [] : [check.remediation]));
|
|
166
170
|
const checkedPaths = normalized.payloadMode === 'verbose' ? checkedPathList : checkedPathList.filter((path) => existsSync(path) || path === mcp.path || path === userMemory.path);
|
|
167
|
-
return { ok: true, value: { version: 1, kind: 'provider-doctor', project: normalized.project, providerAdapter: 'claude', scope: normalized.scope, workspaceRoot: normalized.workspaceRoot, runtimeContext: providerRuntimeContext(normalized), configClassification: managedUserGlobalClassification(checkedPathList), status, payloadMode: normalized.payloadMode, overallStatus: status, checkCount: checks.length, passedCount: checks.filter((check) => check.status === 'pass').length, warningCount: checks.filter((check) => check.status === 'warn' || check.status === 'not-configured').length, errorCount: failedChecks.length, skippedCount: checks.filter((check) => check.status === 'skip').length, failedChecks, summary: summarizeDoctor(status, failedChecks.length, recommendations.length), recommendations, checks: compactChecksValue, checkedPaths, bytes: { originalBytes: Buffer.byteLength(JSON.stringify({ checks, checkedPaths: checkedPathList }), 'utf8'), compactBytes: Buffer.byteLength(JSON.stringify({ checks: compactChecksValue, checkedPaths }), 'utf8') }, verboseAvailable: normalized.payloadMode === 'compact', fullContentRef: `provider-doctor:claude:${canonicalScope}:${normalized.workspaceRoot}`, generatedAt: 'read-only-snapshot', safety: { ...PROVIDER_HEALTH_SAFETY, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES } } };
|
|
171
|
+
return { ok: true, value: { version: 1, kind: 'provider-doctor', project: normalized.project, providerAdapter: 'claude', scope: normalized.scope, workspaceRoot: normalized.workspaceRoot, runtimeContext: providerRuntimeContext(normalized), configClassification: managedUserGlobalClassification(checkedPathList), status, payloadMode: normalized.payloadMode, overallStatus: status, checkCount: checks.length, passedCount: checks.filter((check) => check.status === 'pass').length, warningCount: checks.filter((check) => check.status === 'warn' || check.status === 'not-configured').length, errorCount: failedChecks.length, skippedCount: checks.filter((check) => check.status === 'skip').length, failedChecks, summary: summarizeDoctor(status, failedChecks.length, recommendations.length), recommendations, checks: compactChecksValue, checkedPaths, providerEvidence: evidence, bytes: { originalBytes: Buffer.byteLength(JSON.stringify({ checks, checkedPaths: checkedPathList, providerEvidence: evidence }), 'utf8'), compactBytes: Buffer.byteLength(JSON.stringify({ checks: compactChecksValue, checkedPaths, providerEvidence: evidence }), 'utf8') }, verboseAvailable: normalized.payloadMode === 'compact', fullContentRef: `provider-doctor:claude:${canonicalScope}:${normalized.workspaceRoot}`, generatedAt: 'read-only-snapshot', safety: { ...PROVIDER_HEALTH_SAFETY, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES } } };
|
|
168
172
|
}
|
|
169
173
|
}
|
|
170
174
|
function externalProjectClassification(paths) {
|
|
@@ -34,6 +34,26 @@ export function classifyProviderConfigPaths(paths) {
|
|
|
34
34
|
detectedExternalUserConfig: paths.filter((path) => path.diagnostics.ownership === 'external-user' && path.exists).map((path) => path.path),
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
|
+
export function providerEvidence(input) {
|
|
38
|
+
const hostIntrospectionAvailable = input.hostIntrospectionAvailable ?? false;
|
|
39
|
+
const hostToolPresenceVerified = input.hostToolPresenceVerified ?? false;
|
|
40
|
+
return {
|
|
41
|
+
staticManifestKnown: input.staticManifestKnown,
|
|
42
|
+
renderedConfigExpected: input.renderedConfigExpected,
|
|
43
|
+
configPathInspected: input.configPathInspected,
|
|
44
|
+
hostIntrospectionAvailable,
|
|
45
|
+
hostToolPresenceVerified,
|
|
46
|
+
evidenceLevel: hostIntrospectionAvailable ? 'host-introspection' : input.configPathInspected ? 'config-inspection' : 'static-manifest',
|
|
47
|
+
notes: [
|
|
48
|
+
'VGXNESS inspected static provider manifests and expected provider configuration metadata only.',
|
|
49
|
+
input.configPathInspected
|
|
50
|
+
? 'VGXNESS inspected provider config paths as read-only diagnostics.'
|
|
51
|
+
: 'VGXNESS did not inspect provider config paths; evidence is limited to static manifests.',
|
|
52
|
+
'No provider host process was launched during this diagnostic.',
|
|
53
|
+
'MCP host tool presence is not verified by provider status/doctor.',
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
37
57
|
export const REQUIRED_PROVIDER_MCP_TOOLS = ['vgxness_provider_status', 'vgxness_provider_doctor', 'vgxness_provider_change_plan'];
|
|
38
58
|
export const REQUIRED_PROVIDER_NATIVE_MCP_TOOLS = [
|
|
39
59
|
'vgxness_provider_status',
|
|
@@ -9,7 +9,7 @@ import { inspectClaudeUserMemory } from './claude-code-user-memory.js';
|
|
|
9
9
|
import { resolveOpenCodeMcpInstallTarget } from './client-install-opencode-contract.js';
|
|
10
10
|
import { vgxnessOpenCodeDefaultAgent, vgxnessOpenCodePromptContractVersion, vgxnessOpenCodeSddSubagents } from './opencode-default-agent-config.js';
|
|
11
11
|
import { buildCanonicalAgentManifestDiagnostic } from './provider-canonical-agent-manifest.js';
|
|
12
|
-
import { normalizeProviderHealthInput, PROVIDER_HEALTH_SAFETY, CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, classifyProviderConfigPaths, providerConfigPathDiagnostics, providerRuntimeContext, providerHealthFailure, isUserGlobalScope, REQUIRED_PROVIDER_MCP_TOOLS, rollupProviderHealth, } from './provider-health-types.js';
|
|
12
|
+
import { normalizeProviderHealthInput, PROVIDER_HEALTH_SAFETY, CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, classifyProviderConfigPaths, providerConfigPathDiagnostics, providerRuntimeContext, providerEvidence, providerHealthFailure, isUserGlobalScope, REQUIRED_PROVIDER_MCP_TOOLS, rollupProviderHealth, } from './provider-health-types.js';
|
|
13
13
|
const projectConfigTargets = ['.opencode/opencode.json', 'opencode.json', '.opencode/opencode.jsonc', 'opencode.jsonc'];
|
|
14
14
|
export class ProviderStatusService {
|
|
15
15
|
deps;
|
|
@@ -29,6 +29,7 @@ export class ProviderStatusService {
|
|
|
29
29
|
const managerConfigured = hasConfiguredManager(providerConfig);
|
|
30
30
|
const subagentsConfigured = hasConfiguredSubagents(providerConfig);
|
|
31
31
|
const tools = requiredToolPresence();
|
|
32
|
+
const evidence = providerEvidence({ staticManifestKnown: true, renderedConfigExpected: true, configPathInspected: paths.length > 0 });
|
|
32
33
|
const configStatus = resolveConfigStatus(paths, mcpEntry, managerConfigured, subagentsConfigured);
|
|
33
34
|
const status = rollupProviderHealth([canonicalAgentManifest.status, configStatus, ...tools.map((tool) => (tool.present ? 'pass' : 'fail'))]);
|
|
34
35
|
const sdd = normalized.change.length > 0 ? this.readSdd(normalized.project, normalized.change) : undefined;
|
|
@@ -39,12 +40,14 @@ export class ProviderStatusService {
|
|
|
39
40
|
const verboseShape = {
|
|
40
41
|
config: { status: configStatus, paths, mcpEntry },
|
|
41
42
|
canonicalAgentManifest,
|
|
43
|
+
providerEvidence: evidence,
|
|
42
44
|
sdd,
|
|
43
45
|
mcpRequiredTools: tools,
|
|
44
46
|
};
|
|
45
47
|
const compactShape = {
|
|
46
48
|
config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') },
|
|
47
49
|
canonicalAgentManifest,
|
|
50
|
+
providerEvidence: evidence,
|
|
48
51
|
sdd: sdd === undefined ? undefined : compactSdd(sdd, 'compact'),
|
|
49
52
|
mcpRequiredTools: tools,
|
|
50
53
|
};
|
|
@@ -70,6 +73,7 @@ export class ProviderStatusService {
|
|
|
70
73
|
nextAction: nextActionFor(status, mcpEntry, sdd?.next),
|
|
71
74
|
checkedPaths,
|
|
72
75
|
canonicalAgentManifest,
|
|
76
|
+
providerEvidence: evidence,
|
|
73
77
|
...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
|
|
74
78
|
mcpRequiredTools: tools,
|
|
75
79
|
originalBytes,
|
|
@@ -112,8 +116,9 @@ export class ProviderStatusService {
|
|
|
112
116
|
const sdd = normalized.change.length > 0 ? this.readSdd(normalized.project, normalized.change) : undefined;
|
|
113
117
|
const checkedPaths = normalized.payloadMode === 'verbose' ? [mcpState.path, agents.directoryPath, ...agents.agents.map((agent) => agent.path), projectMemory.path, ...claudeAdvisoryPaths(normalized.workspaceRoot)] : [mcpState.path, projectMemory.path, ...agents.agents.filter((agent) => agent.exists || agent.status !== 'missing').map((agent) => agent.path), ...advisory];
|
|
114
118
|
const tools = [...requiredToolPresence(), { tool: 'claude-cli', present: false, diagnostic: 'Read-only status does not execute `claude --version`; CLI presence is checked during explicit apply/preflight only.' }];
|
|
115
|
-
const
|
|
116
|
-
const
|
|
119
|
+
const evidence = providerEvidence({ staticManifestKnown: true, renderedConfigExpected: true, configPathInspected: paths.length > 0 });
|
|
120
|
+
const verboseShape = { config: { status: configStatus, paths, mcpEntry }, canonicalAgentManifest, providerEvidence: evidence, agents, projectMemory, advisory, sdd, mcpRequiredTools: tools };
|
|
121
|
+
const compactShape = { config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') }, canonicalAgentManifest, providerEvidence: evidence, agentSummary: summarizeClaudeAgents(agents), projectMemory: { status: projectMemory.status, action: projectMemory.action }, advisory, sdd: sdd === undefined ? undefined : compactSdd(sdd, 'compact'), mcpRequiredTools: tools };
|
|
117
122
|
const originalBytes = Buffer.byteLength(JSON.stringify(verboseShape), 'utf8');
|
|
118
123
|
const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
|
|
119
124
|
const issueCount = [canonicalAgentManifest.status, configStatus, ...agentStatuses].filter((item) => item === 'fail' || item === 'not-configured').length;
|
|
@@ -136,6 +141,7 @@ export class ProviderStatusService {
|
|
|
136
141
|
nextAction: nextActionFor(status, mcpEntry, sdd?.next),
|
|
137
142
|
checkedPaths,
|
|
138
143
|
canonicalAgentManifest,
|
|
144
|
+
providerEvidence: evidence,
|
|
139
145
|
...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
|
|
140
146
|
mcpRequiredTools: tools,
|
|
141
147
|
originalBytes,
|
|
@@ -163,11 +169,12 @@ export class ProviderStatusService {
|
|
|
163
169
|
const agentStatuses = agents.agents.map((agent) => (agent.status === 'managed' ? 'pass' : agent.status === 'missing' ? 'not-configured' : 'fail'));
|
|
164
170
|
const configStatus = claudeConfigHealthStatus([...paths.map((path) => path.status), ...agentStatuses]);
|
|
165
171
|
const tools = [...requiredToolPresence(), { tool: 'claude-cli', present: false, diagnostic: 'Read-only status does not execute `claude --version`; no Claude Code process was launched.' }];
|
|
172
|
+
const evidence = providerEvidence({ staticManifestKnown: true, renderedConfigExpected: true, configPathInspected: paths.length > 0 });
|
|
166
173
|
const status = rollupProviderHealth([canonicalAgentManifest.status, configStatus]);
|
|
167
174
|
const sdd = normalized.change.length > 0 ? this.readSdd(normalized.project, normalized.change) : undefined;
|
|
168
175
|
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)];
|
|
169
|
-
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 };
|
|
170
|
-
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 };
|
|
176
|
+
const verboseShape = { config: { status: configStatus, paths, mcpEntry }, canonicalAgentManifest, providerEvidence: evidence, agents, userMemory: { status: userMemory.status, action: userMemory.action }, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, scopeWarnings, sdd, mcpRequiredTools: tools };
|
|
177
|
+
const compactShape = { config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') }, canonicalAgentManifest, providerEvidence: evidence, 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 };
|
|
171
178
|
const originalBytes = Buffer.byteLength(JSON.stringify(verboseShape), 'utf8');
|
|
172
179
|
const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
|
|
173
180
|
const reportBase = {
|
|
@@ -188,6 +195,7 @@ export class ProviderStatusService {
|
|
|
188
195
|
nextAction: nextActionFor(status, mcpEntry, sdd?.next),
|
|
189
196
|
checkedPaths,
|
|
190
197
|
canonicalAgentManifest,
|
|
198
|
+
providerEvidence: evidence,
|
|
191
199
|
...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
|
|
192
200
|
mcpRequiredTools: tools,
|
|
193
201
|
originalBytes,
|
|
@@ -335,7 +343,11 @@ function inspectOpenCodeMcpEntry(paths) {
|
|
|
335
343
|
}
|
|
336
344
|
}
|
|
337
345
|
function requiredToolPresence() {
|
|
338
|
-
return REQUIRED_PROVIDER_MCP_TOOLS.map((tool) => ({
|
|
346
|
+
return REQUIRED_PROVIDER_MCP_TOOLS.map((tool) => ({
|
|
347
|
+
tool,
|
|
348
|
+
present: true,
|
|
349
|
+
diagnostic: 'Static VGXNESS manifest expects this MCP tool; provider host tool presence was not verified.',
|
|
350
|
+
}));
|
|
339
351
|
}
|
|
340
352
|
function resolveConfigStatus(paths, mcpEntry, managerConfigured, subagentsConfigured) {
|
|
341
353
|
if (paths.some((path) => path.status === 'fail') || mcpEntry.status === 'fail')
|
package/dist/mcp/schema.js
CHANGED
|
@@ -109,7 +109,125 @@ const contextCockpitLevels = ['compact', 'expanded', 'verbose'];
|
|
|
109
109
|
const providerChangePlanProviders = ['opencode', 'claude', 'antigravity', 'custom'];
|
|
110
110
|
const providerChangePlanTypes = ['opencode-mcp-install', 'claude-mcp-install', 'setup', 'install', 'config-preparation'];
|
|
111
111
|
const sddPhaseInputSchema = z.union([z.enum(sddPhases), z.literal('apply')]);
|
|
112
|
+
const sddPhaseSchema = z.enum(sddPhases);
|
|
112
113
|
const jsonValueSchema = z.lazy(() => z.union([z.string(), z.number().finite(), z.boolean(), z.null(), z.array(jsonValueSchema), z.record(z.string(), jsonValueSchema)]));
|
|
114
|
+
const providerEvidenceOutputSchema = z
|
|
115
|
+
.object({
|
|
116
|
+
staticManifestKnown: z.boolean(),
|
|
117
|
+
renderedConfigExpected: z.boolean(),
|
|
118
|
+
configPathInspected: z.boolean(),
|
|
119
|
+
hostIntrospectionAvailable: z.boolean(),
|
|
120
|
+
hostToolPresenceVerified: z.boolean(),
|
|
121
|
+
evidenceLevel: z.enum(['static-manifest', 'config-inspection', 'host-introspection']),
|
|
122
|
+
notes: z.array(z.string()),
|
|
123
|
+
})
|
|
124
|
+
.passthrough();
|
|
125
|
+
const sddPhaseGateOutputSchema = z
|
|
126
|
+
.object({
|
|
127
|
+
phase: sddPhaseSchema,
|
|
128
|
+
topicKey: z.string(),
|
|
129
|
+
artifactPresent: z.boolean(),
|
|
130
|
+
artifactStatus: z.enum(['missing', 'draft', 'accepted', 'rejected', 'superseded']),
|
|
131
|
+
artifactState: z.enum(['missing', 'draft', 'accepted', 'rejected', 'superseded', 'legacy']),
|
|
132
|
+
accepted: z.boolean(),
|
|
133
|
+
acceptedByHuman: z.boolean(),
|
|
134
|
+
agentCallable: z.boolean(),
|
|
135
|
+
requiresHumanAcceptance: z.boolean(),
|
|
136
|
+
draftPresent: z.boolean(),
|
|
137
|
+
contentFrozen: z.boolean(),
|
|
138
|
+
runnable: z.boolean(),
|
|
139
|
+
blocked: z.boolean(),
|
|
140
|
+
blockedReasons: z.array(z.string()),
|
|
141
|
+
humanOnly: z.boolean(),
|
|
142
|
+
preflightRequired: z.boolean(),
|
|
143
|
+
requiresPreflight: z.boolean(),
|
|
144
|
+
requiresProviderWriteConsent: z.boolean(),
|
|
145
|
+
canDraft: z.boolean(),
|
|
146
|
+
canMarkReady: z.boolean(),
|
|
147
|
+
canAccept: z.boolean(),
|
|
148
|
+
canReopen: z.boolean(),
|
|
149
|
+
nextAllowedActions: z.array(z.string()),
|
|
150
|
+
requiredPriorPhase: sddPhaseSchema.optional(),
|
|
151
|
+
blockerReason: z.enum(['missing', 'draft', 'accepted', 'legacy', 'rejected', 'superseded']).optional(),
|
|
152
|
+
})
|
|
153
|
+
.passthrough();
|
|
154
|
+
const sddReadinessGatesOutputSchema = z
|
|
155
|
+
.object({
|
|
156
|
+
phase: sddPhaseGateOutputSchema,
|
|
157
|
+
prerequisites: z.array(sddPhaseGateOutputSchema),
|
|
158
|
+
runnable: z.boolean(),
|
|
159
|
+
blocked: z.boolean(),
|
|
160
|
+
blockedReasons: z.array(z.string()),
|
|
161
|
+
humanOnly: z.boolean(),
|
|
162
|
+
preflightRequired: z.boolean(),
|
|
163
|
+
agentCallable: z.boolean(),
|
|
164
|
+
canDraft: z.boolean(),
|
|
165
|
+
canMarkReady: z.boolean(),
|
|
166
|
+
canAccept: z.boolean(),
|
|
167
|
+
canReopen: z.boolean(),
|
|
168
|
+
nextAllowedActions: z.array(z.string()),
|
|
169
|
+
requiresProviderWriteConsent: z.boolean(),
|
|
170
|
+
})
|
|
171
|
+
.passthrough();
|
|
172
|
+
const sddCockpitGatesOutputSchema = z
|
|
173
|
+
.object({
|
|
174
|
+
phases: z.array(sddPhaseGateOutputSchema),
|
|
175
|
+
changeComplete: z.boolean(),
|
|
176
|
+
nextPhase: sddPhaseSchema.optional(),
|
|
177
|
+
nextPhaseRunnable: z.boolean(),
|
|
178
|
+
runnableNextPhases: z.array(sddPhaseSchema),
|
|
179
|
+
blockedTransitions: z.array(sddPhaseGateOutputSchema),
|
|
180
|
+
blockedReasons: z.array(z.string()),
|
|
181
|
+
requiresHumanAcceptance: z.boolean(),
|
|
182
|
+
humanOnly: z.boolean(),
|
|
183
|
+
preflightRequired: z.boolean(),
|
|
184
|
+
agentCallable: z.boolean(),
|
|
185
|
+
canDraft: z.boolean(),
|
|
186
|
+
canMarkReady: z.boolean(),
|
|
187
|
+
canAccept: z.boolean(),
|
|
188
|
+
canReopen: z.boolean(),
|
|
189
|
+
nextAllowedActions: z.array(z.string()),
|
|
190
|
+
requiresProviderWriteConsent: z.boolean(),
|
|
191
|
+
})
|
|
192
|
+
.passthrough();
|
|
193
|
+
const sddRecommendedActionOutputSchema = z
|
|
194
|
+
.object({
|
|
195
|
+
id: z.string(),
|
|
196
|
+
label: z.string(),
|
|
197
|
+
title: z.string(),
|
|
198
|
+
description: z.string(),
|
|
199
|
+
kind: z.enum(['inspect', 'draft-phase', 'mark-ready', 'accept-human', 'reopen-human']),
|
|
200
|
+
category: z.enum(['inspection', 'sdd-phase', 'human-governance']),
|
|
201
|
+
phase: sddPhaseSchema.optional(),
|
|
202
|
+
targetTool: z.string(),
|
|
203
|
+
suggestedArgs: z.record(z.string(), jsonValueSchema),
|
|
204
|
+
readOnly: z.boolean(),
|
|
205
|
+
mutating: z.boolean(),
|
|
206
|
+
agentCallable: z.boolean(),
|
|
207
|
+
humanOnly: z.boolean(),
|
|
208
|
+
requiresHumanApproval: z.boolean(),
|
|
209
|
+
requiresHumanConfirmation: z.boolean(),
|
|
210
|
+
requiresPreflight: z.boolean(),
|
|
211
|
+
requiresProviderWriteConsent: z.boolean(),
|
|
212
|
+
reason: z.string(),
|
|
213
|
+
rationale: z.string(),
|
|
214
|
+
blockingPrerequisites: z.array(z
|
|
215
|
+
.object({
|
|
216
|
+
phase: sddPhaseSchema,
|
|
217
|
+
topicKey: z.string(),
|
|
218
|
+
reason: z.string(),
|
|
219
|
+
artifactId: z.string().optional(),
|
|
220
|
+
})
|
|
221
|
+
.passthrough()),
|
|
222
|
+
})
|
|
223
|
+
.passthrough();
|
|
224
|
+
const mcpSuccessOutputSchema = (value) => z
|
|
225
|
+
.object({
|
|
226
|
+
ok: z.literal(true),
|
|
227
|
+
tool: z.enum(SUPPORTED_VGX_MCP_TOOL_NAMES),
|
|
228
|
+
value,
|
|
229
|
+
})
|
|
230
|
+
.passthrough();
|
|
113
231
|
export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
114
232
|
vgxness_sdd_status: z
|
|
115
233
|
.object({
|
|
@@ -503,6 +621,64 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
|
|
|
503
621
|
})
|
|
504
622
|
.passthrough(),
|
|
505
623
|
};
|
|
624
|
+
export const VGX_MCP_TOOL_OUTPUT_SCHEMAS = {
|
|
625
|
+
vgxness_sdd_ready: mcpSuccessOutputSchema(z
|
|
626
|
+
.object({
|
|
627
|
+
change: z.string(),
|
|
628
|
+
phase: sddPhaseSchema,
|
|
629
|
+
ready: z.boolean(),
|
|
630
|
+
gates: sddReadinessGatesOutputSchema.optional(),
|
|
631
|
+
})
|
|
632
|
+
.passthrough()),
|
|
633
|
+
vgxness_sdd_get_readiness: mcpSuccessOutputSchema(z
|
|
634
|
+
.object({
|
|
635
|
+
change: z.string(),
|
|
636
|
+
phase: sddPhaseSchema,
|
|
637
|
+
ready: z.boolean(),
|
|
638
|
+
gates: sddReadinessGatesOutputSchema.optional(),
|
|
639
|
+
})
|
|
640
|
+
.passthrough()),
|
|
641
|
+
vgxness_sdd_cockpit: mcpSuccessOutputSchema(z
|
|
642
|
+
.object({
|
|
643
|
+
project: z.string(),
|
|
644
|
+
change: z.string(),
|
|
645
|
+
gates: sddCockpitGatesOutputSchema.optional(),
|
|
646
|
+
readModel: z
|
|
647
|
+
.object({
|
|
648
|
+
project: z.string(),
|
|
649
|
+
change: z.string(),
|
|
650
|
+
gates: sddCockpitGatesOutputSchema.optional(),
|
|
651
|
+
phases: z.array(z
|
|
652
|
+
.object({
|
|
653
|
+
phase: sddPhaseSchema,
|
|
654
|
+
gates: sddPhaseGateOutputSchema.optional(),
|
|
655
|
+
})
|
|
656
|
+
.passthrough()),
|
|
657
|
+
})
|
|
658
|
+
.passthrough(),
|
|
659
|
+
})
|
|
660
|
+
.passthrough()),
|
|
661
|
+
vgxness_sdd_continue: mcpSuccessOutputSchema(z
|
|
662
|
+
.object({
|
|
663
|
+
kind: z.literal('sdd-continuation-plan'),
|
|
664
|
+
project: z.string(),
|
|
665
|
+
change: z.string(),
|
|
666
|
+
recommendedActions: z.array(sddRecommendedActionOutputSchema),
|
|
667
|
+
})
|
|
668
|
+
.passthrough()),
|
|
669
|
+
vgxness_provider_status: mcpSuccessOutputSchema(z
|
|
670
|
+
.object({
|
|
671
|
+
kind: z.literal('provider-status'),
|
|
672
|
+
providerEvidence: providerEvidenceOutputSchema,
|
|
673
|
+
})
|
|
674
|
+
.passthrough()),
|
|
675
|
+
vgxness_provider_doctor: mcpSuccessOutputSchema(z
|
|
676
|
+
.object({
|
|
677
|
+
kind: z.literal('provider-doctor'),
|
|
678
|
+
providerEvidence: providerEvidenceOutputSchema,
|
|
679
|
+
})
|
|
680
|
+
.passthrough()),
|
|
681
|
+
};
|
|
506
682
|
export function successEnvelope(tool, value) {
|
|
507
683
|
return { ok: true, tool, value };
|
|
508
684
|
}
|
package/dist/mcp/stdio-server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { runBootAgentSeedUpgrade } from '../agents/boot-upgrade.js';
|
|
4
4
|
import { createVgxMcpControlPlane } from './control-plane.js';
|
|
5
|
-
import { EXPOSED_VGX_MCP_TOOL_NAMES, toInternalVgxMcpToolName, VGX_MCP_TOOL_INPUT_SCHEMAS, } from './schema.js';
|
|
5
|
+
import { EXPOSED_VGX_MCP_TOOL_NAMES, toInternalVgxMcpToolName, VGX_MCP_TOOL_INPUT_SCHEMAS, VGX_MCP_TOOL_OUTPUT_SCHEMAS, } from './schema.js';
|
|
6
6
|
export async function startVgxMcpStdioServer(options = {}) {
|
|
7
7
|
const controlPlane = createVgxMcpControlPlane(options.databasePath === undefined ? {} : { databasePath: options.databasePath });
|
|
8
8
|
runBootAgentSeedUpgrade(controlPlane.database);
|
|
@@ -39,18 +39,20 @@ export async function startVgxMcpStdioServer(options = {}) {
|
|
|
39
39
|
function registerVgxTools(server, controlPlane) {
|
|
40
40
|
for (const publicToolName of EXPOSED_VGX_MCP_TOOL_NAMES) {
|
|
41
41
|
const toolName = toInternalVgxMcpToolName(publicToolName);
|
|
42
|
+
const outputSchema = VGX_MCP_TOOL_OUTPUT_SCHEMAS[toolName];
|
|
42
43
|
server.registerTool(publicToolName, {
|
|
43
44
|
title: publicToolName,
|
|
44
45
|
description: descriptionForTool(publicToolName),
|
|
45
46
|
inputSchema: VGX_MCP_TOOL_INPUT_SCHEMAS[toolName],
|
|
47
|
+
...(outputSchema === undefined ? {} : { outputSchema }),
|
|
46
48
|
}, async (args) => toMcpTextResult(controlPlane.callVgxTool(toolName, argsForTool(args))));
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
function descriptionForTool(publicToolName) {
|
|
50
52
|
if (publicToolName === 'provider_status')
|
|
51
|
-
return '
|
|
53
|
+
return 'Agent-callable read-only provider status report; inspects expected config paths without installing, repairing, or writing provider config, and does not verify true host tool presence. Use it to decide whether to inspect doctor output or request explicit setup consent.';
|
|
52
54
|
if (publicToolName === 'provider_doctor')
|
|
53
|
-
return '
|
|
55
|
+
return 'Agent-callable read-only provider doctor advisory; checks VGXNESS-known provider config health without install/repair/config writes and without proving true host tool presence. Use it to decide the next setup or troubleshooting recommendation.';
|
|
54
56
|
if (publicToolName === 'provider_change_plan')
|
|
55
57
|
return 'Read-only provider change plan preview; composes status, doctor, and OpenCode install planning without writing provider config.';
|
|
56
58
|
if (publicToolName === 'opencode_handoff_preview')
|
|
@@ -66,9 +68,21 @@ function descriptionForTool(publicToolName) {
|
|
|
66
68
|
if (publicToolName === 'context_cockpit')
|
|
67
69
|
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.';
|
|
68
70
|
if (publicToolName === 'sdd_cockpit')
|
|
69
|
-
return '
|
|
71
|
+
return 'Agent-callable read-only SDD cockpit summary; returns phase metadata/readModel without artifact bodies or state changes and preserves explicit human acceptance semantics. Use it to decide the next safe SDD action or blocker.';
|
|
70
72
|
if (publicToolName === 'sdd_continue')
|
|
71
|
-
return '
|
|
73
|
+
return 'Agent-callable read-only SDD continuation advisory; returns blocker actions, suggested next steps, interrupted-run context, and safety notes without provider execution, run creation, artifact mutation, provider config writes, or openspec writes. Use it to choose the next manual or agent-safe action.';
|
|
74
|
+
if (publicToolName === 'sdd_status')
|
|
75
|
+
return 'Agent-callable read-only SDD status summary; reports phase presence/state and next ready phase without creating, accepting, or mutating artifacts. Use it to decide whether to inspect, draft, mark ready, or wait for human acceptance.';
|
|
76
|
+
if (publicToolName === 'sdd_get_readiness')
|
|
77
|
+
return 'Agent-callable read-only SDD readiness check; reports whether a phase can proceed and why, without marking ready or changing artifacts. Use it to decide whether to call sdd_ready or resolve prerequisites first.';
|
|
78
|
+
if (publicToolName === 'sdd_ready')
|
|
79
|
+
return 'Agent-callable state-changing SDD readiness marker; marks an existing phase artifact ready when prerequisites allow, but does not human-accept it. Use only after readiness is clear, then ask a human to accept when governance requires acceptance.';
|
|
80
|
+
if (publicToolName === 'sdd_accept_artifact')
|
|
81
|
+
return 'Human-only mutating SDD acceptance gate; records explicit human acceptance for an artifact and must not be called by agents on their own behalf. Use only after a human approval decision, then continue to the next phase.';
|
|
82
|
+
if (publicToolName === 'sdd_reopen_artifact')
|
|
83
|
+
return 'Human-only mutating SDD reopen gate; records an explicit human decision to reopen an artifact and must not be called by agents on their own behalf. Use only after human instruction, then revise or re-review the phase.';
|
|
84
|
+
if (publicToolName === 'run_preflight')
|
|
85
|
+
return 'Agent-callable advisory/planning-only run preflight; evaluates permissions and records a plan but does not execute the checked shell/git/install/network/provider/secrets operation. Use it to decide whether human approval or a separate executor step is required.';
|
|
72
86
|
const toolName = toInternalVgxMcpToolName(publicToolName);
|
|
73
87
|
return `VGX control-plane tool ${toolName}`;
|
|
74
88
|
}
|
|
@@ -78,6 +92,7 @@ function argsForTool(args) {
|
|
|
78
92
|
function toMcpTextResult(envelope) {
|
|
79
93
|
const result = {
|
|
80
94
|
content: [{ type: 'text', text: JSON.stringify(envelope, null, 2) }],
|
|
95
|
+
structuredContent: envelope,
|
|
81
96
|
};
|
|
82
97
|
if (!envelope.ok)
|
|
83
98
|
result.isError = true;
|
|
@@ -30,6 +30,7 @@ export function buildSddCockpitReadModel(cockpit) {
|
|
|
30
30
|
phases,
|
|
31
31
|
nextAction,
|
|
32
32
|
blockers,
|
|
33
|
+
...(cockpit.gates === undefined ? {} : { gates: cockpit.gates }),
|
|
33
34
|
guidance: guidanceFor(cockpit, nextAction, blockers),
|
|
34
35
|
summary: summarize(phases),
|
|
35
36
|
contentIncluded: false,
|
|
@@ -68,6 +69,7 @@ function toPhaseReadModel(phase) {
|
|
|
68
69
|
blocked: !phase.readiness.ready || phase.blockers.length > 0,
|
|
69
70
|
reasons,
|
|
70
71
|
},
|
|
72
|
+
...(phase.gates === undefined ? {} : { gates: phase.gates }),
|
|
71
73
|
guidance: guidanceForPhase(phase, status, canAccept),
|
|
72
74
|
};
|
|
73
75
|
}
|
|
@@ -5,6 +5,7 @@ export function sddContinuationPlanFrom(input) {
|
|
|
5
5
|
const suggestedCommand = suggestedContinuationCommand(input.project, input.next, inspectCommand, dbFlag);
|
|
6
6
|
const blockerGuidance = input.next.blockerGuidance ?? [];
|
|
7
7
|
const blockerActions = blockerGuidance.map((blocker) => continuationBlockerAction(input.project, input.next.change, blocker, dbFlag));
|
|
8
|
+
const recommendedActions = continuationRecommendedActions(input.project, input.next, blockerGuidance);
|
|
8
9
|
const relatedRunContext = relatedRunContextView(input.project, input.relatedRunContext, dbFlag);
|
|
9
10
|
return {
|
|
10
11
|
kind: 'sdd-continuation-plan',
|
|
@@ -18,6 +19,7 @@ export function sddContinuationPlanFrom(input) {
|
|
|
18
19
|
suggestedCommand,
|
|
19
20
|
inspectCommand,
|
|
20
21
|
blockerActions,
|
|
22
|
+
recommendedActions,
|
|
21
23
|
...(relatedRunContext === undefined ? {} : { relatedRunContext }),
|
|
22
24
|
...(input.explicitDatabasePath === undefined ? {} : { explicitDatabasePath: input.explicitDatabasePath }),
|
|
23
25
|
safety: [
|
|
@@ -28,6 +30,153 @@ export function sddContinuationPlanFrom(input) {
|
|
|
28
30
|
],
|
|
29
31
|
};
|
|
30
32
|
}
|
|
33
|
+
function continuationRecommendedActions(project, next, blockerGuidance) {
|
|
34
|
+
const actions = [inspectCockpitAction(project, next.change)];
|
|
35
|
+
if (next.status === 'runnable' && next.nextPhase !== undefined)
|
|
36
|
+
actions.push(draftPhaseAction(project, next.change, next.nextPhase, next.reason));
|
|
37
|
+
for (const blocker of blockerGuidance) {
|
|
38
|
+
const action = recommendedActionForBlocker(project, next.change, blocker);
|
|
39
|
+
if (action !== undefined)
|
|
40
|
+
actions.push(action);
|
|
41
|
+
}
|
|
42
|
+
return actions;
|
|
43
|
+
}
|
|
44
|
+
function inspectCockpitAction(project, change) {
|
|
45
|
+
return {
|
|
46
|
+
id: `sdd.${change}.inspect-cockpit`,
|
|
47
|
+
label: 'Inspect SDD cockpit',
|
|
48
|
+
title: 'Inspect SDD cockpit',
|
|
49
|
+
description: 'Read the current SDD cockpit/read model before choosing the next action.',
|
|
50
|
+
kind: 'inspect',
|
|
51
|
+
category: 'inspection',
|
|
52
|
+
targetTool: 'vgxness_sdd_cockpit',
|
|
53
|
+
suggestedArgs: { project, change },
|
|
54
|
+
readOnly: true,
|
|
55
|
+
mutating: false,
|
|
56
|
+
agentCallable: true,
|
|
57
|
+
humanOnly: false,
|
|
58
|
+
requiresHumanApproval: false,
|
|
59
|
+
requiresHumanConfirmation: false,
|
|
60
|
+
requiresPreflight: false,
|
|
61
|
+
requiresProviderWriteConsent: false,
|
|
62
|
+
reason: 'Continuation plans are advisory; cockpit inspection is a safe MCP-native way to refresh state.',
|
|
63
|
+
rationale: 'Keeps agents grounded in current artifact states without mutating SDD state or provider configuration.',
|
|
64
|
+
blockingPrerequisites: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function draftPhaseAction(project, change, phase, reason) {
|
|
68
|
+
return {
|
|
69
|
+
id: `sdd.${change}.${phase}.draft`,
|
|
70
|
+
label: `Prepare ${phase} draft through normal SDD phase flow`,
|
|
71
|
+
title: `Prepare ${phase} draft`,
|
|
72
|
+
description: 'The next SDD phase appears runnable; draft generation must still use the normal phase workflow and any required preflight/confirmation gates.',
|
|
73
|
+
kind: 'draft-phase',
|
|
74
|
+
category: 'sdd-phase',
|
|
75
|
+
phase,
|
|
76
|
+
targetTool: 'vgxness_sdd_get_readiness',
|
|
77
|
+
suggestedArgs: { project, change, phase },
|
|
78
|
+
readOnly: false,
|
|
79
|
+
mutating: true,
|
|
80
|
+
agentCallable: true,
|
|
81
|
+
humanOnly: false,
|
|
82
|
+
requiresHumanApproval: false,
|
|
83
|
+
requiresHumanConfirmation: false,
|
|
84
|
+
requiresPreflight: true,
|
|
85
|
+
requiresProviderWriteConsent: false,
|
|
86
|
+
reason,
|
|
87
|
+
rationale: 'This continuation tool is read-only, so it can recommend the next phase but cannot execute providers or save artifacts itself.',
|
|
88
|
+
blockingPrerequisites: [],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function recommendedActionForBlocker(project, change, blocker) {
|
|
92
|
+
if (blocker.reason === 'draft' || blocker.reason === 'legacy')
|
|
93
|
+
return humanAcceptAction(project, change, blocker);
|
|
94
|
+
if (blocker.reason === 'rejected')
|
|
95
|
+
return humanReopenAction(project, change, blocker);
|
|
96
|
+
if (blocker.reason === 'missing')
|
|
97
|
+
return draftPhaseAction(project, change, blocker.phase, blocker.action);
|
|
98
|
+
return inspectArtifactAction(project, change, blocker);
|
|
99
|
+
}
|
|
100
|
+
function humanAcceptAction(project, change, blocker) {
|
|
101
|
+
return {
|
|
102
|
+
id: `sdd.${change}.${blocker.phase}.accept-human`,
|
|
103
|
+
label: `Human review and acceptance required for ${blocker.phase}`,
|
|
104
|
+
title: `Accept ${blocker.phase} artifact as a human`,
|
|
105
|
+
description: 'A human must explicitly accept this artifact before it counts as accepted; agents must not accept on the human’s behalf.',
|
|
106
|
+
kind: 'accept-human',
|
|
107
|
+
category: 'human-governance',
|
|
108
|
+
phase: blocker.phase,
|
|
109
|
+
targetTool: 'vgxness_sdd_accept_artifact',
|
|
110
|
+
suggestedArgs: { project, change, phase: blocker.phase },
|
|
111
|
+
readOnly: false,
|
|
112
|
+
mutating: true,
|
|
113
|
+
agentCallable: false,
|
|
114
|
+
humanOnly: true,
|
|
115
|
+
requiresHumanApproval: true,
|
|
116
|
+
requiresHumanConfirmation: true,
|
|
117
|
+
requiresPreflight: true,
|
|
118
|
+
requiresProviderWriteConsent: false,
|
|
119
|
+
reason: blocker.action,
|
|
120
|
+
rationale: 'Human-only acceptance is an explicit governance event and cannot be inferred from draft content, readiness, or artifact presence.',
|
|
121
|
+
blockingPrerequisites: [blockingPrerequisite(blocker)],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function humanReopenAction(project, change, blocker) {
|
|
125
|
+
return {
|
|
126
|
+
id: `sdd.${change}.${blocker.phase}.reopen-human`,
|
|
127
|
+
label: `Human reopen required for rejected ${blocker.phase}`,
|
|
128
|
+
title: `Reopen ${blocker.phase} artifact as a human`,
|
|
129
|
+
description: 'A human may reopen the rejected artifact to draft; agents must not perform this governance action on the human’s behalf.',
|
|
130
|
+
kind: 'reopen-human',
|
|
131
|
+
category: 'human-governance',
|
|
132
|
+
phase: blocker.phase,
|
|
133
|
+
targetTool: 'vgxness_sdd_reopen_artifact',
|
|
134
|
+
suggestedArgs: { project, change, phase: blocker.phase },
|
|
135
|
+
readOnly: false,
|
|
136
|
+
mutating: true,
|
|
137
|
+
agentCallable: false,
|
|
138
|
+
humanOnly: true,
|
|
139
|
+
requiresHumanApproval: true,
|
|
140
|
+
requiresHumanConfirmation: true,
|
|
141
|
+
requiresPreflight: true,
|
|
142
|
+
requiresProviderWriteConsent: false,
|
|
143
|
+
reason: blocker.action,
|
|
144
|
+
rationale: 'Reopen is a human governance transition from rejected back to draft and remains outside this read-only planner.',
|
|
145
|
+
blockingPrerequisites: [blockingPrerequisite(blocker)],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function inspectArtifactAction(project, change, blocker) {
|
|
149
|
+
return {
|
|
150
|
+
id: `sdd.${change}.${blocker.phase}.inspect`,
|
|
151
|
+
label: `Inspect ${blocker.phase} artifact`,
|
|
152
|
+
title: `Inspect ${blocker.phase} artifact`,
|
|
153
|
+
description: 'Inspect the blocking artifact before deciding whether human governance or phase work is needed.',
|
|
154
|
+
kind: 'inspect',
|
|
155
|
+
category: 'inspection',
|
|
156
|
+
phase: blocker.phase,
|
|
157
|
+
targetTool: 'vgxness_sdd_get_artifact',
|
|
158
|
+
suggestedArgs: { project, change, phase: blocker.phase, payloadMode: 'compact' },
|
|
159
|
+
readOnly: true,
|
|
160
|
+
mutating: false,
|
|
161
|
+
agentCallable: true,
|
|
162
|
+
humanOnly: false,
|
|
163
|
+
requiresHumanApproval: false,
|
|
164
|
+
requiresHumanConfirmation: false,
|
|
165
|
+
requiresPreflight: false,
|
|
166
|
+
requiresProviderWriteConsent: false,
|
|
167
|
+
reason: blocker.action,
|
|
168
|
+
rationale: 'Artifact inspection is read-only and helps agents understand the blocker without changing SDD state.',
|
|
169
|
+
blockingPrerequisites: [blockingPrerequisite(blocker)],
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function blockingPrerequisite(blocker) {
|
|
173
|
+
return {
|
|
174
|
+
phase: blocker.phase,
|
|
175
|
+
topicKey: blocker.topicKey,
|
|
176
|
+
reason: blocker.reason,
|
|
177
|
+
...(blocker.artifactId === undefined ? {} : { artifactId: blocker.artifactId }),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
31
180
|
function relatedRunContextView(project, relatedRunContext, dbFlag) {
|
|
32
181
|
if (relatedRunContext === undefined)
|
|
33
182
|
return undefined;
|
|
@@ -40,16 +40,18 @@ export class SddWorkflowService {
|
|
|
40
40
|
const validated = this.validatePhaseInput(input);
|
|
41
41
|
if (!validated.ok)
|
|
42
42
|
return validated;
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
45
|
-
return
|
|
46
|
-
const
|
|
43
|
+
const snapshot = this.loadPhaseSnapshot(validated.value.project, validated.value.change);
|
|
44
|
+
if (!snapshot.ok)
|
|
45
|
+
return snapshot;
|
|
46
|
+
const phases = snapshot.value.phases;
|
|
47
|
+
const readiness = getReadinessFromStatuses(validated.value.change, validated.value.phase, phases, this.options);
|
|
47
48
|
return {
|
|
48
49
|
ok: true,
|
|
49
50
|
value: {
|
|
50
51
|
change: validated.value.change,
|
|
51
52
|
phase: validated.value.phase,
|
|
52
53
|
...readiness,
|
|
54
|
+
gates: readinessGatesForPhase(validated.value.change, validated.value.phase, phases, readiness),
|
|
53
55
|
},
|
|
54
56
|
};
|
|
55
57
|
}
|
|
@@ -88,6 +90,7 @@ export class SddWorkflowService {
|
|
|
88
90
|
};
|
|
89
91
|
const artifact = phaseStatus.present ? cockpitArtifactSummaryFromSnapshotItem(phaseStatus) : undefined;
|
|
90
92
|
const blockers = cockpitBlockersForPhase(phaseStatus, readiness);
|
|
93
|
+
const gates = phaseGateFromStatus(phaseStatus, readiness);
|
|
91
94
|
return {
|
|
92
95
|
phase: phaseStatus.phase,
|
|
93
96
|
topicKey: phaseStatus.topicKey,
|
|
@@ -98,6 +101,7 @@ export class SddWorkflowService {
|
|
|
98
101
|
readiness,
|
|
99
102
|
...(artifact === undefined ? {} : { artifact }),
|
|
100
103
|
blockers,
|
|
104
|
+
gates,
|
|
101
105
|
};
|
|
102
106
|
});
|
|
103
107
|
const artifacts = cockpitPhases.map((phase) => phase.artifact).filter((artifact) => artifact !== undefined);
|
|
@@ -123,6 +127,7 @@ export class SddWorkflowService {
|
|
|
123
127
|
acceptedCount: cockpitPhases.filter((phase) => phase.accepted).length,
|
|
124
128
|
legacyCount: cockpitPhases.filter((phase) => phase.legacy).length,
|
|
125
129
|
aggregateBlockers,
|
|
130
|
+
gates: cockpitGatesFromPhases(cockpitPhases, next),
|
|
126
131
|
inspectCommand: `vgxness sdd cockpit --project ${validated.value.project} --change ${validated.value.change} --json`,
|
|
127
132
|
};
|
|
128
133
|
return ok(cockpit);
|
|
@@ -518,6 +523,124 @@ function cockpitArtifactSummaryFromSnapshotItem(item) {
|
|
|
518
523
|
updatedAt: item.updatedAt,
|
|
519
524
|
};
|
|
520
525
|
}
|
|
526
|
+
function readinessGatesForPhase(change, phase, phases, readiness) {
|
|
527
|
+
const targetStatus = phases.find((candidate) => candidate.phase === phase) ?? { phase, topicKey: sddTopicKey(change, phase), present: false, state: 'missing', accepted: false, legacy: false, warnings: [] };
|
|
528
|
+
const targetGate = phaseGateFromStatus(targetStatus, readiness);
|
|
529
|
+
const prerequisites = sddPrerequisites[phase].map((prerequisite) => {
|
|
530
|
+
const prerequisiteStatus = phases.find((candidate) => candidate.phase === prerequisite) ?? {
|
|
531
|
+
phase: prerequisite,
|
|
532
|
+
topicKey: sddTopicKey(change, prerequisite),
|
|
533
|
+
present: false,
|
|
534
|
+
state: 'missing',
|
|
535
|
+
accepted: false,
|
|
536
|
+
legacy: false,
|
|
537
|
+
warnings: [],
|
|
538
|
+
};
|
|
539
|
+
const blocker = readiness.blockedPrerequisites?.find((candidate) => candidate.phase === prerequisite);
|
|
540
|
+
return phaseGateFromStatus(prerequisiteStatus, {
|
|
541
|
+
ready: blocker === undefined,
|
|
542
|
+
blockedPrerequisites: blocker === undefined ? [] : [blocker],
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
const blockedReasons = [...targetGate.blockedReasons, ...prerequisites.flatMap((gate) => gate.blockedReasons)];
|
|
546
|
+
return {
|
|
547
|
+
phase: targetGate,
|
|
548
|
+
prerequisites,
|
|
549
|
+
runnable: readiness.ready,
|
|
550
|
+
blocked: !readiness.ready,
|
|
551
|
+
blockedReasons: [...new Set(blockedReasons)],
|
|
552
|
+
humanOnly: targetGate.humanOnly || prerequisites.some((gate) => gate.humanOnly),
|
|
553
|
+
preflightRequired: targetGate.preflightRequired,
|
|
554
|
+
agentCallable: true,
|
|
555
|
+
canDraft: targetGate.canDraft,
|
|
556
|
+
canMarkReady: targetGate.canMarkReady,
|
|
557
|
+
canAccept: targetGate.canAccept || prerequisites.some((gate) => gate.canAccept),
|
|
558
|
+
canReopen: targetGate.canReopen || prerequisites.some((gate) => gate.canReopen),
|
|
559
|
+
nextAllowedActions: [...new Set([targetGate, ...prerequisites].flatMap((gate) => gate.nextAllowedActions))],
|
|
560
|
+
requiresProviderWriteConsent: false,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function cockpitGatesFromPhases(phases, next) {
|
|
564
|
+
const phaseGates = phases.map((phase) => phase.gates ?? phaseGateFromStatus(phase, phase.readiness));
|
|
565
|
+
const blockedTransitions = phaseGates.filter((gate) => gate.blocked);
|
|
566
|
+
const blockedReasons = [...new Set(blockedTransitions.flatMap((gate) => gate.blockedReasons))];
|
|
567
|
+
const runnableNextPhases = phaseGates.filter((gate) => gate.runnable && !gate.artifactPresent).map((gate) => gate.phase);
|
|
568
|
+
return {
|
|
569
|
+
phases: phaseGates,
|
|
570
|
+
changeComplete: phases.length === sddPhases.length && phases.every((phase) => phase.accepted),
|
|
571
|
+
...(next.nextPhase === undefined ? {} : { nextPhase: next.nextPhase }),
|
|
572
|
+
nextPhaseRunnable: next.status === 'runnable',
|
|
573
|
+
runnableNextPhases,
|
|
574
|
+
blockedTransitions,
|
|
575
|
+
blockedReasons,
|
|
576
|
+
requiresHumanAcceptance: phaseGates.some((gate) => gate.requiresHumanAcceptance),
|
|
577
|
+
humanOnly: phaseGates.some((gate) => gate.humanOnly),
|
|
578
|
+
preflightRequired: next.status === 'runnable',
|
|
579
|
+
agentCallable: true,
|
|
580
|
+
canDraft: phaseGates.some((gate) => gate.canDraft),
|
|
581
|
+
canMarkReady: phaseGates.some((gate) => gate.canMarkReady),
|
|
582
|
+
canAccept: phaseGates.some((gate) => gate.canAccept),
|
|
583
|
+
canReopen: phaseGates.some((gate) => gate.canReopen),
|
|
584
|
+
nextAllowedActions: [...new Set(phaseGates.flatMap((gate) => gate.nextAllowedActions))],
|
|
585
|
+
requiresProviderWriteConsent: false,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function phaseGateFromStatus(status, readiness) {
|
|
589
|
+
const artifactStatus = status.state ?? (status.present ? 'draft' : 'missing');
|
|
590
|
+
const acceptedByHuman = status.accepted === true && status.acceptance?.actor.type === 'human';
|
|
591
|
+
const blockedPrerequisite = readiness.blockedPrerequisites?.[0];
|
|
592
|
+
const blockedReasons = [
|
|
593
|
+
...((readiness.blockedPrerequisites ?? []).map((blocker) => `${blocker.phase}:${blocker.reason}`)),
|
|
594
|
+
...(status.present && status.accepted !== true ? [`${status.phase}:${blockerReasonForStatus(status)}`] : []),
|
|
595
|
+
...(status.legacy === true ? [`${status.phase}:legacy`] : []),
|
|
596
|
+
];
|
|
597
|
+
const requiresHumanAcceptance = status.present && !acceptedByHuman && artifactStatus !== 'missing';
|
|
598
|
+
const canDraft = readiness.ready && !status.present;
|
|
599
|
+
const canMarkReady = readiness.ready;
|
|
600
|
+
const canAccept = status.present && !acceptedByHuman && status.legacy !== true && (artifactStatus === 'draft' || artifactStatus === 'accepted');
|
|
601
|
+
const canReopen = status.present && artifactStatus === 'rejected';
|
|
602
|
+
const nextAllowedActions = phaseNextAllowedActions({ canDraft, canMarkReady, canAccept, canReopen, present: status.present });
|
|
603
|
+
return {
|
|
604
|
+
phase: status.phase,
|
|
605
|
+
topicKey: status.topicKey,
|
|
606
|
+
artifactPresent: status.present,
|
|
607
|
+
artifactStatus,
|
|
608
|
+
artifactState: status.legacy === true ? 'legacy' : artifactStatus,
|
|
609
|
+
accepted: status.accepted === true,
|
|
610
|
+
acceptedByHuman,
|
|
611
|
+
agentCallable: true,
|
|
612
|
+
requiresHumanAcceptance,
|
|
613
|
+
draftPresent: artifactStatus === 'draft',
|
|
614
|
+
contentFrozen: acceptedByHuman,
|
|
615
|
+
runnable: readiness.ready && !status.present,
|
|
616
|
+
blocked: !readiness.ready || status.legacy === true || (status.present && status.accepted !== true),
|
|
617
|
+
blockedReasons: [...new Set(blockedReasons)],
|
|
618
|
+
humanOnly: requiresHumanAcceptance,
|
|
619
|
+
preflightRequired: readiness.ready && !status.present,
|
|
620
|
+
requiresPreflight: readiness.ready && !status.present,
|
|
621
|
+
requiresProviderWriteConsent: false,
|
|
622
|
+
canDraft,
|
|
623
|
+
canMarkReady,
|
|
624
|
+
canAccept,
|
|
625
|
+
canReopen,
|
|
626
|
+
nextAllowedActions,
|
|
627
|
+
...(blockedPrerequisite === undefined ? {} : { requiredPriorPhase: blockedPrerequisite.phase, blockerReason: blockedPrerequisite.reason }),
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function phaseNextAllowedActions(input) {
|
|
631
|
+
const actions = ['vgxness_sdd_get_readiness', 'vgxness_sdd_cockpit'];
|
|
632
|
+
if (input.canDraft)
|
|
633
|
+
actions.push('vgxness_sdd_save_artifact');
|
|
634
|
+
if (input.canMarkReady)
|
|
635
|
+
actions.push('vgxness_sdd_ready');
|
|
636
|
+
if (input.present)
|
|
637
|
+
actions.push('vgxness_sdd_get_artifact');
|
|
638
|
+
if (input.canAccept)
|
|
639
|
+
actions.push('vgxness_sdd_accept_artifact');
|
|
640
|
+
if (input.canReopen)
|
|
641
|
+
actions.push('vgxness_sdd_reopen_artifact');
|
|
642
|
+
return [...new Set(actions)];
|
|
643
|
+
}
|
|
521
644
|
function compactGovernanceArtifact(artifact) {
|
|
522
645
|
return {
|
|
523
646
|
id: artifact.id,
|