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.
Files changed (54) hide show
  1. package/README.md +15 -5
  2. package/dist/agents/agent-activation-service.js +13 -4
  3. package/dist/agents/agent-registry-service.js +8 -2
  4. package/dist/agents/agent-resolver.js +33 -3
  5. package/dist/agents/agent-seed-upgrade-service.js +231 -0
  6. package/dist/agents/boot-upgrade.js +59 -0
  7. package/dist/agents/canonical-agent-manifest.js +39 -18
  8. package/dist/agents/canonical-agent-projection.js +38 -4
  9. package/dist/agents/manager-profile-overlay-service.js +14 -0
  10. package/dist/agents/repositories/agent-seed-history.js +128 -0
  11. package/dist/cli/cli-help.js +14 -3
  12. package/dist/cli/commands/index.js +1 -0
  13. package/dist/cli/commands/interactive-entrypoint-dispatcher.js +8 -0
  14. package/dist/cli/commands/mcp-dispatcher.js +7 -0
  15. package/dist/cli/commands/memory-sdd-dispatcher.js +71 -5
  16. package/dist/cli/commands/status-dispatcher.js +130 -0
  17. package/dist/cli/commands/workflow-dispatcher.js +11 -5
  18. package/dist/cli/dispatcher.js +11 -1
  19. package/dist/cli/product-resume-renderer.js +32 -0
  20. package/dist/cli/product-status-renderer.js +74 -0
  21. package/dist/cli/sdd-renderer.js +80 -3
  22. package/dist/code/cli/code-command.js +7 -4
  23. package/dist/code/reporting/summary.js +4 -1
  24. package/dist/code/runtime/code-runtime.js +27 -4
  25. package/dist/code/runtime/sdd-context.js +18 -2
  26. package/dist/governance/governance-report-builder.js +18 -7
  27. package/dist/mcp/claude-code-agent-config.js +10 -4
  28. package/dist/mcp/client-install-claude-code-contract.js +19 -4
  29. package/dist/mcp/client-install-claude-code.js +2 -2
  30. package/dist/mcp/control-plane.js +78 -5
  31. package/dist/mcp/provider-status.js +89 -88
  32. package/dist/mcp/schema.js +42 -8
  33. package/dist/mcp/stdio-server.js +6 -0
  34. package/dist/mcp/validation.js +77 -5
  35. package/dist/memory/sqlite/migrations/016_agent_seed_history.sql +15 -0
  36. package/dist/resume/product-resume.js +166 -0
  37. package/dist/runs/repositories/runs.js +12 -1
  38. package/dist/runs/run-service.js +62 -5
  39. package/dist/runs/schema.js +4 -0
  40. package/dist/sdd/schema.js +20 -0
  41. package/dist/sdd/sdd-continuation-plan.js +81 -0
  42. package/dist/sdd/sdd-workflow-service.js +146 -18
  43. package/dist/skills/skill-resolver.js +21 -4
  44. package/dist/status/product-status.js +117 -0
  45. package/docs/architecture.md +9 -1
  46. package/docs/cli.md +38 -13
  47. package/docs/code-runtime.md +3 -0
  48. package/docs/contributing.md +1 -1
  49. package/docs/glossary.md +2 -2
  50. package/docs/mcp.md +20 -6
  51. package/docs/project-health-audit-v1.9.1.md +126 -0
  52. package/docs/providers.md +4 -4
  53. package/docs/safety.md +1 -1
  54. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { canonicalAgentManifest, canonicalDefaultAgentName, canonicalOpenCodeManagerPrompt, canonicalPromptContractVersion, canonicalSddSubagentNames, createCanonicalOpenCodeSddSubagentPrompt, createCanonicalOpenCodeSddTaskPermissions, validateCanonicalAgentManifest, } from './canonical-agent-manifest.js';
1
+ import { canonicalAgentManifest, canonicalDefaultAgentName, canonicalOpenCodeManagerReasoningEffort, canonicalOpenCodeManagerPrompt, canonicalPromptContractVersion, canonicalSddSubagentNames, createCanonicalOpenCodeSddSubagentPrompt, createCanonicalOpenCodeSddTaskPermissions, validateCanonicalAgentManifest, } from './canonical-agent-manifest.js';
2
2
  export function canonicalManifestValidationErrors(manifest = canonicalAgentManifest) {
3
3
  const validation = validateCanonicalAgentManifest(manifest);
4
4
  return validation.ok ? [] : validation.errors;
@@ -19,10 +19,10 @@ export function projectCanonicalAgentManifestToOpenCode(manifest = canonicalAgen
19
19
  description: 'VGXNESS SDD Manager - coordinates MCP state and SDD sub-agents, avoids inline execution when delegation is safer',
20
20
  mode: 'primary',
21
21
  ...(manager.adapters?.opencode?.model !== undefined ? { model: manager.adapters.opencode.model } : {}),
22
- options: { reasoningEffort: 'high', vgxnessPromptContractVersion: canonicalPromptContractVersion },
23
- permission: { task: createCanonicalOpenCodeSddTaskPermissions() },
22
+ options: { reasoningEffort: canonicalOpenCodeManagerReasoningEffort, vgxnessPromptContractVersion: canonicalPromptContractVersion },
23
+ permission: openCodePermissionsFor(manager, { task: createCanonicalOpenCodeSddTaskPermissions() }),
24
24
  prompt: canonicalOpenCodeManagerPrompt,
25
- reasoningEffort: 'high',
25
+ reasoningEffort: canonicalOpenCodeManagerReasoningEffort,
26
26
  tools: { bash: true, delegate: true, delegation_list: true, delegation_read: true, edit: true, read: true, write: true },
27
27
  variant: '',
28
28
  },
@@ -38,6 +38,7 @@ export function projectCanonicalAgentManifestToOpenCode(manifest = canonicalAgen
38
38
  ...(subagent.adapters?.opencode?.model !== undefined ? { model: subagent.adapters.opencode.model } : {}),
39
39
  options: { vgxnessPromptContractVersion: canonicalPromptContractVersion },
40
40
  prompt: createCanonicalOpenCodeSddSubagentPrompt(name),
41
+ permission: openCodePermissionsFor(subagent),
41
42
  tools: { bash: true, edit: true, read: true, write: true },
42
43
  };
43
44
  }
@@ -73,6 +74,13 @@ export function projectCanonicalAgentManifestToClaudeCode(manifest = canonicalAg
73
74
  }
74
75
  return { defaultAgent: canonicalDefaultAgentName, agents };
75
76
  }
77
+ export function withEffectiveManagerInstructions(projection, instructions) {
78
+ const trimmed = instructions?.trim() ?? '';
79
+ if (trimmed === '')
80
+ return projection;
81
+ const agents = projection.agents.map((agent) => agent.canonicalName === projection.defaultAgent ? { ...agent, instructions: trimmed } : agent);
82
+ return { defaultAgent: projection.defaultAgent, agents };
83
+ }
76
84
  export function projectCanonicalAgentManifestToClaudeProjectMemory(manifest = canonicalAgentManifest) {
77
85
  assertValidCanonicalManifest(manifest);
78
86
  return {
@@ -105,6 +113,32 @@ function assertValidCanonicalManifest(manifest) {
105
113
  if (errors.length > 0)
106
114
  throw new Error(`Invalid canonical agent manifest: ${errors.join('; ')}`);
107
115
  }
116
+ function openCodePermissionsFor(agent, additional = {}) {
117
+ const adapterPermission = asRecord(agent.adapters?.opencode?.config?.permission);
118
+ return { ...adapterPermission, ...additional, ...mapCanonicalPermissionsToOpenCode(agent.permissions) };
119
+ }
120
+ function mapCanonicalPermissionsToOpenCode(permissions) {
121
+ const mapped = {};
122
+ if (permissions === undefined)
123
+ return mapped;
124
+ for (const [canonical, openCode] of openCodePermissionMappings) {
125
+ const decision = permissions[canonical];
126
+ if (decision !== undefined)
127
+ mapped[openCode] = decision;
128
+ }
129
+ return mapped;
130
+ }
131
+ function asRecord(value) {
132
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
133
+ return {};
134
+ return value;
135
+ }
136
+ const openCodePermissionMappings = [
137
+ ['read', 'read'],
138
+ ['edit', 'edit'],
139
+ ['shell', 'bash'],
140
+ ['external-directory', 'external_directory'],
141
+ ];
108
142
  function toSeedAgent(agent) {
109
143
  const seed = {
110
144
  name: agent.name,
@@ -1,3 +1,5 @@
1
+ import { AgentRegistryService } from './agent-registry-service.js';
2
+ import { ManagerProfileOverlayRepository } from './repositories/manager-profile-overlays.js';
1
3
  export class ManagerProfileOverlayService {
2
4
  dependencies;
3
5
  constructor(dependencies) {
@@ -36,3 +38,15 @@ function ok(value) {
36
38
  function validationFailure(message) {
37
39
  return { ok: false, error: { code: 'validation_failed', message } };
38
40
  }
41
+ export function computeEffectiveManagerInstructions(database, project = 'vgxness', scope = 'project', managerName = 'vgxness-manager') {
42
+ const registry = new AgentRegistryService(database);
43
+ const overlays = new ManagerProfileOverlayRepository(database);
44
+ const service = new ManagerProfileOverlayService({ agents: registry, overlays });
45
+ const resolved = service.resolveEffectiveManager({ project, scope, managerName });
46
+ if (!resolved.ok || resolved.value.overlay === undefined)
47
+ return undefined;
48
+ const overlayInstructions = resolved.value.overlay.instructions.trim();
49
+ if (overlayInstructions === '')
50
+ return undefined;
51
+ return overlayInstructions;
52
+ }
@@ -0,0 +1,128 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export class AgentSeedHistoryRepository {
3
+ db;
4
+ constructor(db) {
5
+ this.db = db;
6
+ }
7
+ append(input) {
8
+ const validation = validate(input);
9
+ if (!validation.ok)
10
+ return validation;
11
+ try {
12
+ const id = randomUUID();
13
+ const appliedAt = new Date().toISOString();
14
+ this.db.connection
15
+ .prepare(`
16
+ INSERT INTO agent_seed_history(id, applied_at, from_version, to_version, agent_name, project, scope, outcome, reason, source)
17
+ VALUES (@id, @appliedAt, @fromVersion, @toVersion, @agentName, @project, @scope, @outcome, @reason, @source)
18
+ `)
19
+ .run({
20
+ id,
21
+ appliedAt,
22
+ fromVersion: input.fromVersion,
23
+ toVersion: input.toVersion,
24
+ agentName: input.agentName,
25
+ project: input.project,
26
+ scope: input.scope,
27
+ outcome: input.outcome,
28
+ reason: input.reason ?? null,
29
+ source: input.source,
30
+ });
31
+ const entry = {
32
+ id,
33
+ appliedAt,
34
+ fromVersion: input.fromVersion,
35
+ toVersion: input.toVersion,
36
+ agentName: input.agentName,
37
+ project: input.project,
38
+ scope: input.scope,
39
+ outcome: input.outcome,
40
+ reason: input.reason ?? null,
41
+ source: input.source,
42
+ };
43
+ return ok(entry);
44
+ }
45
+ catch (cause) {
46
+ return fail('Failed to append agent_seed_history entry', cause);
47
+ }
48
+ }
49
+ listRecent(filters = {}) {
50
+ try {
51
+ const where = [];
52
+ const parameters = {};
53
+ for (const [key, column] of [
54
+ ['project', 'project'],
55
+ ['scope', 'scope'],
56
+ ['agentName', 'agent_name'],
57
+ ]) {
58
+ const value = filters[key];
59
+ if (value !== undefined) {
60
+ where.push(`${column}=@${key}`);
61
+ parameters[key] = value;
62
+ }
63
+ }
64
+ const limit = Math.max(1, Math.min(filters.limit ?? 100, 1000));
65
+ const rows = this.db.connection
66
+ .prepare(`
67
+ SELECT * FROM agent_seed_history
68
+ ${where.length ? `WHERE ${where.join(' AND ')}` : ''}
69
+ ORDER BY applied_at DESC
70
+ LIMIT ${limit}
71
+ `)
72
+ .all(parameters);
73
+ return ok(rows.map(map));
74
+ }
75
+ catch (cause) {
76
+ return fail('Failed to list agent_seed_history entries', cause);
77
+ }
78
+ }
79
+ }
80
+ function validate(input) {
81
+ if (!input.project.trim())
82
+ return validationFailure('agent_seed_history project is required');
83
+ if (input.scope !== 'project' && input.scope !== 'personal')
84
+ return validationFailure('agent_seed_history scope is invalid');
85
+ if (!input.agentName.trim())
86
+ return validationFailure('agent_seed_history agentName is required');
87
+ if (!Number.isInteger(input.toVersion) || input.toVersion <= 0)
88
+ return validationFailure('agent_seed_history toVersion must be a positive integer');
89
+ if (input.fromVersion !== null && (!Number.isInteger(input.fromVersion) || input.fromVersion < 0))
90
+ return validationFailure('agent_seed_history fromVersion must be null or non-negative integer');
91
+ if (!input.source.trim())
92
+ return validationFailure('agent_seed_history source is required');
93
+ if (input.outcome !== 'created' &&
94
+ input.outcome !== 'upgraded' &&
95
+ input.outcome !== 'noop' &&
96
+ input.outcome !== 'overwrote-custom-instructions' &&
97
+ input.outcome !== 'validation_failed' &&
98
+ input.outcome !== 'db_error') {
99
+ return validationFailure(`agent_seed_history outcome is invalid: ${input.outcome}`);
100
+ }
101
+ return ok(undefined);
102
+ }
103
+ function map(row) {
104
+ return {
105
+ id: String(row.id),
106
+ appliedAt: String(row.applied_at),
107
+ fromVersion: row.from_version === null || row.from_version === undefined ? null : Number(row.from_version),
108
+ toVersion: Number(row.to_version),
109
+ agentName: String(row.agent_name),
110
+ project: String(row.project),
111
+ scope: row.scope,
112
+ outcome: String(row.outcome),
113
+ reason: row.reason === null || row.reason === undefined ? null : String(row.reason),
114
+ source: String(row.source),
115
+ };
116
+ }
117
+ function ok(value) {
118
+ return { ok: true, value };
119
+ }
120
+ function validationFailure(message) {
121
+ return { ok: false, error: { code: 'validation_failed', message } };
122
+ }
123
+ function fail(message, cause) {
124
+ const error = { code: 'validation_failed', message };
125
+ if (cause !== undefined)
126
+ error.cause = cause;
127
+ return { ok: false, error };
128
+ }
@@ -6,6 +6,9 @@ Global flags:
6
6
  --version, -v Print the installed package version.
7
7
 
8
8
  Areas:
9
+ status [--project <name>] [--change <id>] [--db <path>] [--json]
10
+ next [--project <name>] [--change <id>] [--db <path>] [--json]
11
+ resume [--project <name>] [--run-id <id>] [--db <path>] [--json]
9
12
  init [--project <name>] [--provider opencode|claude|none] [--scope user|project] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents] [--json]
10
13
  setup plan [--project <name>] [--provider opencode|claude|none] [--scope user|project] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents] [--json]
11
14
  setup apply --yes [--project <name>] [--provider opencode|claude|none] [--scope user|project] [--db global|project-local|custom|<path>] [--db-path <path>] [--mode mcp-only|mcp-plus-agents]
@@ -16,6 +19,10 @@ Areas:
16
19
  verification plan --type docs-only|test-only|cli|mcp|sdd-storage|provider-setup|package-release|workflow-runs [--json]
17
20
  verification report save --project <name> --change <id> --file <report.json> [--db <path>] [--json]
18
21
  verification report get --project <name> --change <id> [--db <path>] [--json]
22
+ Status answers "where am I?" with the human front-door cockpit.
23
+ Next answers "what should I do now?" with a shorter next-action view.
24
+ Resume answers "how do I continue interrupted work?" with run inspection guidance.
25
+ Without --change or --run-id they stay orientation-only and do not open local memory; with --change or --run-id they read SQLite read-only. Pass --json for automation.
19
26
  Setup plan/init default to human-readable read-only output; pass --json for automation. Setup apply writes OpenCode config only with --yes and uses the global user DB plus mcp-plus-agents by default.
20
27
  Setup status defaults to human-readable read-only output; pass --json for automation. It never writes provider config or executes providers.
21
28
  Doctor defaults to human-readable output; pass --json for automation.
@@ -38,8 +45,8 @@ Areas:
38
45
  code plan "<task>" [--json|--events-jsonl] [--max-source-bytes <n>] [--provider fake|openai-compatible] [--model <id>] [--stream] [--transcript off|summary|full] [--memory off|ask|auto]
39
46
  code craft-preview "<task>" [--events-jsonl] [--provider fake|openai-compatible] [--model <id>] [--stream]
40
47
  code craft "<task>" [--json|--events-jsonl --approval-channel stdio] [--max-source-bytes <n>] [--provider fake|openai-compatible] [--model <id>] [--stream] [--approval-policy ask|allow|deny] [--verification none|suggest|run|repair] [--transcript off|summary|full] [--memory off|ask|auto]
41
- code sdd <change> <phase> [--json] [--save-artifact] [--provider fake|openai-compatible] [--model <id>] [--stream] [--verification none|suggest|run|repair] [--transcript off|summary|full] [--memory off|ask|auto]
42
- VGXNESS Code inspect and plan are native read-only repository flows. Craft is bounded edit-capable work with approval-gated edits and shell verification; craft JSONL approvals require explicit --approval-channel stdio. SDD mode follows phase-specific artifact boundaries and saves only with --save-artifact.
48
+ code sdd <change> <phase> [--json] [--save-artifact] [--draft-run] [--provider fake|openai-compatible] [--model <id>] [--stream] [--verification none|suggest|run|repair] [--transcript off|summary|full] [--memory off|ask|auto]
49
+ VGXNESS Code inspect and plan are native read-only repository flows. Craft is bounded edit-capable work with approval-gated edits and shell verification; craft JSONL approvals require explicit --approval-channel stdio. SDD mode follows phase-specific artifact boundaries and saves only with --save-artifact. --draft-run lets planning phases use draft prerequisites only; human acceptance is still required and apply-progress remains gated.
43
50
 
44
51
  orchestrator preview --project <name> --intent <text> [--change <id>] [--db <path>]
45
52
  Orchestrator preview classifies natural-language requests only; it never executes providers, edits files, records runs, or writes provider config.
@@ -103,16 +110,20 @@ Areas:
103
110
 
104
111
  sdd status --project <name> --change <id> [--json]
105
112
  sdd next --project <name> --change <id> [--json]
113
+ sdd continue --project <name> --change <id> [--db <path>] [--json]
106
114
  sdd cockpit --project <name> --change <id> [--json]
107
115
  sdd ready --project <name> --change <id> --phase <phase>
108
116
  sdd save-artifact --project <name> --change <id> --phase <phase> --content <text>
109
117
  sdd accept-artifact --project <name> --change <id> --phase <phase> --actor <human-id> [--display-name <name>] [--note <text>] [--accepted-at <iso>] [--json] [--db <path>]
118
+ sdd reopen-artifact --project <name> --change <id> --phase <phase> --actor <human-id> [--display-name <name>] [--note <text>] [--json] [--db <path>]
110
119
  sdd get-artifact --project <name> --change <id> --phase <phase> [--json] [--db <path>]
111
120
  sdd list-artifacts --project <name> --change <id> [--json] [--db <path>]
112
121
  SDD artifact reads use the selected local SQLite memory store via --db, VGXNESS_DB_PATH, or the global default.
113
- SDD status, next, get-artifact, and list-artifacts default to human-readable output; pass --json for existing automation shapes.
122
+ SDD status, next, continue, get-artifact, and list-artifacts default to human-readable output; pass --json for existing automation shapes.
123
+ SDD continue is a read-only continuation planner; it never executes providers, mutates artifacts, creates runs, writes provider config, or creates openspec/.
114
124
  SDD cockpit defaults to human-readable read-only output; pass --json for metadata-only artifact summaries.
115
125
  SDD accept-artifact records explicit human-only acceptance; save-artifact remains draft-only and never implies acceptance. --accepted-at requires a timezone and is normalized to UTC ISO. Human output is default; pass --json for content-free success output.
126
+ SDD reopen-artifact records explicit human-only reopening of rejected artifacts only, moving them back to draft so they can be edited and accepted again.
116
127
  sdd export --project <name> --change <id> [--file <package.json>]
117
128
  sdd import --project <name> --change <id> --file <package.json> [--write] [--overwrite]
118
129
 
@@ -3,6 +3,7 @@ export { runCodeCliCommand, runDefaultInteractiveEntrypoint } from './interactiv
3
3
  export { runDoctorAliasCommand, runMcpDoctorCommand, runMcpDoctorOpenCodeCommand, runMcpInstallCommand, runMcpSetupCommand } from './mcp-dispatcher.js';
4
4
  export { runMemoryCommand, runMemoryImportCommand, runOpenCodeCommand, runOrchestratorCommand, runSddCommand } from './memory-sdd-dispatcher.js';
5
5
  export { runApprovalsCommand, runPermissionsCommand, runRunsCommand } from './run-permission-dispatcher.js';
6
+ export { runProductNextCommand, runProductResumeCommand, runProductStatusCommand } from './status-dispatcher.js';
6
7
  export { applySetupPlanInput, collectRunDetails, collectRunInsights, createSetupLifecycleService, promptSetupWizard, readSetupStatus, runInitCommand, runSetupApplyCommand, runSetupBackupCommand, runSetupLifecycleCommand, runSetupPlanCommand, runSetupRollbackCommand, runSetupTuiCommand, setupMcpEvidence, setupPlanInputFromFlags, } from './setup-dispatcher.js';
7
8
  export { runWorkflowExecuteCommand, runWorkflowPreviewCommand, runWorkflowRunCommand } from './workflow-dispatcher.js';
8
9
  export { runVerificationPlanCommand, runVerificationReportCommand } from './verification-dispatcher.js';
@@ -69,6 +69,12 @@ export async function runCodeCliCommand(parsed, environment) {
69
69
  return usageFailure('--approval-channel stdio is supported only for code craft --events-jsonl');
70
70
  if (eventsJsonl && command !== 'inspect' && command !== 'plan' && command !== 'craft-preview' && approvalChannel.value !== 'stdio')
71
71
  return usageFailure('code craft --events-jsonl requires --approval-channel stdio; JSONL without approvals is currently supported only for read-only inspect, plan, and craft-preview');
72
+ const draftRunFlag = parsed.flags['draft-run'];
73
+ if (draftRunFlag !== undefined && command !== 'sdd')
74
+ return usageFailure('--draft-run is supported only for code sdd');
75
+ if (draftRunFlag !== undefined && draftRunFlag !== true)
76
+ return usageFailure('--draft-run does not accept a value; use --draft-run for code sdd draft mode');
77
+ const sddRuntimeMode = draftRunFlag === true ? 'draft-run' : undefined;
72
78
  const maxSourceBytes = optionalNumberFlag(parsed.flags, 'max-source-bytes');
73
79
  if (!maxSourceBytes.ok)
74
80
  return resultFailure(maxSourceBytes);
@@ -109,6 +115,7 @@ export async function runCodeCliCommand(parsed, environment) {
109
115
  ...(approvalPolicy.value === undefined ? {} : { approvalPolicy: approvalPolicy.value }),
110
116
  ...(verificationMode.value === undefined ? {} : { verificationMode: verificationMode.value }),
111
117
  ...(transcriptMode.value === undefined ? {} : { transcriptMode: transcriptMode.value }),
118
+ ...(sddRuntimeMode === undefined ? {} : { sddRuntimeMode }),
112
119
  });
113
120
  }
114
121
  const selectedDatabasePath = databasePathFor(parsed.flags, environment);
@@ -135,6 +142,7 @@ export async function runCodeCliCommand(parsed, environment) {
135
142
  env: environment.env,
136
143
  eventsJsonl,
137
144
  persistArtifact: parsed.flags['save-artifact'] === true || parsed.flags.persist === true,
145
+ ...(sddRuntimeMode === undefined ? {} : { sddRuntimeMode }),
138
146
  ...(maxSourceBytes.value !== undefined ? { maxSourceBytes: maxSourceBytes.value } : {}),
139
147
  ...(approvalPolicy.value === undefined ? {} : { approvalPolicy: approvalPolicy.value }),
140
148
  ...(verificationMode.value === undefined ? {} : { verificationMode: verificationMode.value }),
@@ -6,7 +6,9 @@ import { createMcpClientSetupPreview, isMcpClientSetupProvider } from '../../mcp
6
6
  import { createMcpDoctorReport } from '../../mcp/doctor.js';
7
7
  import { createOpenCodeMcpVisibilityReport } from '../../mcp/opencode-visibility.js';
8
8
  import { resolveClaudeCodeScope } from '../../mcp/claude-code-scope.js';
9
+ import { computeEffectiveManagerInstructions } from '../../agents/manager-profile-overlay-service.js';
9
10
  import { RunService } from '../../runs/run-service.js';
11
+ import { isTerminalRunStatus } from '../../runs/schema.js';
10
12
  import { databasePathFor, databasePathSelectionFor, opencodeInstallScopeFlag, optionalNumberFlag, optionalStringFlag } from '../cli-flags.js';
11
13
  import { usageFailure, validationFailure } from '../cli-help.js';
12
14
  import { jsonResult, openCliDatabase, resultFailure } from '../cli-helpers.js';
@@ -42,6 +44,7 @@ export function runMcpInstallCommand(parsed, environment) {
42
44
  return resultFailure(opened);
43
45
  try {
44
46
  const preflight = createClaudeCodeInstallPreflight(parsed, opened.value, environment);
47
+ const effectiveManagerInstructions = computeEffectiveManagerInstructions(opened.value);
45
48
  const result = await installClaudeCodeMcpClient({
46
49
  cwd: environment.cwd,
47
50
  databasePath: databasePath.value.path,
@@ -50,6 +53,7 @@ export function runMcpInstallCommand(parsed, environment) {
50
53
  overwriteVgxness,
51
54
  scope: claudeScope.value,
52
55
  preflight,
56
+ ...(effectiveManagerInstructions === undefined ? {} : { effectiveManagerInstructions }),
53
57
  });
54
58
  return result.status === 'installed' ? jsonResult({ ok: true, value: result }) : resultFailure({ ok: false, error: { code: 'validation_failed', message: `${result.reason}: ${result.message}` } });
55
59
  }
@@ -107,6 +111,9 @@ function createClaudeCodeInstallPreflight(parsed, database, environment) {
107
111
  const details = service.getRun(runId);
108
112
  if (!details.ok)
109
113
  return details;
114
+ if (isTerminalRunStatus(details.value.status)) {
115
+ return validationFailure(`VGXNESS run ${runId} is in terminal state '${details.value.status}'; Claude Code provider config writes require a live run (created/planned/running/needs-human). Start a new run with \`vgxness run start\` and pass its --run-id.`);
116
+ }
110
117
  const phase = optionalStringFlag(parsed.flags, 'phase') ?? details.value.phase;
111
118
  const agentId = optionalStringFlag(parsed.flags, 'agent-id') ?? details.value.selectedAgentId;
112
119
  const preflight = service.preflightOperation({
@@ -6,14 +6,15 @@ import { planMemoryImportDryRun, validateMemoryImportPackage, writeMemoryImportO
6
6
  import { MemoryService } from '../../memory/memory-service.js';
7
7
  import { createNaturalLanguagePlan } from '../../orchestrator/natural-language-planner.js';
8
8
  import { OpenCodeInjectionPreviewService } from '../../providers/opencode/injection-preview.js';
9
+ import { RunService } from '../../runs/run-service.js';
9
10
  import { ArtifactPortabilityService } from '../../sdd/artifact-portability-service.js';
10
- import { isSddPhase, sddPhases } from '../../sdd/schema.js';
11
+ import { normalizeSddArtifact, normalizeSddPhaseInput, sddPhases } from '../../sdd/schema.js';
11
12
  import { SddWorkflowService } from '../../sdd/sdd-workflow-service.js';
12
13
  import { SkillRegistryService } from '../../skills/skill-registry-service.js';
13
14
  import { acceptedAtFlag, databasePathSelectionFor, optionalNumberFlag, optionalScopeFlag, optionalStringFlag, optionalTrimmedFlag, requiredFlag, requiredTrimmedFlag, scopeFlag, } from '../cli-flags.js';
14
15
  import { okText, usageFailure, validationFailure } from '../cli-help.js';
15
16
  import { formatMemoryImportValidationErrors, jsonResult, openCliDatabase, readJsonFile, resultFailure, validateMemoryImportMode, writeJsonFile, } from '../cli-helpers.js';
16
- import { renderSddArtifactAccepted, renderSddCockpit, renderSddArtifactDetail, renderSddArtifactList, renderSddNext, renderSddStatus, } from '../sdd-renderer.js';
17
+ import { renderSddArtifactAccepted, renderSddArtifactReopened, renderSddCockpit, renderSddArtifactDetail, renderSddArtifactList, renderSddContinuationPlan, renderSddNext, renderSddStatus, sddContinuationPlanFrom, } from '../sdd-renderer.js';
17
18
  export function runMemoryCommand(command, parsed, database) {
18
19
  const handlers = createMemoryToolHandlers({ service: new MemoryService(database), config: { localMemoryEnabled: true } });
19
20
  const context = { actor: 'cli' };
@@ -122,6 +123,26 @@ export function runSddCommand(command, parsed, database, environment) {
122
123
  return jsonResult(next);
123
124
  return okText(renderSddNext({ project: project.value, decision: next.value }));
124
125
  }
126
+ if (command === 'continue') {
127
+ const next = service.getNext({ project: project.value, change: change.value });
128
+ if (!next.ok)
129
+ return jsonResult(next);
130
+ const cockpit = service.getCockpit({ project: project.value, change: change.value });
131
+ if (!cockpit.ok)
132
+ return jsonResult(cockpit);
133
+ const explicitDatabasePath = optionalStringFlag(parsed.flags, 'db');
134
+ const relatedRun = new RunService(database).findRelatedInterruptedSddRun({ project: project.value, change: change.value });
135
+ if (!relatedRun.ok)
136
+ return jsonResult(relatedRun);
137
+ const plan = sddContinuationPlanFrom({
138
+ project: project.value,
139
+ next: next.value,
140
+ cockpit: cockpit.value,
141
+ ...(relatedRun.value === undefined ? {} : { relatedRunContext: relatedRun.value }),
142
+ ...(explicitDatabasePath === undefined ? {} : { explicitDatabasePath }),
143
+ });
144
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: plan }) : okText(renderSddContinuationPlan(plan));
145
+ }
125
146
  if (command === 'cockpit') {
126
147
  const cockpit = service.getCockpit({ project: project.value, change: change.value });
127
148
  if (!cockpit.ok || parsed.flags.json === true)
@@ -159,7 +180,8 @@ export function runSddCommand(command, parsed, database, environment) {
159
180
  return resultFailure(note);
160
181
  if (!acceptedAt.ok)
161
182
  return resultFailure(acceptedAt);
162
- if (!isSddPhase(phase.value))
183
+ const canonicalPhase = normalizeSddPhaseInput(phase.value);
184
+ if (canonicalPhase === undefined)
163
185
  return resultFailure(validationFailure(`Unknown SDD phase: ${phase.value}. Expected one of: ${sddPhases.join(', ')}`));
164
186
  const acceptedBy = {
165
187
  type: 'human',
@@ -169,7 +191,7 @@ export function runSddCommand(command, parsed, database, environment) {
169
191
  const accepted = service.acceptArtifact({
170
192
  project: project.value,
171
193
  change: change.value,
172
- phase: phase.value,
194
+ phase: canonicalPhase,
173
195
  acceptedBy,
174
196
  acceptedAt: acceptedAt.value,
175
197
  ...(note.value === undefined ? {} : { note: note.value }),
@@ -179,7 +201,7 @@ export function runSddCommand(command, parsed, database, environment) {
179
201
  const view = {
180
202
  project: accepted.value.project,
181
203
  change: change.value,
182
- phase: phase.value,
204
+ phase: canonicalPhase,
183
205
  topicKey: accepted.value.topicKey,
184
206
  artifactId: accepted.value.id,
185
207
  status: 'accepted',
@@ -189,6 +211,50 @@ export function runSddCommand(command, parsed, database, environment) {
189
211
  };
190
212
  return parsed.flags.json === true ? jsonResult({ ok: true, value: view }) : okText(renderSddArtifactAccepted(view));
191
213
  }
214
+ if (command === 'reopen-artifact') {
215
+ const phase = requiredTrimmedFlag(parsed.flags, 'phase');
216
+ const actor = requiredTrimmedFlag(parsed.flags, 'actor');
217
+ const displayName = optionalTrimmedFlag(parsed.flags, 'display-name');
218
+ const note = optionalTrimmedFlag(parsed.flags, 'note');
219
+ if (!phase.ok)
220
+ return resultFailure(phase);
221
+ if (!actor.ok)
222
+ return resultFailure(actor);
223
+ if (!displayName.ok)
224
+ return resultFailure(displayName);
225
+ if (!note.ok)
226
+ return resultFailure(note);
227
+ const canonicalPhase = normalizeSddPhaseInput(phase.value);
228
+ if (canonicalPhase === undefined)
229
+ return resultFailure(validationFailure(`Unknown SDD phase: ${phase.value}. Expected one of: ${sddPhases.join(', ')}`));
230
+ const reopenedBy = {
231
+ type: 'human',
232
+ id: actor.value,
233
+ displayName: displayName.value ?? actor.value,
234
+ };
235
+ const reopened = service.reopenArtifact({
236
+ project: project.value,
237
+ change: change.value,
238
+ phase: canonicalPhase,
239
+ reopenedBy,
240
+ ...(note.value === undefined ? {} : { note: note.value }),
241
+ });
242
+ if (!reopened.ok)
243
+ return resultFailure(reopened);
244
+ const normalized = normalizeSddArtifact(reopened.value);
245
+ const view = {
246
+ project: reopened.value.project,
247
+ change: change.value,
248
+ phase: canonicalPhase,
249
+ topicKey: reopened.value.topicKey,
250
+ artifactId: reopened.value.id,
251
+ status: normalized.metadata.status,
252
+ reopenedBy,
253
+ reopenedAt: normalized.metadata.reopen?.reopenedAt ?? normalized.metadata.updatedAt ?? reopened.value.updatedAt,
254
+ ...(note.value === undefined ? {} : { note: note.value }),
255
+ };
256
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: view }) : okText(renderSddArtifactReopened(view));
257
+ }
192
258
  if (command === 'get-artifact') {
193
259
  const phase = requiredFlag(parsed.flags, 'phase');
194
260
  if (!phase.ok)
@@ -0,0 +1,130 @@
1
+ import { MemoryService } from '../../memory/memory-service.js';
2
+ import { openMemoryDatabase } from '../../memory/sqlite/database.js';
3
+ import { SddWorkflowService } from '../../sdd/sdd-workflow-service.js';
4
+ import { buildProductStatus } from '../../status/product-status.js';
5
+ import { RunService } from '../../runs/run-service.js';
6
+ import { buildProductResume } from '../../resume/product-resume.js';
7
+ import { databasePathFor, optionalStringFlag, requiredFlag } from '../cli-flags.js';
8
+ import { jsonResult, resultFailure } from '../cli-helpers.js';
9
+ import { okText, usageFailure } from '../cli-help.js';
10
+ import { renderProductResume } from '../product-resume-renderer.js';
11
+ import { buildProductNext, renderProductNext, renderProductStatus } from '../product-status-renderer.js';
12
+ export function runProductStatusCommand(parsed, environment) {
13
+ const extraPositionals = rejectExtraPositionals(parsed, 'status');
14
+ if (extraPositionals !== undefined)
15
+ return extraPositionals;
16
+ const status = buildProductStatusForCli(parsed, environment);
17
+ if (!status.ok)
18
+ return status.value;
19
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: status.value }) : okText(renderProductStatus(status.value));
20
+ }
21
+ export function runProductNextCommand(parsed, environment) {
22
+ const extraPositionals = rejectExtraPositionals(parsed, 'next');
23
+ if (extraPositionals !== undefined)
24
+ return extraPositionals;
25
+ const status = buildProductStatusForCli(parsed, environment);
26
+ if (!status.ok)
27
+ return status.value;
28
+ const next = buildProductNext(status.value);
29
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: next }) : okText(renderProductNext(next));
30
+ }
31
+ export function runProductResumeCommand(parsed, environment) {
32
+ const extraPositionals = rejectExtraPositionals(parsed, 'resume');
33
+ if (extraPositionals !== undefined)
34
+ return extraPositionals;
35
+ const resume = buildProductResumeForCli(parsed, environment);
36
+ if (!resume.ok)
37
+ return resume.value;
38
+ return parsed.flags.json === true ? jsonResult({ ok: true, value: resume.value }) : okText(renderProductResume(resume.value));
39
+ }
40
+ function rejectExtraPositionals(parsed, command) {
41
+ const extra = parsed.positionals.slice(1);
42
+ return extra.length === 0 ? undefined : usageFailure(`${command} does not accept positional arguments: ${extra.join(' ')}`);
43
+ }
44
+ function buildProductStatusForCli(parsed, environment) {
45
+ const projectFlag = parsed.flags.project === undefined ? { ok: true, value: undefined } : requiredFlag(parsed.flags, 'project');
46
+ if (!projectFlag.ok)
47
+ return { ok: false, value: resultFailure(projectFlag) };
48
+ const changeFlag = parsed.flags.change === undefined ? { ok: true, value: undefined } : requiredFlag(parsed.flags, 'change');
49
+ if (!changeFlag.ok)
50
+ return { ok: false, value: resultFailure(changeFlag) };
51
+ const project = projectFlag.value;
52
+ const change = changeFlag.value;
53
+ if (change === undefined) {
54
+ const status = buildProductStatus({ cwd: environment.cwd, ...(project === undefined ? {} : { project }) });
55
+ return { ok: true, value: status };
56
+ }
57
+ const selectedDatabasePath = databasePathFor(parsed.flags, environment);
58
+ if (!selectedDatabasePath.ok)
59
+ return { ok: false, value: resultFailure(selectedDatabasePath) };
60
+ const explicitDatabasePath = optionalStringFlag(parsed.flags, 'db');
61
+ const opened = openMemoryDatabase({ path: selectedDatabasePath.value, readonly: true });
62
+ if (!opened.ok) {
63
+ const status = buildProductStatus({
64
+ cwd: environment.cwd,
65
+ ...(project === undefined ? {} : { project }),
66
+ change,
67
+ databasePath: selectedDatabasePath.value,
68
+ databaseError: opened.error.message,
69
+ });
70
+ return { ok: true, value: status };
71
+ }
72
+ try {
73
+ const status = buildProductStatus({
74
+ cwd: environment.cwd,
75
+ ...(project === undefined ? {} : { project }),
76
+ change,
77
+ databasePath: selectedDatabasePath.value,
78
+ ...(explicitDatabasePath === undefined ? {} : { explicitDatabasePath }),
79
+ sdd: new SddWorkflowService(new MemoryService(opened.value)),
80
+ runs: new RunService(opened.value),
81
+ });
82
+ return { ok: true, value: status };
83
+ }
84
+ finally {
85
+ opened.value.close();
86
+ }
87
+ }
88
+ function buildProductResumeForCli(parsed, environment) {
89
+ const projectFlag = parsed.flags.project === undefined ? { ok: true, value: undefined } : requiredFlag(parsed.flags, 'project');
90
+ if (!projectFlag.ok)
91
+ return { ok: false, value: resultFailure(projectFlag) };
92
+ const runIdFlag = parsed.flags['run-id'] === undefined ? { ok: true, value: undefined } : requiredFlag(parsed.flags, 'run-id');
93
+ if (!runIdFlag.ok)
94
+ return { ok: false, value: resultFailure(runIdFlag) };
95
+ const project = projectFlag.value;
96
+ const runId = runIdFlag.value;
97
+ if (runId === undefined && project === undefined) {
98
+ const resume = buildProductResume({ cwd: environment.cwd, ...(project === undefined ? {} : { project }) });
99
+ return { ok: true, value: resume };
100
+ }
101
+ const selectedDatabasePath = databasePathFor(parsed.flags, environment);
102
+ if (!selectedDatabasePath.ok)
103
+ return { ok: false, value: resultFailure(selectedDatabasePath) };
104
+ const opened = openMemoryDatabase({ path: selectedDatabasePath.value, readonly: true });
105
+ if (!opened.ok) {
106
+ const visibleDatabasePath = runId === undefined && parsed.flags.db === undefined ? undefined : selectedDatabasePath.value;
107
+ const resume = buildProductResume({
108
+ cwd: environment.cwd,
109
+ ...(project === undefined ? {} : { project }),
110
+ ...(runId === undefined ? {} : { runId }),
111
+ ...(visibleDatabasePath === undefined ? {} : { databasePath: visibleDatabasePath }),
112
+ databaseError: opened.error.message,
113
+ });
114
+ return { ok: true, value: resume };
115
+ }
116
+ try {
117
+ const flaggedDatabasePath = parsed.flags.db === undefined ? undefined : selectedDatabasePath.value;
118
+ const resume = buildProductResume({
119
+ cwd: environment.cwd,
120
+ ...(project === undefined ? {} : { project }),
121
+ ...(runId === undefined ? {} : { runId }),
122
+ ...(flaggedDatabasePath === undefined ? {} : { databasePath: flaggedDatabasePath }),
123
+ runs: new RunService(opened.value),
124
+ });
125
+ return { ok: true, value: resume };
126
+ }
127
+ finally {
128
+ opened.value.close();
129
+ }
130
+ }
@@ -1,7 +1,8 @@
1
1
  import { AgentRegistryService } from '../../agents/agent-registry-service.js';
2
+ import { canonicalOpenCodeDefaultModel } from '../../agents/canonical-agent-manifest.js';
2
3
  import { createNaturalLanguagePlan } from '../../orchestrator/natural-language-planner.js';
3
4
  import { RunService } from '../../runs/run-service.js';
4
- import { isSddPhase, sddPhases } from '../../sdd/schema.js';
5
+ import { normalizeSddPhaseInput, sddPhases } from '../../sdd/schema.js';
5
6
  import { CommandAllowlistAdapter, commandAllowlistIds } from '../../workflows/command-allowlist-adapter.js';
6
7
  import { GuardedProviderWorkflowExecutor, operationMetadataForWorkflowExecution, SafeNonDispatchingWorkflowExecutor, workflowExecutorSafety, } from '../../workflows/workflow-executor.js';
7
8
  import { getWorkflowDefinition, listWorkflows } from '../../workflows/workflow-registry.js';
@@ -248,11 +249,16 @@ export function runWorkflowRunCommand(workflow, parsed, database) {
248
249
  const selected = getWorkflowDefinition(workflow);
249
250
  const planner = createNaturalLanguagePlan({ project, intent: intent.value });
250
251
  const recommended = getWorkflowDefinition(planner.workflow);
251
- const phase = optionalStringFlag(parsed.flags, 'phase') ?? selected.defaultPhase;
252
- if (selected.id === 'sdd' && !isSddPhase(phase))
253
- return resultFailure(validationFailure(`Unknown SDD phase: ${phase}. Expected one of: ${sddPhases.join(', ')}`));
252
+ const phaseInput = optionalStringFlag(parsed.flags, 'phase') ?? selected.defaultPhase;
253
+ let phase = phaseInput;
254
+ if (selected.id === 'sdd') {
255
+ const canonicalPhase = normalizeSddPhaseInput(phaseInput);
256
+ if (canonicalPhase === undefined)
257
+ return resultFailure(validationFailure(`Unknown SDD phase: ${phaseInput}. Expected one of: ${sddPhases.join(', ')}`));
258
+ phase = canonicalPhase;
259
+ }
254
260
  const providerAdapter = optionalStringFlag(parsed.flags, 'provider-adapter') ?? 'opencode';
255
- const model = optionalStringFlag(parsed.flags, 'model') ?? 'openai/gpt-5.5';
261
+ const model = optionalStringFlag(parsed.flags, 'model') ?? canonicalOpenCodeDefaultModel;
256
262
  const registry = new AgentRegistryService(database);
257
263
  const explicitAgentId = optionalStringFlag(parsed.flags, 'agent-id');
258
264
  const selectedAgent = resolveWorkflowRunAgent({