vgxness 1.9.2 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/dist/agents/agent-resolver.js +33 -3
- package/dist/agents/canonical-agent-manifest.js +68 -21
- package/dist/agents/canonical-agent-projection.js +46 -4
- package/dist/cli/cli-help.js +14 -3
- package/dist/cli/commands/index.js +1 -0
- package/dist/cli/commands/interactive-entrypoint-dispatcher.js +8 -0
- package/dist/cli/commands/mcp-dispatcher.js +49 -18
- package/dist/cli/commands/memory-sdd-dispatcher.js +71 -5
- package/dist/cli/commands/setup-dispatcher.js +22 -10
- package/dist/cli/commands/status-dispatcher.js +130 -0
- package/dist/cli/commands/workflow-dispatcher.js +11 -5
- package/dist/cli/dispatcher.js +9 -1
- package/dist/cli/product-resume-renderer.js +32 -0
- package/dist/cli/product-status-renderer.js +81 -0
- package/dist/cli/sdd-renderer.js +90 -7
- package/dist/cli/tui/main-menu/main-menu-read-model.js +8 -8
- package/dist/cli/tui/setup/setup-tui-services.js +27 -10
- package/dist/code/cli/code-command.js +7 -4
- package/dist/code/reporting/summary.js +4 -1
- package/dist/code/runtime/code-runtime.js +27 -4
- package/dist/code/runtime/sdd-context.js +18 -2
- package/dist/governance/governance-report-builder.js +18 -7
- package/dist/mcp/claude-code-agent-config.js +10 -4
- package/dist/mcp/client-install-opencode-contract.js +2 -2
- package/dist/mcp/client-install-opencode.js +10 -6
- package/dist/mcp/control-plane.js +56 -0
- package/dist/mcp/opencode-default-agent-config.js +7 -4
- package/dist/mcp/provider-status.js +86 -81
- package/dist/mcp/schema.js +25 -7
- package/dist/mcp/stdio-server.js +4 -0
- package/dist/mcp/validation.js +39 -3
- package/dist/resume/product-resume.js +166 -0
- package/dist/runs/repositories/runs.js +12 -1
- package/dist/runs/run-service.js +62 -5
- package/dist/sdd/schema.js +8 -0
- package/dist/sdd/sdd-continuation-plan.js +81 -0
- package/dist/sdd/sdd-workflow-service.js +103 -16
- package/dist/skills/skill-resolver.js +21 -4
- package/dist/status/product-status.js +121 -0
- package/docs/architecture.md +1 -1
- package/docs/cli.md +40 -12
- package/docs/code-runtime.md +3 -0
- package/docs/glossary.md +1 -1
- package/docs/mcp.md +18 -4
- package/package.json +1 -1
|
@@ -11,10 +11,10 @@ const statCards = [
|
|
|
11
11
|
{ label: 'actions', value: '5', badge: '', description: 'safe routes' },
|
|
12
12
|
{ label: 'provider', value: '1', badge: '', description: 'OpenCode' },
|
|
13
13
|
{ label: 'writes', value: '0', badge: '', description: 'dashboard render' },
|
|
14
|
-
{ label: 'SDD', value: '
|
|
14
|
+
{ label: 'SDD', value: 'OpenCode', badge: '', description: 'daily surface' },
|
|
15
15
|
];
|
|
16
16
|
const statusSnapshotLines = [
|
|
17
|
-
'OpenCode
|
|
17
|
+
'OpenCode + VGXNESS MCP is the daily SDD surface; dashboard does not call providers.',
|
|
18
18
|
'Advanced checks stay explicit: setup status, mcp doctor opencode.',
|
|
19
19
|
];
|
|
20
20
|
const optionCopy = {
|
|
@@ -34,17 +34,17 @@ const optionCopy = {
|
|
|
34
34
|
},
|
|
35
35
|
sdd: {
|
|
36
36
|
label: 'SDD workflow',
|
|
37
|
-
description: '
|
|
37
|
+
description: 'diagnostics, recovery, fallback',
|
|
38
38
|
badges: [tuiBadges.previewOnly],
|
|
39
39
|
detailTitle: 'SDD workflow',
|
|
40
|
-
detailLines: ['Use OpenCode conversation plus VGXNESS MCP for daily progression.'],
|
|
40
|
+
detailLines: ['Use OpenCode conversation plus VGXNESS MCP and hidden SDD subagents for daily progression; this TUI stays read-only for setup, diagnostics, recovery, and fallback commands.'],
|
|
41
41
|
},
|
|
42
42
|
'advanced-cli': {
|
|
43
43
|
label: 'Advanced CLI',
|
|
44
|
-
description: '
|
|
44
|
+
description: 'fallback scripts, no automation',
|
|
45
45
|
badges: [tuiBadges.manual],
|
|
46
46
|
detailTitle: 'Advanced CLI',
|
|
47
|
-
detailLines: ['Prints explicit command references; no provider config is written.'],
|
|
47
|
+
detailLines: ['Prints explicit diagnostic, recovery, fallback, and scripting command references; no provider config is written.'],
|
|
48
48
|
},
|
|
49
49
|
exit: {
|
|
50
50
|
label: 'Quit',
|
|
@@ -62,13 +62,13 @@ export function buildMainMenuViewModel(state) {
|
|
|
62
62
|
subtitle: dashboardHero.subtitle,
|
|
63
63
|
hero: dashboardHero,
|
|
64
64
|
statCards,
|
|
65
|
-
contextLines: ['Pick a route. Dashboard render stays passive/read-only.'],
|
|
65
|
+
contextLines: ['Pick a route. Dashboard render stays passive/read-only; daily SDD continues in OpenCode.'],
|
|
66
66
|
options,
|
|
67
67
|
detail: { title: focused.detailTitle, lines: focused.detailLines, badges: focused.badges },
|
|
68
68
|
statusSnapshot: { title: 'Signal', lines: statusSnapshotLines, badges: [tuiBadges.readOnly] },
|
|
69
69
|
safetyLines: ['read-only • no provider writes • explicit confirmation'],
|
|
70
70
|
helpLines: state.helpVisible
|
|
71
|
-
? ['Keys: ↑/↓ or j/k move, Enter open, ?/h help, q/Esc quit.', 'Guidance entries print commands only; setup writes still require confirmation.']
|
|
71
|
+
? ['Keys: ↑/↓ or j/k move, Enter open, ?/h help, q/Esc quit.', 'Guidance entries print fallback/diagnostic commands only; setup writes still require confirmation.']
|
|
72
72
|
: [],
|
|
73
73
|
footer: state.viewport.mode === 'narrow' ? 'no provider writes • explicit confirmation' : 'j/k • enter • q • no provider writes • explicit confirmation',
|
|
74
74
|
};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { computeEffectiveManagerInstructions } from '../../../agents/manager-profile-overlay-service.js';
|
|
1
2
|
import { installOpenCodeMcpClient } from '../../../mcp/client-install-opencode.js';
|
|
3
|
+
import { openMemoryDatabase } from '../../../memory/sqlite/database.js';
|
|
4
|
+
import { prepareMemoryDatabasePath } from '../../../memory/storage-paths.js';
|
|
2
5
|
import { vgxnessSetupDefaults } from '../../../setup/setup-defaults.js';
|
|
3
6
|
import { createSetupPlan } from '../../../setup/setup-plan.js';
|
|
4
7
|
export function setupPlanInputFromTui(input) {
|
|
@@ -37,16 +40,30 @@ export async function applySetupTuiPlan(planInput, runtime) {
|
|
|
37
40
|
};
|
|
38
41
|
if (plan.value.status !== 'ready')
|
|
39
42
|
return { ok: false, error: { code: 'validation_failed', message: `Setup plan is ${plan.value.status}; resolve blockers before applying.` } };
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
const prepared = prepareMemoryDatabasePath(plan.value.db.path);
|
|
44
|
+
if (!prepared.ok)
|
|
45
|
+
return prepared;
|
|
46
|
+
const opened = openMemoryDatabase({ path: plan.value.db.path });
|
|
47
|
+
if (!opened.ok)
|
|
48
|
+
return opened;
|
|
49
|
+
let installed;
|
|
50
|
+
try {
|
|
51
|
+
const effectiveManagerInstructions = computeEffectiveManagerInstructions(opened.value, planInput.project);
|
|
52
|
+
installed = await installOpenCodeMcpClient({
|
|
53
|
+
cwd: runtime.cwd,
|
|
54
|
+
databasePath: plan.value.db.path,
|
|
55
|
+
databasePathSource: plan.value.db.source === 'global-default' || plan.value.db.source === 'environment' ? plan.value.db.source : 'flag',
|
|
56
|
+
scope: planInput.scope ?? vgxnessSetupDefaults.defaultOpenCodeScope,
|
|
57
|
+
env: runtime.env,
|
|
58
|
+
confirmed: true,
|
|
59
|
+
mcpOnly: planInput.installMode === 'mcp-only',
|
|
60
|
+
...(planInput.overwriteVgxness === undefined ? {} : { overwriteVgxness: planInput.overwriteVgxness }),
|
|
61
|
+
...(effectiveManagerInstructions === undefined ? {} : { effectiveManagerInstructions }),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
opened.value.close();
|
|
66
|
+
}
|
|
50
67
|
if (installed.status !== 'installed')
|
|
51
68
|
return { ok: false, error: { code: 'validation_failed', message: `${installed.reason}: ${installed.message}` } };
|
|
52
69
|
return {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeSddPhaseInput } from '../../sdd/schema.js';
|
|
1
2
|
import { resolveCodeConfig } from '../config/defaults.js';
|
|
2
3
|
import { CodeProviderRegistry } from '../providers/provider-registry.js';
|
|
3
4
|
import { exitCodeForCodeStatus, renderHumanCodeSummary } from '../reporting/summary.js';
|
|
@@ -31,11 +32,12 @@ export async function runCodeCommand(input) {
|
|
|
31
32
|
};
|
|
32
33
|
const intent = input.command === 'sdd' ? `Run SDD ${input.args[1] ?? ''} for ${input.args[0] ?? ''}`.trim() : input.args.join(' ').trim();
|
|
33
34
|
const changeId = input.command === 'sdd' ? input.args[0] : undefined;
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
const phaseInput = input.command === 'sdd' ? input.args[1] : undefined;
|
|
36
|
+
const phase = phaseInput === undefined ? undefined : normalizeSddPhaseInput(phaseInput);
|
|
37
|
+
if (input.command === 'sdd' && (changeId === undefined || phaseInput === undefined))
|
|
36
38
|
return { exitCode: 1, stdout: '', stderr: 'Missing sdd change and phase\n' };
|
|
37
|
-
if (input.command === 'sdd' &&
|
|
38
|
-
return { exitCode: 1, stdout: '', stderr: `Unknown SDD phase: ${
|
|
39
|
+
if (input.command === 'sdd' && phaseInput !== undefined && phase === undefined)
|
|
40
|
+
return { exitCode: 1, stdout: '', stderr: `Unknown SDD phase: ${phaseInput}\n` };
|
|
39
41
|
if (!intent)
|
|
40
42
|
return {
|
|
41
43
|
exitCode: 1,
|
|
@@ -77,6 +79,7 @@ export async function runCodeCommand(input) {
|
|
|
77
79
|
cwd: input.cwd,
|
|
78
80
|
...(changeId === undefined ? {} : { changeId }),
|
|
79
81
|
...(phase !== undefined && isSddCodePhase(phase) ? { phase } : {}),
|
|
82
|
+
...(input.sddRuntimeMode === undefined ? {} : { sddRuntimeMode: input.sddRuntimeMode }),
|
|
80
83
|
...(input.persistArtifact === undefined ? {} : { persistArtifact: input.persistArtifact }),
|
|
81
84
|
...(input.sddGateway === undefined ? {} : { sddGateway: input.sddGateway }),
|
|
82
85
|
...(input.memoryGateway === undefined ? {} : { memoryGateway: input.memoryGateway }),
|
|
@@ -21,7 +21,10 @@ export function renderHumanCodeSummary(summary) {
|
|
|
21
21
|
...(summary.sdd === undefined
|
|
22
22
|
? []
|
|
23
23
|
: [
|
|
24
|
-
`SDD: ${summary.sdd.changeId}/${summary.sdd.phase} saved=${summary.sdd.artifactSaved ? 'yes' : 'no'}`,
|
|
24
|
+
`SDD: ${summary.sdd.changeId}/${summary.sdd.phase} mode=${summary.sdd.sddRuntimeMode ?? 'strict'} saved=${summary.sdd.artifactSaved ? 'yes' : 'no'}`,
|
|
25
|
+
...(summary.sdd.sddRuntimeMode === 'draft-run'
|
|
26
|
+
? ['SDD draft-run note: draft prerequisites are used for planning only; human acceptance is still required; apply-progress remains gated.']
|
|
27
|
+
: []),
|
|
25
28
|
`Next SDD step: ${summary.sdd.nextRecommendation}`,
|
|
26
29
|
]),
|
|
27
30
|
`Risks: ${summary.risks.length === 0 ? 'none reported' : summary.risks.join('; ')}`,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { normalizeSddPhaseInput } from '../../sdd/schema.js';
|
|
2
3
|
import { resolveCodeConfig } from '../config/defaults.js';
|
|
3
4
|
import { buildCodePrompt } from '../prompts/prompt-builder.js';
|
|
4
5
|
import { CodeProviderError } from '../providers/provider-adapter.js';
|
|
@@ -30,7 +31,13 @@ export class CodeRuntime {
|
|
|
30
31
|
if (input.changeId === undefined || input.phase === undefined || !isSddCodePhase(input.phase))
|
|
31
32
|
throw new Error('SDD mode requires a valid changeId and phase');
|
|
32
33
|
const gateway = input.sddGateway ?? new InMemorySddGateway();
|
|
33
|
-
sddContext = await loadSddContext({
|
|
34
|
+
sddContext = await loadSddContext({
|
|
35
|
+
gateway,
|
|
36
|
+
project,
|
|
37
|
+
changeId: input.changeId,
|
|
38
|
+
phase: input.phase,
|
|
39
|
+
...(input.sddRuntimeMode === undefined ? {} : { runtimeMode: input.sddRuntimeMode }),
|
|
40
|
+
});
|
|
34
41
|
}
|
|
35
42
|
const availableDefinitions = input.mode === 'sdd' && input.phase !== undefined
|
|
36
43
|
? sddToolDefinitions({ phase: input.phase, persist: input.persistArtifact === true })
|
|
@@ -117,6 +124,7 @@ export class CodeRuntime {
|
|
|
117
124
|
...(diagnostics === undefined ? {} : { diagnostics: diagnostics }),
|
|
118
125
|
},
|
|
119
126
|
...(sddContext === undefined ? {} : { changeId: sddContext.changeId, sddPhase: sddContext.phase }),
|
|
127
|
+
...(sddContext === undefined ? {} : { sddRuntimeMode: sddContext.runtimeMode }),
|
|
120
128
|
});
|
|
121
129
|
if (sddContext !== undefined) {
|
|
122
130
|
await this.checkpoint(run.id, session, 'sdd-context-loaded', {
|
|
@@ -480,7 +488,15 @@ export class CodeRuntime {
|
|
|
480
488
|
memory,
|
|
481
489
|
...(sddContext === undefined
|
|
482
490
|
? {}
|
|
483
|
-
: {
|
|
491
|
+
: {
|
|
492
|
+
sdd: {
|
|
493
|
+
changeId: sddContext.changeId,
|
|
494
|
+
phase: sddContext.phase,
|
|
495
|
+
...(sddContext.runtimeMode === undefined ? {} : { sddRuntimeMode: sddContext.runtimeMode }),
|
|
496
|
+
artifactSaved,
|
|
497
|
+
nextRecommendation: sddContext.next.recommendedAction,
|
|
498
|
+
},
|
|
499
|
+
}),
|
|
484
500
|
};
|
|
485
501
|
const summary = config.transcriptMode === 'off' ? baseSummary : { ...baseSummary, transcript: buildTranscript(config.transcriptMode, session.checkpoints, toolResults) };
|
|
486
502
|
if (sddContext !== undefined)
|
|
@@ -533,7 +549,13 @@ export class CodeRuntime {
|
|
|
533
549
|
verification: { status: 'not-run', reason: 'SDD phase blocked before provider execution.' },
|
|
534
550
|
filesChanged: [],
|
|
535
551
|
risks: [answer],
|
|
536
|
-
sdd: {
|
|
552
|
+
sdd: {
|
|
553
|
+
changeId: sddContext.changeId,
|
|
554
|
+
phase: sddContext.phase,
|
|
555
|
+
...(sddContext.runtimeMode === undefined ? {} : { sddRuntimeMode: sddContext.runtimeMode }),
|
|
556
|
+
artifactSaved: false,
|
|
557
|
+
nextRecommendation: sddContext.next.recommendedAction,
|
|
558
|
+
},
|
|
537
559
|
};
|
|
538
560
|
await this.runGateway.finalize(runId, { status: 'blocked', outcome: 'blocked', outcomeReason: answer });
|
|
539
561
|
return summary;
|
|
@@ -641,7 +663,8 @@ export class CodeRuntime {
|
|
|
641
663
|
changedFiles: [],
|
|
642
664
|
};
|
|
643
665
|
if (call.name === 'sdd_read_artifact') {
|
|
644
|
-
const
|
|
666
|
+
const requestedPhase = typeof call.input.phase === 'string' ? normalizeSddPhaseInput(call.input.phase) : undefined;
|
|
667
|
+
const phase = requestedPhase !== undefined && isSddCodePhase(requestedPhase) ? requestedPhase : context.phase;
|
|
645
668
|
const artifact = await gateway.getArtifact({ project, change: context.changeId, phase });
|
|
646
669
|
return {
|
|
647
670
|
result: { toolCallId: call.id, toolName: call.name, ok: true, output: { phase, artifact }, metadata: { capability, truncated: false } },
|
|
@@ -10,6 +10,7 @@ export function shellEnabledForSddPhase(phase) {
|
|
|
10
10
|
return phase === 'apply-progress' || phase === 'verify';
|
|
11
11
|
}
|
|
12
12
|
export async function loadSddContext(input) {
|
|
13
|
+
const runtimeMode = input.runtimeMode ?? 'strict';
|
|
13
14
|
const [status, next] = await Promise.all([
|
|
14
15
|
input.gateway.getStatus({ project: input.project, change: input.changeId }),
|
|
15
16
|
input.gateway.getNextPhase({ project: input.project, change: input.changeId }),
|
|
@@ -22,7 +23,7 @@ export async function loadSddContext(input) {
|
|
|
22
23
|
if (artifact !== null)
|
|
23
24
|
artifacts.push(toContextArtifact(input.project, input.changeId, phase, phase === input.phase ? 'active' : 'accepted', artifact.content, phaseStatus));
|
|
24
25
|
}
|
|
25
|
-
return { changeId: input.changeId, phase: input.phase, status, next, artifacts };
|
|
26
|
+
return { changeId: input.changeId, phase: input.phase, runtimeMode, status, next, artifacts };
|
|
26
27
|
}
|
|
27
28
|
function toContextArtifact(project, change, phase, role, content, phaseStatus) {
|
|
28
29
|
const bytes = Buffer.byteLength(content, 'utf8');
|
|
@@ -98,6 +99,8 @@ export function validateSddReadiness(context) {
|
|
|
98
99
|
recommendedAction: `Accept or restore prerequisites before running ${context.phase}.`,
|
|
99
100
|
},
|
|
100
101
|
};
|
|
102
|
+
if (isDraftRun(context) && isPlanningPhase(context.phase))
|
|
103
|
+
return { ok: true };
|
|
101
104
|
if (present)
|
|
102
105
|
return { ok: true };
|
|
103
106
|
if (context.status.nextReadyPhase === context.phase)
|
|
@@ -123,10 +126,23 @@ export function activeBlockedPrerequisites(context) {
|
|
|
123
126
|
return [{ phase, topicKey, reason: 'missing' }];
|
|
124
127
|
if (status.accepted === true && status.legacy !== true)
|
|
125
128
|
return [];
|
|
126
|
-
|
|
129
|
+
if (status.legacy === true)
|
|
130
|
+
return [{ phase, topicKey, reason: 'legacy', ...(status.artifactId === undefined ? {} : { artifactId: status.artifactId }) }];
|
|
131
|
+
if (draftPrerequisiteAllowed(context, status.state ?? 'draft'))
|
|
132
|
+
return [];
|
|
133
|
+
const reason = status.state === 'rejected' || status.state === 'superseded' ? status.state : 'draft';
|
|
127
134
|
return [{ phase, topicKey, reason, ...(status.artifactId === undefined ? {} : { artifactId: status.artifactId }) }];
|
|
128
135
|
});
|
|
129
136
|
}
|
|
137
|
+
function draftPrerequisiteAllowed(context, state) {
|
|
138
|
+
return isDraftRun(context) && isPlanningPhase(context.phase) && state === 'draft';
|
|
139
|
+
}
|
|
140
|
+
function isDraftRun(context) {
|
|
141
|
+
return context.runtimeMode === 'draft-run';
|
|
142
|
+
}
|
|
143
|
+
function isPlanningPhase(phase) {
|
|
144
|
+
return phase === 'explore' || phase === 'proposal' || phase === 'spec' || phase === 'design' || phase === 'tasks';
|
|
145
|
+
}
|
|
130
146
|
function acceptedArtifactPhases(active) {
|
|
131
147
|
return prerequisitePhases(active);
|
|
132
148
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getSddPhasePermissionMatrixForPhase, permissionCategories, sddPhasePermissionMatrixVersion } from '../permissions/policy-evaluator.js';
|
|
2
|
-
import { isSddPhase, normalizeSddArtifact } from '../sdd/schema.js';
|
|
2
|
+
import { isSddPhase, normalizeSddArtifact, normalizeSddPhaseInput } from '../sdd/schema.js';
|
|
3
3
|
import { fingerprintManagerProfileOverlay } from './overlay-fingerprint.js';
|
|
4
4
|
export class GovernanceReportBuilder {
|
|
5
5
|
services;
|
|
@@ -27,11 +27,13 @@ export class GovernanceReportBuilder {
|
|
|
27
27
|
warnings.push('run-service-unavailable');
|
|
28
28
|
}
|
|
29
29
|
const workflow = input.workflow ?? run?.workflow;
|
|
30
|
-
const
|
|
30
|
+
const rawPhase = input.phase ?? run?.phase;
|
|
31
|
+
const phase = canonicalReportPhase(workflow, rawPhase);
|
|
31
32
|
const agent = buildAgentRef(input, run);
|
|
32
33
|
const change = input.change;
|
|
33
34
|
const payloadMode = input.payloadMode ?? 'compact';
|
|
34
35
|
const resolvedRunId = input.runId ?? run?.id;
|
|
36
|
+
const permissionsPhase = phase === undefined ? undefined : (normalizeSddPhaseInput(phase) ?? phase);
|
|
35
37
|
const report = {
|
|
36
38
|
version: 1,
|
|
37
39
|
kind: 'governance-report',
|
|
@@ -44,7 +46,7 @@ export class GovernanceReportBuilder {
|
|
|
44
46
|
...(agent === undefined ? {} : { agent }),
|
|
45
47
|
sdd: {},
|
|
46
48
|
permissions: {
|
|
47
|
-
...buildPermissionsMode(
|
|
49
|
+
...buildPermissionsMode(permissionsPhase),
|
|
48
50
|
},
|
|
49
51
|
openCode: {
|
|
50
52
|
mode: 'audit-only',
|
|
@@ -72,7 +74,8 @@ export class GovernanceReportBuilder {
|
|
|
72
74
|
tryBuildOptimizedSddSnapshot(report, project, change, phase, payloadMode, warnings) {
|
|
73
75
|
if (this.services.sdd.getGovernanceSnapshot === undefined)
|
|
74
76
|
return false;
|
|
75
|
-
|
|
77
|
+
const canonicalPhase = phase === undefined ? undefined : normalizeSddPhaseInput(phase);
|
|
78
|
+
if (phase !== undefined && canonicalPhase === undefined) {
|
|
76
79
|
warnings.push('sdd-readiness-skipped-non-sdd-phase');
|
|
77
80
|
return false;
|
|
78
81
|
}
|
|
@@ -80,7 +83,7 @@ export class GovernanceReportBuilder {
|
|
|
80
83
|
project,
|
|
81
84
|
change,
|
|
82
85
|
payloadMode,
|
|
83
|
-
...(
|
|
86
|
+
...(canonicalPhase === undefined ? {} : { phase: canonicalPhase }),
|
|
84
87
|
});
|
|
85
88
|
if (!snapshot.ok) {
|
|
86
89
|
warnings.push(`sdd-snapshot-unavailable:${snapshot.error.code}`);
|
|
@@ -125,8 +128,9 @@ export class GovernanceReportBuilder {
|
|
|
125
128
|
else {
|
|
126
129
|
warnings.push(`sdd-artifacts-unavailable:${listed.error.code}`);
|
|
127
130
|
}
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
const canonicalPhase = phase === undefined ? undefined : normalizeSddPhaseInput(phase);
|
|
132
|
+
if (canonicalPhase !== undefined) {
|
|
133
|
+
const readiness = this.services.sdd.getReady({ project, change, phase: canonicalPhase });
|
|
130
134
|
if (readiness.ok) {
|
|
131
135
|
report.sdd.readiness = readiness.value;
|
|
132
136
|
for (const blocker of readiness.value.blockedPrerequisites ?? [])
|
|
@@ -280,6 +284,13 @@ function buildAgentRef(input, run) {
|
|
|
280
284
|
function dedupe(values) {
|
|
281
285
|
return [...new Set(values)];
|
|
282
286
|
}
|
|
287
|
+
function canonicalReportPhase(workflow, phase) {
|
|
288
|
+
if (phase === undefined)
|
|
289
|
+
return undefined;
|
|
290
|
+
if (workflow !== undefined && workflow.trim().toLowerCase() !== 'sdd')
|
|
291
|
+
return phase;
|
|
292
|
+
return normalizeSddPhaseInput(phase) ?? phase;
|
|
293
|
+
}
|
|
283
294
|
function validationFailure(message) {
|
|
284
295
|
return { ok: false, error: { code: 'validation_failed', message } };
|
|
285
296
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
3
|
+
import { join, parse, resolve } from 'node:path';
|
|
4
4
|
import { projectCanonicalAgentManifestToClaudeCode } from '../agents/canonical-agent-projection.js';
|
|
5
5
|
import { assertInsideWorkspace } from './claude-code-config.js';
|
|
6
6
|
export const claudeCodeGeneratedMarker = 'VGXNESS-GENERATED';
|
|
@@ -29,10 +29,16 @@ export function resolveClaudeAgentTarget(input) {
|
|
|
29
29
|
return { workspaceRoot: input.workspaceRoot, scope, directoryPath: join(home, '.claude', 'agents'), ...(input.env === undefined ? {} : { env: input.env }) };
|
|
30
30
|
}
|
|
31
31
|
export function safeHomeDirectory(env = process.env) {
|
|
32
|
-
const
|
|
33
|
-
|
|
32
|
+
const homeDrive = env.HOMEDRIVE?.trim();
|
|
33
|
+
const homePath = env.HOMEPATH?.trim();
|
|
34
|
+
const homeDrivePath = homeDrive && homePath ? `${homeDrive}${homePath}` : '';
|
|
35
|
+
const candidate = env.HOME?.trim() || env.USERPROFILE?.trim() || homeDrivePath || homedir();
|
|
36
|
+
if (!candidate || candidate.startsWith('~') || candidate.includes('\0'))
|
|
34
37
|
throw new Error('Unable to resolve a safe home directory for Claude user agents.');
|
|
35
|
-
|
|
38
|
+
const expanded = resolve(candidate);
|
|
39
|
+
if (parse(expanded).root === expanded)
|
|
40
|
+
throw new Error('Unable to resolve a safe home directory for Claude user agents.');
|
|
41
|
+
return expanded;
|
|
36
42
|
}
|
|
37
43
|
export function isVgxnessOwnedClaudeAgentMarkdown(contents) {
|
|
38
44
|
return contents.includes(claudeCodeGeneratedMarker) && contents.includes('provider=claude') && contents.includes('artifact=claude-code-subagent');
|
|
@@ -195,10 +195,10 @@ function parseConfig(path) {
|
|
|
195
195
|
function isRecord(value) {
|
|
196
196
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
197
197
|
}
|
|
198
|
-
export function findConflictingVgxnessAgents(config, agentPlan) {
|
|
198
|
+
export function findConflictingVgxnessAgents(config, agentPlan, input = {}) {
|
|
199
199
|
if (!agentPlan.installsAgents || !isRecord(config.agent))
|
|
200
200
|
return [];
|
|
201
|
-
const defaults = createOpenCodeDefaultAgentConfig().agents;
|
|
201
|
+
const defaults = createOpenCodeDefaultAgentConfig(input).agents;
|
|
202
202
|
return agentPlan.agentNames.filter((name) => Object.hasOwn(config.agent, name) && !deepEqual(config.agent[name], defaults[name]));
|
|
203
203
|
}
|
|
204
204
|
function deepEqual(left, right) {
|
|
@@ -37,36 +37,40 @@ export async function installOpenCodeMcpClient(input) {
|
|
|
37
37
|
const reparsed = parseConfig(plan.targetPath);
|
|
38
38
|
if (!reparsed.ok)
|
|
39
39
|
return refusal(reparsed.reason, reparsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
40
|
-
const conflictingAgents = findConflictingVgxnessAgents(reparsed.value, agentPlan
|
|
40
|
+
const conflictingAgents = findConflictingVgxnessAgents(reparsed.value, agentPlan, {
|
|
41
|
+
effectiveManagerInstructions: input.effectiveManagerInstructions,
|
|
42
|
+
});
|
|
41
43
|
if (conflictingAgents.length > 0 && input.overwriteVgxness !== true)
|
|
42
44
|
return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
43
45
|
return refusal('unsupported_config_shape', 'OpenCode config appeared after planning; rerun setup apply so it can be merged safely without overwriting user config.', input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
44
46
|
}
|
|
45
|
-
writeConfig(plan.targetPath, mergeVgxnessOpenCodeConfig({ $schema: opencodeConfigSchema }, plan.server, plan.installsAgents));
|
|
47
|
+
writeConfig(plan.targetPath, mergeVgxnessOpenCodeConfig({ $schema: opencodeConfigSchema }, plan.server, plan.installsAgents, input.effectiveManagerInstructions));
|
|
46
48
|
return validateInstalledResult(plan.targetPath, undefined, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
47
49
|
}
|
|
48
50
|
const parsed = parseConfig(plan.targetPath);
|
|
49
51
|
if (!parsed.ok)
|
|
50
52
|
return refusal(parsed.reason, parsed.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
51
|
-
const conflictingAgents = findConflictingVgxnessAgents(parsed.value, agentPlan
|
|
53
|
+
const conflictingAgents = findConflictingVgxnessAgents(parsed.value, agentPlan, {
|
|
54
|
+
effectiveManagerInstructions: input.effectiveManagerInstructions,
|
|
55
|
+
});
|
|
52
56
|
if (conflictingAgents.length > 0 && input.overwriteVgxness !== true)
|
|
53
57
|
return agentConflictRefusal(conflictingAgents, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan);
|
|
54
58
|
const backup = createBackup(plan.targetPath, plan.scope);
|
|
55
59
|
if (!backup.ok)
|
|
56
60
|
return refusal('post_write_validation_failed', backup.error.message, input.databasePath, databasePathSource, server, applySafety(plan), plan.targetPath, plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
57
61
|
const config = parsed.value;
|
|
58
|
-
const mergedConfig = mergeVgxnessOpenCodeConfig({ ...config, $schema: plan.existingSchema ?? opencodeConfigSchema }, plan.server, plan.installsAgents);
|
|
62
|
+
const mergedConfig = mergeVgxnessOpenCodeConfig({ ...config, $schema: plan.existingSchema ?? opencodeConfigSchema }, plan.server, plan.installsAgents, input.effectiveManagerInstructions);
|
|
59
63
|
writeConfig(plan.targetPath, mergedConfig);
|
|
60
64
|
return validateInstalledResult(plan.targetPath, backup.value, server, input.databasePath, databasePathSource, applySafety(plan), plan.verificationHints, plan.warnings, plan.manualTest, agentPlan, plan.overwriteVgxness);
|
|
61
65
|
}
|
|
62
|
-
function mergeVgxnessOpenCodeConfig(config, server, installsAgents) {
|
|
66
|
+
function mergeVgxnessOpenCodeConfig(config, server, installsAgents, effectiveManagerInstructions) {
|
|
63
67
|
const merged = {
|
|
64
68
|
...config,
|
|
65
69
|
$schema: typeof config.$schema === 'string' ? config.$schema : opencodeConfigSchema,
|
|
66
70
|
mcp: { ...(isRecord(config.mcp) ? config.mcp : {}), vgxness: server },
|
|
67
71
|
};
|
|
68
72
|
if (installsAgents) {
|
|
69
|
-
const defaults = createOpenCodeDefaultAgentConfig();
|
|
73
|
+
const defaults = createOpenCodeDefaultAgentConfig({ effectiveManagerInstructions });
|
|
70
74
|
merged.instructions = mergeOpenCodeInstructions(config.instructions);
|
|
71
75
|
merged.default_agent = defaults.defaultAgent;
|
|
72
76
|
merged.agent = { ...(isRecord(config.agent) ? config.agent : {}), ...defaults.agents };
|
|
@@ -8,6 +8,7 @@ import { openMemoryDatabase } from '../memory/sqlite/database.js';
|
|
|
8
8
|
import { prepareMemoryDatabasePath, resolveMemoryDatabasePath } from '../memory/storage-paths.js';
|
|
9
9
|
import { OpenCodeManagerPayloadService } from '../providers/opencode/manager-payload.js';
|
|
10
10
|
import { RunService } from '../runs/run-service.js';
|
|
11
|
+
import { sddContinuationPlanFrom } from '../sdd/sdd-continuation-plan.js';
|
|
11
12
|
import { SddWorkflowService } from '../sdd/sdd-workflow-service.js';
|
|
12
13
|
import { SkillRegistryService } from '../skills/skill-registry-service.js';
|
|
13
14
|
import { VerificationPlanService } from '../verification/index.js';
|
|
@@ -43,6 +44,8 @@ export function callVgxTool(call, services) {
|
|
|
43
44
|
return toEnvelope(validated.tool, services.sdd.getNext(validated.input));
|
|
44
45
|
case 'vgxness_sdd_cockpit':
|
|
45
46
|
return toEnvelope(validated.tool, services.sdd.getCockpit(validated.input));
|
|
47
|
+
case 'vgxness_sdd_continue':
|
|
48
|
+
return sddContinueEnvelope(validated.input, services);
|
|
46
49
|
case 'vgxness_governance_report':
|
|
47
50
|
return auditedEnvelope(validated.tool, new GovernanceReportBuilder({
|
|
48
51
|
sdd: governanceSddServices(services.sdd),
|
|
@@ -96,6 +99,8 @@ export function callVgxTool(call, services) {
|
|
|
96
99
|
return toEnvelope(validated.tool, services.runs.appendCheckpoint(validated.input));
|
|
97
100
|
case 'vgxness_run_finalize':
|
|
98
101
|
return toEnvelope(validated.tool, services.runs.updateFinalStatus(validated.input));
|
|
102
|
+
case 'vgxness_run_resume_candidates':
|
|
103
|
+
return runResumeCandidatesEnvelope(validated.input, services);
|
|
99
104
|
case 'vgxness_run_resume_inspect':
|
|
100
105
|
return toEnvelope(validated.tool, services.runs.getRunOperatorResumePlan(validated.input.runId));
|
|
101
106
|
case 'vgxness_run_resume_gate':
|
|
@@ -238,6 +243,23 @@ function governanceSddServices(services) {
|
|
|
238
243
|
},
|
|
239
244
|
};
|
|
240
245
|
}
|
|
246
|
+
function sddContinueEnvelope(input, services) {
|
|
247
|
+
const next = services.sdd.getNext({ project: input.project, change: input.change });
|
|
248
|
+
if (!next.ok)
|
|
249
|
+
return errorEnvelope(next.error.code, next.error.message, 'vgxness_sdd_continue');
|
|
250
|
+
const cockpit = services.sdd.getCockpit({ project: input.project, change: input.change });
|
|
251
|
+
if (!cockpit.ok)
|
|
252
|
+
return errorEnvelope(cockpit.error.code, cockpit.error.message, 'vgxness_sdd_continue');
|
|
253
|
+
const relatedRun = services.runs.findRelatedInterruptedSddRun({ project: input.project, change: input.change });
|
|
254
|
+
if (!relatedRun.ok)
|
|
255
|
+
return errorEnvelope(relatedRun.error.code, relatedRun.error.message, 'vgxness_sdd_continue');
|
|
256
|
+
return successEnvelope('vgxness_sdd_continue', sddContinuationPlanFrom({
|
|
257
|
+
project: input.project,
|
|
258
|
+
next: next.value,
|
|
259
|
+
cockpit: cockpit.value,
|
|
260
|
+
...(relatedRun.value === undefined ? {} : { relatedRunContext: relatedRun.value }),
|
|
261
|
+
}));
|
|
262
|
+
}
|
|
241
263
|
export function createVgxMcpControlPlane(options = {}) {
|
|
242
264
|
const databasePath = resolveControlPlaneDatabasePath(options);
|
|
243
265
|
if (!databasePath.ok)
|
|
@@ -346,6 +368,40 @@ function listRunsEnvelope(input, services) {
|
|
|
346
368
|
? successEnvelope('vgxness_run_list', result.value.slice(0, limit))
|
|
347
369
|
: errorEnvelope(result.error.code, result.error.message, 'vgxness_run_list');
|
|
348
370
|
}
|
|
371
|
+
function runResumeCandidatesEnvelope(input, services) {
|
|
372
|
+
const limit = input.limit ?? 5;
|
|
373
|
+
const result = services.runs.listRecentInterruptedRuns({ project: input.project, limit });
|
|
374
|
+
if (!result.ok)
|
|
375
|
+
return errorEnvelope(result.error.code, result.error.message, 'vgxness_run_resume_candidates');
|
|
376
|
+
return successEnvelope('vgxness_run_resume_candidates', {
|
|
377
|
+
kind: 'run-resume-candidates',
|
|
378
|
+
version: 1,
|
|
379
|
+
project: input.project,
|
|
380
|
+
statuses: ['failed', 'blocked', 'needs-human'],
|
|
381
|
+
limit,
|
|
382
|
+
candidates: result.value.map((candidate) => ({
|
|
383
|
+
...candidate,
|
|
384
|
+
recommendation: 'Inspect this interrupted run before deciding whether to continue manually.',
|
|
385
|
+
inspectTool: 'run_resume_inspect',
|
|
386
|
+
inspectInput: { runId: candidate.runId },
|
|
387
|
+
resumeCommand: `vgxness resume --project ${input.project} --run-id ${candidate.runId}`,
|
|
388
|
+
})),
|
|
389
|
+
nextStep: result.value.length === 0
|
|
390
|
+
? 'No interrupted run candidates were found for this project. Start from the current project status or a known runId.'
|
|
391
|
+
: 'Call run_resume_inspect with a candidate runId, then use run_resume_gate only when inspect returns a relevant approvalId.',
|
|
392
|
+
safety: {
|
|
393
|
+
readOnly: true,
|
|
394
|
+
runMutation: false,
|
|
395
|
+
retryAdmitted: false,
|
|
396
|
+
providerInvoked: false,
|
|
397
|
+
writesProviderConfig: false,
|
|
398
|
+
writesArtifacts: false,
|
|
399
|
+
writesOpenSpec: false,
|
|
400
|
+
createsSandbox: false,
|
|
401
|
+
createsWorktree: false,
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
}
|
|
349
405
|
function toEnvelope(tool, result) {
|
|
350
406
|
return result.ok ? successEnvelope(tool, result.value) : errorEnvelope(result.error.code, result.error.message, tool);
|
|
351
407
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { projectCanonicalAgentManifestToOpenCode, } from '../agents/canonical-agent-projection.js';
|
|
2
|
-
import { canonicalDefaultAgentName, canonicalPromptContractVersion, canonicalSddSubagentNames, createCanonicalOpenCodeSddTaskPermissions, } from '../agents/canonical-agent-manifest.js';
|
|
1
|
+
import { projectCanonicalAgentManifestToOpenCode, withEffectiveOpenCodeManagerInstructions, } from '../agents/canonical-agent-projection.js';
|
|
2
|
+
import { canonicalDefaultAgentName, canonicalPromptContractVersion, canonicalSddSubagentNames, createCanonicalOpenCodeSddMcpToolPermissions, createCanonicalOpenCodeSddTaskPermissions, } from '../agents/canonical-agent-manifest.js';
|
|
3
3
|
export const vgxnessOpenCodeDefaultAgent = canonicalDefaultAgentName;
|
|
4
4
|
export const vgxnessOpenCodePromptContractVersion = canonicalPromptContractVersion;
|
|
5
5
|
export const vgxnessOpenCodeInstructionsPath = 'AGENTS.md';
|
|
@@ -7,11 +7,14 @@ export const vgxnessOpenCodeSddSubagents = canonicalSddSubagentNames;
|
|
|
7
7
|
export function createOpenCodeSddTaskPermissions() {
|
|
8
8
|
return createCanonicalOpenCodeSddTaskPermissions();
|
|
9
9
|
}
|
|
10
|
+
export function createOpenCodeSddMcpToolPermissions() {
|
|
11
|
+
return createCanonicalOpenCodeSddMcpToolPermissions();
|
|
12
|
+
}
|
|
10
13
|
export function createOpenCodeDefaultAgentInstallPlan(input = {}) {
|
|
11
14
|
if (input.mcpOnly === true)
|
|
12
15
|
return { installsAgents: false, agentNames: [] };
|
|
13
16
|
return { installsAgents: true, agentNames: [vgxnessOpenCodeDefaultAgent, ...vgxnessOpenCodeSddSubagents], defaultAgent: vgxnessOpenCodeDefaultAgent };
|
|
14
17
|
}
|
|
15
|
-
export function createOpenCodeDefaultAgentConfig() {
|
|
16
|
-
return projectCanonicalAgentManifestToOpenCode();
|
|
18
|
+
export function createOpenCodeDefaultAgentConfig(input = {}) {
|
|
19
|
+
return withEffectiveOpenCodeManagerInstructions(projectCanonicalAgentManifestToOpenCode(), input.effectiveManagerInstructions);
|
|
17
20
|
}
|