vgxness 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +23 -2
  2. package/dist/agents/agent-seed-service.js +10 -0
  3. package/dist/agents/canonical-agent-manifest.js +177 -0
  4. package/dist/agents/canonical-agent-projection.js +164 -0
  5. package/dist/agents/renderers/claude-renderer.js +30 -52
  6. package/dist/cli/bun-bin.js +6 -0
  7. package/dist/cli/cli-flags.js +1 -1
  8. package/dist/cli/cli-help.js +7 -4
  9. package/dist/cli/commands/agent-skill-dispatcher.js +6 -5
  10. package/dist/cli/commands/interactive-entrypoint-dispatcher.js +2 -2
  11. package/dist/cli/commands/mcp-dispatcher.js +75 -3
  12. package/dist/cli/commands/setup-dispatcher.js +9 -0
  13. package/dist/cli/index.js +1 -1
  14. package/dist/cli/tui/main-menu/main-menu-read-model.js +41 -44
  15. package/dist/cli/tui/main-menu/main-menu-render-shape.js +15 -15
  16. package/dist/cli/tui/opentui/main-menu/screen.js +39 -41
  17. package/dist/cli/tui/opentui/main-menu/smoke.js +1 -1
  18. package/dist/cli/tui/opentui/main-menu/view.js +1 -1
  19. package/dist/cli/tui/setup/setup-tui-read-model.js +15 -12
  20. package/dist/governance/governance-report-builder.js +45 -26
  21. package/dist/mcp/claude-code-agent-config.js +95 -0
  22. package/dist/mcp/claude-code-cli.js +71 -0
  23. package/dist/mcp/claude-code-config.js +84 -0
  24. package/dist/mcp/claude-code-project-memory.js +127 -0
  25. package/dist/mcp/claude-code-scope.js +18 -0
  26. package/dist/mcp/client-install-claude-code-contract.js +114 -0
  27. package/dist/mcp/client-install-claude-code.js +136 -0
  28. package/dist/mcp/index.js +8 -0
  29. package/dist/mcp/opencode-default-agent-config.js +7 -113
  30. package/dist/mcp/provider-canonical-agent-manifest.js +39 -0
  31. package/dist/mcp/provider-change-plan.js +109 -1
  32. package/dist/mcp/provider-doctor.js +105 -1
  33. package/dist/mcp/provider-health-types.js +4 -0
  34. package/dist/mcp/provider-status.js +159 -3
  35. package/dist/mcp/schema.js +6 -5
  36. package/dist/mcp/validation.js +1 -1
  37. package/dist/memory/memory-service.js +4 -0
  38. package/dist/sdd/sdd-workflow-service.js +129 -59
  39. package/dist/setup/providers/claude-setup-adapter.js +13 -8
  40. package/dist/setup/setup-plan.js +60 -1
  41. package/docs/architecture.md +55 -113
  42. package/docs/cli.md +90 -2
  43. package/docs/code-runtime.md +218 -0
  44. package/docs/contributing.md +120 -0
  45. package/docs/glossary.md +211 -0
  46. package/docs/mcp.md +144 -0
  47. package/docs/prd.md +23 -26
  48. package/docs/providers.md +150 -0
  49. package/docs/roadmap.md +88 -0
  50. package/docs/safety.md +147 -0
  51. package/docs/storage.md +93 -0
  52. package/package.json +1 -1
  53. package/docs/funcionamiento-del-sistema.md +0 -865
  54. package/docs/harness-gap-analysis.md +0 -243
  55. package/docs/vgxcode.md +0 -87
  56. package/docs/vgxness-code.md +0 -48
@@ -1,15 +1,27 @@
1
1
  import { existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { inspectClaudeCodeAgents } from './claude-code-agent-config.js';
3
+ import { claudeAdvisoryPaths, inspectClaudeCodeMcpConfig } from './claude-code-config.js';
4
+ import { inspectClaudeProjectMemory } from './claude-code-project-memory.js';
5
+ import { resolveClaudeCodeScope } from './claude-code-scope.js';
2
6
  import { vgxnessOpenCodeDefaultAgent, vgxnessOpenCodeSddSubagents } from './opencode-default-agent-config.js';
3
- import { normalizeProviderHealthInput, PROVIDER_HEALTH_SAFETY, providerHealthFailure, REQUIRED_PROVIDER_NATIVE_MCP_TOOLS, rollupProviderDoctor, } from './provider-health-types.js';
7
+ import { buildCanonicalAgentManifestDiagnostic } from './provider-canonical-agent-manifest.js';
8
+ import { normalizeProviderHealthInput, PROVIDER_HEALTH_SAFETY, CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, providerHealthFailure, isUserGlobalScope, REQUIRED_PROVIDER_NATIVE_MCP_TOOLS, rollupProviderDoctor, } from './provider-health-types.js';
4
9
  import { inspectOpenCodeConfigPaths } from './provider-status.js';
5
10
  export class ProviderDoctorService {
11
+ deps;
12
+ constructor(deps = {}) {
13
+ this.deps = deps;
14
+ }
6
15
  runDoctor(input = {}) {
7
16
  return this.getDoctor(input);
8
17
  }
9
18
  getDoctor(input = {}) {
10
19
  const normalized = normalizeProviderHealthInput(input);
20
+ if (normalized.providerAdapter === 'claude')
21
+ return this.getClaudeDoctor(normalized);
11
22
  if (normalized.providerAdapter !== 'opencode')
12
23
  return providerHealthFailure(`Unsupported provider adapter: ${normalized.providerAdapter}`);
24
+ const canonicalAgentManifest = (this.deps.canonicalAgentManifestDiagnostic ?? buildCanonicalAgentManifestDiagnostic)();
13
25
  const paths = inspectOpenCodeConfigPaths(normalized.workspaceRoot, normalized.env);
14
26
  const before = snapshotPaths(paths.map((path) => path.path), normalized.workspaceRoot);
15
27
  const readableJson = paths.filter((path) => path.parsed);
@@ -21,6 +33,7 @@ export class ProviderDoctorService {
21
33
  detail: `Workspace root ${existsSync(normalized.workspaceRoot) ? 'exists' : 'does not exist'}: ${normalized.workspaceRoot}`,
22
34
  },
23
35
  { id: 'provider-supported', status: 'pass', detail: 'OpenCode provider adapter selected.' },
36
+ canonicalAgentManifestCheck(canonicalAgentManifest, normalized.payloadMode),
24
37
  configReadableCheck(readableJson.length, paths.filter((path) => path.exists).length),
25
38
  mcpEntryCheck(config),
26
39
  defaultAgentCheck(config),
@@ -71,6 +84,70 @@ export class ProviderDoctorService {
71
84
  },
72
85
  };
73
86
  }
87
+ getClaudeDoctor(normalized) {
88
+ const resolvedScope = resolveClaudeCodeScope(normalized.scope);
89
+ if (!resolvedScope.ok)
90
+ return resolvedScope;
91
+ if (isUserGlobalScope(normalized.scope) || resolvedScope.value.canonical === 'local')
92
+ return this.getClaudeUserGlobalDoctor(normalized, resolvedScope.value.canonical, resolvedScope.value.warnings);
93
+ const mcp = inspectClaudeCodeMcpConfig(normalized.workspaceRoot);
94
+ const agents = inspectClaudeCodeAgents(normalized.workspaceRoot);
95
+ const projectMemory = inspectClaudeProjectMemory(normalized.workspaceRoot);
96
+ const advisoryPaths = claudeAdvisoryPaths(normalized.workspaceRoot);
97
+ const checkedPathList = [mcp.path, agents.directoryPath, ...agents.agents.map((agent) => agent.path), projectMemory.path, ...advisoryPaths];
98
+ const before = snapshotPaths(checkedPathList, normalized.workspaceRoot);
99
+ const missingAgents = agents.agents.filter((agent) => agent.status === 'missing');
100
+ const blockingAgents = agents.agents.filter((agent) => agent.status === 'conflicting' || agent.status === 'invalid');
101
+ const badFrontmatter = agents.agents.filter((agent) => agent.exists && agent.frontmatter === 'invalid');
102
+ const badMarkers = agents.agents.filter((agent) => agent.exists && !agent.generatedMarker);
103
+ const memoryFiles = advisoryPaths.filter((path) => existsSync(path));
104
+ const checks = [
105
+ { id: 'workspace-root', status: existsSync(normalized.workspaceRoot) ? 'pass' : 'fail', detail: `Workspace root ${existsSync(normalized.workspaceRoot) ? 'exists' : 'does not exist'}: ${normalized.workspaceRoot}` },
106
+ { id: 'provider-supported', status: 'pass', detail: 'Claude provider adapter selected for CLI MCP registration diagnostics, project .mcp.json compatibility, and Claude agents support.' },
107
+ { id: 'claude-scope', status: 'pass', detail: 'Claude scope project resolved to canonical project.' },
108
+ { id: 'claude-cli-presence', status: 'warn', detail: 'Read-only doctor does not execute `claude --version`; CLI availability is diagnosed during explicit apply/preflight only.' },
109
+ { id: 'claude-project-mcp-readable', status: mcp.status === 'invalid' ? 'fail' : mcp.status === 'missing' ? 'not-configured' : 'pass', detail: mcp.message, ...(mcp.status === 'invalid' ? { remediation: 'Fix malformed .mcp.json before installing VGXNESS Claude support.' } : {}) },
110
+ { id: 'claude-vgxness-mcp-entry', status: mcp.status === 'configured' ? 'pass' : mcp.status === 'conflicting' ? 'fail' : 'not-configured', detail: mcp.message, ...(mcp.status === 'conflicting' ? { remediation: 'Manually reconcile mcpServers.vgxness before applying VGXNESS Claude support.' } : {}) },
111
+ { id: 'claude-agents-directory', status: agents.directoryExists ? 'pass' : 'not-configured', detail: agents.directoryExists ? 'Claude project agents directory exists.' : 'Claude project agents directory is missing; confirmed apply may create it.' },
112
+ {
113
+ id: 'claude-vgxness-agents',
114
+ status: blockingAgents.length > 0 ? 'fail' : missingAgents.length > 0 ? 'not-configured' : 'pass',
115
+ detail: blockingAgents.length > 0
116
+ ? `Conflicting or invalid VGXNESS Claude agents: ${blockingAgents.map((agent) => agent.agentName).join(', ')}.`
117
+ : missingAgents.length > 0
118
+ ? `Missing VGXNESS Claude agents: ${missingAgents.map((agent) => agent.agentName).join(', ')}.`
119
+ : 'Expected VGXNESS Claude agent targets were inspected.',
120
+ },
121
+ { id: 'claude-agent-frontmatter', status: badFrontmatter.length === 0 ? 'pass' : 'fail', detail: badFrontmatter.length === 0 ? 'Expected Claude agent frontmatter is valid.' : `Invalid or missing frontmatter for: ${badFrontmatter.map((agent) => agent.agentName).join(', ')}.` },
122
+ { id: 'claude-agent-generated-metadata', status: badMarkers.length === 0 ? 'pass' : 'fail', detail: badMarkers.length === 0 ? 'Existing VGXNESS Claude agent files include generated metadata markers.' : `Missing VGXNESS generated marker for: ${badMarkers.map((agent) => agent.agentName).join(', ')}.` },
123
+ claudeProjectMemoryCheck(projectMemory),
124
+ { id: 'claude-project-memory-advisory', status: memoryFiles.length === 0 ? 'pass' : 'warn', detail: memoryFiles.length === 0 ? 'No Claude project memory/settings advisory files were found.' : `Advisory only; VGXNESS will not modify: ${memoryFiles.join(', ')}.` },
125
+ readonlySafetyCheck(before, snapshotPaths(checkedPathList, normalized.workspaceRoot)),
126
+ ];
127
+ const status = rollupProviderDoctor(checks.map((check) => check.status));
128
+ const compactChecksValue = compactChecks(checks, normalized.payloadMode);
129
+ const checkedPaths = normalized.payloadMode === 'verbose' ? checkedPathList : checkedPathList.filter((path) => existsSync(path) || path === mcp.path);
130
+ const failedChecks = checks.filter((check) => check.status === 'fail').map((check) => check.id);
131
+ const recommendations = checks.flatMap((check) => (check.remediation === undefined ? [] : [check.remediation]));
132
+ return { ok: true, value: { version: 1, kind: 'provider-doctor', project: normalized.project, providerAdapter: 'claude', scope: normalized.scope, workspaceRoot: normalized.workspaceRoot, 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 } };
133
+ }
134
+ getClaudeUserGlobalDoctor(normalized, canonicalScope = 'user', scopeWarnings = []) {
135
+ const checks = [
136
+ { id: 'workspace-root', status: existsSync(normalized.workspaceRoot) ? 'pass' : 'fail', detail: `Workspace root ${existsSync(normalized.workspaceRoot) ? 'exists' : 'does not exist'}: ${normalized.workspaceRoot}` },
137
+ { id: 'provider-supported', status: 'warn', detail: `Claude ${canonicalScope} scope is supported as read-only advisory status/doctor/change-plan. Future install requires explicit confirmation/preflight and Claude CLI; private config mutation is unsupported.` },
138
+ { id: 'claude-scope', status: scopeWarnings.length === 0 ? 'pass' : 'warn', detail: scopeWarnings.join(' ') || `Claude scope resolved to canonical ${canonicalScope}.` },
139
+ { id: 'claude-cli-presence', status: 'warn', detail: 'Read-only doctor does not execute `claude --version`; no provider process was launched.' },
140
+ { id: 'provider-config-readonly-safety', status: 'pass', detail: `No Claude ${canonicalScope} paths were inspected or mutated; no repair/install/write occurred.` },
141
+ ];
142
+ const status = rollupProviderDoctor(checks.map((check) => check.status));
143
+ const compactChecksValue = compactChecks(checks, normalized.payloadMode);
144
+ const failedChecks = checks.filter((check) => check.status === 'fail').map((check) => check.id);
145
+ const recommendations = [
146
+ `If you choose to configure Claude ${canonicalScope} scope manually, run Claude Code yourself and use a command like: claude mcp add --scope ${canonicalScope} vgxness -- vgxness mcp start.`,
147
+ 'VGXNESS will not read or write private Claude config files during status/doctor/change-plan, repair config, or execute Claude Code from read-only surfaces.',
148
+ ];
149
+ return { ok: true, value: { version: 1, kind: 'provider-doctor', project: normalized.project, providerAdapter: 'claude', scope: normalized.scope, workspaceRoot: normalized.workspaceRoot, 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: `Claude ${canonicalScope} doctor is advisory and read-only; no private Claude files were inspected, no config was changed, and Claude Code was not executed.`, recommendations, checks: compactChecksValue, checkedPaths: [], bytes: { originalBytes: Buffer.byteLength(JSON.stringify({ checks, checkedPaths: [] }), '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 } } };
150
+ }
74
151
  }
75
152
  function summarizeDoctor(status, failedCount, recommendationCount) {
76
153
  if (status === 'healthy')
@@ -115,6 +192,20 @@ function configReadableCheck(readableCount, existingCount) {
115
192
  detail: `Found ${existingCount} OpenCode config path(s), ${readableCount} readable JSON config(s). Project config can override user/global config.`,
116
193
  };
117
194
  }
195
+ function canonicalAgentManifestCheck(diagnostic, mode) {
196
+ if (diagnostic.status === 'pass') {
197
+ const detail = mode === 'verbose'
198
+ ? `Canonical agent manifest validates: defaultAgent=${diagnostic.defaultAgentName}, promptContractVersion=${diagnostic.promptContractVersion}, agents=${diagnostic.agentCount}, managers=${diagnostic.managerCount}, subagents=${diagnostic.subagentCount}, providerSupport=${diagnostic.providerSupport.map((support) => `${support.provider}:${support.level}${support.reason === undefined ? '' : ` (${support.reason})`}`).join(', ')}.`
199
+ : `Canonical agent manifest validates: defaultAgent=${diagnostic.defaultAgentName}, promptContractVersion=${diagnostic.promptContractVersion}, agents=${diagnostic.agentCount}, managers=${diagnostic.managerCount}, subagents=${diagnostic.subagentCount}.`;
200
+ return { id: 'canonical-agent-manifest', status: 'pass', detail };
201
+ }
202
+ return {
203
+ id: 'canonical-agent-manifest',
204
+ status: 'fail',
205
+ detail: `Canonical agent manifest validation failed: ${diagnostic.validationErrors.join('; ') || 'unknown validation failure'}.`,
206
+ remediation: 'Fix the canonical agent manifest validation errors before relying on provider projection or provider setup.',
207
+ };
208
+ }
118
209
  function mcpEntryCheck(config) {
119
210
  const mcp = isRecord(config?.mcp) ? config.mcp : undefined;
120
211
  const entry = mcp?.vgxness;
@@ -169,6 +260,19 @@ function readonlySafetyCheck(before, after) {
169
260
  }
170
261
  : { id: 'provider-config-readonly-safety', status: 'fail', detail: 'Provider config path existence or mtimes changed during read-only doctor.' };
171
262
  }
263
+ function claudeProjectMemoryCheck(state) {
264
+ const status = state.status === 'managed-current' ? 'pass' : state.status === 'managed-stale' ? 'warn' : state.status === 'blocked' ? 'fail' : 'not-configured';
265
+ return {
266
+ id: 'claude-project-memory-managed-block',
267
+ status,
268
+ detail: state.message,
269
+ ...(state.status === 'blocked'
270
+ ? { remediation: 'Manually reconcile VGXNESS Claude project-memory markers before installing.' }
271
+ : state.status === 'managed-current'
272
+ ? {}
273
+ : { remediation: 'Run confirmed Claude project install when ready.' }),
274
+ };
275
+ }
172
276
  function snapshotPaths(paths, workspaceRoot) {
173
277
  const unique = [...paths, `${workspaceRoot}/.vgx`];
174
278
  return Object.fromEntries(unique.map((path) => [path, existsSync(path) ? statSync(path).mtimeMs : false]));
@@ -11,6 +11,10 @@ export const PROVIDER_HEALTH_SAFETY = {
11
11
  runsRepair: false,
12
12
  executesProvider: false,
13
13
  };
14
+ export const CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES = ['status', 'doctor', 'change-plan'];
15
+ export function isUserGlobalScope(scope) {
16
+ return scope === 'personal' || scope === 'user';
17
+ }
14
18
  export const REQUIRED_PROVIDER_MCP_TOOLS = ['vgxness_provider_status', 'vgxness_provider_doctor', 'vgxness_provider_change_plan'];
15
19
  export const REQUIRED_PROVIDER_NATIVE_MCP_TOOLS = [
16
20
  'vgxness_provider_status',
@@ -1,8 +1,14 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
+ import { inspectClaudeCodeAgents } from './claude-code-agent-config.js';
4
+ import { createClaudeCodeCliRegistrationPreview } from './claude-code-cli.js';
5
+ import { claudeAdvisoryPaths, claudeMcpConfigPathStatus, claudeMcpEntryStatus, inspectClaudeCodeMcpConfig } from './claude-code-config.js';
6
+ import { inspectClaudeProjectMemory } from './claude-code-project-memory.js';
7
+ import { resolveClaudeCodeScope } from './claude-code-scope.js';
3
8
  import { resolveOpenCodeMcpInstallTarget } from './client-install-opencode-contract.js';
4
9
  import { vgxnessOpenCodeDefaultAgent, vgxnessOpenCodePromptContractVersion, vgxnessOpenCodeSddSubagents } from './opencode-default-agent-config.js';
5
- import { normalizeProviderHealthInput, PROVIDER_HEALTH_SAFETY, providerHealthFailure, REQUIRED_PROVIDER_MCP_TOOLS, rollupProviderHealth, } from './provider-health-types.js';
10
+ import { buildCanonicalAgentManifestDiagnostic } from './provider-canonical-agent-manifest.js';
11
+ import { normalizeProviderHealthInput, PROVIDER_HEALTH_SAFETY, CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, providerHealthFailure, isUserGlobalScope, REQUIRED_PROVIDER_MCP_TOOLS, rollupProviderHealth, } from './provider-health-types.js';
6
12
  const projectConfigTargets = ['.opencode/opencode.json', 'opencode.json', '.opencode/opencode.jsonc', 'opencode.jsonc'];
7
13
  export class ProviderStatusService {
8
14
  deps;
@@ -11,8 +17,11 @@ export class ProviderStatusService {
11
17
  }
12
18
  getStatus(input = {}) {
13
19
  const normalized = normalizeProviderHealthInput(input);
20
+ if (normalized.providerAdapter === 'claude')
21
+ return this.getClaudeStatus(normalized);
14
22
  if (normalized.providerAdapter !== 'opencode')
15
23
  return providerHealthFailure(`Unsupported provider adapter: ${normalized.providerAdapter}`);
24
+ const canonicalAgentManifest = (this.deps.canonicalAgentManifestDiagnostic ?? buildCanonicalAgentManifestDiagnostic)();
16
25
  const paths = inspectOpenCodeConfigPaths(normalized.workspaceRoot, normalized.env);
17
26
  const mcpEntry = inspectOpenCodeMcpEntry(paths);
18
27
  const providerConfig = readFirstParsedConfig(paths);
@@ -20,7 +29,7 @@ export class ProviderStatusService {
20
29
  const subagentsConfigured = hasConfiguredSubagents(providerConfig);
21
30
  const tools = requiredToolPresence();
22
31
  const configStatus = resolveConfigStatus(paths, mcpEntry, managerConfigured, subagentsConfigured);
23
- const status = rollupProviderHealth([configStatus, ...tools.map((tool) => (tool.present ? 'pass' : 'fail'))]);
32
+ const status = rollupProviderHealth([canonicalAgentManifest.status, configStatus, ...tools.map((tool) => (tool.present ? 'pass' : 'fail'))]);
24
33
  const sdd = normalized.change.length > 0 ? this.readSdd(normalized.project, normalized.change) : undefined;
25
34
  const generatedAt = 'read-only-snapshot';
26
35
  const checkedPaths = normalized.payloadMode === 'verbose'
@@ -28,17 +37,19 @@ export class ProviderStatusService {
28
37
  : paths.filter((path) => path.exists || path.status !== 'not-configured').map((path) => path.path);
29
38
  const verboseShape = {
30
39
  config: { status: configStatus, paths, mcpEntry },
40
+ canonicalAgentManifest,
31
41
  sdd,
32
42
  mcpRequiredTools: tools,
33
43
  };
34
44
  const compactShape = {
35
45
  config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') },
46
+ canonicalAgentManifest,
36
47
  sdd: sdd === undefined ? undefined : compactSdd(sdd, 'compact'),
37
48
  mcpRequiredTools: tools,
38
49
  };
39
50
  const originalBytes = Buffer.byteLength(JSON.stringify(verboseShape), 'utf8');
40
51
  const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
41
- const issueCount = [configStatus, ...tools.map((tool) => (tool.present ? 'pass' : 'fail'))].filter((item) => item === 'fail' || item === 'not-configured').length;
52
+ const issueCount = [canonicalAgentManifest.status, configStatus, ...tools.map((tool) => (tool.present ? 'pass' : 'fail'))].filter((item) => item === 'fail' || item === 'not-configured').length;
42
53
  const warningCount = [configStatus, mcpEntry.status, ...paths.map((path) => path.status)].filter((item) => item === 'warn').length;
43
54
  return {
44
55
  ok: true,
@@ -58,6 +69,7 @@ export class ProviderStatusService {
58
69
  summary: summarizeStatus(status, mcpEntry),
59
70
  nextAction: nextActionFor(status, mcpEntry, sdd?.next),
60
71
  checkedPaths,
72
+ canonicalAgentManifest,
61
73
  config: { status: configStatus, paths: compactPaths(paths, normalized.payloadMode), mcpEntry: compactMcpEntry(mcpEntry, normalized.payloadMode) },
62
74
  ...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
63
75
  mcpRequiredTools: tools,
@@ -77,6 +89,141 @@ export class ProviderStatusService {
77
89
  const next = this.deps.sdd.getNext({ project, change });
78
90
  return { change, ...(status.ok ? { status: status.value } : {}), ...(next.ok ? { next: next.value } : {}) };
79
91
  }
92
+ getClaudeStatus(normalized) {
93
+ const resolvedScope = resolveClaudeCodeScope(normalized.scope);
94
+ if (!resolvedScope.ok)
95
+ return resolvedScope;
96
+ if (isUserGlobalScope(normalized.scope) || resolvedScope.value.canonical === 'local')
97
+ return this.getClaudeUserGlobalStatus(normalized, resolvedScope.value.canonical, resolvedScope.value.warnings);
98
+ const canonicalAgentManifest = (this.deps.canonicalAgentManifestDiagnostic ?? buildCanonicalAgentManifestDiagnostic)();
99
+ const mcpState = inspectClaudeCodeMcpConfig(normalized.workspaceRoot);
100
+ const agents = inspectClaudeCodeAgents(normalized.workspaceRoot);
101
+ const projectMemory = inspectClaudeProjectMemory(normalized.workspaceRoot);
102
+ const paths = [claudeMcpConfigPathStatus(mcpState), claudeProjectMemoryPathStatus(projectMemory)];
103
+ const mcpEntry = claudeMcpEntryStatus(mcpState);
104
+ const agentStatuses = agents.agents.map((agent) => (agent.status === 'managed' ? 'pass' : agent.status === 'missing' ? 'not-configured' : 'fail'));
105
+ const advisory = claudeAdvisoryPaths(normalized.workspaceRoot).filter((path) => existsSync(path));
106
+ const configStatus = claudeConfigHealthStatus([...paths.map((path) => path.status), ...agentStatuses]);
107
+ const status = rollupProviderHealth([canonicalAgentManifest.status, configStatus]);
108
+ const sdd = normalized.change.length > 0 ? this.readSdd(normalized.project, normalized.change) : undefined;
109
+ 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];
110
+ 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.' }];
111
+ const verboseShape = { config: { status: configStatus, paths, mcpEntry }, canonicalAgentManifest, agents, projectMemory, advisory, sdd, mcpRequiredTools: tools };
112
+ const compactShape = { config: { status: configStatus, paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') }, canonicalAgentManifest, agentSummary: summarizeClaudeAgents(agents), projectMemory: { status: projectMemory.status, action: projectMemory.action }, advisory, sdd: sdd === undefined ? undefined : compactSdd(sdd, 'compact'), mcpRequiredTools: tools };
113
+ const originalBytes = Buffer.byteLength(JSON.stringify(verboseShape), 'utf8');
114
+ const compactBytes = Buffer.byteLength(JSON.stringify(compactShape), 'utf8');
115
+ const issueCount = [canonicalAgentManifest.status, configStatus, ...agentStatuses].filter((item) => item === 'fail' || item === 'not-configured').length;
116
+ const warningCount = advisory.length + resolvedScope.value.warnings.length + paths.filter((path) => path.status === 'warn').length;
117
+ return {
118
+ ok: true,
119
+ value: {
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
+ payloadMode: normalized.payloadMode,
128
+ overallStatus: status,
129
+ inspectedPaths: checkedPaths,
130
+ issueCount,
131
+ warningCount,
132
+ summary: summarizeClaudeStatus(status, mcpEntry),
133
+ nextAction: nextActionFor(status, mcpEntry, sdd?.next),
134
+ checkedPaths,
135
+ canonicalAgentManifest,
136
+ config: { status: configStatus, paths: compactPaths(paths, normalized.payloadMode), mcpEntry: compactMcpEntry(mcpEntry, normalized.payloadMode) },
137
+ ...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
138
+ mcpRequiredTools: tools,
139
+ originalBytes,
140
+ compactBytes,
141
+ verboseAvailable: normalized.payloadMode === 'compact',
142
+ fullContentRef: `provider-status:claude:${normalized.workspaceRoot}`,
143
+ generatedAt: 'read-only-snapshot',
144
+ safety: PROVIDER_HEALTH_SAFETY,
145
+ },
146
+ };
147
+ }
148
+ getClaudeUserGlobalStatus(normalized, canonicalScope = 'user', scopeWarnings = []) {
149
+ const canonicalAgentManifest = (this.deps.canonicalAgentManifestDiagnostic ?? buildCanonicalAgentManifestDiagnostic)();
150
+ const paths = [
151
+ {
152
+ label: `Claude ${canonicalScope} scope`,
153
+ path: `${canonicalScope} Claude configuration (not inspected)`,
154
+ exists: false,
155
+ readable: false,
156
+ parsed: false,
157
+ status: 'warn',
158
+ detail: `Claude ${canonicalScope} scope is advisory only in read-only status: VGXNESS does not read private Claude files, write config, execute Claude Code, or install provider config.`,
159
+ },
160
+ ];
161
+ const mcpEntry = {
162
+ configured: false,
163
+ status: 'warn',
164
+ serverName: 'vgxness',
165
+ detail: `${canonicalScope} Claude MCP status is not inspected by VGXNESS; use this as planning guidance only. Future apply uses Claude CLI argv only: ${createClaudeCodeCliRegistrationPreview(canonicalScope).join(' ')}`,
166
+ };
167
+ const tools = requiredToolPresence();
168
+ const status = rollupProviderHealth([canonicalAgentManifest.status, 'warn']);
169
+ const sdd = normalized.change.length > 0 ? this.readSdd(normalized.project, normalized.change) : undefined;
170
+ const verboseShape = { config: { status: 'warn', paths, mcpEntry }, canonicalAgentManifest, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, scopeWarnings, sdd, mcpRequiredTools: tools };
171
+ const compactShape = { config: { status: 'warn', paths: compactPaths(paths, 'compact'), mcpEntry: compactMcpEntry(mcpEntry, 'compact') }, canonicalAgentManifest, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES, sdd: sdd === undefined ? undefined : compactSdd(sdd, 'compact'), mcpRequiredTools: tools };
172
+ return {
173
+ ok: true,
174
+ value: {
175
+ version: 1,
176
+ kind: 'provider-status',
177
+ project: normalized.project,
178
+ providerAdapter: 'claude',
179
+ scope: normalized.scope,
180
+ workspaceRoot: normalized.workspaceRoot,
181
+ status,
182
+ payloadMode: normalized.payloadMode,
183
+ overallStatus: status,
184
+ inspectedPaths: [],
185
+ issueCount: canonicalAgentManifest.status === 'fail' ? 1 : 0,
186
+ warningCount: 1 + scopeWarnings.length,
187
+ summary: `Claude ${canonicalScope} scope status is read-only/advisory; no private Claude files are inspected or written, and Claude Code is not executed.`,
188
+ nextAction: { kind: 'review', message: 'Review the planning-only guidance; future apply requires explicit confirmation/preflight and uses Claude CLI argv, not private config mutation.' },
189
+ checkedPaths: [],
190
+ canonicalAgentManifest,
191
+ config: { status: 'warn', paths: compactPaths(paths, normalized.payloadMode), mcpEntry: compactMcpEntry(mcpEntry, normalized.payloadMode) },
192
+ ...(sdd === undefined ? {} : { sdd: compactSdd(sdd, normalized.payloadMode) }),
193
+ mcpRequiredTools: tools,
194
+ originalBytes: Buffer.byteLength(JSON.stringify(verboseShape), 'utf8'),
195
+ compactBytes: Buffer.byteLength(JSON.stringify(compactShape), 'utf8'),
196
+ verboseAvailable: normalized.payloadMode === 'compact',
197
+ fullContentRef: `provider-status:claude:user-global:${normalized.workspaceRoot}`,
198
+ generatedAt: 'read-only-snapshot',
199
+ safety: { ...PROVIDER_HEALTH_SAFETY, scopeCapabilities: CLAUDE_USER_GLOBAL_SCOPE_CAPABILITIES },
200
+ },
201
+ };
202
+ }
203
+ }
204
+ function summarizeClaudeAgents(agents) {
205
+ return { expected: agents.agents.length, managed: agents.agents.filter((agent) => agent.status === 'managed').length, missing: agents.agents.filter((agent) => agent.status === 'missing').length, conflicting: agents.agents.filter((agent) => agent.status === 'conflicting' || agent.status === 'invalid').length };
206
+ }
207
+ function claudeConfigHealthStatus(statuses) {
208
+ if (statuses.some((status) => status === 'fail'))
209
+ return 'fail';
210
+ if (statuses.some((status) => status === 'not-configured'))
211
+ return 'not-configured';
212
+ if (statuses.some((status) => status === 'warn'))
213
+ return 'warn';
214
+ return 'pass';
215
+ }
216
+ function claudeProjectMemoryPathStatus(state) {
217
+ const status = state.status === 'managed-current' ? 'pass' : state.status === 'managed-stale' ? 'warn' : state.status === 'blocked' ? 'fail' : 'not-configured';
218
+ return {
219
+ label: 'project CLAUDE.md managed block',
220
+ path: state.path,
221
+ exists: state.exists,
222
+ readable: state.status !== 'blocked' || state.reason !== 'unreadable',
223
+ parsed: state.status === 'managed-current' || state.status === 'managed-stale',
224
+ status,
225
+ detail: state.message,
226
+ };
80
227
  }
81
228
  function compactPaths(paths, mode) {
82
229
  if (mode === 'verbose')
@@ -212,6 +359,15 @@ function summarizeStatus(status, mcpEntry) {
212
359
  return 'OpenCode provider config has warnings; no changes were made.';
213
360
  return 'OpenCode provider status check found a blocking issue; no changes were made.';
214
361
  }
362
+ function summarizeClaudeStatus(status, mcpEntry) {
363
+ if (status === 'ready')
364
+ return 'Claude project .mcp.json and VGXNESS agent files are readable and configured.';
365
+ if (status === 'not-configured')
366
+ return mcpEntry.detail;
367
+ if (status === 'warning')
368
+ return 'Claude provider project config has warnings; no changes were made.';
369
+ return 'Claude provider status check found a blocking issue; no changes were made.';
370
+ }
215
371
  function nextActionFor(status, mcpEntry, sddNext) {
216
372
  if (sddNext !== undefined)
217
373
  return { kind: 'advance-sdd', message: 'Continue the next SDD phase inside OpenCode using MCP status/ready/artifact tools and hidden SDD subagents.' };
@@ -90,6 +90,7 @@ export function toInternalVgxMcpToolName(toolName) {
90
90
  return EXPOSED_TO_INTERNAL_TOOL_NAMES[toolName];
91
91
  }
92
92
  const scopes = ['project', 'personal'];
93
+ const providerScopes = ['project', 'personal', 'local', 'user'];
93
94
  const agentModes = ['agent', 'subagent'];
94
95
  const memoryTypes = ['architecture', 'decision', 'bugfix', 'pattern', 'config', 'discovery', 'learning', 'preference', 'manual'];
95
96
  const activityKinds = ['prompt', 'tool_call', 'artifact', 'summary', 'error'];
@@ -429,8 +430,8 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
429
430
  vgxness_provider_status: z
430
431
  .object({
431
432
  project: z.string().min(1).optional(),
432
- scope: z.enum(scopes).optional(),
433
- providerAdapter: z.literal('opencode').optional(),
433
+ scope: z.enum(providerScopes).optional(),
434
+ providerAdapter: z.enum(['opencode', 'claude']).optional(),
434
435
  workspaceRoot: z.string().min(1).optional(),
435
436
  change: z.string().min(1).optional(),
436
437
  payloadMode: z.enum(payloadModes).optional(),
@@ -439,8 +440,8 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
439
440
  vgxness_provider_doctor: z
440
441
  .object({
441
442
  project: z.string().min(1).optional(),
442
- scope: z.enum(scopes).optional(),
443
- providerAdapter: z.literal('opencode').optional(),
443
+ scope: z.enum(providerScopes).optional(),
444
+ providerAdapter: z.enum(['opencode', 'claude']).optional(),
444
445
  workspaceRoot: z.string().min(1).optional(),
445
446
  expectedPromptContractVersion: z.number().int().positive().optional(),
446
447
  payloadMode: z.enum(payloadModes).optional(),
@@ -449,7 +450,7 @@ export const VGX_MCP_TOOL_INPUT_SCHEMAS = {
449
450
  vgxness_provider_change_plan: z
450
451
  .object({
451
452
  project: z.string().min(1).optional(),
452
- scope: z.enum(scopes).optional(),
453
+ scope: z.enum(providerScopes).optional(),
453
454
  provider: z.enum(providerChangePlanProviders),
454
455
  changeType: z.enum(providerChangePlanTypes),
455
456
  workspaceRoot: z.string().min(1),
@@ -187,7 +187,7 @@ function validateProviderHealthInput(input, tool) {
187
187
  return scope;
188
188
  if (scope.value !== undefined)
189
189
  result.scope = scope.value;
190
- const providerAdapter = readOptionalOneOf(record.value, 'providerAdapter', ['opencode'], tool);
190
+ const providerAdapter = readOptionalOneOf(record.value, 'providerAdapter', ['opencode', 'claude'], tool);
191
191
  if (!providerAdapter.ok)
192
192
  return providerAdapter;
193
193
  if (providerAdapter.value !== undefined)
@@ -161,6 +161,10 @@ export class MemoryService {
161
161
  const result = this.artifacts.listByTopicPrefix(project, topicPrefix);
162
162
  return this.record(result, context, { operation: 'artifact.list', targetType: 'artifact', topicKey: topicPrefix });
163
163
  }
164
+ /** Read artifacts for read-only/status projections without writing provenance traces. */
165
+ listArtifactsByTopicPrefixNoTrace(project, topicPrefix) {
166
+ return this.artifacts.listByTopicPrefix(project, topicPrefix);
167
+ }
164
168
  close() {
165
169
  this.database.close();
166
170
  }