vgxness 1.9.1 → 1.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/dist/agents/agent-activation-service.js +13 -4
- package/dist/agents/agent-registry-service.js +8 -2
- package/dist/agents/agent-resolver.js +33 -3
- package/dist/agents/agent-seed-upgrade-service.js +231 -0
- package/dist/agents/boot-upgrade.js +59 -0
- package/dist/agents/canonical-agent-manifest.js +39 -18
- package/dist/agents/canonical-agent-projection.js +38 -4
- package/dist/agents/manager-profile-overlay-service.js +14 -0
- package/dist/agents/repositories/agent-seed-history.js +128 -0
- 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 +7 -0
- package/dist/cli/commands/memory-sdd-dispatcher.js +71 -5
- package/dist/cli/commands/status-dispatcher.js +130 -0
- package/dist/cli/commands/workflow-dispatcher.js +11 -5
- package/dist/cli/dispatcher.js +11 -1
- package/dist/cli/product-resume-renderer.js +32 -0
- package/dist/cli/product-status-renderer.js +74 -0
- package/dist/cli/sdd-renderer.js +80 -3
- 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-claude-code-contract.js +19 -4
- package/dist/mcp/client-install-claude-code.js +2 -2
- package/dist/mcp/control-plane.js +78 -5
- package/dist/mcp/provider-status.js +89 -88
- package/dist/mcp/schema.js +42 -8
- package/dist/mcp/stdio-server.js +6 -0
- package/dist/mcp/validation.js +77 -5
- package/dist/memory/sqlite/migrations/016_agent_seed_history.sql +15 -0
- 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/runs/schema.js +4 -0
- package/dist/sdd/schema.js +20 -0
- package/dist/sdd/sdd-continuation-plan.js +81 -0
- package/dist/sdd/sdd-workflow-service.js +146 -18
- package/dist/skills/skill-resolver.js +21 -4
- package/dist/status/product-status.js +117 -0
- package/docs/architecture.md +9 -1
- package/docs/cli.md +38 -13
- package/docs/code-runtime.md +3 -0
- package/docs/contributing.md +1 -1
- package/docs/glossary.md +2 -2
- package/docs/mcp.md +20 -6
- package/docs/project-health-audit-v1.9.1.md +126 -0
- package/docs/providers.md +4 -4
- package/docs/safety.md +1 -1
- package/package.json +1 -1
package/dist/cli/dispatcher.js
CHANGED
|
@@ -4,7 +4,8 @@ import { isWorkflowId } from '../workflows/schema.js';
|
|
|
4
4
|
import { databasePathFor, parseArgs, requiredFlag } from './cli-flags.js';
|
|
5
5
|
import { okText, usageFailure, visibleHelpText } from './cli-help.js';
|
|
6
6
|
import { openCliDatabase, resultFailure } from './cli-helpers.js';
|
|
7
|
-
import {
|
|
7
|
+
import { runBootAgentSeedUpgrade } from '../agents/boot-upgrade.js';
|
|
8
|
+
import { runAgentCommand, runApprovalsCommand, runCodeCliCommand, runDefaultInteractiveEntrypoint, runDoctorAliasCommand, runInitCommand, runMcpDoctorCommand, runMcpInstallCommand, runMcpSetupCommand, runMemoryCommand, runMemoryImportCommand, runOpenCodeCommand, runOrchestratorCommand, runPermissionsCommand, runProductNextCommand, runProductResumeCommand, runRunsCommand, runSddCommand, runSetupApplyCommand, runSetupLifecycleCommand, runSetupPlanCommand, runSetupRollbackCommand, runProductStatusCommand, runSkillCommand, runSubagentCommand, runVerificationPlanCommand, runVerificationReportCommand, runWorkflowExecuteCommand, runWorkflowPreviewCommand, runWorkflowRunCommand, } from './commands/index.js';
|
|
8
9
|
const _promptBuffers = new WeakMap();
|
|
9
10
|
const require = createRequire(import.meta.url);
|
|
10
11
|
const packageJson = require('../../package.json');
|
|
@@ -16,6 +17,12 @@ export function dispatchCli(argv, environment) {
|
|
|
16
17
|
const [area, command] = parsed.positionals;
|
|
17
18
|
if (!area || area === 'help' || area === '--help' || area === '-h')
|
|
18
19
|
return okText(visibleHelpText());
|
|
20
|
+
if (area === 'status')
|
|
21
|
+
return runProductStatusCommand(parsed, environment);
|
|
22
|
+
if (area === 'next')
|
|
23
|
+
return runProductNextCommand(parsed, environment);
|
|
24
|
+
if (area === 'resume')
|
|
25
|
+
return runProductResumeCommand(parsed, environment);
|
|
19
26
|
if (area === 'init')
|
|
20
27
|
return runSetupPlanCommand(parsed, environment);
|
|
21
28
|
if (area === 'doctor')
|
|
@@ -48,6 +55,7 @@ export function dispatchCli(argv, environment) {
|
|
|
48
55
|
const opened = openCliDatabase(databasePath);
|
|
49
56
|
if (!opened.ok)
|
|
50
57
|
return resultFailure(opened);
|
|
58
|
+
runBootAgentSeedUpgrade(opened.value, environment.env);
|
|
51
59
|
try {
|
|
52
60
|
if (isWorkflowId(area) && command === 'run')
|
|
53
61
|
return runWorkflowRunCommand(area, parsed, opened.value);
|
|
@@ -190,10 +198,12 @@ function validateCommand(area, command) {
|
|
|
190
198
|
command === 'execute' ||
|
|
191
199
|
command === 'status' ||
|
|
192
200
|
command === 'next' ||
|
|
201
|
+
command === 'continue' ||
|
|
193
202
|
command === 'cockpit' ||
|
|
194
203
|
command === 'ready' ||
|
|
195
204
|
command === 'save-artifact' ||
|
|
196
205
|
command === 'accept-artifact' ||
|
|
206
|
+
command === 'reopen-artifact' ||
|
|
197
207
|
command === 'get-artifact' ||
|
|
198
208
|
command === 'list-artifacts' ||
|
|
199
209
|
command === 'export' ||
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function renderProductResume(resume) {
|
|
2
|
+
const lines = [
|
|
3
|
+
'Resume',
|
|
4
|
+
...resume.resume.map((line) => `- ${line}`),
|
|
5
|
+
'',
|
|
6
|
+
'Why',
|
|
7
|
+
...resume.why.map((line) => `- ${line}`),
|
|
8
|
+
'',
|
|
9
|
+
'Blockers',
|
|
10
|
+
...(resume.blockers.length === 0 ? ['- none'] : resume.blockers.map((line) => `- ${line}`)),
|
|
11
|
+
...(resume.candidateRuns.length === 0
|
|
12
|
+
? []
|
|
13
|
+
: [
|
|
14
|
+
'',
|
|
15
|
+
'Candidate runs',
|
|
16
|
+
...resume.candidateRuns.flatMap((run) => [
|
|
17
|
+
`- ${run.runId} [${run.status}] ${run.workflow}/${run.phase}`,
|
|
18
|
+
...(run.userIntent === undefined ? [] : [` Intent: ${run.userIntent}`]),
|
|
19
|
+
` Latest checkpoint: ${run.latestCheckpointLabel ?? 'none'}`,
|
|
20
|
+
` Resume command: ${run.command}`,
|
|
21
|
+
]),
|
|
22
|
+
]),
|
|
23
|
+
'',
|
|
24
|
+
'Command',
|
|
25
|
+
`- ${resume.command}`,
|
|
26
|
+
...(resume.manualNextCommands.length === 0 ? [] : ['', 'Related commands', ...resume.manualNextCommands.map((line) => `- ${line}`)]),
|
|
27
|
+
'',
|
|
28
|
+
'Safety',
|
|
29
|
+
...resume.safety.map((line) => `- ${line}`),
|
|
30
|
+
];
|
|
31
|
+
return `${lines.join('\n')}\n`;
|
|
32
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export function renderProductStatus(status) {
|
|
2
|
+
const lines = [
|
|
3
|
+
'Status',
|
|
4
|
+
...status.status.map((line) => `- ${line}`),
|
|
5
|
+
'',
|
|
6
|
+
'Blockers',
|
|
7
|
+
...status.blockers.map((line) => `- ${line}`),
|
|
8
|
+
'',
|
|
9
|
+
'Next',
|
|
10
|
+
...status.next.map((line) => `- ${line}`),
|
|
11
|
+
'',
|
|
12
|
+
'Command',
|
|
13
|
+
`- ${status.command}`,
|
|
14
|
+
'',
|
|
15
|
+
...(status.relatedRunContext === undefined ? [] : relatedRunContextLines(status.relatedRunContext, true)),
|
|
16
|
+
'Safety',
|
|
17
|
+
...status.safety.map((line) => `- ${line}`),
|
|
18
|
+
];
|
|
19
|
+
return `${lines.join('\n')}\n`;
|
|
20
|
+
}
|
|
21
|
+
export function buildProductNext(status) {
|
|
22
|
+
const blockers = status.blockers.filter((line) => line !== 'none');
|
|
23
|
+
const blocked = status.sddNextStatus === undefined ? blockers.length > 0 : status.sddNextStatus === 'blocked';
|
|
24
|
+
const visibleBlockers = blocked ? blockers : [];
|
|
25
|
+
const command = blocked ? buildBlockedNextCommand(status) : status.command;
|
|
26
|
+
return {
|
|
27
|
+
version: 1,
|
|
28
|
+
kind: 'product-next',
|
|
29
|
+
project: status.project,
|
|
30
|
+
...(status.change === undefined ? {} : { change: status.change }),
|
|
31
|
+
blocked,
|
|
32
|
+
next: status.next,
|
|
33
|
+
why: status.status,
|
|
34
|
+
blockers: visibleBlockers,
|
|
35
|
+
command,
|
|
36
|
+
...(status.relatedRunContext === undefined ? {} : { relatedRunContext: status.relatedRunContext }),
|
|
37
|
+
safety: status.safety,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function buildBlockedNextCommand(status) {
|
|
41
|
+
const change = status.change === undefined ? '<change>' : status.change;
|
|
42
|
+
const dbHint = status.command.includes('--db <path>') ? ' --db <path>' : '';
|
|
43
|
+
return `vgxness next --project ${status.project.value} --change ${change}${dbHint}`;
|
|
44
|
+
}
|
|
45
|
+
export function renderProductNext(next) {
|
|
46
|
+
const lines = [
|
|
47
|
+
'Next',
|
|
48
|
+
...next.next.map((line) => `- ${line}`),
|
|
49
|
+
'',
|
|
50
|
+
'Why',
|
|
51
|
+
...next.why.map((line) => `- ${line}`),
|
|
52
|
+
...(next.blockers.length === 0 ? [] : ['', 'Blockers', ...next.blockers.map((line) => `- ${line}`)]),
|
|
53
|
+
'',
|
|
54
|
+
'Command',
|
|
55
|
+
`- ${next.command}`,
|
|
56
|
+
'',
|
|
57
|
+
...(next.relatedRunContext === undefined ? [] : relatedRunContextLines(next.relatedRunContext, true)),
|
|
58
|
+
'Safety',
|
|
59
|
+
...next.safety.map((line) => `- ${line}`),
|
|
60
|
+
];
|
|
61
|
+
return `${lines.join('\n')}\n`;
|
|
62
|
+
}
|
|
63
|
+
function relatedRunContextLines(relatedRunContext, includeTrailingBlank) {
|
|
64
|
+
return [
|
|
65
|
+
'Related interrupted run:',
|
|
66
|
+
`- Run ID: ${relatedRunContext.runId}`,
|
|
67
|
+
`- Status: ${relatedRunContext.status}`,
|
|
68
|
+
`- Workflow/phase: ${relatedRunContext.workflow}/${relatedRunContext.phase}`,
|
|
69
|
+
`- Latest checkpoint: ${relatedRunContext.latestCheckpointLabel ?? 'none'}`,
|
|
70
|
+
`- Recommendation: ${relatedRunContext.recommendation}`,
|
|
71
|
+
`- Resume command: ${relatedRunContext.resumeCommand}`,
|
|
72
|
+
...(includeTrailingBlank ? [''] : []),
|
|
73
|
+
];
|
|
74
|
+
}
|
package/dist/cli/sdd-renderer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { normalizeSddArtifact, sddPhases } from '../sdd/schema.js';
|
|
1
|
+
import { normalizeSddArtifact, sddPhases, } from '../sdd/schema.js';
|
|
2
|
+
export { sddContinuationPlanFrom } from '../sdd/sdd-continuation-plan.js';
|
|
2
3
|
export function renderSddStatus(input) {
|
|
3
4
|
const missing = input.status.phases.filter((phase) => !phase.present).map((phase) => phase.topicKey);
|
|
4
5
|
const blockers = input.status.phases.filter((phase) => phase.present && phase.accepted !== true);
|
|
@@ -34,6 +35,7 @@ export function renderSddStatus(input) {
|
|
|
34
35
|
}
|
|
35
36
|
export function renderSddNext(input) {
|
|
36
37
|
const blockers = input.decision.blockedPrerequisites ?? [];
|
|
38
|
+
const guidance = input.decision.blockerGuidance ?? [];
|
|
37
39
|
const phase = input.decision.nextPhase ?? 'none';
|
|
38
40
|
const commandGuidance = input.decision.status === 'runnable' && input.decision.nextPhase !== undefined
|
|
39
41
|
? `vgxness code sdd ${input.decision.change} ${input.decision.nextPhase} --save-artifact`
|
|
@@ -47,17 +49,70 @@ export function renderSddNext(input) {
|
|
|
47
49
|
`Reason: ${input.decision.reason}`,
|
|
48
50
|
'',
|
|
49
51
|
'Blockers:',
|
|
50
|
-
...(blockers.length === 0
|
|
52
|
+
...(blockers.length === 0
|
|
53
|
+
? ['- none']
|
|
54
|
+
: blockers.map((blocker) => `- ${blocker.phase}: ${blocker.reason} at ${blocker.topicKey}${blocker.action === undefined ? '' : `; action=${blocker.action}`}`)),
|
|
51
55
|
'',
|
|
52
56
|
'Missing topic keys:',
|
|
53
57
|
...(input.decision.missingArtifactTopicKeys.length === 0 ? ['- none'] : input.decision.missingArtifactTopicKeys.map((topicKey) => `- ${topicKey}`)),
|
|
54
58
|
'',
|
|
59
|
+
'Missing prerequisite topic keys:',
|
|
60
|
+
...((input.decision.missingPrerequisiteTopicKeys ?? []).length === 0
|
|
61
|
+
? ['- none']
|
|
62
|
+
: (input.decision.missingPrerequisiteTopicKeys ?? []).map((topicKey) => `- ${topicKey}`)),
|
|
63
|
+
'',
|
|
64
|
+
'Next actions:',
|
|
65
|
+
...(guidance.length === 0 ? ['- none'] : guidance.map((item) => `- ${item.phase}: ${item.action}`)),
|
|
66
|
+
'',
|
|
55
67
|
`Recommended action: ${input.decision.recommendedAction}`,
|
|
56
68
|
`Command: ${commandGuidance}`,
|
|
57
69
|
`JSON: vgxness sdd next --project ${input.project} --change ${input.decision.change} --json`,
|
|
58
70
|
];
|
|
59
71
|
return `${lines.join('\n')}\n`;
|
|
60
72
|
}
|
|
73
|
+
export function renderSddContinuationPlan(plan) {
|
|
74
|
+
const lines = [
|
|
75
|
+
'SDD Continue (read-only)',
|
|
76
|
+
`Project: ${plan.project}`,
|
|
77
|
+
`Change: ${plan.change}`,
|
|
78
|
+
`Status: ${plan.status}`,
|
|
79
|
+
`Next phase: ${plan.nextPhase ?? 'none'}`,
|
|
80
|
+
`Actionable phase: ${plan.actionablePhase ?? 'none'}`,
|
|
81
|
+
`Reason: ${plan.reason}`,
|
|
82
|
+
'',
|
|
83
|
+
'Recommended plan:',
|
|
84
|
+
`- ${plan.recommendedAction}`,
|
|
85
|
+
`- Suggested command: ${plan.suggestedCommand}`,
|
|
86
|
+
`- Inspect command: ${plan.inspectCommand}`,
|
|
87
|
+
'',
|
|
88
|
+
'Blocker-specific next actions:',
|
|
89
|
+
...(plan.blockerActions.length === 0
|
|
90
|
+
? ['- none']
|
|
91
|
+
: plan.blockerActions.map((item) => {
|
|
92
|
+
const draftRun = item.draftRunCommand === undefined ? '' : `; draftRunCommand=${item.draftRunCommand}`;
|
|
93
|
+
const warning = item.warning === undefined ? '' : `; warning=${item.warning}`;
|
|
94
|
+
return `- ${item.phase}: ${item.action}; command=${item.command}${draftRun}${warning}`;
|
|
95
|
+
})),
|
|
96
|
+
'',
|
|
97
|
+
...(plan.relatedRunContext === undefined
|
|
98
|
+
? []
|
|
99
|
+
: [
|
|
100
|
+
'Related interrupted run:',
|
|
101
|
+
`- Run ID: ${plan.relatedRunContext.runId}`,
|
|
102
|
+
`- Status: ${plan.relatedRunContext.status}`,
|
|
103
|
+
`- Workflow/phase: ${plan.relatedRunContext.workflow}/${plan.relatedRunContext.phase}`,
|
|
104
|
+
`- Latest checkpoint: ${plan.relatedRunContext.latestCheckpointLabel ?? 'none'}`,
|
|
105
|
+
`- Recommendation: ${plan.relatedRunContext.recommendation}`,
|
|
106
|
+
`- Resume command: ${plan.relatedRunContext.resumeCommand}`,
|
|
107
|
+
'',
|
|
108
|
+
]),
|
|
109
|
+
'Safety:',
|
|
110
|
+
...plan.safety.map((line) => `- ${line}`),
|
|
111
|
+
'',
|
|
112
|
+
`JSON: vgxness sdd continue --project ${plan.project} --change ${plan.change} --json${continuationDbFlag(plan.explicitDatabasePath)}`,
|
|
113
|
+
];
|
|
114
|
+
return `${lines.join('\n')}\n`;
|
|
115
|
+
}
|
|
61
116
|
export function renderSddCockpit(cockpit) {
|
|
62
117
|
const blockers = cockpit.aggregateBlockers;
|
|
63
118
|
const lines = [
|
|
@@ -71,13 +126,18 @@ export function renderSddCockpit(cockpit) {
|
|
|
71
126
|
`Legacy artifacts: ${cockpit.legacyCount}`,
|
|
72
127
|
'',
|
|
73
128
|
'Aggregate blockers:',
|
|
74
|
-
...(blockers.length === 0
|
|
129
|
+
...(blockers.length === 0
|
|
130
|
+
? ['- none']
|
|
131
|
+
: blockers.map((blocker) => `- ${blocker.kind}: ${blocker.phase} at ${blocker.topicKey} - ${blocker.reason}${blocker.action === undefined ? '' : `; action=${blocker.action}`}`)),
|
|
75
132
|
'',
|
|
76
133
|
`Inspect: ${cockpit.inspectCommand}`,
|
|
77
134
|
`Cockpit JSON: ${cockpit.inspectCommand}`,
|
|
78
135
|
];
|
|
79
136
|
return `${lines.join('\n')}\n`;
|
|
80
137
|
}
|
|
138
|
+
function continuationDbFlag(explicitDatabasePath) {
|
|
139
|
+
return explicitDatabasePath === undefined ? '' : ` --db ${explicitDatabasePath}`;
|
|
140
|
+
}
|
|
81
141
|
export function renderSddArtifactAccepted(input) {
|
|
82
142
|
const lines = [
|
|
83
143
|
'SDD Artifact Accepted',
|
|
@@ -93,6 +153,23 @@ export function renderSddArtifactAccepted(input) {
|
|
|
93
153
|
];
|
|
94
154
|
return `${lines.join('\n')}\n`;
|
|
95
155
|
}
|
|
156
|
+
export function renderSddArtifactReopened(input) {
|
|
157
|
+
const lines = [
|
|
158
|
+
'SDD Artifact Reopened',
|
|
159
|
+
`- Project: ${input.project}`,
|
|
160
|
+
`- Change: ${input.change}`,
|
|
161
|
+
`- Phase: ${input.phase}`,
|
|
162
|
+
`- Status: ${input.status}`,
|
|
163
|
+
`- Topic key: ${input.topicKey}`,
|
|
164
|
+
`- Artifact ID: ${input.artifactId}`,
|
|
165
|
+
`- Reopened by: ${input.reopenedBy.displayName} (${input.reopenedBy.id})`,
|
|
166
|
+
`- Reopened at: ${input.reopenedAt}`,
|
|
167
|
+
...(input.note === undefined ? [] : [`- Note: ${input.note}`]),
|
|
168
|
+
`Next step: update the ${input.phase} artifact, then request human acceptance again.`,
|
|
169
|
+
`JSON: vgxness sdd reopen-artifact --project ${input.project} --change ${input.change} --phase ${input.phase} --actor ${input.reopenedBy.id} --json`,
|
|
170
|
+
];
|
|
171
|
+
return `${lines.join('\n')}\n`;
|
|
172
|
+
}
|
|
96
173
|
export function renderSddArtifactList(input) {
|
|
97
174
|
const artifactsByPhase = new Map(input.artifacts.map((artifact) => [artifact.phase, artifact]));
|
|
98
175
|
const hasArtifacts = input.artifacts.length > 0;
|
|
@@ -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');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withEffectiveManagerInstructions } from '../agents/canonical-agent-projection.js';
|
|
1
2
|
import { expectedClaudeCodeAgentFiles, inspectClaudeCodeAgents, renderClaudeCodeAgentMarkdown } from './claude-code-agent-config.js';
|
|
2
3
|
import { buildClaudeCodeMcpAddCommand } from './claude-code-cli.js';
|
|
3
4
|
import { createClaudeCodeMcpDoctorCommand, createClaudeCodeMcpServerConfig, inspectClaudeCodeMcpConfig, resolveClaudeCodeMcpJsonPath } from './claude-code-config.js';
|
|
@@ -63,11 +64,25 @@ export function planClaudeCodeMcpInstall(input) {
|
|
|
63
64
|
status: 'would_install',
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
|
-
export function expectedClaudeCodeRenderedAgents(workspaceRoot) {
|
|
67
|
-
|
|
67
|
+
export function expectedClaudeCodeRenderedAgents(workspaceRoot, options) {
|
|
68
|
+
const files = expectedClaudeCodeAgentFiles(workspaceRoot);
|
|
69
|
+
const projection = withEffectiveManagerInstructions({ defaultAgent: 'vgxness-manager', agents: files.map(({ path: _path, ...rest }) => rest) }, options?.effectiveManagerInstructions);
|
|
70
|
+
return projection.agents.map((agent) => {
|
|
71
|
+
const file = files.find((entry) => entry.name === agent.name);
|
|
72
|
+
if (file === undefined)
|
|
73
|
+
throw new Error(`Expected Claude agent file missing for ${agent.name}`);
|
|
74
|
+
return { path: file.path, agentName: agent.name, contents: renderClaudeCodeAgentMarkdown(agent) };
|
|
75
|
+
});
|
|
68
76
|
}
|
|
69
|
-
export function expectedClaudeCodeRenderedUserAgents(workspaceRoot, env) {
|
|
70
|
-
|
|
77
|
+
export function expectedClaudeCodeRenderedUserAgents(workspaceRoot, env, options) {
|
|
78
|
+
const files = expectedClaudeCodeAgentFiles({ workspaceRoot, scope: 'user', ...(env === undefined ? {} : { env }) });
|
|
79
|
+
const projection = withEffectiveManagerInstructions({ defaultAgent: 'vgxness-manager', agents: files.map(({ path: _path, ...rest }) => rest) }, options?.effectiveManagerInstructions);
|
|
80
|
+
return projection.agents.map((agent) => {
|
|
81
|
+
const file = files.find((entry) => entry.name === agent.name);
|
|
82
|
+
if (file === undefined)
|
|
83
|
+
throw new Error(`Expected Claude user agent file missing for ${agent.name}`);
|
|
84
|
+
return { path: file.path, agentName: agent.name, contents: renderClaudeCodeAgentMarkdown(agent) };
|
|
85
|
+
});
|
|
71
86
|
}
|
|
72
87
|
function planUserInstall(input, server, overwriteVgxness, cliCommand, scopeWarnings) {
|
|
73
88
|
const mcpState = inspectClaudeCodeUserMcpConfig(input.env);
|
|
@@ -62,7 +62,7 @@ export async function installClaudeCodeMcpClient(input) {
|
|
|
62
62
|
if (!mcpWrite.ok)
|
|
63
63
|
return refusal('post_write_validation_failed', mcpWrite.error.message, plan, server, writtenPaths, backups);
|
|
64
64
|
writtenPaths.push(mcpWrite.value);
|
|
65
|
-
for (const agent of expectedClaudeCodeRenderedAgents(input.cwd)) {
|
|
65
|
+
for (const agent of expectedClaudeCodeRenderedAgents(input.cwd, { effectiveManagerInstructions: input.effectiveManagerInstructions })) {
|
|
66
66
|
const target = plan.targets.find((item) => item.kind === 'agent-file' && item.path === agent.path);
|
|
67
67
|
if (target?.action !== 'create' && target?.action !== 'update-vgxness')
|
|
68
68
|
continue;
|
|
@@ -140,7 +140,7 @@ async function applyUserInstall(input, plan, server, writtenPaths, backups) {
|
|
|
140
140
|
if (!mcpWrite.ok)
|
|
141
141
|
return refusal('post_write_validation_failed', mcpWrite.error.message, plan, server, writtenPaths, backups);
|
|
142
142
|
writtenPaths.push(mcpWrite.value);
|
|
143
|
-
for (const agent of expectedClaudeCodeRenderedUserAgents(input.cwd, input.env)) {
|
|
143
|
+
for (const agent of expectedClaudeCodeRenderedUserAgents(input.cwd, input.env, { effectiveManagerInstructions: input.effectiveManagerInstructions })) {
|
|
144
144
|
const target = plan.targets.find((item) => item.kind === 'agent-file' && item.path === agent.path);
|
|
145
145
|
if (target?.action !== 'create' && target?.action !== 'update-vgxness')
|
|
146
146
|
continue;
|