vgxness 1.6.0 → 1.8.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/cli/cli-help.js +2 -2
- package/dist/cli/setup-status-renderer.js +6 -1
- package/dist/cli/tui/main-menu/main-menu-read-model.js +1 -1
- package/dist/cli/tui/setup/setup-tui-read-model.js +7 -7
- package/dist/mcp/claude-code-user-config.js +55 -0
- package/dist/mcp/claude-code-user-memory.js +90 -0
- package/dist/mcp/client-install-claude-code-contract.js +57 -6
- package/dist/mcp/client-install-claude-code.js +78 -8
- package/dist/mcp/control-plane.js +18 -1
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/provider-change-plan.js +24 -53
- package/dist/mcp/provider-doctor.js +23 -7
- package/dist/mcp/provider-status.js +22 -29
- package/dist/mcp/schema.js +1 -1
- package/dist/mcp/validation.js +1 -1
- package/dist/sdd/schema.js +15 -0
- package/dist/sdd/sdd-workflow-service.js +59 -29
- package/dist/setup/providers/claude-setup-adapter.js +51 -6
- package/dist/setup/providers/opencode-setup-adapter.js +2 -2
- package/dist/setup/providers/provider-setup-adapter.js +3 -0
- package/dist/setup/setup-lifecycle-service.js +2 -1
- package/dist/setup/setup-plan.js +2 -2
- package/docs/architecture.md +2 -2
- package/docs/cli.md +4 -4
- package/docs/providers.md +5 -5
- package/package.json +1 -1
package/dist/cli/cli-help.js
CHANGED
|
@@ -69,7 +69,7 @@ Areas:
|
|
|
69
69
|
Use --overwrite-vgxness (alias --reinstall) to reinstall only VGXNESS-managed OpenCode entries while preserving unrelated config; --yes is still required to write.
|
|
70
70
|
It writes only after --yes. The default target is $HOME/.config/opencode/opencode.json; use --scope project to target .opencode/opencode.json explicitly.
|
|
71
71
|
Project OpenCode config can override user config. Plans are read-only; applies refuse unsafe existing config and create backups before merge.
|
|
72
|
-
Claude support is
|
|
72
|
+
Claude support is first-class for guarded MCP setup. Claude scopes are local|project|user; compatibility aliases personal/global map to user with warnings. Plans show Claude CLI argv, project .mcp.json compatibility, user ~/.claude.json MCP merge, agents, and guarded CLAUDE.md managed memory separately. Project scope confirmed applies write .mcp.json, .claude/agents/*.md, and the project-root CLAUDE.md managed block as needed. User/global confirmed applies narrowly merge only mcpServers.vgxness in ~/.claude.json, write ~/.claude/agents/*.md, and manage only the VGXNESS block in ~/.claude/CLAUDE.md. Confirmed Claude writes/CLI execution require VGXNESS run preflight metadata (--run-id, with optional --phase/--agent-id). Status/doctor/change-plan are read-only and do not execute Claude Code.
|
|
73
73
|
|
|
74
74
|
skills register --project <name> --name <name> --description <text>
|
|
75
75
|
skills list [--project <name>] [--scope project|personal]
|
|
@@ -98,7 +98,7 @@ Areas:
|
|
|
98
98
|
No args in an interactive TTY opens the OpenTUI main menu.
|
|
99
99
|
No args without a TTY prints static safe setup guidance and exits 0 without opening project state.
|
|
100
100
|
Setup TUI may launch without --project; Installation remains available and project-scoped checks are deferred while project screens render project-required recovery states.
|
|
101
|
-
Provider setup support: OpenCode supported
|
|
101
|
+
Provider setup support: OpenCode first-class supported default guided install; Claude first-class supported guarded install for CLI MCP registration, project compatibility, and project/user agent planning; Antigravity placeholder; Custom/future extension point.
|
|
102
102
|
Provider config writes/install/apply are external-only and require explicit confirmation.
|
|
103
103
|
|
|
104
104
|
sdd status --project <name> --change <id> [--json]
|
|
@@ -41,5 +41,10 @@ function renderDefaults(status) {
|
|
|
41
41
|
}
|
|
42
42
|
function renderProvider(provider) {
|
|
43
43
|
const safety = provider.actions.some((action) => action.safety.writesProviderConfig) ? 'external write action flagged' : 'no provider writes';
|
|
44
|
-
return `${provider.displayName} ${provider.status}/${provider.supportLevel} (${safety})`;
|
|
44
|
+
return `${provider.displayName} ${provider.status}/${renderSupportLevel(provider.supportLevel)} (${safety})`;
|
|
45
|
+
}
|
|
46
|
+
function renderSupportLevel(supportLevel) {
|
|
47
|
+
if (supportLevel === 'supported-primary' || supportLevel === 'supported-secondary')
|
|
48
|
+
return 'supported';
|
|
49
|
+
return supportLevel;
|
|
45
50
|
}
|
|
@@ -14,7 +14,7 @@ const statCards = [
|
|
|
14
14
|
{ label: 'SDD', value: 'guided', badge: '', description: 'MCP flow' },
|
|
15
15
|
];
|
|
16
16
|
const statusSnapshotLines = [
|
|
17
|
-
'OpenCode
|
|
17
|
+
'OpenCode supported; dashboard does not call providers.',
|
|
18
18
|
'Advanced checks stay explicit: setup status, mcp doctor opencode.',
|
|
19
19
|
];
|
|
20
20
|
const optionCopy = {
|
|
@@ -31,7 +31,7 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
|
|
|
31
31
|
workspaceRootLabel: compactPath(workspaceRoot, 72),
|
|
32
32
|
readinessLabel: label(readiness),
|
|
33
33
|
readinessBadge: readinessBadge(readiness),
|
|
34
|
-
providerLabel: isOpenCode ? 'OpenCode' : isClaude ? 'Claude (supported
|
|
34
|
+
providerLabel: isOpenCode ? 'OpenCode' : isClaude ? 'Claude (first-class supported)' : 'Manual / none',
|
|
35
35
|
databaseLabel: plan === undefined ? 'pending' : plan.db.mode,
|
|
36
36
|
databasePathLabel: compactPath(databasePath, 72),
|
|
37
37
|
databaseSourceLabel: String(databaseSource),
|
|
@@ -47,8 +47,8 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
|
|
|
47
47
|
opencodeActionLabel: isOpenCode ? opencodeActionLabel(opencode?.action) : isClaude ? 'No guided setup write; use guarded Claude install with run preflight.' : 'No automatic provider write; use manual setup guidance.',
|
|
48
48
|
memoryPathExplanation: memoryExplanation,
|
|
49
49
|
providerInstallabilityLabel: isOpenCode
|
|
50
|
-
? 'OpenCode is the default guided install provider. Claude is supported
|
|
51
|
-
: 'Manual / none is read-only guidance. Claude remains supported
|
|
50
|
+
? 'OpenCode is the default guided install provider. Claude is first-class supported via guarded mcp install claude --scope project --yes --run-id <id>.'
|
|
51
|
+
: 'Manual / none is read-only guidance. Claude remains supported through explicit guarded apply outside this guided OpenCode flow.',
|
|
52
52
|
agentReadinessLabel: agentReadiness.label,
|
|
53
53
|
agentReadinessDetail: agentReadiness.detail,
|
|
54
54
|
plannedActions: plan?.actions.map((action) => ({
|
|
@@ -79,7 +79,7 @@ export function setupTuiViewModelFromPlan(plan, status, state) {
|
|
|
79
79
|
],
|
|
80
80
|
providerChoices: [
|
|
81
81
|
choice('provider:opencode', 'OpenCode', 'Default guided install provider; writes still require final confirmation.', (selections?.provider ?? plan?.provider ?? 'opencode') === 'opencode', state?.focusedChoiceId, [badgeLabels.recommended]),
|
|
82
|
-
choice('provider:claude-supported
|
|
82
|
+
choice('provider:claude-supported', 'Claude (first-class supported)', 'Claude CLI MCP registration and project compatibility apply only via guarded mcp install claude --scope project --yes --run-id <id>; explicit install path.', (selections?.provider ?? plan?.provider) === 'claude', state?.focusedChoiceId, ['[supported]', badgeLabels.readOnly]),
|
|
83
83
|
choice('provider:none', 'Manual / none', 'No automatic provider config write; follow manual setup guidance.', (selections?.provider ?? plan?.provider) === 'none', state?.focusedChoiceId, [badgeLabels.manual, badgeLabels.readOnly]),
|
|
84
84
|
],
|
|
85
85
|
scopeChoices: isOpenCode
|
|
@@ -106,13 +106,13 @@ function previewDetailLines(input) {
|
|
|
106
106
|
const opencode = plan?.opencode;
|
|
107
107
|
const actions = plan?.actions.map((action) => `Planned action: ${action.description}${action.targetPath === undefined ? '' : ` Target: ${compactPath(action.targetPath, 72)}`}${action.backupRequired ? ' Backup required.' : ''}`) ?? ['Planned action: create read-only preview plan.'];
|
|
108
108
|
return [
|
|
109
|
-
`Provider: ${input.provider === 'opencode' ? 'OpenCode [recommended default]' : input.provider === 'claude' ? 'Claude [supported
|
|
109
|
+
`Provider: ${input.provider === 'opencode' ? 'OpenCode [recommended default]' : input.provider === 'claude' ? 'Claude [first-class supported] [guarded explicit apply]' : 'Manual / none [manual] [read-only]'}`,
|
|
110
110
|
`Memory path: ${plan?.db.mode ?? 'pending'} at ${compactPath(input.databasePath, 72)} (source: ${String(input.databaseSource)})`,
|
|
111
111
|
`Memory guidance: ${memoryPathExplanation(plan, input.databasePath, input.databaseSource)}`,
|
|
112
112
|
`Scope: ${input.isOpenCode ? (opencode?.scope ?? 'user') : 'disabled for manual/none provider'}`,
|
|
113
113
|
`Install mode: ${input.isOpenCode ? (opencode?.installsAgents === false ? 'mcp-only' : 'mcp-plus-agents') : 'disabled for manual/none provider'}`,
|
|
114
114
|
`Reinstall VGXNESS entries: ${input.isOpenCode ? String(opencode?.overwriteVgxness ?? false) : 'disabled for manual/none provider'}`,
|
|
115
|
-
`Provider installability: ${input.isOpenCode ? 'OpenCode installable after final confirmation; Claude
|
|
115
|
+
`Provider installability: ${input.isOpenCode ? 'OpenCode installable after final confirmation; Claude first-class support requires guarded mcp install claude --scope project --yes --run-id <id>.' : 'No guided provider install from this selection; Claude uses guarded explicit apply.'}`,
|
|
116
116
|
`Agent readiness: ${agentReadinessFromPlan(plan)}`,
|
|
117
117
|
`Target config: ${input.isOpenCode && opencode?.targetPath !== undefined ? compactPath(opencode.targetPath, 72) : 'none; no provider config will be written'}`,
|
|
118
118
|
`Safety: ${input.isOpenCode ? '[will write after confirm] only on final confirmation' : '[read-only] manual/no-provider-write mode'}`,
|
|
@@ -132,7 +132,7 @@ function helpLines(screen) {
|
|
|
132
132
|
'Next/back: Tab continues; Shift+Tab goes back. Enter continues on review and confirms only on final confirmation.',
|
|
133
133
|
'Cancel/close: q or Esc cancels setup; when help is open, ?/h toggles it closed.',
|
|
134
134
|
reviewLine,
|
|
135
|
-
'Provider support: OpenCode is the default guided install provider; Claude is supported
|
|
135
|
+
'Provider support: OpenCode is the default guided install provider; Claude is first-class supported for guarded explicit apply via mcp install claude --scope project --yes --run-id <id>; Manual / none writes no provider config.',
|
|
136
136
|
'Agent readiness: the preview checks vgxness-manager/SDD readiness guidance; preview screens never seed agents.',
|
|
137
137
|
'No-write guarantee: no provider config is written before explicit final confirmation.',
|
|
138
138
|
];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isManagedClaudeCodeMcpServer } from './claude-code-config.js';
|
|
4
|
+
import { safeHomeDirectory } from './claude-code-agent-config.js';
|
|
5
|
+
export function resolveClaudeCodeUserMcpJsonPath(env = process.env) {
|
|
6
|
+
return join(safeHomeDirectory(env), '.claude.json');
|
|
7
|
+
}
|
|
8
|
+
export function inspectClaudeCodeUserMcpConfig(env = process.env) {
|
|
9
|
+
const path = resolveClaudeCodeUserMcpJsonPath(env);
|
|
10
|
+
if (!existsSync(path))
|
|
11
|
+
return { status: 'missing', path, exists: false, parsed: false, message: 'Claude user ~/.claude.json does not exist.' };
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
14
|
+
if (!isRecord(parsed))
|
|
15
|
+
return { status: 'invalid', path, exists: true, parsed: false, message: 'Claude user ~/.claude.json must be a JSON object.' };
|
|
16
|
+
if (parsed.mcpServers !== undefined && !isRecord(parsed.mcpServers))
|
|
17
|
+
return { status: 'invalid', path, exists: true, parsed: false, message: 'Claude user ~/.claude.json mcpServers must be a JSON object.' };
|
|
18
|
+
const entry = isRecord(parsed.mcpServers) ? parsed.mcpServers.vgxness : undefined;
|
|
19
|
+
if (entry === undefined)
|
|
20
|
+
return { status: 'stale', path, exists: true, parsed: true, config: parsed, message: 'Claude user ~/.claude.json is readable but mcpServers.vgxness is missing.' };
|
|
21
|
+
if (isManagedClaudeCodeMcpServer(entry))
|
|
22
|
+
return { status: 'configured', path, exists: true, parsed: true, config: parsed, message: 'Claude user ~/.claude.json has a managed mcpServers.vgxness entry.' };
|
|
23
|
+
return { status: 'conflicting', path, exists: true, parsed: true, config: parsed, message: 'Claude user ~/.claude.json has a conflicting mcpServers.vgxness entry.' };
|
|
24
|
+
}
|
|
25
|
+
catch (cause) {
|
|
26
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
27
|
+
return { status: 'invalid', path, exists: true, parsed: false, message: `Claude user ~/.claude.json could not be read or parsed: ${message}` };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function mergeClaudeCodeUserMcpConfig(existing, server) {
|
|
31
|
+
return { ...existing, mcpServers: { ...(isRecord(existing.mcpServers) ? existing.mcpServers : {}), vgxness: server } };
|
|
32
|
+
}
|
|
33
|
+
export function claudeUserMcpConfigPathStatus(state) {
|
|
34
|
+
return {
|
|
35
|
+
label: 'user ~/.claude.json',
|
|
36
|
+
path: state.path,
|
|
37
|
+
exists: state.exists,
|
|
38
|
+
readable: state.status !== 'invalid',
|
|
39
|
+
parsed: state.parsed,
|
|
40
|
+
status: state.status === 'configured' ? 'pass' : state.status === 'invalid' || state.status === 'conflicting' ? 'fail' : 'not-configured',
|
|
41
|
+
detail: state.message,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function claudeUserMcpEntryStatus(state) {
|
|
45
|
+
if (state.status === 'configured')
|
|
46
|
+
return { configured: true, status: 'pass', serverName: 'vgxness', enabled: true, detail: state.message };
|
|
47
|
+
if (state.status === 'conflicting')
|
|
48
|
+
return { configured: true, status: 'fail', serverName: 'vgxness', detail: state.message };
|
|
49
|
+
if (state.status === 'invalid')
|
|
50
|
+
return { configured: false, status: 'fail', serverName: 'vgxness', detail: state.message };
|
|
51
|
+
return { configured: false, status: 'not-configured', serverName: 'vgxness', detail: state.message };
|
|
52
|
+
}
|
|
53
|
+
function isRecord(value) {
|
|
54
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
55
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { projectCanonicalAgentManifestToClaudeProjectMemory } from '../agents/canonical-agent-projection.js';
|
|
4
|
+
import { safeHomeDirectory } from './claude-code-agent-config.js';
|
|
5
|
+
export const claudeUserMemoryBeginMarker = '<!-- BEGIN VGXNESS-MANAGED CLAUDE USER MEMORY -->';
|
|
6
|
+
export const claudeUserMemoryEndMarker = '<!-- END VGXNESS-MANAGED CLAUDE USER MEMORY -->';
|
|
7
|
+
export function resolveClaudeUserMemoryPath(env = process.env) {
|
|
8
|
+
return join(safeHomeDirectory(env), '.claude', 'CLAUDE.md');
|
|
9
|
+
}
|
|
10
|
+
export function renderClaudeUserMemoryBlock() {
|
|
11
|
+
const projection = projectCanonicalAgentManifestToClaudeProjectMemory();
|
|
12
|
+
return [
|
|
13
|
+
claudeUserMemoryBeginMarker,
|
|
14
|
+
`<!-- VGXNESS-GENERATED owner=vgxness provider=claude artifact=claude-user-memory safe-update=true defaultAgent=${projection.defaultAgent} promptContractVersion=${projection.promptContractVersion} canonicalSource=canonical-agent-manifest repositoryInstructions=${projection.repositoryInstructions} -->`,
|
|
15
|
+
'',
|
|
16
|
+
'# VGXNESS Claude User Memory',
|
|
17
|
+
'',
|
|
18
|
+
'Global/user Claude Code guidance installed by VGXNESS. Use the VGXNESS MCP server for SDD, runs, memory, provider status, and governance-aware workflow progress.',
|
|
19
|
+
'',
|
|
20
|
+
...projection.guidance.slice(1).map((line) => `- ${line}`),
|
|
21
|
+
'',
|
|
22
|
+
claudeUserMemoryEndMarker,
|
|
23
|
+
].join('\n');
|
|
24
|
+
}
|
|
25
|
+
export function inspectClaudeUserMemory(env = process.env) {
|
|
26
|
+
const path = resolveClaudeUserMemoryPath(env);
|
|
27
|
+
if (!existsSync(path))
|
|
28
|
+
return { status: 'missing', path, exists: false, action: 'create', backupRequired: false, message: 'Claude user memory ~/.claude/CLAUDE.md does not exist.' };
|
|
29
|
+
let contents;
|
|
30
|
+
try {
|
|
31
|
+
const bytes = readFileSync(path);
|
|
32
|
+
contents = bytes.toString('utf8');
|
|
33
|
+
if (!Buffer.from(contents, 'utf8').equals(bytes))
|
|
34
|
+
return blocked(path, true, 'non_utf8', 'Claude user memory is not valid UTF-8; refusing safe managed-block updates.');
|
|
35
|
+
}
|
|
36
|
+
catch (cause) {
|
|
37
|
+
return blocked(path, true, 'unreadable', `Claude user memory could not be read: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
38
|
+
}
|
|
39
|
+
const beginCount = countOccurrences(contents, claudeUserMemoryBeginMarker);
|
|
40
|
+
const endCount = countOccurrences(contents, claudeUserMemoryEndMarker);
|
|
41
|
+
if (beginCount === 0 && endCount === 0)
|
|
42
|
+
return { status: 'unmanaged', path, exists: true, action: 'append-managed-block', backupRequired: true, message: 'Claude user memory exists without a VGXNESS managed block; apply would append one after backup.', contents };
|
|
43
|
+
if (beginCount === 0)
|
|
44
|
+
return blocked(path, true, 'missing_begin_marker', 'Claude user memory has an end marker without a begin marker.');
|
|
45
|
+
if (endCount === 0)
|
|
46
|
+
return blocked(path, true, 'missing_end_marker', 'Claude user memory has a begin marker without an end marker.');
|
|
47
|
+
if (beginCount > 1 || endCount > 1)
|
|
48
|
+
return blocked(path, true, 'duplicate_markers', 'Claude user memory has duplicate VGXNESS managed-block markers.');
|
|
49
|
+
const start = contents.indexOf(claudeUserMemoryBeginMarker);
|
|
50
|
+
const endMarkerStart = contents.indexOf(claudeUserMemoryEndMarker);
|
|
51
|
+
if (endMarkerStart < start)
|
|
52
|
+
return blocked(path, true, 'reordered_markers', 'Claude user memory end marker appears before begin marker.');
|
|
53
|
+
const end = endMarkerStart + claudeUserMemoryEndMarker.length;
|
|
54
|
+
const inner = contents.slice(start + claudeUserMemoryBeginMarker.length, endMarkerStart);
|
|
55
|
+
if (inner.includes(claudeUserMemoryBeginMarker) || inner.includes(claudeUserMemoryEndMarker))
|
|
56
|
+
return blocked(path, true, 'nested_markers', 'Claude user memory has nested VGXNESS managed-block markers.');
|
|
57
|
+
if (!inner.includes('owner=vgxness') || !inner.includes('provider=claude') || !inner.includes('artifact=claude-user-memory') || !inner.includes('safe-update=true'))
|
|
58
|
+
return blocked(path, true, 'conflicting_ownership', 'Claude user memory managed block is not a VGXNESS-owned user-memory block.');
|
|
59
|
+
const expected = renderClaudeUserMemoryBlock();
|
|
60
|
+
const actual = contents.slice(start, end);
|
|
61
|
+
if (actual === expected)
|
|
62
|
+
return { status: 'managed-current', path, exists: true, action: 'none', backupRequired: false, message: 'Claude user memory has the current VGXNESS managed block.', blockRange: { start, end }, contents };
|
|
63
|
+
return { status: 'managed-stale', path, exists: true, action: 'update-managed-block', backupRequired: true, message: 'Claude user memory has a stale VGXNESS managed block; apply would update only that block after backup.', blockRange: { start, end }, staleReasons: ['managed_block_differs_from_canonical_render'], contents };
|
|
64
|
+
}
|
|
65
|
+
export function mergeClaudeUserMemory(state) {
|
|
66
|
+
const block = renderClaudeUserMemoryBlock();
|
|
67
|
+
if (state.status === 'missing')
|
|
68
|
+
return { ok: true, value: { path: state.path, contents: `${block}\n` } };
|
|
69
|
+
if (state.status === 'unmanaged') {
|
|
70
|
+
const separator = state.contents.length === 0 ? '' : state.contents.endsWith('\n') ? '\n' : '\n\n';
|
|
71
|
+
return { ok: true, value: { path: state.path, contents: `${state.contents}${separator}${block}\n` } };
|
|
72
|
+
}
|
|
73
|
+
if (state.status === 'managed-stale')
|
|
74
|
+
return { ok: true, value: { path: state.path, contents: state.contents.slice(0, state.blockRange.start) + block + state.contents.slice(state.blockRange.end) } };
|
|
75
|
+
if (state.status === 'managed-current')
|
|
76
|
+
return { ok: true, value: { path: state.path, contents: state.contents } };
|
|
77
|
+
return { ok: false, error: { code: 'validation_failed', message: state.message } };
|
|
78
|
+
}
|
|
79
|
+
function blocked(path, exists, reason, message) {
|
|
80
|
+
return { status: 'blocked', path, exists, action: 'blocked', backupRequired: false, reason, message };
|
|
81
|
+
}
|
|
82
|
+
function countOccurrences(contents, needle) {
|
|
83
|
+
let count = 0;
|
|
84
|
+
let index = contents.indexOf(needle);
|
|
85
|
+
while (index !== -1) {
|
|
86
|
+
count += 1;
|
|
87
|
+
index = contents.indexOf(needle, index + needle.length);
|
|
88
|
+
}
|
|
89
|
+
return count;
|
|
90
|
+
}
|
|
@@ -3,6 +3,8 @@ import { buildClaudeCodeMcpAddCommand } from './claude-code-cli.js';
|
|
|
3
3
|
import { createClaudeCodeMcpDoctorCommand, createClaudeCodeMcpServerConfig, inspectClaudeCodeMcpConfig, resolveClaudeCodeMcpJsonPath } from './claude-code-config.js';
|
|
4
4
|
import { inspectClaudeProjectMemory } from './claude-code-project-memory.js';
|
|
5
5
|
import { resolveClaudeCodeScope } from './claude-code-scope.js';
|
|
6
|
+
import { inspectClaudeCodeUserMcpConfig, resolveClaudeCodeUserMcpJsonPath } from './claude-code-user-config.js';
|
|
7
|
+
import { inspectClaudeUserMemory } from './claude-code-user-memory.js';
|
|
6
8
|
export function planClaudeCodeMcpInstall(input) {
|
|
7
9
|
const source = input.databasePathSource ?? 'flag';
|
|
8
10
|
const server = createClaudeCodeMcpServerConfig(input.databasePath, source);
|
|
@@ -14,8 +16,11 @@ export function planClaudeCodeMcpInstall(input) {
|
|
|
14
16
|
if (!cliCommand.ok)
|
|
15
17
|
return refused(input, server, 'unsupported_scope', cliCommand.error.message, [], [], overwriteVgxness);
|
|
16
18
|
if (resolvedScope.value.canonical !== 'project') {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
if (resolvedScope.value.canonical === 'local') {
|
|
20
|
+
const targets = [{ kind: 'cli-mcp-registration', scope: resolvedScope.value.canonical, command: cliCommand.value, action: 'register' }];
|
|
21
|
+
return { ...base(input, server, targets, [], false, overwriteVgxness, resolvedScope.value.canonical, cliCommand.value, resolvedScope.value.warnings), status: 'would_install' };
|
|
22
|
+
}
|
|
23
|
+
return planUserInstall(input, server, overwriteVgxness, cliCommand.value, resolvedScope.value.warnings);
|
|
19
24
|
}
|
|
20
25
|
let mcpPath;
|
|
21
26
|
try {
|
|
@@ -61,6 +66,40 @@ export function planClaudeCodeMcpInstall(input) {
|
|
|
61
66
|
export function expectedClaudeCodeRenderedAgents(workspaceRoot) {
|
|
62
67
|
return expectedClaudeCodeAgentFiles(workspaceRoot).map((agent) => ({ path: agent.path, agentName: agent.name, contents: renderClaudeCodeAgentMarkdown(agent) }));
|
|
63
68
|
}
|
|
69
|
+
export function expectedClaudeCodeRenderedUserAgents(workspaceRoot, env) {
|
|
70
|
+
return expectedClaudeCodeAgentFiles({ workspaceRoot, scope: 'user', ...(env === undefined ? {} : { env }) }).map((agent) => ({ path: agent.path, agentName: agent.name, contents: renderClaudeCodeAgentMarkdown(agent) }));
|
|
71
|
+
}
|
|
72
|
+
function planUserInstall(input, server, overwriteVgxness, cliCommand, scopeWarnings) {
|
|
73
|
+
const mcpState = inspectClaudeCodeUserMcpConfig(input.env);
|
|
74
|
+
const targets = [{ kind: 'cli-mcp-registration', scope: 'user', command: cliCommand, action: 'register' }];
|
|
75
|
+
const preservedTopLevelKeys = mcpState.parsed ? Object.keys(mcpState.config) : [];
|
|
76
|
+
if (mcpState.status === 'missing')
|
|
77
|
+
targets.push({ kind: 'user-mcp-json', path: mcpState.path, external: true, action: 'create' });
|
|
78
|
+
else if (mcpState.status === 'stale')
|
|
79
|
+
targets.push({ kind: 'user-mcp-json', path: mcpState.path, external: true, action: 'merge' });
|
|
80
|
+
else if (mcpState.status === 'configured')
|
|
81
|
+
targets.push({ kind: 'user-mcp-json', path: mcpState.path, external: true, action: 'update-vgxness' });
|
|
82
|
+
else
|
|
83
|
+
targets.push({ kind: 'user-mcp-json', path: mcpState.path, external: true, action: 'blocked', reason: mcpState.message });
|
|
84
|
+
const agentInspection = inspectClaudeCodeAgents({ workspaceRoot: input.cwd, scope: 'user', ...(input.env === undefined ? {} : { env: input.env }) });
|
|
85
|
+
for (const agent of agentInspection.agents) {
|
|
86
|
+
if (agent.status === 'missing')
|
|
87
|
+
targets.push({ kind: 'agent-file', path: agent.path, agentName: agent.agentName, scope: 'user', external: true, action: 'create' });
|
|
88
|
+
else if (agent.status === 'managed')
|
|
89
|
+
targets.push({ kind: 'agent-file', path: agent.path, agentName: agent.agentName, scope: 'user', external: true, action: 'update-vgxness' });
|
|
90
|
+
else
|
|
91
|
+
targets.push({ kind: 'agent-file', path: agent.path, agentName: agent.agentName, scope: 'user', external: true, action: 'blocked', reason: agent.detail });
|
|
92
|
+
}
|
|
93
|
+
const userMemory = inspectClaudeUserMemory(input.env);
|
|
94
|
+
targets.push({ kind: 'user-memory', path: userMemory.path, external: true, action: userMemory.action, status: userMemory.status, backupRequired: userMemory.backupRequired, ...(userMemory.status === 'blocked' ? { reason: userMemory.message } : {}) });
|
|
95
|
+
const blocked = targets.find((target) => target.action === 'blocked');
|
|
96
|
+
if (blocked !== undefined) {
|
|
97
|
+
const reason = blocked.kind === 'user-mcp-json' ? userMcpRefusalReason(mcpState.status) : blocked.kind === 'user-memory' ? userMemoryRefusalReason(userMemory) : 'existing_vgxness_agent';
|
|
98
|
+
return refused(input, server, reason, blocked.reason ?? 'Claude user install plan is blocked by an existing conflicting target.', targets, preservedTopLevelKeys, overwriteVgxness);
|
|
99
|
+
}
|
|
100
|
+
const backupRequired = targets.some((target) => (target.kind === 'user-mcp-json' && target.action !== 'create') || (target.kind === 'agent-file' && target.action === 'update-vgxness') || (target.kind === 'user-memory' && target.backupRequired));
|
|
101
|
+
return { ...base(input, server, targets, preservedTopLevelKeys, backupRequired, overwriteVgxness, 'user', cliCommand, scopeWarnings), status: 'would_install' };
|
|
102
|
+
}
|
|
64
103
|
function refused(input, server, reason, message, targets, preservedTopLevelKeys, overwriteVgxness) {
|
|
65
104
|
const resolved = resolveClaudeCodeScope(input.scope);
|
|
66
105
|
const canonical = resolved.ok ? resolved.value.canonical : 'project';
|
|
@@ -69,7 +108,7 @@ function refused(input, server, reason, message, targets, preservedTopLevelKeys,
|
|
|
69
108
|
}
|
|
70
109
|
function base(input, server, targets, preservedTopLevelKeys, backupRequired, overwriteVgxness, canonicalClaudeScope, cliCommand, scopeWarnings) {
|
|
71
110
|
const source = input.databasePathSource ?? 'flag';
|
|
72
|
-
const targetPath = canonicalClaudeScope === 'project' ? resolveClaudeCodeMcpJsonPath(input.cwd) : `claude-cli:${canonicalClaudeScope}:vgxness`;
|
|
111
|
+
const targetPath = canonicalClaudeScope === 'project' ? resolveClaudeCodeMcpJsonPath(input.cwd) : canonicalClaudeScope === 'user' ? resolveClaudeCodeUserMcpJsonPath(input.env) : `claude-cli:${canonicalClaudeScope}:vgxness`;
|
|
73
112
|
return {
|
|
74
113
|
version: 1,
|
|
75
114
|
kind: 'mcp-client-install-claude-code',
|
|
@@ -83,9 +122,9 @@ function base(input, server, targets, preservedTopLevelKeys, backupRequired, ove
|
|
|
83
122
|
safety: { operation: 'plan', mutating: false, writesProviderConfig: false, targetPath, backupRequired, mergePolicy: backupRequired ? 'merge-preserve-existing' : 'create' },
|
|
84
123
|
warnings: [
|
|
85
124
|
...scopeWarnings,
|
|
86
|
-
'Claude Code MCP registration is modeled as
|
|
87
|
-
'Project compatibility may write .mcp.json, .claude/agents/*.md, and a guarded project-root CLAUDE.md managed block after explicit confirmation/preflight
|
|
88
|
-
'
|
|
125
|
+
'Claude Code MCP registration is modeled as structured config/argv, never shell strings.',
|
|
126
|
+
'Project compatibility may write .mcp.json, .claude/agents/*.md, and a guarded project-root CLAUDE.md managed block after explicit confirmation/preflight.',
|
|
127
|
+
'Claude user/global support narrowly merges only mcpServers.vgxness in ~/.claude.json and writes VGXNESS-owned ~/.claude/agents/*.md plus a managed block in ~/.claude/CLAUDE.md after confirmation/preflight; unknown config keys and non-managed memory content are preserved.',
|
|
89
128
|
],
|
|
90
129
|
verificationHints: [
|
|
91
130
|
{ kind: 'restart-client', message: 'Restart or reload Claude Code after confirmed project config installation.' },
|
|
@@ -107,8 +146,20 @@ function mcpRefusalReason(status) {
|
|
|
107
146
|
return 'existing_vgxness_mcp';
|
|
108
147
|
return 'invalid_mcp_shape';
|
|
109
148
|
}
|
|
149
|
+
function userMcpRefusalReason(status) {
|
|
150
|
+
if (status === 'invalid')
|
|
151
|
+
return 'malformed_json';
|
|
152
|
+
if (status === 'conflicting')
|
|
153
|
+
return 'existing_vgxness_mcp';
|
|
154
|
+
return 'invalid_mcp_shape';
|
|
155
|
+
}
|
|
110
156
|
function projectMemoryRefusalReason(state) {
|
|
111
157
|
if (state.status === 'blocked' && state.reason === 'conflicting_ownership')
|
|
112
158
|
return 'conflicting_claude_project_memory';
|
|
113
159
|
return 'malformed_claude_project_memory';
|
|
114
160
|
}
|
|
161
|
+
function userMemoryRefusalReason(state) {
|
|
162
|
+
if (state.status === 'blocked' && state.reason === 'conflicting_ownership')
|
|
163
|
+
return 'conflicting_claude_project_memory';
|
|
164
|
+
return 'malformed_claude_project_memory';
|
|
165
|
+
}
|
|
@@ -5,11 +5,13 @@ import { parseClaudeAgentFrontmatter } from './claude-code-agent-config.js';
|
|
|
5
5
|
import { runClaudeCodeCliCommand } from './claude-code-cli.js';
|
|
6
6
|
import { createClaudeCodeMcpServerConfig, inspectClaudeCodeMcpConfig, mergeClaudeCodeMcpConfig } from './claude-code-config.js';
|
|
7
7
|
import { inspectClaudeProjectMemory, mergeClaudeProjectMemory } from './claude-code-project-memory.js';
|
|
8
|
-
import { expectedClaudeCodeRenderedAgents, planClaudeCodeMcpInstall } from './client-install-claude-code-contract.js';
|
|
8
|
+
import { expectedClaudeCodeRenderedAgents, expectedClaudeCodeRenderedUserAgents, planClaudeCodeMcpInstall } from './client-install-claude-code-contract.js';
|
|
9
|
+
import { inspectClaudeCodeUserMcpConfig, mergeClaudeCodeUserMcpConfig } from './claude-code-user-config.js';
|
|
10
|
+
import { inspectClaudeUserMemory, mergeClaudeUserMemory } from './claude-code-user-memory.js';
|
|
9
11
|
export async function installClaudeCodeMcpClient(input) {
|
|
10
12
|
const source = input.databasePathSource ?? 'flag';
|
|
11
13
|
const server = createClaudeCodeMcpServerConfig(input.databasePath, source);
|
|
12
|
-
const plan = planClaudeCodeMcpInstall({ cwd: input.cwd, databasePath: input.databasePath, databasePathSource: source, ...(input.overwriteVgxness !== undefined ? { overwriteVgxness: input.overwriteVgxness } : {}), ...(input.scope !== undefined ? { scope: input.scope } : {}) });
|
|
14
|
+
const plan = planClaudeCodeMcpInstall({ cwd: input.cwd, databasePath: input.databasePath, databasePathSource: source, ...(input.overwriteVgxness !== undefined ? { overwriteVgxness: input.overwriteVgxness } : {}), ...(input.scope !== undefined ? { scope: input.scope } : {}), ...(input.env !== undefined ? { env: input.env } : {}) });
|
|
13
15
|
if (plan.status === 'refused')
|
|
14
16
|
return refusal(plan.reason, plan.message, plan, server, [], []);
|
|
15
17
|
if (!input.confirmed)
|
|
@@ -23,7 +25,7 @@ export async function installClaudeCodeMcpClient(input) {
|
|
|
23
25
|
for (const targetPath of preflightPaths) {
|
|
24
26
|
const preflight = await input.preflight({
|
|
25
27
|
category: 'provider-tool',
|
|
26
|
-
operation: 'write claude project provider config',
|
|
28
|
+
operation: plan.canonicalClaudeScope === 'user' ? 'write claude user provider config' : 'write claude project provider config',
|
|
27
29
|
targetPath,
|
|
28
30
|
workspaceRoot: input.cwd,
|
|
29
31
|
providerToolName: 'claude-code',
|
|
@@ -36,7 +38,7 @@ export async function installClaudeCodeMcpClient(input) {
|
|
|
36
38
|
}
|
|
37
39
|
const backups = [];
|
|
38
40
|
const writtenPaths = [];
|
|
39
|
-
if (plan.canonicalClaudeScope
|
|
41
|
+
if (plan.canonicalClaudeScope === 'local') {
|
|
40
42
|
if (input.cliRunner === undefined)
|
|
41
43
|
return refusal('preflight_failed', 'Claude CLI MCP registration apply requires an injected runner boundary; read-only plans never execute Claude Code.', plan, server, [], []);
|
|
42
44
|
if (plan.cliCommand === undefined)
|
|
@@ -47,6 +49,8 @@ export async function installClaudeCodeMcpClient(input) {
|
|
|
47
49
|
return refusal('preflight_failed', `Claude CLI MCP registration failed with exit code ${cli.exitCode ?? 'unknown'}.`, plan, server, [], [], cliResult);
|
|
48
50
|
return { version: 1, kind: 'mcp-client-install-claude-code', status: 'installed', targetPath: plan.targetPath, writtenPaths: [], backups: [], safety: applySafety(plan), server, warnings: plan.warnings, verificationHints: plan.verificationHints, agentNames: plan.agentNames, overwriteVgxness: plan.overwriteVgxness, cliResult };
|
|
49
51
|
}
|
|
52
|
+
if (plan.canonicalClaudeScope === 'user')
|
|
53
|
+
return applyUserInstall(input, plan, server, writtenPaths, backups);
|
|
50
54
|
const existingTargets = plan.targets.filter((target) => target.kind !== 'cli-mcp-registration' && (target.action === 'merge' || target.action === 'update-vgxness' || target.action === 'append-managed-block'));
|
|
51
55
|
for (const target of existingTargets) {
|
|
52
56
|
const backup = createBackup(target.path, target.kind === 'project-memory' ? 'project-memory' : 'config');
|
|
@@ -88,6 +92,25 @@ function writeMcpJson(cwd, plan, server) {
|
|
|
88
92
|
const after = inspectClaudeCodeMcpConfig(cwd);
|
|
89
93
|
return after.status === 'configured' ? { ok: true, value: plan.targetPath } : { ok: false, error: { code: 'validation_failed', message: 'Claude .mcp.json did not validate after write.' } };
|
|
90
94
|
}
|
|
95
|
+
function writeUserMcpJson(env, plan, server) {
|
|
96
|
+
const target = plan.targets.find((item) => item.kind === 'user-mcp-json');
|
|
97
|
+
if (target === undefined)
|
|
98
|
+
return { ok: true, value: plan.targetPath };
|
|
99
|
+
if (target.action === 'blocked')
|
|
100
|
+
return { ok: false, error: { code: 'validation_failed', message: target.reason ?? 'Claude user config is blocked.' } };
|
|
101
|
+
const state = inspectClaudeCodeUserMcpConfig(env);
|
|
102
|
+
if (state.status === 'invalid' || state.status === 'conflicting')
|
|
103
|
+
return { ok: false, error: { code: 'validation_failed', message: state.message } };
|
|
104
|
+
if (target.action === 'create' && existsSync(target.path))
|
|
105
|
+
return { ok: false, error: { code: 'validation_failed', message: 'Claude user ~/.claude.json appeared after planning; rerun apply to merge safely.' } };
|
|
106
|
+
if (state.path !== target.path)
|
|
107
|
+
return { ok: false, error: { code: 'validation_failed', message: 'Claude user config path changed after planning; rerun apply.' } };
|
|
108
|
+
const merged = mergeClaudeCodeUserMcpConfig(state.parsed ? state.config : {}, server);
|
|
109
|
+
mkdirSync(dirname(target.path), { recursive: true });
|
|
110
|
+
writeFileSync(target.path, `${JSON.stringify(merged, null, 2)}\n`);
|
|
111
|
+
const after = inspectClaudeCodeUserMcpConfig(env);
|
|
112
|
+
return after.status === 'configured' ? { ok: true, value: target.path } : { ok: false, error: { code: 'validation_failed', message: 'Claude user ~/.claude.json did not validate after write.' } };
|
|
113
|
+
}
|
|
91
114
|
function writeProjectMemory(plan) {
|
|
92
115
|
const target = plan.targets.find((item) => item.kind === 'project-memory');
|
|
93
116
|
if (target === undefined || target.action === 'none')
|
|
@@ -105,14 +128,61 @@ function writeProjectMemory(plan) {
|
|
|
105
128
|
const after = inspectClaudeProjectMemory(dirname(target.path));
|
|
106
129
|
return after.status === 'managed-current' ? { ok: true, value: target.path } : { ok: false, error: { code: 'validation_failed', message: 'Claude project memory did not validate as managed-current after write.' } };
|
|
107
130
|
}
|
|
108
|
-
function
|
|
131
|
+
async function applyUserInstall(input, plan, server, writtenPaths, backups) {
|
|
132
|
+
const existingTargets = plan.targets.filter((target) => target.kind !== 'cli-mcp-registration' && (target.action === 'merge' || target.action === 'update-vgxness' || target.action === 'append-managed-block' || target.action === 'update-managed-block'));
|
|
133
|
+
for (const target of existingTargets) {
|
|
134
|
+
const backup = createBackup(target.path, target.kind === 'user-memory' ? 'user-memory' : 'config', 'user');
|
|
135
|
+
if (!backup.ok)
|
|
136
|
+
return refusal('backup_failed', backup.error.message, plan, server, writtenPaths, backups);
|
|
137
|
+
backups.push(toBackupSummary(backup.value));
|
|
138
|
+
}
|
|
139
|
+
const mcpWrite = writeUserMcpJson(input.env, plan, server);
|
|
140
|
+
if (!mcpWrite.ok)
|
|
141
|
+
return refusal('post_write_validation_failed', mcpWrite.error.message, plan, server, writtenPaths, backups);
|
|
142
|
+
writtenPaths.push(mcpWrite.value);
|
|
143
|
+
for (const agent of expectedClaudeCodeRenderedUserAgents(input.cwd, input.env)) {
|
|
144
|
+
const target = plan.targets.find((item) => item.kind === 'agent-file' && item.path === agent.path);
|
|
145
|
+
if (target?.action !== 'create' && target?.action !== 'update-vgxness')
|
|
146
|
+
continue;
|
|
147
|
+
mkdirSync(dirname(agent.path), { recursive: true });
|
|
148
|
+
writeFileSync(agent.path, agent.contents);
|
|
149
|
+
const validation = parseClaudeAgentFrontmatter(readFileSync(agent.path, 'utf8'));
|
|
150
|
+
if (!validation.ok || validation.data.name !== agent.agentName)
|
|
151
|
+
return refusal('post_write_validation_failed', `Claude user agent ${agent.agentName} failed post-write validation.`, plan, server, writtenPaths, backups);
|
|
152
|
+
writtenPaths.push(agent.path);
|
|
153
|
+
}
|
|
154
|
+
const memoryWrite = writeUserMemory(input.env, plan);
|
|
155
|
+
if (!memoryWrite.ok)
|
|
156
|
+
return refusal('post_write_validation_failed', memoryWrite.error.message, plan, server, writtenPaths, backups);
|
|
157
|
+
if (memoryWrite.value !== undefined)
|
|
158
|
+
writtenPaths.push(memoryWrite.value);
|
|
159
|
+
return { version: 1, kind: 'mcp-client-install-claude-code', status: 'installed', targetPath: plan.targetPath, writtenPaths, backups, safety: applySafety(plan), server, warnings: plan.warnings, verificationHints: plan.verificationHints, agentNames: plan.agentNames, overwriteVgxness: plan.overwriteVgxness };
|
|
160
|
+
}
|
|
161
|
+
function writeUserMemory(env, plan) {
|
|
162
|
+
const target = plan.targets.find((item) => item.kind === 'user-memory');
|
|
163
|
+
if (target === undefined || target.action === 'none')
|
|
164
|
+
return { ok: true, value: undefined };
|
|
165
|
+
if (target.action === 'blocked')
|
|
166
|
+
return { ok: false, error: { code: 'validation_failed', message: target.reason ?? 'Claude user memory is blocked.' } };
|
|
167
|
+
const state = inspectClaudeUserMemory(env);
|
|
168
|
+
if (state.path !== target.path || state.action !== target.action)
|
|
169
|
+
return { ok: false, error: { code: 'validation_failed', message: 'Claude user memory changed after planning; rerun apply.' } };
|
|
170
|
+
const merged = mergeClaudeUserMemory(state);
|
|
171
|
+
if (!merged.ok)
|
|
172
|
+
return merged;
|
|
173
|
+
mkdirSync(dirname(target.path), { recursive: true });
|
|
174
|
+
writeFileSync(target.path, merged.value.contents);
|
|
175
|
+
const after = inspectClaudeUserMemory(env);
|
|
176
|
+
return after.status === 'managed-current' ? { ok: true, value: target.path } : { ok: false, error: { code: 'validation_failed', message: 'Claude user memory did not validate as managed-current after write.' } };
|
|
177
|
+
}
|
|
178
|
+
function createBackup(path, kind, scope = 'project') {
|
|
109
179
|
return createManagedProviderConfigBackup({
|
|
110
180
|
targetPath: path,
|
|
111
181
|
provider: 'claude',
|
|
112
|
-
scope
|
|
182
|
+
scope,
|
|
113
183
|
createdByOperation: 'mcp-client-install-claude-code',
|
|
114
|
-
reason: kind === 'project-memory' ? 'pre-project-memory-update-safety' : 'pre-merge-safety',
|
|
115
|
-
description: kind === 'project-memory' ? 'Backup existing Claude Code project memory before appending or updating the VGXNESS managed block.' : 'Backup existing Claude Code
|
|
184
|
+
reason: kind === 'project-memory' ? 'pre-project-memory-update-safety' : kind === 'user-memory' ? 'pre-user-memory-update-safety' : 'pre-merge-safety',
|
|
185
|
+
description: kind === 'project-memory' ? 'Backup existing Claude Code project memory before appending or updating the VGXNESS managed block.' : kind === 'user-memory' ? 'Backup existing Claude Code user memory before appending or updating the VGXNESS managed block.' : 'Backup existing Claude Code config before merging VGXNESS MCP or agent configuration.',
|
|
116
186
|
});
|
|
117
187
|
}
|
|
118
188
|
function refusal(reason, message, plan, server, writtenPaths, backups, cliResult) {
|
|
@@ -29,7 +29,7 @@ export function callVgxTool(call, services) {
|
|
|
29
29
|
case 'vgxness_sdd_get_readiness':
|
|
30
30
|
return auditedEnvelope(validated.tool, services.sdd.getReady(validated.input), services, sddReadinessAuditPayload(validated.input));
|
|
31
31
|
case 'vgxness_sdd_save_artifact':
|
|
32
|
-
return
|
|
32
|
+
return auditedEnvelope(validated.tool, services.sdd.saveArtifact(validated.input), services, sddSaveAuditPayload(validated.input));
|
|
33
33
|
case 'vgxness_sdd_accept_artifact':
|
|
34
34
|
return auditedEnvelope(validated.tool, services.sdd.acceptArtifact(toAcceptArtifactServiceInput(validated.input)), services, sddAcceptanceAuditPayload(validated.input));
|
|
35
35
|
case 'vgxness_sdd_get_artifact':
|
|
@@ -170,6 +170,23 @@ function sddAcceptanceAuditPayload(input) {
|
|
|
170
170
|
}),
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
|
+
function sddSaveAuditPayload(input) {
|
|
174
|
+
return {
|
|
175
|
+
runId: input.runId,
|
|
176
|
+
title: 'Audit: sdd-artifact-saved',
|
|
177
|
+
relatedType: 'sdd-artifact',
|
|
178
|
+
relatedId: `${input.change}:${input.phase}`,
|
|
179
|
+
payload: (value) => ({
|
|
180
|
+
eventType: 'sdd-artifact-saved',
|
|
181
|
+
project: input.project,
|
|
182
|
+
change: input.change,
|
|
183
|
+
phase: input.phase,
|
|
184
|
+
topicKey: value.topicKey,
|
|
185
|
+
artifactId: value.id,
|
|
186
|
+
...(input.agentId === undefined ? {} : { agentId: input.agentId }),
|
|
187
|
+
}),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
173
190
|
function governanceReportAuditPayload(input) {
|
|
174
191
|
return {
|
|
175
192
|
runId: input.runId,
|
package/dist/mcp/index.js
CHANGED
|
@@ -10,6 +10,8 @@ export * from './claude-code-cli.js';
|
|
|
10
10
|
export * from './claude-code-config.js';
|
|
11
11
|
export * from './claude-code-project-memory.js';
|
|
12
12
|
export * from './claude-code-scope.js';
|
|
13
|
+
export * from './claude-code-user-config.js';
|
|
14
|
+
export * from './claude-code-user-memory.js';
|
|
13
15
|
export * from './control-plane.js';
|
|
14
16
|
export * from './doctor.js';
|
|
15
17
|
export * from './opencode-visibility.js';
|