gmc-openspec 1.1.0 → 1.4.1

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 (110) hide show
  1. package/README.md +2 -2
  2. package/bin/openspec.js +3 -1
  3. package/dist/cli/index.d.ts +4 -1
  4. package/dist/cli/index.js +36 -2
  5. package/dist/commands/config.js +4 -4
  6. package/dist/commands/context-store.d.ts +3 -0
  7. package/dist/commands/context-store.js +475 -0
  8. package/dist/commands/initiative.d.ts +13 -0
  9. package/dist/commands/initiative.js +318 -0
  10. package/dist/commands/workflow/index.d.ts +2 -0
  11. package/dist/commands/workflow/index.js +1 -0
  12. package/dist/commands/workflow/initiative-link.d.ts +24 -0
  13. package/dist/commands/workflow/initiative-link.js +47 -0
  14. package/dist/commands/workflow/instructions.js +10 -2
  15. package/dist/commands/workflow/new-change.d.ts +4 -0
  16. package/dist/commands/workflow/new-change.js +72 -23
  17. package/dist/commands/workflow/set-change.d.ts +13 -0
  18. package/dist/commands/workflow/set-change.js +87 -0
  19. package/dist/commands/workflow/shared.d.ts +2 -0
  20. package/dist/commands/workflow/status.js +3 -0
  21. package/dist/commands/workspace/context-status.d.ts +4 -0
  22. package/dist/commands/workspace/context-status.js +59 -0
  23. package/dist/commands/workspace/open-target-selection.d.ts +13 -0
  24. package/dist/commands/workspace/open-target-selection.js +146 -0
  25. package/dist/commands/workspace/open-view.d.ts +62 -0
  26. package/dist/commands/workspace/open-view.js +249 -0
  27. package/dist/commands/workspace/open.d.ts +16 -8
  28. package/dist/commands/workspace/open.js +40 -14
  29. package/dist/commands/workspace/opener-selection.d.ts +11 -0
  30. package/dist/commands/workspace/opener-selection.js +98 -0
  31. package/dist/commands/workspace/operations.d.ts +14 -8
  32. package/dist/commands/workspace/operations.js +228 -160
  33. package/dist/commands/workspace/prompt-theme.d.ts +29 -0
  34. package/dist/commands/workspace/prompt-theme.js +24 -0
  35. package/dist/commands/workspace/registration.d.ts +13 -0
  36. package/dist/commands/workspace/registration.js +84 -0
  37. package/dist/commands/workspace/selection.d.ts +3 -0
  38. package/dist/commands/workspace/selection.js +42 -40
  39. package/dist/commands/workspace/setup-prompts.d.ts +13 -0
  40. package/dist/commands/workspace/setup-prompts.js +121 -0
  41. package/dist/commands/workspace/types.d.ts +15 -0
  42. package/dist/commands/workspace.js +59 -340
  43. package/dist/core/artifact-graph/index.d.ts +2 -1
  44. package/dist/core/artifact-graph/instruction-loader.d.ts +10 -23
  45. package/dist/core/artifact-graph/instruction-loader.js +28 -89
  46. package/dist/core/artifact-graph/types.d.ts +0 -7
  47. package/dist/core/artifact-graph/types.js +0 -19
  48. package/dist/core/change-metadata/index.d.ts +2 -0
  49. package/dist/core/change-metadata/index.js +2 -0
  50. package/dist/core/change-metadata/schema.d.ts +18 -0
  51. package/dist/core/change-metadata/schema.js +28 -0
  52. package/dist/core/change-status-policy.d.ts +50 -0
  53. package/dist/core/change-status-policy.js +70 -0
  54. package/dist/core/collections/index.d.ts +3 -0
  55. package/dist/core/collections/index.js +3 -0
  56. package/dist/core/collections/initiatives/collection.d.ts +4 -0
  57. package/dist/core/collections/initiatives/collection.js +17 -0
  58. package/dist/core/collections/initiatives/index.d.ts +6 -0
  59. package/dist/core/collections/initiatives/index.js +6 -0
  60. package/dist/core/collections/initiatives/operations.d.ts +49 -0
  61. package/dist/core/collections/initiatives/operations.js +175 -0
  62. package/dist/core/collections/initiatives/resolution.d.ts +87 -0
  63. package/dist/core/collections/initiatives/resolution.js +374 -0
  64. package/dist/core/collections/initiatives/schema.d.ts +41 -0
  65. package/dist/core/collections/initiatives/schema.js +134 -0
  66. package/dist/core/collections/initiatives/templates.d.ts +12 -0
  67. package/dist/core/collections/initiatives/templates.js +90 -0
  68. package/dist/core/collections/runtime.d.ts +46 -0
  69. package/dist/core/collections/runtime.js +194 -0
  70. package/dist/core/completions/command-registry.d.ts +1 -5
  71. package/dist/core/completions/command-registry.js +475 -70
  72. package/dist/core/completions/shared-flags.d.ts +12 -0
  73. package/dist/core/completions/shared-flags.js +28 -0
  74. package/dist/core/config.js +2 -1
  75. package/dist/core/context-store/binding.d.ts +53 -0
  76. package/dist/core/context-store/binding.js +197 -0
  77. package/dist/core/context-store/errors.d.ts +20 -0
  78. package/dist/core/context-store/errors.js +22 -0
  79. package/dist/core/context-store/foundation.d.ts +55 -0
  80. package/dist/core/context-store/foundation.js +321 -0
  81. package/dist/core/context-store/index.d.ts +6 -0
  82. package/dist/core/context-store/index.js +6 -0
  83. package/dist/core/context-store/operations.d.ts +85 -0
  84. package/dist/core/context-store/operations.js +528 -0
  85. package/dist/core/context-store/registry.d.ts +45 -0
  86. package/dist/core/context-store/registry.js +229 -0
  87. package/dist/core/index.d.ts +2 -0
  88. package/dist/core/index.js +2 -0
  89. package/dist/core/planning-home.js +5 -21
  90. package/dist/core/validation/validator.d.ts +11 -0
  91. package/dist/core/validation/validator.js +19 -2
  92. package/dist/core/workspace/foundation.d.ts +28 -48
  93. package/dist/core/workspace/foundation.js +130 -214
  94. package/dist/core/workspace/index.d.ts +2 -0
  95. package/dist/core/workspace/index.js +2 -0
  96. package/dist/core/workspace/legacy-state.d.ts +28 -0
  97. package/dist/core/workspace/legacy-state.js +200 -0
  98. package/dist/core/workspace/open-surface.d.ts +29 -8
  99. package/dist/core/workspace/open-surface.js +122 -44
  100. package/dist/core/workspace/openers.js +11 -6
  101. package/dist/core/workspace/registry.d.ts +24 -0
  102. package/dist/core/workspace/registry.js +146 -0
  103. package/dist/core/workspace/skills.d.ts +4 -2
  104. package/dist/core/workspace/skills.js +2 -2
  105. package/dist/core/workspace/state-io.d.ts +10 -0
  106. package/dist/core/workspace/state-io.js +119 -0
  107. package/dist/utils/change-metadata.d.ts +5 -2
  108. package/dist/utils/change-metadata.js +6 -12
  109. package/dist/utils/change-utils.d.ts +2 -2
  110. package/package.json +1 -1
@@ -1,37 +1,18 @@
1
1
  import chalk from 'chalk';
2
- import * as nodeFs from 'node:fs';
3
- import * as path from 'node:path';
4
- import { createWorkspaceSkillSkippedReport, generateWorkspaceAgentSkills, getDefaultWorkspaceOpenerChoiceValue, getWorkspaceSkillCapableTools, getWorkspaceSkillToolIds, getWorkspaceOpenerLabel, isWorkspaceAgentOpenerId, listWorkspaceOpenerChoices, parseWorkspacePreferredOpenerValue, parseWorkspaceSkillToolsValue, updateWorkspaceAgentSkills, listWorkspaceRegistryEntries, readOptionalWorkspaceLocalState, writeWorkspaceLocalState, } from '../core/workspace/index.js';
2
+ import { createWorkspaceSkillSkippedReport, generateWorkspaceAgentSkills, getWorkspaceSkillCapableTools, getWorkspaceSkillToolIds, getWorkspaceOpenerLabel, parseWorkspaceSkillToolsValue, updateWorkspaceAgentSkills, listKnownWorkspaceEntries, readWorkspaceViewState, syncWorkspaceOpenSurface, writeWorkspaceViewState, } from '../core/workspace/index.js';
5
3
  import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
6
- import { addWorkspaceLink, createManagedWorkspace, inferLinkName, loadWorkspaceForDoctor, loadWorkspaceForList, parseSetupLinks, readWorkspaceForMutation, readRegistry, recordSelectedWorkspaceAfterMutation, resolveExistingDirectory, updateWorkspaceLink, validateLinkNameForCommand, validateWorkspaceNameForSetup, } from './workspace/operations.js';
4
+ import { addWorkspaceLink, createManagedWorkspace, loadWorkspaceForDoctor, loadWorkspaceForList, parseSetupLinks, readWorkspaceForMutation, updateWorkspaceLink, validateWorkspaceNameForSetup, } from './workspace/operations.js';
7
5
  import { selectWorkspaceForCommand, selectWorkspaceRootForCommand, } from './workspace/selection.js';
8
- import { assertWorkspaceOpenerAvailable, buildWorkspaceOpenCommandForState, launchWorkspaceOpenCommand, readWorkspaceOpenState, } from './workspace/open.js';
6
+ import { launchWorkspaceOpenCommand, } from './workspace/open.js';
7
+ import { buildWorkspaceOpenJsonPayload, prepareWorkspaceOpen, } from './workspace/open-view.js';
8
+ import { getPreferredWorkspaceSkillAgentId, parseSetupOpenerOption, promptPreferredOpener, } from './workspace/opener-selection.js';
9
+ import { workspacePromptTheme } from './workspace/prompt-theme.js';
10
+ import { registerWorkspaceCommandWith } from './workspace/registration.js';
11
+ import { promptSetupLinks } from './workspace/setup-prompts.js';
9
12
  import { WorkspaceCliError, appendStatus, asErrorMessage, asStatus, } from './workspace/types.js';
10
13
  function printJson(payload) {
11
14
  console.log(JSON.stringify(payload, null, 2));
12
15
  }
13
- const workspacePromptTheme = {
14
- prefix: '',
15
- style: {
16
- answer: (text) => chalk.cyan(text),
17
- defaultAnswer: (text) => chalk.dim(text),
18
- error: (text) => chalk.red(text),
19
- help: (text) => chalk.dim(text),
20
- highlight: (text) => chalk.cyan(text),
21
- key: (text) => chalk.cyan(text),
22
- message: (text) => chalk.bold(text),
23
- },
24
- };
25
- const workspaceSelectTheme = {
26
- ...workspacePromptTheme,
27
- icon: {
28
- cursor: chalk.cyan('>'),
29
- },
30
- style: {
31
- ...workspacePromptTheme.style,
32
- keysHelpTip: (keys) => chalk.dim(keys.map(([key, action]) => `${key}: ${action}`).join(' | ')),
33
- },
34
- };
35
16
  function printWorkspaceSetupIntro() {
36
17
  console.log(chalk.bold('Workspace setup'));
37
18
  console.log('');
@@ -63,125 +44,6 @@ async function promptWorkspaceName(initialName) {
63
44
  },
64
45
  });
65
46
  }
66
- async function promptExistingPath(message, defaultPath) {
67
- const { input } = await import('@inquirer/prompts');
68
- const pathInput = await input({
69
- message,
70
- default: defaultPath,
71
- prefill: defaultPath ? 'editable' : undefined,
72
- required: true,
73
- theme: workspacePromptTheme,
74
- validate(value) {
75
- const resolvedPath = path.isAbsolute(value)
76
- ? path.resolve(value)
77
- : path.resolve(process.cwd(), value);
78
- return nodeFs.existsSync(resolvedPath) && nodeFs.statSync(resolvedPath).isDirectory()
79
- ? true
80
- : 'Enter an existing repo or folder path.';
81
- },
82
- });
83
- return resolveExistingDirectory(pathInput);
84
- }
85
- async function promptLinkName(existingLinks) {
86
- const { input } = await import('@inquirer/prompts');
87
- return input({
88
- message: 'Link name:',
89
- required: true,
90
- theme: workspacePromptTheme,
91
- validate(value) {
92
- try {
93
- validateLinkNameForCommand(value);
94
- }
95
- catch (error) {
96
- return asErrorMessage(error);
97
- }
98
- if (existingLinks[value]) {
99
- return `Link name '${value}' is already linked to ${existingLinks[value]}.`;
100
- }
101
- return true;
102
- },
103
- });
104
- }
105
- async function promptSetupLinks() {
106
- const { select } = await import('@inquirer/prompts');
107
- const links = {};
108
- console.log('');
109
- console.log(chalk.bold('[2/5] Link repos or folders'));
110
- console.log(chalk.dim('Start with the current directory, or enter another repo path.'));
111
- console.log('');
112
- while (true) {
113
- const linkCount = Object.keys(links).length;
114
- const resolvedPath = await promptExistingPath(linkCount === 0 ? 'Repo or folder path:' : 'Another repo or folder path:', linkCount === 0 ? '.' : undefined);
115
- let linkName = inferLinkName(resolvedPath);
116
- try {
117
- validateLinkNameForCommand(linkName);
118
- }
119
- catch {
120
- linkName = await promptLinkName(links);
121
- }
122
- if (links[linkName]) {
123
- console.log(`Link name '${linkName}' is already linked to ${links[linkName]}.`);
124
- linkName = await promptLinkName(links);
125
- }
126
- links[linkName] = resolvedPath;
127
- console.log(chalk.green(`Added link '${linkName}'`));
128
- console.log(chalk.dim(` ${resolvedPath}`));
129
- const nextAction = await select({
130
- message: 'Continue',
131
- default: 'finish',
132
- choices: [
133
- {
134
- name: 'Create workspace files',
135
- short: 'Create workspace files',
136
- value: 'finish',
137
- description: 'Run a workspace check after setup',
138
- },
139
- {
140
- name: 'Add another repo or folder',
141
- short: 'Add another',
142
- value: 'add',
143
- description: 'Include another local directory in this workspace',
144
- },
145
- ],
146
- theme: workspaceSelectTheme,
147
- });
148
- if (nextAction === 'finish') {
149
- return links;
150
- }
151
- }
152
- }
153
- function formatOpenerChoiceName(choice) {
154
- return choice.unavailableNote ? `${choice.label} (${choice.unavailableNote})` : choice.label;
155
- }
156
- async function promptPreferredOpener(message, openerChoices = listWorkspaceOpenerChoices()) {
157
- const { select } = await import('@inquirer/prompts');
158
- const selectedValue = await select({
159
- message,
160
- default: getDefaultWorkspaceOpenerChoiceValue(openerChoices),
161
- choices: openerChoices.map((choice) => ({
162
- name: formatOpenerChoiceName(choice),
163
- short: choice.label,
164
- value: choice.value,
165
- description: choice.unavailableNote ?? `Use ${choice.label}`,
166
- })),
167
- theme: workspaceSelectTheme,
168
- });
169
- return parseWorkspacePreferredOpenerValue(selectedValue);
170
- }
171
- function parseSetupOpenerOption(opener) {
172
- if (!opener) {
173
- return undefined;
174
- }
175
- try {
176
- return parseWorkspacePreferredOpenerValue(opener);
177
- }
178
- catch (error) {
179
- throw new WorkspaceCliError(asErrorMessage(error), 'unsupported_workspace_opener', {
180
- target: 'workspace.opener',
181
- fix: 'Use --opener codex, --opener claude, --opener github-copilot, or --opener editor.',
182
- });
183
- }
184
- }
185
47
  function parseSetupToolsOption(tools) {
186
48
  try {
187
49
  return parseWorkspaceSkillToolsValue(tools);
@@ -204,12 +66,6 @@ function parseUpdateToolsOption(tools) {
204
66
  });
205
67
  }
206
68
  }
207
- function getPreferredWorkspaceSkillAgentId(preferredOpener) {
208
- if (!preferredOpener || preferredOpener.kind !== 'agent') {
209
- return null;
210
- }
211
- return getWorkspaceSkillToolIds().includes(preferredOpener.id) ? preferredOpener.id : null;
212
- }
213
69
  async function promptWorkspaceSkillAgents(preferredOpener) {
214
70
  const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
215
71
  const preferredAgentId = getPreferredWorkspaceSkillAgentId(preferredOpener);
@@ -238,18 +94,6 @@ async function promptWorkspaceSkillAgents(preferredOpener) {
238
94
  choices: sortedChoices,
239
95
  });
240
96
  }
241
- function parseAgentOverride(agent) {
242
- if (!isWorkspaceAgentOpenerId(agent)) {
243
- throw new WorkspaceCliError(`Unsupported workspace agent '${agent}'. Supported agents: codex, claude, github-copilot.`, 'unsupported_workspace_agent', {
244
- target: 'workspace.opener',
245
- fix: 'Use --agent codex, --agent claude, or --agent github-copilot.',
246
- });
247
- }
248
- return {
249
- kind: 'agent',
250
- id: agent,
251
- };
252
- }
253
97
  function printStatusLines(statuses) {
254
98
  for (const status of statuses) {
255
99
  const label = status.severity === 'warning' ? 'Warning' : 'Issue';
@@ -281,7 +125,14 @@ function collectWorkspaceIssues(workspace) {
281
125
  function printDoctorHuman(result) {
282
126
  console.log(`Workspace: ${result.workspace.name}`);
283
127
  console.log(`Location: ${result.workspace.root}`);
284
- console.log(`Planning path: ${result.workspace.planning_path}`);
128
+ if (result.workspace.context) {
129
+ const selector = result.workspace.context.store_selector;
130
+ const suffix = selector.kind === 'path' ? ` via ${selector.path}` : '';
131
+ console.log(`Context: ${result.workspace.context.store}/${result.workspace.context.initiative}${suffix}`);
132
+ }
133
+ else {
134
+ console.log('Context: (none)');
135
+ }
285
136
  console.log('');
286
137
  printStatusLines(result.status);
287
138
  if (result.status.length > 0) {
@@ -290,6 +141,15 @@ function printDoctorHuman(result) {
290
141
  console.log('Linked repos or folders:');
291
142
  printLinksHuman(result.workspace.links);
292
143
  const issues = collectWorkspaceIssues(result.workspace);
144
+ console.log('');
145
+ console.log('Advisory edit boundaries:');
146
+ if (result.workspace.context) {
147
+ console.log(' Initiative/context-store files are shared coordination context.');
148
+ }
149
+ else {
150
+ console.log(' No initiative coordination context is attached.');
151
+ }
152
+ console.log(' Linked repos and folders are local implementation context when selected.');
293
153
  if (issues.length === 0) {
294
154
  console.log('');
295
155
  console.log('No workspace issues found.');
@@ -409,12 +269,9 @@ function setWorkspaceSkillFailureExitCode(report) {
409
269
  }
410
270
  }
411
271
  async function writeWorkspaceSkillState(workspaceRoot, selectedAgentIds, report) {
412
- const localState = (await readOptionalWorkspaceLocalState(workspaceRoot)) ?? {
413
- version: 1,
414
- paths: {},
415
- };
416
- await writeWorkspaceLocalState(workspaceRoot, {
417
- ...localState,
272
+ const viewState = await readWorkspaceViewState(workspaceRoot);
273
+ await writeWorkspaceViewState(workspaceRoot, {
274
+ ...viewState,
418
275
  workspace_skills: {
419
276
  selected_agents: selectedAgentIds,
420
277
  last_applied_profile: report.profile,
@@ -424,66 +281,6 @@ async function writeWorkspaceSkillState(workspaceRoot, selectedAgentIds, report)
424
281
  },
425
282
  });
426
283
  }
427
- async function resolveWorkspaceOpenOpener(localState, options) {
428
- if (options.agent && options.editor) {
429
- throw new WorkspaceCliError('workspace open accepts either --agent <tool> or --editor, not both.', 'workspace_opener_conflict', {
430
- target: 'workspace.opener',
431
- fix: 'Choose one opener override.',
432
- });
433
- }
434
- if (options.agent) {
435
- return parseAgentOverride(options.agent);
436
- }
437
- if (options.editor) {
438
- return parseWorkspacePreferredOpenerValue('editor');
439
- }
440
- if (localState.preferred_opener) {
441
- return localState.preferred_opener;
442
- }
443
- if (!resolveNoInteractive(options) && isInteractive(options)) {
444
- const openerChoices = listWorkspaceOpenerChoices().filter((choice) => choice.available);
445
- if (openerChoices.length === 0) {
446
- throw new WorkspaceCliError('No supported workspace opener is available on PATH.', 'workspace_no_available_openers', {
447
- target: 'workspace.opener',
448
- fix: "Install VS Code ('code'), Codex ('codex'), or Claude ('claude'), then retry.",
449
- });
450
- }
451
- return promptPreferredOpener('Open with:', openerChoices);
452
- }
453
- throw new WorkspaceCliError('This workspace does not have a preferred opener yet.', 'workspace_opener_unset', {
454
- target: 'workspace.opener',
455
- fix: 'Pass --agent <tool> or --editor, or run workspace setup interactively to choose a default opener.',
456
- });
457
- }
458
- function assertWorkspaceOpenSupportedOptions(options) {
459
- if (options.prepareOnly) {
460
- throw new WorkspaceCliError('workspace open supports launching through a selected opener; preview output is reserved for a future context/query surface.', 'workspace_open_prepare_only_unsupported', {
461
- target: 'workspace.open',
462
- fix: 'Run openspec workspace open with --agent <tool> or --editor.',
463
- });
464
- }
465
- if (options.json) {
466
- throw new WorkspaceCliError('workspace open supports launching through a selected opener; machine-readable context is reserved for a future context/query surface.', 'workspace_open_json_unsupported', {
467
- target: 'workspace.open',
468
- fix: 'Use openspec workspace doctor --json for current workspace status.',
469
- });
470
- }
471
- if (options.change) {
472
- throw new WorkspaceCliError('workspace open currently supports root workspace open only; change-scoped open belongs to future workspace change planning.', 'workspace_open_change_unsupported', {
473
- target: 'workspace.change',
474
- fix: 'Open the root workspace, then start implementation from an explicit change workflow.',
475
- });
476
- }
477
- }
478
- function resolveOpenWorkspaceName(positionalName, options) {
479
- if (positionalName && options.workspace && positionalName !== options.workspace) {
480
- throw new WorkspaceCliError(`Conflicting workspace selectors: positional '${positionalName}' and --workspace '${options.workspace}'.`, 'workspace_selection_conflict', {
481
- target: 'workspace.name',
482
- fix: 'Use either the positional workspace name or --workspace with the same value.',
483
- });
484
- }
485
- return positionalName ?? options.workspace;
486
- }
487
284
  function resolveUpdateWorkspaceName(positionalName, options) {
488
285
  if (positionalName && options.workspace && positionalName !== options.workspace) {
489
286
  throw new WorkspaceCliError(`Conflicting workspace selectors: positional '${positionalName}' and --workspace '${options.workspace}'.`, 'workspace_selection_conflict', {
@@ -493,16 +290,20 @@ function resolveUpdateWorkspaceName(positionalName, options) {
493
290
  }
494
291
  return positionalName ?? options.workspace;
495
292
  }
496
- function printWorkspaceOpenHuman(selectedName, selectedRoot, opener, skipped) {
497
- console.log(`Opening workspace: ${selectedName}`);
498
- console.log(`Location: ${selectedRoot}`);
499
- console.log(`Opener: ${getWorkspaceOpenerLabel(opener)}`);
500
- if (skipped.length === 0) {
293
+ function printWorkspaceOpenHuman(prepared) {
294
+ console.log(`Opening workspace: ${prepared.selected.name}`);
295
+ console.log(`Location: ${prepared.selected.root}`);
296
+ if (prepared.initiative) {
297
+ console.log(`Initiative: ${prepared.initiative.store}/${prepared.initiative.id}`);
298
+ console.log(`Initiative path: ${prepared.initiative.root}`);
299
+ }
300
+ console.log(`Opener: ${getWorkspaceOpenerLabel(prepared.opener)}`);
301
+ if (prepared.skipped.length === 0) {
501
302
  return;
502
303
  }
503
304
  console.log('');
504
305
  console.log('Skipped linked repos or folders:');
505
- for (const link of skipped) {
306
+ for (const link of prepared.skipped) {
506
307
  const location = link.path ?? '(no local path recorded)';
507
308
  console.log(` ${link.name} -> ${location}`);
508
309
  }
@@ -584,8 +385,6 @@ class WorkspaceCommand {
584
385
  console.log('');
585
386
  printWorkspaceListHuman([doctorResult.workspace]);
586
387
  console.log('');
587
- console.log(`Planning path: ${doctorResult.workspace.planning_path}`);
588
- console.log('');
589
388
  console.log('Workspace check:');
590
389
  printWorkspaceCheckSummaryHuman(doctorResult);
591
390
  console.log('');
@@ -603,8 +402,7 @@ class WorkspaceCommand {
603
402
  }
604
403
  async list(options = {}) {
605
404
  try {
606
- const registry = await readRegistry();
607
- const entries = listWorkspaceRegistryEntries(registry);
405
+ const entries = await listKnownWorkspaceEntries();
608
406
  const workspaces = await Promise.all(entries.map((entry) => loadWorkspaceForList(entry)));
609
407
  const payload = { workspaces, status: [] };
610
408
  if (options.json) {
@@ -696,19 +494,19 @@ class WorkspaceCommand {
696
494
  }
697
495
  }
698
496
  async updateSelected(selected, options) {
699
- const { localState } = await readWorkspaceForMutation(selected);
497
+ const viewState = await readWorkspaceForMutation(selected);
498
+ await syncWorkspaceOpenSurface(selected.root, viewState);
700
499
  const hasExplicitToolSelection = options.tools !== undefined;
701
500
  const selectedAgentIds = hasExplicitToolSelection
702
501
  ? parseUpdateToolsOption(options.tools ?? '')
703
- : localState.workspace_skills?.selected_agents ?? [];
502
+ : viewState.workspace_skills?.selected_agents ?? [];
704
503
  const previousSkillState = hasExplicitToolSelection
705
- ? localState.workspace_skills ?? { selected_agents: [] }
706
- : localState.workspace_skills;
504
+ ? viewState.workspace_skills ?? { selected_agents: [] }
505
+ : viewState.workspace_skills;
707
506
  const skillReport = await updateWorkspaceAgentSkills(selected.root, selectedAgentIds, previousSkillState);
708
- const shouldStoreSelection = hasExplicitToolSelection || Boolean(localState.workspace_skills);
507
+ const shouldStoreSelection = hasExplicitToolSelection || Boolean(viewState.workspace_skills);
709
508
  if (shouldStoreSelection && !hasWorkspaceSkillFailures(skillReport)) {
710
509
  await writeWorkspaceSkillState(selected.root, selectedAgentIds, skillReport);
711
- await recordSelectedWorkspaceAfterMutation(selected);
712
510
  }
713
511
  const doctorResult = await loadWorkspaceForDoctor(selected);
714
512
  if (options.json) {
@@ -737,22 +535,20 @@ class WorkspaceCommand {
737
535
  }
738
536
  async open(positionalName, options = {}) {
739
537
  try {
740
- assertWorkspaceOpenSupportedOptions(options);
741
- const workspaceName = resolveOpenWorkspaceName(positionalName, options);
742
- const selected = await selectWorkspaceForCommand({
743
- ...options,
744
- workspace: workspaceName,
745
- }, 'open', { preferPositionalName: true });
746
- const state = await readWorkspaceOpenState(selected);
747
- const opener = await resolveWorkspaceOpenOpener(state.localState, options);
748
- assertWorkspaceOpenerAvailable(opener, state.codeWorkspacePath);
749
- const { command, skipped } = await buildWorkspaceOpenCommandForState(opener, selected.root, state);
750
- printStatusLines(selected.status);
751
- if (selected.status.length > 0) {
752
- console.log('');
538
+ const prepared = await prepareWorkspaceOpen(positionalName, options);
539
+ if (!options.json) {
540
+ printStatusLines(prepared.selected.status);
541
+ if (prepared.selected.status.length > 0) {
542
+ console.log('');
543
+ }
544
+ printWorkspaceOpenHuman(prepared);
545
+ }
546
+ await launchWorkspaceOpenCommand(prepared.command, {
547
+ stdio: options.json ? 'ignore' : 'inherit',
548
+ });
549
+ if (options.json) {
550
+ printJson(buildWorkspaceOpenJsonPayload(prepared));
753
551
  }
754
- printWorkspaceOpenHuman(selected.name, selected.root, opener, skipped);
755
- await launchWorkspaceOpenCommand(command);
756
552
  }
757
553
  catch (error) {
758
554
  this.handleFailure(options.json, { workspace: null, status: [] }, error);
@@ -785,84 +581,7 @@ export async function runWorkspaceUpdateForRoot(workspaceRoot, options = {}) {
785
581
  const workspaceCommand = new WorkspaceCommand();
786
582
  await workspaceCommand.updateRoot(workspaceRoot, options);
787
583
  }
788
- function collectOption(value, previous) {
789
- return [...previous, value];
790
- }
791
- function addWorkspaceSelectionOptions(command) {
792
- return command
793
- .option('--workspace <name>', 'Workspace name from the local workspace registry')
794
- .option('--json', 'Output as JSON')
795
- .option('--no-interactive', 'Disable prompts');
796
- }
797
584
  export function registerWorkspaceCommand(program) {
798
- const workspaceCommand = new WorkspaceCommand();
799
- const workspace = program
800
- .command('workspace')
801
- .description('Set up and inspect coordination workspaces');
802
- workspace
803
- .command('setup')
804
- .description('Set up a workspace and link existing repos or folders')
805
- .option('--name <name>', 'Workspace name')
806
- .option('--link <link>', 'Repo or folder link. Use <path> or <name>=<path>.', collectOption, [])
807
- .option('--opener <id>', 'Preferred opener: codex, claude, github-copilot, or editor')
808
- .option('--tools <tools>', `Install OpenSpec skills for agents. Use "all", "none", or a comma-separated list of: ${getWorkspaceSkillToolIds().join(', ')}`)
809
- .option('--json', 'Output as JSON')
810
- .option('--no-interactive', 'Disable prompts')
811
- .action(async (options) => {
812
- await workspaceCommand.setup(options);
813
- });
814
- workspace
815
- .command('list')
816
- .description('List known OpenSpec workspaces')
817
- .option('--json', 'Output as JSON')
818
- .action(async (options) => {
819
- await workspaceCommand.list(options);
820
- });
821
- workspace
822
- .command('ls')
823
- .description('List known OpenSpec workspaces')
824
- .option('--json', 'Output as JSON')
825
- .action(async (options) => {
826
- await workspaceCommand.list(options);
827
- });
828
- addWorkspaceSelectionOptions(workspace
829
- .command('link [nameOrPath] [path]')
830
- .description('Link an existing repo or folder to a workspace')).action(async (nameOrPath, linkPath, options) => {
831
- await workspaceCommand.link(nameOrPath, linkPath, options);
832
- });
833
- addWorkspaceSelectionOptions(workspace
834
- .command('relink <name> <path>')
835
- .description('Update the local path for an existing workspace link')).action(async (linkName, linkPath, options) => {
836
- await workspaceCommand.relink(linkName, linkPath, options);
837
- });
838
- addWorkspaceSelectionOptions(workspace
839
- .command('doctor')
840
- .description('Check what a workspace can resolve on this machine')).action(async (options) => {
841
- await workspaceCommand.doctor(options);
842
- });
843
- workspace
844
- .command('update [name]')
845
- .description('Refresh workspace-local OpenSpec agent skills from the active global profile')
846
- .option('--workspace <name>', 'Workspace name from the local workspace registry')
847
- .option('--tools <tools>', `Select agents for workspace skills. Use "all", "none", or a comma-separated list of: ${getWorkspaceSkillToolIds().join(', ')}. Global profile selects workflows; --tools selects agents.`)
848
- .option('--json', 'Output as JSON')
849
- .option('--no-interactive', 'Disable prompts')
850
- .action(async (name, options) => {
851
- await workspaceCommand.update(name, options);
852
- });
853
- workspace
854
- .command('open [name]')
855
- .description('Open a workspace in an agent or VS Code editor')
856
- .option('--workspace <name>', 'Workspace name from the local workspace registry')
857
- .option('--agent <tool>', 'Use an agent for this session: codex, claude, or github-copilot')
858
- .option('--editor', 'Open the workspace in VS Code editor mode')
859
- .option('--prepare-only', 'Unsupported: preview surfaces belong to a future context/query command')
860
- .option('--json', 'Unsupported: machine-readable context belongs to a future context/query command')
861
- .option('--change <id>', 'Unsupported: change-scoped open belongs to future workspace change planning')
862
- .option('--no-interactive', 'Disable prompts')
863
- .action(async (name, options) => {
864
- await workspaceCommand.open(name, options);
865
- });
866
- // Intentionally no public `workspace create` command in this slice.
585
+ registerWorkspaceCommandWith(program, new WorkspaceCommand());
867
586
  }
868
587
  //# sourceMappingURL=workspace.js.map
@@ -4,5 +4,6 @@ export { ArtifactGraph } from './graph.js';
4
4
  export { detectCompleted } from './state.js';
5
5
  export { artifactOutputExists, isGlobPattern, resolveArtifactOutputs } from './outputs.js';
6
6
  export { resolveSchema, listSchemas, listSchemasWithInfo, getSchemaDir, getPackageSchemasDir, getUserSchemasDir, SchemaLoadError, type SchemaInfo, } from './resolver.js';
7
- export { loadTemplate, loadChangeContext, generateInstructions, formatChangeStatus, TemplateLoadError, type ChangeContext, type LoadChangeContextOptions, type ArtifactInstructions, type DependencyInfo, type ArtifactStatus, type ChangeStatus, type ArtifactPathSummary, type PlanningHomeSummary, type AffectedAreasSummary, type ActionContext, } from './instruction-loader.js';
7
+ export { loadTemplate, loadChangeContext, generateInstructions, formatChangeStatus, TemplateLoadError, type ChangeContext, type LoadChangeContextOptions, type ArtifactInstructions, type DependencyInfo, type ArtifactStatus, type ChangeStatus, type ArtifactPathSummary, } from './instruction-loader.js';
8
+ export type { PlanningHomeSummary, AffectedAreasSummary, ActionContext, } from '../change-status-policy.js';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,7 @@
1
1
  import { ArtifactGraph } from './graph.js';
2
+ import { type ActionContext, type AffectedAreasSummary, type PlanningHomeSummary } from '../change-status-policy.js';
2
3
  import type { PlanningHome } from '../planning-home.js';
4
+ import type { ChangeMetadata, InitiativeLink } from '../change-metadata/index.js';
3
5
  import type { CompletedSet } from './types.js';
4
6
  /**
5
7
  * Error thrown when loading a template fails.
@@ -26,6 +28,10 @@ export interface ChangeContext {
26
28
  projectRoot: string;
27
29
  /** Resolved planning home for this change */
28
30
  planningHome?: PlanningHome;
31
+ /** Parsed change metadata, when present */
32
+ metadata?: ChangeMetadata;
33
+ /** Stored initiative link, when this change is linked to shared context */
34
+ initiative?: InitiativeLink;
29
35
  }
30
36
  export interface LoadChangeContextOptions {
31
37
  changeDir?: string;
@@ -45,6 +51,8 @@ export interface ArtifactInstructions {
45
51
  changeDir: string;
46
52
  /** Resolved planning home for this change */
47
53
  planningHome?: PlanningHomeSummary;
54
+ /** Stored initiative link, when this change is linked to shared context */
55
+ initiative?: InitiativeLink;
48
56
  /** Output path pattern (e.g., "proposal.md") */
49
57
  outputPath: string;
50
58
  /** Absolute output path or glob pattern resolved under the change directory */
@@ -102,6 +110,8 @@ export interface ChangeStatus {
102
110
  schemaName: string;
103
111
  /** Resolved planning home for this change */
104
112
  planningHome?: PlanningHomeSummary;
113
+ /** Stored initiative link, when this change is linked to shared context */
114
+ initiative?: InitiativeLink;
105
115
  /** Full path to the change root */
106
116
  changeRoot: string;
107
117
  /** Absolute artifact path details keyed by artifact ID */
@@ -124,29 +134,6 @@ export interface ArtifactPathSummary {
124
134
  resolvedOutputPath: string;
125
135
  existingOutputPaths: string[];
126
136
  }
127
- export interface PlanningHomeSummary {
128
- kind: 'repo' | 'workspace';
129
- root: string;
130
- changesDir: string;
131
- defaultSchema: string;
132
- workspaceName?: string;
133
- }
134
- export interface AffectedAreasSummary {
135
- known: string[];
136
- unresolved: boolean;
137
- invalid: string[];
138
- }
139
- export interface ActionContext {
140
- mode: 'repo-local' | 'workspace-planning';
141
- sourceOfTruth: 'repo' | 'workspace';
142
- planningArtifacts: string[];
143
- linkedContext: Array<{
144
- name: string;
145
- }>;
146
- allowedEditRoots: string[];
147
- requiresAffectedAreaSelection: boolean;
148
- constraints: string[];
149
- }
150
137
  /**
151
138
  * Loads a template from a schema's templates directory.
152
139
  *