@fission-ai/openspec 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/README.md +9 -6
  2. package/bin/openspec.js +3 -1
  3. package/dist/cli/index.d.ts +4 -1
  4. package/dist/cli/index.js +46 -2
  5. package/dist/commands/completion.js +7 -0
  6. package/dist/commands/config.js +64 -5
  7. package/dist/commands/context-store.d.ts +3 -0
  8. package/dist/commands/context-store.js +475 -0
  9. package/dist/commands/initiative.d.ts +13 -0
  10. package/dist/commands/initiative.js +318 -0
  11. package/dist/commands/workflow/index.d.ts +2 -0
  12. package/dist/commands/workflow/index.js +1 -0
  13. package/dist/commands/workflow/initiative-link.d.ts +24 -0
  14. package/dist/commands/workflow/initiative-link.js +47 -0
  15. package/dist/commands/workflow/instructions.d.ts +1 -1
  16. package/dist/commands/workflow/instructions.js +46 -83
  17. package/dist/commands/workflow/new-change.d.ts +6 -0
  18. package/dist/commands/workflow/new-change.js +114 -17
  19. package/dist/commands/workflow/set-change.d.ts +13 -0
  20. package/dist/commands/workflow/set-change.js +87 -0
  21. package/dist/commands/workflow/shared.d.ts +5 -3
  22. package/dist/commands/workflow/shared.js +6 -6
  23. package/dist/commands/workflow/status.js +24 -9
  24. package/dist/commands/workflow/templates.js +5 -4
  25. package/dist/commands/workspace/context-status.d.ts +4 -0
  26. package/dist/commands/workspace/context-status.js +59 -0
  27. package/dist/commands/workspace/open-target-selection.d.ts +13 -0
  28. package/dist/commands/workspace/open-target-selection.js +146 -0
  29. package/dist/commands/workspace/open-view.d.ts +62 -0
  30. package/dist/commands/workspace/open-view.js +249 -0
  31. package/dist/commands/workspace/open.d.ts +37 -0
  32. package/dist/commands/workspace/open.js +110 -0
  33. package/dist/commands/workspace/opener-selection.d.ts +11 -0
  34. package/dist/commands/workspace/opener-selection.js +98 -0
  35. package/dist/commands/workspace/operations.d.ts +29 -0
  36. package/dist/commands/workspace/operations.js +543 -0
  37. package/dist/commands/workspace/prompt-theme.d.ts +29 -0
  38. package/dist/commands/workspace/prompt-theme.js +24 -0
  39. package/dist/commands/workspace/registration.d.ts +13 -0
  40. package/dist/commands/workspace/registration.js +84 -0
  41. package/dist/commands/workspace/selection.d.ts +9 -0
  42. package/dist/commands/workspace/selection.js +115 -0
  43. package/dist/commands/workspace/setup-prompts.d.ts +13 -0
  44. package/dist/commands/workspace/setup-prompts.js +121 -0
  45. package/dist/commands/workspace/types.d.ts +103 -0
  46. package/dist/commands/workspace/types.js +36 -0
  47. package/dist/commands/workspace.d.ts +6 -0
  48. package/dist/commands/workspace.js +587 -0
  49. package/dist/core/artifact-graph/index.d.ts +3 -1
  50. package/dist/core/artifact-graph/index.js +1 -0
  51. package/dist/core/artifact-graph/instruction-loader.d.ts +41 -1
  52. package/dist/core/artifact-graph/instruction-loader.js +51 -9
  53. package/dist/core/artifact-graph/outputs.d.ts +14 -0
  54. package/dist/core/artifact-graph/outputs.js +39 -0
  55. package/dist/core/artifact-graph/state.js +2 -25
  56. package/dist/core/artifact-graph/types.d.ts +0 -5
  57. package/dist/core/artifact-graph/types.js +0 -14
  58. package/dist/core/change-metadata/index.d.ts +2 -0
  59. package/dist/core/change-metadata/index.js +2 -0
  60. package/dist/core/change-metadata/schema.d.ts +18 -0
  61. package/dist/core/change-metadata/schema.js +28 -0
  62. package/dist/core/change-status-policy.d.ts +50 -0
  63. package/dist/core/change-status-policy.js +70 -0
  64. package/dist/core/collections/index.d.ts +3 -0
  65. package/dist/core/collections/index.js +3 -0
  66. package/dist/core/collections/initiatives/collection.d.ts +4 -0
  67. package/dist/core/collections/initiatives/collection.js +17 -0
  68. package/dist/core/collections/initiatives/index.d.ts +6 -0
  69. package/dist/core/collections/initiatives/index.js +6 -0
  70. package/dist/core/collections/initiatives/operations.d.ts +49 -0
  71. package/dist/core/collections/initiatives/operations.js +175 -0
  72. package/dist/core/collections/initiatives/resolution.d.ts +87 -0
  73. package/dist/core/collections/initiatives/resolution.js +374 -0
  74. package/dist/core/collections/initiatives/schema.d.ts +41 -0
  75. package/dist/core/collections/initiatives/schema.js +134 -0
  76. package/dist/core/collections/initiatives/templates.d.ts +12 -0
  77. package/dist/core/collections/initiatives/templates.js +90 -0
  78. package/dist/core/collections/runtime.d.ts +46 -0
  79. package/dist/core/collections/runtime.js +194 -0
  80. package/dist/core/completions/command-registry.d.ts +1 -5
  81. package/dist/core/completions/command-registry.js +532 -32
  82. package/dist/core/completions/completion-provider.d.ts +11 -0
  83. package/dist/core/completions/completion-provider.js +27 -0
  84. package/dist/core/completions/generators/bash-generator.d.ts +3 -0
  85. package/dist/core/completions/generators/bash-generator.js +60 -4
  86. package/dist/core/completions/generators/fish-generator.js +3 -0
  87. package/dist/core/completions/generators/powershell-generator.d.ts +3 -0
  88. package/dist/core/completions/generators/powershell-generator.js +63 -4
  89. package/dist/core/completions/generators/zsh-generator.d.ts +3 -0
  90. package/dist/core/completions/generators/zsh-generator.js +46 -22
  91. package/dist/core/completions/installers/zsh-installer.d.ts +0 -8
  92. package/dist/core/completions/installers/zsh-installer.js +3 -31
  93. package/dist/core/completions/shared-flags.d.ts +12 -0
  94. package/dist/core/completions/shared-flags.js +28 -0
  95. package/dist/core/completions/templates/bash-templates.d.ts +1 -1
  96. package/dist/core/completions/templates/bash-templates.js +6 -0
  97. package/dist/core/completions/templates/fish-templates.d.ts +1 -1
  98. package/dist/core/completions/templates/fish-templates.js +6 -0
  99. package/dist/core/completions/templates/powershell-templates.d.ts +1 -1
  100. package/dist/core/completions/templates/powershell-templates.js +9 -0
  101. package/dist/core/completions/templates/zsh-templates.d.ts +1 -1
  102. package/dist/core/completions/templates/zsh-templates.js +9 -0
  103. package/dist/core/completions/types.d.ts +23 -1
  104. package/dist/core/config.js +3 -1
  105. package/dist/core/context-store/binding.d.ts +53 -0
  106. package/dist/core/context-store/binding.js +197 -0
  107. package/dist/core/context-store/errors.d.ts +20 -0
  108. package/dist/core/context-store/errors.js +22 -0
  109. package/dist/core/context-store/foundation.d.ts +55 -0
  110. package/dist/core/context-store/foundation.js +321 -0
  111. package/dist/core/context-store/index.d.ts +6 -0
  112. package/dist/core/context-store/index.js +6 -0
  113. package/dist/core/context-store/operations.d.ts +85 -0
  114. package/dist/core/context-store/operations.js +528 -0
  115. package/dist/core/context-store/registry.d.ts +45 -0
  116. package/dist/core/context-store/registry.js +229 -0
  117. package/dist/core/global-config.d.ts +6 -1
  118. package/dist/core/global-config.js +15 -16
  119. package/dist/core/index.d.ts +5 -1
  120. package/dist/core/index.js +4 -0
  121. package/dist/core/parsers/change-parser.js +7 -3
  122. package/dist/core/parsers/markdown-parser.d.ts +4 -0
  123. package/dist/core/parsers/markdown-parser.js +41 -1
  124. package/dist/core/parsers/requirement-blocks.js +5 -5
  125. package/dist/core/parsers/spec-structure.d.ts +9 -0
  126. package/dist/core/parsers/spec-structure.js +88 -0
  127. package/dist/core/planning-home.d.ts +21 -0
  128. package/dist/core/planning-home.js +108 -0
  129. package/dist/core/profiles.d.ts +1 -1
  130. package/dist/core/profiles.js +1 -1
  131. package/dist/core/specs-apply.js +9 -1
  132. package/dist/core/templates/workflows/apply-change.js +10 -4
  133. package/dist/core/templates/workflows/archive-change.js +21 -15
  134. package/dist/core/templates/workflows/bulk-archive-change.js +14 -10
  135. package/dist/core/templates/workflows/continue-change.js +6 -4
  136. package/dist/core/templates/workflows/explore.js +8 -10
  137. package/dist/core/templates/workflows/ff-change.js +8 -6
  138. package/dist/core/templates/workflows/new-change.js +6 -6
  139. package/dist/core/templates/workflows/onboard.js +13 -15
  140. package/dist/core/templates/workflows/propose.js +8 -6
  141. package/dist/core/templates/workflows/sync-specs.js +28 -10
  142. package/dist/core/templates/workflows/verify-change.js +16 -10
  143. package/dist/core/update.d.ts +5 -0
  144. package/dist/core/update.js +20 -0
  145. package/dist/core/validation/validator.d.ts +11 -0
  146. package/dist/core/validation/validator.js +28 -2
  147. package/dist/core/workspace/foundation.d.ts +67 -0
  148. package/dist/core/workspace/foundation.js +295 -0
  149. package/dist/core/workspace/index.d.ts +8 -0
  150. package/dist/core/workspace/index.js +8 -0
  151. package/dist/core/workspace/legacy-state.d.ts +28 -0
  152. package/dist/core/workspace/legacy-state.js +200 -0
  153. package/dist/core/workspace/link-input.d.ts +9 -0
  154. package/dist/core/workspace/link-input.js +32 -0
  155. package/dist/core/workspace/open-surface.d.ts +45 -0
  156. package/dist/core/workspace/open-surface.js +215 -0
  157. package/dist/core/workspace/openers.d.ts +21 -0
  158. package/dist/core/workspace/openers.js +124 -0
  159. package/dist/core/workspace/registry.d.ts +24 -0
  160. package/dist/core/workspace/registry.js +146 -0
  161. package/dist/core/workspace/skills.d.ts +57 -0
  162. package/dist/core/workspace/skills.js +334 -0
  163. package/dist/core/workspace/state-io.d.ts +10 -0
  164. package/dist/core/workspace/state-io.js +119 -0
  165. package/dist/telemetry/config.d.ts +7 -1
  166. package/dist/telemetry/config.js +87 -19
  167. package/dist/telemetry/index.js +19 -0
  168. package/dist/utils/change-metadata.d.ts +5 -2
  169. package/dist/utils/change-metadata.js +7 -13
  170. package/dist/utils/change-utils.d.ts +9 -0
  171. package/dist/utils/change-utils.js +7 -5
  172. package/dist/utils/file-system.d.ts +5 -0
  173. package/dist/utils/file-system.js +21 -1
  174. package/package.json +2 -1
  175. package/schemas/workspace-planning/schema.yaml +72 -0
  176. package/schemas/workspace-planning/templates/design.md +33 -0
  177. package/schemas/workspace-planning/templates/proposal.md +28 -0
  178. package/schemas/workspace-planning/templates/spec.md +9 -0
  179. package/schemas/workspace-planning/templates/tasks.md +15 -0
@@ -0,0 +1,587 @@
1
+ import chalk from 'chalk';
2
+ import { createWorkspaceSkillSkippedReport, generateWorkspaceAgentSkills, getWorkspaceSkillCapableTools, getWorkspaceSkillToolIds, getWorkspaceOpenerLabel, parseWorkspaceSkillToolsValue, updateWorkspaceAgentSkills, listKnownWorkspaceEntries, readWorkspaceViewState, syncWorkspaceOpenSurface, writeWorkspaceViewState, } from '../core/workspace/index.js';
3
+ import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
4
+ import { addWorkspaceLink, createManagedWorkspace, loadWorkspaceForDoctor, loadWorkspaceForList, parseSetupLinks, readWorkspaceForMutation, updateWorkspaceLink, validateWorkspaceNameForSetup, } from './workspace/operations.js';
5
+ import { selectWorkspaceForCommand, selectWorkspaceRootForCommand, } from './workspace/selection.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';
12
+ import { WorkspaceCliError, appendStatus, asErrorMessage, asStatus, } from './workspace/types.js';
13
+ function printJson(payload) {
14
+ console.log(JSON.stringify(payload, null, 2));
15
+ }
16
+ function printWorkspaceSetupIntro() {
17
+ console.log(chalk.bold('Workspace setup'));
18
+ console.log('');
19
+ }
20
+ function isPromptCancellationError(error) {
21
+ return (error instanceof Error &&
22
+ (error.name === 'ExitPromptError' || error.message.includes('force closed the prompt with SIGINT')));
23
+ }
24
+ async function promptWorkspaceName(initialName) {
25
+ if (initialName) {
26
+ return validateWorkspaceNameForSetup(initialName);
27
+ }
28
+ const { input } = await import('@inquirer/prompts');
29
+ console.log(chalk.bold('[1/5] Name the workspace'));
30
+ console.log(chalk.dim('Use a stable name for the repo group, e.g. platform.'));
31
+ console.log('');
32
+ return input({
33
+ message: 'Workspace name:',
34
+ required: true,
35
+ theme: workspacePromptTheme,
36
+ validate(value) {
37
+ try {
38
+ validateWorkspaceNameForSetup(value);
39
+ return true;
40
+ }
41
+ catch {
42
+ return 'Workspace names must be kebab-case with lowercase letters, numbers, and single hyphen separators.';
43
+ }
44
+ },
45
+ });
46
+ }
47
+ function parseSetupToolsOption(tools) {
48
+ try {
49
+ return parseWorkspaceSkillToolsValue(tools);
50
+ }
51
+ catch (error) {
52
+ throw new WorkspaceCliError(asErrorMessage(error), 'invalid_workspace_setup_tools', {
53
+ target: 'workspace.skills',
54
+ fix: `Use --tools all, --tools none, or one of: ${getWorkspaceSkillToolIds().join(', ')}`,
55
+ });
56
+ }
57
+ }
58
+ function parseUpdateToolsOption(tools) {
59
+ try {
60
+ return parseWorkspaceSkillToolsValue(tools);
61
+ }
62
+ catch (error) {
63
+ throw new WorkspaceCliError(asErrorMessage(error), 'invalid_workspace_update_tools', {
64
+ target: 'workspace.skills',
65
+ fix: `Use --tools all, --tools none, or one of: ${getWorkspaceSkillToolIds().join(', ')}`,
66
+ });
67
+ }
68
+ }
69
+ async function promptWorkspaceSkillAgents(preferredOpener) {
70
+ const { searchableMultiSelect } = await import('../prompts/searchable-multi-select.js');
71
+ const preferredAgentId = getPreferredWorkspaceSkillAgentId(preferredOpener);
72
+ const tools = getWorkspaceSkillCapableTools();
73
+ const sortedChoices = tools
74
+ .map((tool) => ({
75
+ name: tool.name,
76
+ value: tool.value,
77
+ preSelected: tool.value === preferredAgentId,
78
+ }))
79
+ .sort((a, b) => {
80
+ if (a.preSelected !== b.preSelected) {
81
+ return a.preSelected ? -1 : 1;
82
+ }
83
+ return a.name.localeCompare(b.name);
84
+ });
85
+ if (preferredAgentId) {
86
+ const preferredTool = tools.find((tool) => tool.value === preferredAgentId);
87
+ if (preferredTool) {
88
+ console.log(`${preferredTool.name} matches your preferred opener and is pre-selected.`);
89
+ }
90
+ }
91
+ return searchableMultiSelect({
92
+ message: 'Which agents should get OpenSpec skills in this workspace?',
93
+ pageSize: 15,
94
+ choices: sortedChoices,
95
+ });
96
+ }
97
+ function printStatusLines(statuses) {
98
+ for (const status of statuses) {
99
+ const label = status.severity === 'warning' ? 'Warning' : 'Issue';
100
+ console.log(`${label}: ${status.message}`);
101
+ if (status.fix) {
102
+ console.log(`Fix: ${status.fix}`);
103
+ }
104
+ }
105
+ }
106
+ function printLinksHuman(links) {
107
+ if (links.length === 0) {
108
+ console.log(' (no linked repos or folders)');
109
+ return;
110
+ }
111
+ for (const link of links) {
112
+ const suffix = link.status.some((status) => status.severity === 'error') ? ' [issue]' : '';
113
+ console.log(` ${link.name} -> ${link.path ?? '(no local path recorded)'}${suffix}`);
114
+ if (link.repo_specs_path) {
115
+ console.log(` repo specs: ${link.repo_specs_path}`);
116
+ }
117
+ }
118
+ }
119
+ function collectWorkspaceIssues(workspace) {
120
+ return [
121
+ ...workspace.status,
122
+ ...workspace.links.flatMap((link) => link.status),
123
+ ];
124
+ }
125
+ function printDoctorHuman(result) {
126
+ console.log(`Workspace: ${result.workspace.name}`);
127
+ console.log(`Location: ${result.workspace.root}`);
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
+ }
136
+ console.log('');
137
+ printStatusLines(result.status);
138
+ if (result.status.length > 0) {
139
+ console.log('');
140
+ }
141
+ console.log('Linked repos or folders:');
142
+ printLinksHuman(result.workspace.links);
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.');
153
+ if (issues.length === 0) {
154
+ console.log('');
155
+ console.log('No workspace issues found.');
156
+ return;
157
+ }
158
+ console.log('');
159
+ console.log('Issues:');
160
+ for (const issue of issues) {
161
+ console.log(` - ${issue.message}`);
162
+ if (issue.target) {
163
+ console.log(` Target: ${issue.target}`);
164
+ }
165
+ if (issue.fix) {
166
+ console.log(` Fix: ${issue.fix}`);
167
+ }
168
+ }
169
+ }
170
+ function printWorkspaceListHuman(workspaces) {
171
+ console.log(chalk.bold(`OpenSpec workspaces (${workspaces.length})`));
172
+ for (const workspace of workspaces) {
173
+ console.log('');
174
+ console.log(chalk.bold(workspace.name));
175
+ console.log(` Location: ${workspace.root}`);
176
+ if (workspace.status.length > 0) {
177
+ console.log(' Status:');
178
+ for (const status of workspace.status) {
179
+ const statusLabel = status.severity === 'warning' ? chalk.yellow('Warning') : chalk.red('Issue');
180
+ console.log(` ${statusLabel}: ${status.message}`);
181
+ if (status.fix) {
182
+ console.log(` Fix: ${status.fix}`);
183
+ }
184
+ }
185
+ }
186
+ console.log(` Linked repos or folders (${workspace.links.length}):`);
187
+ if (workspace.links.length === 0) {
188
+ console.log(chalk.dim(' (none)'));
189
+ continue;
190
+ }
191
+ for (const link of workspace.links) {
192
+ const suffix = link.status.some((status) => status.severity === 'error') ? chalk.red(' [issue]') : '';
193
+ console.log(` ${link.name} -> ${link.path ?? '(no local path recorded)'}${suffix}`);
194
+ if (link.repo_specs_path) {
195
+ console.log(chalk.dim(` repo specs: ${link.repo_specs_path}`));
196
+ }
197
+ }
198
+ }
199
+ }
200
+ function printWorkspaceCheckSummaryHuman(result) {
201
+ printStatusLines(result.status);
202
+ const issues = collectWorkspaceIssues(result.workspace);
203
+ if (issues.length === 0) {
204
+ console.log(' No workspace issues found.');
205
+ return;
206
+ }
207
+ console.log(' Issues:');
208
+ for (const issue of issues) {
209
+ console.log(` - ${issue.message}`);
210
+ if (issue.target) {
211
+ console.log(` Target: ${issue.target}`);
212
+ }
213
+ if (issue.fix) {
214
+ console.log(` Fix: ${issue.fix}`);
215
+ }
216
+ }
217
+ }
218
+ function printLinkMutationHuman(heading, payload) {
219
+ printStatusLines(payload.status);
220
+ console.log(heading);
221
+ console.log(` ${payload.link.name} -> ${payload.link.path}`);
222
+ console.log(`Workspace: ${payload.workspace.name}`);
223
+ }
224
+ function formatWorkspaceSkillAgentResult(result) {
225
+ const workflowCount = result.workflow_ids?.length ?? 0;
226
+ const workflowLabel = workflowCount === 1 ? '1 workflow' : `${workflowCount} workflows`;
227
+ return `${result.name} (${workflowLabel})`;
228
+ }
229
+ function formatWorkspaceSkillRemovedResult(result) {
230
+ const workflowCount = result.workflow_ids?.length ?? 0;
231
+ const workflowLabel = workflowCount === 1 ? '1 workflow' : `${workflowCount} workflows`;
232
+ return `${result.name} (${workflowLabel} removed)`;
233
+ }
234
+ function printWorkspaceSkillReportHuman(report) {
235
+ console.log('Agent skills:');
236
+ console.log(` Profile: ${report.profile}`);
237
+ console.log(` Workflows: ${report.workflow_ids.length > 0 ? report.workflow_ids.join(', ') : '(none selected)'}`);
238
+ if (report.generated.length > 0) {
239
+ console.log(` Generated: ${report.generated.map(formatWorkspaceSkillAgentResult).join(', ')}`);
240
+ }
241
+ if (report.added.length > 0) {
242
+ console.log(` Added: ${report.added.map(formatWorkspaceSkillAgentResult).join(', ')}`);
243
+ }
244
+ if (report.refreshed.length > 0) {
245
+ console.log(` Refreshed: ${report.refreshed.map(formatWorkspaceSkillAgentResult).join(', ')}`);
246
+ }
247
+ if (report.removed.length > 0) {
248
+ console.log(` Removed: ${report.removed.map(formatWorkspaceSkillRemovedResult).join(', ')}`);
249
+ }
250
+ if (report.skipped.length > 0) {
251
+ for (const skipped of report.skipped) {
252
+ const prefix = skipped.name ? `${skipped.name}: ` : '';
253
+ console.log(` Skipped: ${prefix}${skipped.message}`);
254
+ }
255
+ }
256
+ if (report.failed.length > 0) {
257
+ console.log(chalk.red(` Failed: ${report.failed.map((failure) => `${failure.name} (${failure.error})`).join(', ')}`));
258
+ }
259
+ if (report.delivery_notice) {
260
+ console.log(chalk.dim(` ${report.delivery_notice}`));
261
+ }
262
+ }
263
+ function hasWorkspaceSkillFailures(report) {
264
+ return report.failed.length > 0;
265
+ }
266
+ function setWorkspaceSkillFailureExitCode(report) {
267
+ if (hasWorkspaceSkillFailures(report)) {
268
+ process.exitCode = 1;
269
+ }
270
+ }
271
+ async function writeWorkspaceSkillState(workspaceRoot, selectedAgentIds, report) {
272
+ const viewState = await readWorkspaceViewState(workspaceRoot);
273
+ await writeWorkspaceViewState(workspaceRoot, {
274
+ ...viewState,
275
+ workspace_skills: {
276
+ selected_agents: selectedAgentIds,
277
+ last_applied_profile: report.profile,
278
+ last_applied_delivery: report.delivery,
279
+ last_applied_workflow_ids: report.workflow_ids,
280
+ last_applied_at: new Date().toISOString(),
281
+ },
282
+ });
283
+ }
284
+ function resolveUpdateWorkspaceName(positionalName, options) {
285
+ if (positionalName && options.workspace && positionalName !== options.workspace) {
286
+ throw new WorkspaceCliError(`Conflicting workspace selectors: positional '${positionalName}' and --workspace '${options.workspace}'.`, 'workspace_selection_conflict', {
287
+ target: 'workspace.name',
288
+ fix: 'Use either the positional workspace name or --workspace with the same value.',
289
+ });
290
+ }
291
+ return positionalName ?? options.workspace;
292
+ }
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) {
302
+ return;
303
+ }
304
+ console.log('');
305
+ console.log('Skipped linked repos or folders:');
306
+ for (const link of prepared.skipped) {
307
+ const location = link.path ?? '(no local path recorded)';
308
+ console.log(` ${link.name} -> ${location}`);
309
+ }
310
+ console.log('Repair skipped links with openspec workspace doctor.');
311
+ }
312
+ class WorkspaceCommand {
313
+ async setup(options = {}) {
314
+ try {
315
+ const noInteractive = resolveNoInteractive(options);
316
+ if (options.json && !noInteractive) {
317
+ throw new WorkspaceCliError('workspace setup --json requires --no-interactive.', 'setup_json_requires_no_interactive', {
318
+ fix: 'openspec workspace setup --no-interactive --json --name <name> --link <path>',
319
+ });
320
+ }
321
+ const interactive = !noInteractive && isInteractive(options);
322
+ if (interactive) {
323
+ printWorkspaceSetupIntro();
324
+ }
325
+ if (!interactive && (!options.name || (options.link ?? []).length === 0)) {
326
+ throw new WorkspaceCliError('workspace setup --no-interactive requires --name <name> and at least one --link <path>.', 'missing_setup_inputs', {
327
+ fix: 'openspec workspace setup --no-interactive --name platform --link /path/to/repo',
328
+ });
329
+ }
330
+ const workspaceName = interactive
331
+ ? await promptWorkspaceName(options.name)
332
+ : validateWorkspaceNameForSetup(options.name ?? '');
333
+ const links = interactive ? await promptSetupLinks() : await parseSetupLinks(options.link);
334
+ if (interactive) {
335
+ console.log('');
336
+ console.log(chalk.bold('[3/5] Choose preferred opener'));
337
+ }
338
+ const preferredOpener = interactive
339
+ ? await promptPreferredOpener('Preferred opener:')
340
+ : parseSetupOpenerOption(options.opener);
341
+ let selectedWorkspaceSkillAgents;
342
+ if (options.tools !== undefined) {
343
+ selectedWorkspaceSkillAgents = parseSetupToolsOption(options.tools);
344
+ }
345
+ else if (interactive) {
346
+ console.log('');
347
+ console.log(chalk.bold('[4/5] Install agent skills'));
348
+ console.log(chalk.dim('Choose which coding agents should get OpenSpec skills in this workspace.'));
349
+ console.log(chalk.dim('Press Enter with no agents selected to skip skill installation for now.'));
350
+ console.log('');
351
+ selectedWorkspaceSkillAgents = await promptWorkspaceSkillAgents(preferredOpener);
352
+ }
353
+ if (Object.keys(links).length === 0) {
354
+ throw new WorkspaceCliError('workspace setup --no-interactive requires --name <name> and at least one --link <path>.', 'missing_setup_inputs', {
355
+ fix: 'openspec workspace setup --no-interactive --name platform --link /path/to/repo',
356
+ });
357
+ }
358
+ if (interactive) {
359
+ console.log('');
360
+ console.log(chalk.bold('[5/5] Create workspace files'));
361
+ }
362
+ const workspace = await createManagedWorkspace(workspaceName, links, preferredOpener);
363
+ const skillReport = selectedWorkspaceSkillAgents === undefined
364
+ ? createWorkspaceSkillSkippedReport('tools_omitted', 'No workspace skills were installed. Run openspec workspace update --tools <ids> to install them later.')
365
+ : await generateWorkspaceAgentSkills(workspace.root, selectedWorkspaceSkillAgents);
366
+ if (selectedWorkspaceSkillAgents !== undefined && !hasWorkspaceSkillFailures(skillReport)) {
367
+ await writeWorkspaceSkillState(workspace.root, selectedWorkspaceSkillAgents, skillReport);
368
+ }
369
+ const doctorResult = await loadWorkspaceForDoctor({
370
+ name: workspace.name,
371
+ root: workspace.root,
372
+ status: [],
373
+ unregisteredCurrentWorkspace: false,
374
+ });
375
+ if (options.json) {
376
+ printJson({
377
+ workspace: doctorResult.workspace,
378
+ workspace_skills: skillReport,
379
+ status: doctorResult.status,
380
+ });
381
+ setWorkspaceSkillFailureExitCode(skillReport);
382
+ return;
383
+ }
384
+ console.log(chalk.green('Workspace setup complete'));
385
+ console.log('');
386
+ printWorkspaceListHuman([doctorResult.workspace]);
387
+ console.log('');
388
+ console.log('Workspace check:');
389
+ printWorkspaceCheckSummaryHuman(doctorResult);
390
+ console.log('');
391
+ printWorkspaceSkillReportHuman(skillReport);
392
+ console.log('');
393
+ console.log('Next useful commands:');
394
+ console.log(` openspec workspace doctor --workspace ${workspace.name}`);
395
+ console.log(` openspec workspace update --workspace ${workspace.name} --tools <ids>`);
396
+ console.log(' openspec workspace list');
397
+ setWorkspaceSkillFailureExitCode(skillReport);
398
+ }
399
+ catch (error) {
400
+ this.handleFailure(options.json, { workspace: null, status: [] }, error);
401
+ }
402
+ }
403
+ async list(options = {}) {
404
+ try {
405
+ const entries = await listKnownWorkspaceEntries();
406
+ const workspaces = await Promise.all(entries.map((entry) => loadWorkspaceForList(entry)));
407
+ const payload = { workspaces, status: [] };
408
+ if (options.json) {
409
+ printJson(payload);
410
+ return;
411
+ }
412
+ if (workspaces.length === 0) {
413
+ console.log("No OpenSpec workspaces found. Run 'openspec workspace setup' first.");
414
+ return;
415
+ }
416
+ printWorkspaceListHuman(workspaces);
417
+ }
418
+ catch (error) {
419
+ this.handleFailure(options.json, { workspaces: [], status: [] }, error);
420
+ }
421
+ }
422
+ async link(nameOrPath, linkPath, options = {}) {
423
+ try {
424
+ if (!nameOrPath) {
425
+ throw new WorkspaceCliError('workspace link requires a repo or folder path.', 'missing_link_path', {
426
+ fix: 'openspec workspace link /path/to/repo',
427
+ });
428
+ }
429
+ const selected = await selectWorkspaceForCommand(options, 'link');
430
+ const payload = await addWorkspaceLink(selected, nameOrPath, linkPath);
431
+ if (options.json) {
432
+ printJson(payload);
433
+ return;
434
+ }
435
+ printLinkMutationHuman('Linked repo or folder:', payload);
436
+ }
437
+ catch (error) {
438
+ this.handleFailure(options.json, { workspace: null, link: null, status: [] }, error);
439
+ }
440
+ }
441
+ async relink(linkNameInput, linkPath, options = {}) {
442
+ try {
443
+ if (!linkNameInput || !linkPath) {
444
+ throw new WorkspaceCliError('workspace relink requires a link name and repo or folder path.', 'missing_relink_arguments', {
445
+ fix: 'openspec workspace relink <name> /path/to/repo',
446
+ });
447
+ }
448
+ const selected = await selectWorkspaceForCommand(options, 'relink');
449
+ const payload = await updateWorkspaceLink(selected, linkNameInput, linkPath);
450
+ if (options.json) {
451
+ printJson(payload);
452
+ return;
453
+ }
454
+ printLinkMutationHuman('Relinked repo or folder:', payload);
455
+ }
456
+ catch (error) {
457
+ this.handleFailure(options.json, { workspace: null, link: null, status: [] }, error);
458
+ }
459
+ }
460
+ async doctor(options = {}) {
461
+ try {
462
+ const selected = await selectWorkspaceForCommand(options, 'doctor');
463
+ const result = await loadWorkspaceForDoctor(selected);
464
+ if (options.json) {
465
+ printJson(result);
466
+ return;
467
+ }
468
+ printDoctorHuman(result);
469
+ }
470
+ catch (error) {
471
+ this.handleFailure(options.json, { workspace: null, status: [] }, error);
472
+ }
473
+ }
474
+ async update(positionalName, options = {}) {
475
+ try {
476
+ const workspaceName = resolveUpdateWorkspaceName(positionalName, options);
477
+ const selected = await selectWorkspaceForCommand({
478
+ ...options,
479
+ workspace: workspaceName,
480
+ }, 'update', { preferPositionalName: Boolean(positionalName) });
481
+ await this.updateSelected(selected, options);
482
+ }
483
+ catch (error) {
484
+ this.handleFailure(options.json, { workspace: null, workspace_skills: null, status: [] }, error);
485
+ }
486
+ }
487
+ async updateRoot(workspaceRoot, options = {}) {
488
+ try {
489
+ const selected = await selectWorkspaceRootForCommand(workspaceRoot);
490
+ await this.updateSelected(selected, options);
491
+ }
492
+ catch (error) {
493
+ this.handleFailure(options.json, { workspace: null, workspace_skills: null, status: [] }, error);
494
+ }
495
+ }
496
+ async updateSelected(selected, options) {
497
+ const viewState = await readWorkspaceForMutation(selected);
498
+ await syncWorkspaceOpenSurface(selected.root, viewState);
499
+ const hasExplicitToolSelection = options.tools !== undefined;
500
+ const selectedAgentIds = hasExplicitToolSelection
501
+ ? parseUpdateToolsOption(options.tools ?? '')
502
+ : viewState.workspace_skills?.selected_agents ?? [];
503
+ const previousSkillState = hasExplicitToolSelection
504
+ ? viewState.workspace_skills ?? { selected_agents: [] }
505
+ : viewState.workspace_skills;
506
+ const skillReport = await updateWorkspaceAgentSkills(selected.root, selectedAgentIds, previousSkillState);
507
+ const shouldStoreSelection = hasExplicitToolSelection || Boolean(viewState.workspace_skills);
508
+ if (shouldStoreSelection && !hasWorkspaceSkillFailures(skillReport)) {
509
+ await writeWorkspaceSkillState(selected.root, selectedAgentIds, skillReport);
510
+ }
511
+ const doctorResult = await loadWorkspaceForDoctor(selected);
512
+ if (options.json) {
513
+ printJson({
514
+ workspace: doctorResult.workspace,
515
+ workspace_skills: skillReport,
516
+ status: doctorResult.status,
517
+ });
518
+ setWorkspaceSkillFailureExitCode(skillReport);
519
+ return;
520
+ }
521
+ console.log(chalk.green('Workspace update complete'));
522
+ console.log(`Workspace: ${doctorResult.workspace.name}`);
523
+ console.log(`Location: ${doctorResult.workspace.root}`);
524
+ console.log('');
525
+ printStatusLines(doctorResult.status);
526
+ if (doctorResult.status.length > 0) {
527
+ console.log('');
528
+ }
529
+ printWorkspaceSkillReportHuman(skillReport);
530
+ console.log('');
531
+ console.log('Next useful commands:');
532
+ console.log(` openspec workspace doctor --workspace ${doctorResult.workspace.name}`);
533
+ console.log(` openspec workspace update --workspace ${doctorResult.workspace.name} --tools <ids>`);
534
+ setWorkspaceSkillFailureExitCode(skillReport);
535
+ }
536
+ async open(positionalName, options = {}) {
537
+ try {
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));
551
+ }
552
+ }
553
+ catch (error) {
554
+ this.handleFailure(options.json, { workspace: null, status: [] }, error);
555
+ }
556
+ }
557
+ handleFailure(json, payload, error) {
558
+ if (!json && isPromptCancellationError(error)) {
559
+ console.error('Cancelled.');
560
+ process.exitCode = 130;
561
+ return;
562
+ }
563
+ if (json) {
564
+ printJson(appendStatus(payload, asStatus(error)));
565
+ process.exitCode = 1;
566
+ return;
567
+ }
568
+ const status = asStatus(error);
569
+ console.error(`Error: ${status.message}`);
570
+ if (status.fix) {
571
+ console.error(`Fix: ${status.fix}`);
572
+ }
573
+ process.exitCode = 1;
574
+ }
575
+ }
576
+ export async function runWorkspaceUpdate(positionalName, options = {}) {
577
+ const workspaceCommand = new WorkspaceCommand();
578
+ await workspaceCommand.update(positionalName, options);
579
+ }
580
+ export async function runWorkspaceUpdateForRoot(workspaceRoot, options = {}) {
581
+ const workspaceCommand = new WorkspaceCommand();
582
+ await workspaceCommand.updateRoot(workspaceRoot, options);
583
+ }
584
+ export function registerWorkspaceCommand(program) {
585
+ registerWorkspaceCommandWith(program, new WorkspaceCommand());
586
+ }
587
+ //# sourceMappingURL=workspace.js.map
@@ -2,6 +2,8 @@ export { ArtifactSchema, SchemaYamlSchema, type Artifact, type SchemaYaml, type
2
2
  export { loadSchema, parseSchema, SchemaValidationError } from './schema.js';
3
3
  export { ArtifactGraph } from './graph.js';
4
4
  export { detectCompleted } from './state.js';
5
+ export { artifactOutputExists, isGlobPattern, resolveArtifactOutputs } from './outputs.js';
5
6
  export { resolveSchema, listSchemas, listSchemasWithInfo, getSchemaDir, getPackageSchemasDir, getUserSchemasDir, SchemaLoadError, type SchemaInfo, } from './resolver.js';
6
- export { loadTemplate, loadChangeContext, generateInstructions, formatChangeStatus, TemplateLoadError, type ChangeContext, type ArtifactInstructions, type DependencyInfo, type ArtifactStatus, type ChangeStatus, } 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';
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -6,6 +6,7 @@ export { loadSchema, parseSchema, SchemaValidationError } from './schema.js';
6
6
  export { ArtifactGraph } from './graph.js';
7
7
  // State detection
8
8
  export { detectCompleted } from './state.js';
9
+ export { artifactOutputExists, isGlobPattern, resolveArtifactOutputs } from './outputs.js';
9
10
  // Schema resolution
10
11
  export { resolveSchema, listSchemas, listSchemasWithInfo, getSchemaDir, getPackageSchemasDir, getUserSchemasDir, SchemaLoadError, } from './resolver.js';
11
12
  // Instruction loading
@@ -1,4 +1,7 @@
1
1
  import { ArtifactGraph } from './graph.js';
2
+ import { type ActionContext, type AffectedAreasSummary, type PlanningHomeSummary } from '../change-status-policy.js';
3
+ import type { PlanningHome } from '../planning-home.js';
4
+ import type { ChangeMetadata, InitiativeLink } from '../change-metadata/index.js';
2
5
  import type { CompletedSet } from './types.js';
3
6
  /**
4
7
  * Error thrown when loading a template fails.
@@ -23,6 +26,16 @@ export interface ChangeContext {
23
26
  changeDir: string;
24
27
  /** Project root directory */
25
28
  projectRoot: string;
29
+ /** Resolved planning home for this change */
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;
35
+ }
36
+ export interface LoadChangeContextOptions {
37
+ changeDir?: string;
38
+ planningHome?: PlanningHome;
26
39
  }
27
40
  /**
28
41
  * Enriched instructions for creating an artifact.
@@ -36,8 +49,16 @@ export interface ArtifactInstructions {
36
49
  schemaName: string;
37
50
  /** Full path to change directory */
38
51
  changeDir: string;
52
+ /** Resolved planning home for this change */
53
+ planningHome?: PlanningHomeSummary;
54
+ /** Stored initiative link, when this change is linked to shared context */
55
+ initiative?: InitiativeLink;
39
56
  /** Output path pattern (e.g., "proposal.md") */
40
57
  outputPath: string;
58
+ /** Absolute output path or glob pattern resolved under the change directory */
59
+ resolvedOutputPath: string;
60
+ /** Existing concrete output files for this artifact */
61
+ existingOutputPaths: string[];
41
62
  /** Artifact description */
42
63
  description: string;
43
64
  /** Guidance on how to create this artifact (from schema instruction field) */
@@ -87,6 +108,20 @@ export interface ChangeStatus {
87
108
  changeName: string;
88
109
  /** Schema name */
89
110
  schemaName: string;
111
+ /** Resolved planning home for this change */
112
+ planningHome?: PlanningHomeSummary;
113
+ /** Stored initiative link, when this change is linked to shared context */
114
+ initiative?: InitiativeLink;
115
+ /** Full path to the change root */
116
+ changeRoot: string;
117
+ /** Absolute artifact path details keyed by artifact ID */
118
+ artifactPaths: Record<string, ArtifactPathSummary>;
119
+ /** Workspace affected-area summary, when available */
120
+ affectedAreas?: AffectedAreasSummary;
121
+ /** Plain-language next steps for users and agents */
122
+ nextSteps: string[];
123
+ /** Machine-readable action constraints for agents */
124
+ actionContext: ActionContext;
90
125
  /** Whether all artifacts are complete */
91
126
  isComplete: boolean;
92
127
  /** Artifact IDs required before apply phase (from schema's apply.requires) */
@@ -94,6 +129,11 @@ export interface ChangeStatus {
94
129
  /** Status of each artifact */
95
130
  artifacts: ArtifactStatus[];
96
131
  }
132
+ export interface ArtifactPathSummary {
133
+ outputPath: string;
134
+ resolvedOutputPath: string;
135
+ existingOutputPaths: string[];
136
+ }
97
137
  /**
98
138
  * Loads a template from a schema's templates directory.
99
139
  *
@@ -117,7 +157,7 @@ export declare function loadTemplate(schemaName: string, templatePath: string, p
117
157
  * @param schemaName - Optional schema name override. If not provided, auto-detected from metadata.
118
158
  * @returns Change context with graph, completed set, and metadata
119
159
  */
120
- export declare function loadChangeContext(projectRoot: string, changeName: string, schemaName?: string): ChangeContext;
160
+ export declare function loadChangeContext(projectRoot: string, changeName: string, schemaName?: string, options?: LoadChangeContextOptions): ChangeContext;
121
161
  /**
122
162
  * Generates enriched instructions for creating an artifact.
123
163
  *