@fission-ai/openspec 1.4.0 → 1.5.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 (206) hide show
  1. package/README.md +24 -3
  2. package/dist/cli/index.d.ts +5 -0
  3. package/dist/cli/index.js +94 -76
  4. package/dist/commands/change.d.ts +5 -1
  5. package/dist/commands/change.js +17 -15
  6. package/dist/commands/config.js +6 -65
  7. package/dist/commands/context.d.ts +3 -0
  8. package/dist/commands/context.js +155 -0
  9. package/dist/commands/doctor.d.ts +8 -0
  10. package/dist/commands/doctor.js +163 -0
  11. package/dist/commands/shared-gather.d.ts +14 -0
  12. package/dist/commands/shared-gather.js +31 -0
  13. package/dist/commands/shared-output.d.ts +18 -0
  14. package/dist/commands/shared-output.js +61 -0
  15. package/dist/commands/show.d.ts +11 -6
  16. package/dist/commands/show.js +70 -25
  17. package/dist/commands/spec.d.ts +5 -1
  18. package/dist/commands/spec.js +15 -4
  19. package/dist/commands/store.d.ts +3 -0
  20. package/dist/commands/store.js +547 -0
  21. package/dist/commands/validate.d.ts +2 -0
  22. package/dist/commands/validate.js +75 -39
  23. package/dist/commands/workflow/index.d.ts +0 -2
  24. package/dist/commands/workflow/index.js +0 -1
  25. package/dist/commands/workflow/instructions.d.ts +11 -1
  26. package/dist/commands/workflow/instructions.js +71 -22
  27. package/dist/commands/workflow/new-change.d.ts +6 -3
  28. package/dist/commands/workflow/new-change.js +42 -77
  29. package/dist/commands/workflow/shared.d.ts +15 -3
  30. package/dist/commands/workflow/shared.js +20 -3
  31. package/dist/commands/workflow/status.d.ts +2 -0
  32. package/dist/commands/workflow/status.js +19 -17
  33. package/dist/commands/workset-input.d.ts +19 -0
  34. package/dist/commands/workset-input.js +112 -0
  35. package/dist/commands/workset-prompts.d.ts +12 -0
  36. package/dist/commands/workset-prompts.js +143 -0
  37. package/dist/commands/workset.d.ts +25 -0
  38. package/dist/commands/workset.js +446 -0
  39. package/dist/core/archive.d.ts +17 -6
  40. package/dist/core/archive.js +209 -56
  41. package/dist/core/artifact-graph/index.d.ts +1 -1
  42. package/dist/core/artifact-graph/instruction-loader.d.ts +18 -13
  43. package/dist/core/artifact-graph/instruction-loader.js +8 -31
  44. package/dist/core/change-metadata/schema.d.ts +1 -0
  45. package/dist/core/change-metadata/schema.js +4 -2
  46. package/dist/core/change-status-policy.d.ts +5 -18
  47. package/dist/core/change-status-policy.js +2 -37
  48. package/dist/core/command-generation/adapters/bob.js +1 -14
  49. package/dist/core/command-generation/adapters/claude.js +1 -14
  50. package/dist/core/command-generation/adapters/cursor.js +1 -14
  51. package/dist/core/command-generation/adapters/pi.js +1 -14
  52. package/dist/core/command-generation/adapters/windsurf.js +1 -14
  53. package/dist/core/command-generation/yaml.d.ts +22 -0
  54. package/dist/core/command-generation/yaml.js +38 -0
  55. package/dist/core/completions/command-registry.js +87 -289
  56. package/dist/core/completions/generators/zsh-generator.js +3 -1
  57. package/dist/core/completions/installers/bash-installer.js +3 -0
  58. package/dist/core/completions/installers/fish-installer.js +8 -0
  59. package/dist/core/completions/installers/powershell-installer.js +30 -2
  60. package/dist/core/completions/installers/zsh-installer.js +3 -0
  61. package/dist/core/completions/shared-flags.d.ts +1 -0
  62. package/dist/core/completions/shared-flags.js +5 -0
  63. package/dist/core/config-schema.d.ts +2 -1
  64. package/dist/core/config-schema.js +26 -0
  65. package/dist/core/file-state.d.ts +36 -0
  66. package/dist/core/file-state.js +112 -0
  67. package/dist/core/global-config.d.ts +2 -0
  68. package/dist/core/id.d.ts +17 -0
  69. package/dist/core/id.js +30 -0
  70. package/dist/core/index.d.ts +3 -3
  71. package/dist/core/index.js +3 -3
  72. package/dist/core/init.js +24 -4
  73. package/dist/core/list.d.ts +2 -0
  74. package/dist/core/list.js +19 -5
  75. package/dist/core/openers.d.ts +77 -0
  76. package/dist/core/openers.js +251 -0
  77. package/dist/core/openspec-root.d.ts +45 -0
  78. package/dist/core/openspec-root.js +192 -0
  79. package/dist/core/planning-home.d.ts +1 -6
  80. package/dist/core/planning-home.js +2 -43
  81. package/dist/core/project-config.d.ts +44 -1
  82. package/dist/core/project-config.js +160 -10
  83. package/dist/core/references.d.ts +63 -0
  84. package/dist/core/references.js +310 -0
  85. package/dist/core/relationship-health.d.ts +65 -0
  86. package/dist/core/relationship-health.js +64 -0
  87. package/dist/core/root-selection.d.ts +122 -0
  88. package/dist/core/root-selection.js +337 -0
  89. package/dist/core/specs-apply.d.ts +6 -1
  90. package/dist/core/specs-apply.js +6 -4
  91. package/dist/core/store/errors.d.ts +20 -0
  92. package/dist/core/{context-store → store}/errors.js +3 -3
  93. package/dist/core/store/foundation.d.ts +56 -0
  94. package/dist/core/store/foundation.js +251 -0
  95. package/dist/core/store/git.d.ts +23 -0
  96. package/dist/core/store/git.js +137 -0
  97. package/dist/core/{context-store → store}/index.d.ts +0 -1
  98. package/dist/core/{context-store → store}/index.js +0 -1
  99. package/dist/core/store/operations.d.ts +114 -0
  100. package/dist/core/store/operations.js +783 -0
  101. package/dist/core/store/registry.d.ts +58 -0
  102. package/dist/core/store/registry.js +275 -0
  103. package/dist/core/templates/workflows/apply-change.js +5 -4
  104. package/dist/core/templates/workflows/archive-change.js +5 -4
  105. package/dist/core/templates/workflows/bulk-archive-change.js +5 -4
  106. package/dist/core/templates/workflows/continue-change.js +7 -2
  107. package/dist/core/templates/workflows/explore.js +5 -0
  108. package/dist/core/templates/workflows/ff-change.js +5 -0
  109. package/dist/core/templates/workflows/new-change.js +5 -0
  110. package/dist/core/templates/workflows/onboard.js +4 -1
  111. package/dist/core/templates/workflows/propose.js +5 -0
  112. package/dist/core/templates/workflows/store-selection.d.ts +8 -0
  113. package/dist/core/templates/workflows/store-selection.js +8 -0
  114. package/dist/core/templates/workflows/sync-specs.js +5 -4
  115. package/dist/core/templates/workflows/verify-change.js +5 -4
  116. package/dist/core/working-set.d.ts +47 -0
  117. package/dist/core/working-set.js +43 -0
  118. package/dist/core/worksets.d.ts +75 -0
  119. package/dist/core/worksets.js +245 -0
  120. package/dist/core/zod-issues.d.ts +4 -0
  121. package/dist/core/zod-issues.js +10 -0
  122. package/dist/utils/change-metadata.d.ts +1 -0
  123. package/dist/utils/change-metadata.js +1 -1
  124. package/dist/utils/change-utils.js +15 -0
  125. package/dist/utils/file-system.js +36 -17
  126. package/package.json +6 -6
  127. package/dist/commands/context-store.d.ts +0 -3
  128. package/dist/commands/context-store.js +0 -475
  129. package/dist/commands/initiative.d.ts +0 -13
  130. package/dist/commands/initiative.js +0 -318
  131. package/dist/commands/workflow/initiative-link.d.ts +0 -24
  132. package/dist/commands/workflow/initiative-link.js +0 -47
  133. package/dist/commands/workflow/set-change.d.ts +0 -13
  134. package/dist/commands/workflow/set-change.js +0 -87
  135. package/dist/commands/workspace/context-status.d.ts +0 -4
  136. package/dist/commands/workspace/context-status.js +0 -59
  137. package/dist/commands/workspace/open-target-selection.d.ts +0 -13
  138. package/dist/commands/workspace/open-target-selection.js +0 -146
  139. package/dist/commands/workspace/open-view.d.ts +0 -62
  140. package/dist/commands/workspace/open-view.js +0 -249
  141. package/dist/commands/workspace/open.d.ts +0 -37
  142. package/dist/commands/workspace/open.js +0 -110
  143. package/dist/commands/workspace/opener-selection.d.ts +0 -11
  144. package/dist/commands/workspace/opener-selection.js +0 -98
  145. package/dist/commands/workspace/operations.d.ts +0 -29
  146. package/dist/commands/workspace/operations.js +0 -543
  147. package/dist/commands/workspace/prompt-theme.d.ts +0 -29
  148. package/dist/commands/workspace/prompt-theme.js +0 -24
  149. package/dist/commands/workspace/registration.d.ts +0 -13
  150. package/dist/commands/workspace/registration.js +0 -84
  151. package/dist/commands/workspace/selection.d.ts +0 -9
  152. package/dist/commands/workspace/selection.js +0 -115
  153. package/dist/commands/workspace/setup-prompts.d.ts +0 -13
  154. package/dist/commands/workspace/setup-prompts.js +0 -121
  155. package/dist/commands/workspace/types.d.ts +0 -103
  156. package/dist/commands/workspace/types.js +0 -36
  157. package/dist/commands/workspace.d.ts +0 -6
  158. package/dist/commands/workspace.js +0 -587
  159. package/dist/core/collections/index.d.ts +0 -3
  160. package/dist/core/collections/index.js +0 -3
  161. package/dist/core/collections/initiatives/collection.d.ts +0 -4
  162. package/dist/core/collections/initiatives/collection.js +0 -17
  163. package/dist/core/collections/initiatives/index.d.ts +0 -6
  164. package/dist/core/collections/initiatives/index.js +0 -6
  165. package/dist/core/collections/initiatives/operations.d.ts +0 -49
  166. package/dist/core/collections/initiatives/operations.js +0 -175
  167. package/dist/core/collections/initiatives/resolution.d.ts +0 -87
  168. package/dist/core/collections/initiatives/resolution.js +0 -374
  169. package/dist/core/collections/initiatives/schema.d.ts +0 -41
  170. package/dist/core/collections/initiatives/schema.js +0 -134
  171. package/dist/core/collections/initiatives/templates.d.ts +0 -12
  172. package/dist/core/collections/initiatives/templates.js +0 -90
  173. package/dist/core/collections/runtime.d.ts +0 -46
  174. package/dist/core/collections/runtime.js +0 -194
  175. package/dist/core/context-store/binding.d.ts +0 -53
  176. package/dist/core/context-store/binding.js +0 -197
  177. package/dist/core/context-store/errors.d.ts +0 -20
  178. package/dist/core/context-store/foundation.d.ts +0 -55
  179. package/dist/core/context-store/foundation.js +0 -321
  180. package/dist/core/context-store/operations.d.ts +0 -85
  181. package/dist/core/context-store/operations.js +0 -528
  182. package/dist/core/context-store/registry.d.ts +0 -45
  183. package/dist/core/context-store/registry.js +0 -229
  184. package/dist/core/workspace/foundation.d.ts +0 -67
  185. package/dist/core/workspace/foundation.js +0 -295
  186. package/dist/core/workspace/index.d.ts +0 -8
  187. package/dist/core/workspace/index.js +0 -8
  188. package/dist/core/workspace/legacy-state.d.ts +0 -28
  189. package/dist/core/workspace/legacy-state.js +0 -200
  190. package/dist/core/workspace/link-input.d.ts +0 -9
  191. package/dist/core/workspace/link-input.js +0 -32
  192. package/dist/core/workspace/open-surface.d.ts +0 -45
  193. package/dist/core/workspace/open-surface.js +0 -215
  194. package/dist/core/workspace/openers.d.ts +0 -21
  195. package/dist/core/workspace/openers.js +0 -124
  196. package/dist/core/workspace/registry.d.ts +0 -24
  197. package/dist/core/workspace/registry.js +0 -146
  198. package/dist/core/workspace/skills.d.ts +0 -57
  199. package/dist/core/workspace/skills.js +0 -334
  200. package/dist/core/workspace/state-io.d.ts +0 -10
  201. package/dist/core/workspace/state-io.js +0 -119
  202. package/schemas/workspace-planning/schema.yaml +0 -72
  203. package/schemas/workspace-planning/templates/design.md +0 -33
  204. package/schemas/workspace-planning/templates/proposal.md +0 -28
  205. package/schemas/workspace-planning/templates/spec.md +0 -9
  206. package/schemas/workspace-planning/templates/tasks.md +0 -15
package/README.md CHANGED
@@ -47,6 +47,14 @@ Our philosophy:
47
47
  ## See it in action
48
48
 
49
49
  ```text
50
+ You: /opsx:explore
51
+ AI: What would you like to explore?
52
+ You: I want dark mode but I'm not sure how to do it cleanly.
53
+ AI: Let me look at your styling setup...
54
+ Cleanest path here: CSS variables + a small theme context,
55
+ with system-preference detection. No new dependencies. Scope it?
56
+ You: Yes, let's do it.
57
+
50
58
  You: /opsx:propose add-dark-mode
51
59
  AI: Created openspec/changes/add-dark-mode/
52
60
  ✓ proposal.md — why we're doing this, what's changing
@@ -94,9 +102,12 @@ cd your-project
94
102
  openspec init
95
103
  ```
96
104
 
97
- Now tell your AI: `/opsx:propose <what-you-want-to-build>`
105
+ Now talk to your AI:
106
+
107
+ - **Not sure what to build yet?** Start with `/opsx:explore`, a no-stakes thinking partner that reads your code, weighs options, and shapes a plan before anything is written. ([Explore guide](docs/explore.md))
108
+ - **Already know what you want?** Go straight to `/opsx:propose <what-you-want-to-build>`.
98
109
 
99
- If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:bulk-archive`, `/opsx:onboard`), select it with `openspec config profile` and apply with `openspec update`.
110
+ Both are in the default profile. If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/opsx:verify`, `/opsx:bulk-archive`, `/opsx:onboard`), select it with `openspec config profile` and apply with `openspec update`.
100
111
 
101
112
  > [!NOTE]
102
113
  > Not sure if your tool is supported? [View the full list](docs/supported-tools.md) – we support 25+ tools and growing.
@@ -105,14 +116,24 @@ If you want the expanded workflow (`/opsx:new`, `/opsx:continue`, `/opsx:ff`, `/
105
116
 
106
117
  ## Docs
107
118
 
119
+ **Start here:** the **[Documentation Home](docs/README.md)** maps everything. New to OpenSpec? Read [Getting Started](docs/getting-started.md), then [How Commands Work](docs/how-commands-work.md) (where you actually type `/opsx:propose`).
120
+
108
121
  → **[Getting Started](docs/getting-started.md)**: first steps<br>
122
+ → **[Explore First](docs/explore.md)**: think it through with `/opsx:explore` before you commit<br>
123
+ → **[How Commands Work](docs/how-commands-work.md)**: where slash commands run vs the CLI<br>
124
+ → **[Core Concepts at a Glance](docs/overview.md)**: the whole mental model, one page<br>
125
+ → **[Examples & Recipes](docs/examples.md)**: real changes, start to finish<br>
109
126
  → **[Workflows](docs/workflows.md)**: combos and patterns<br>
127
+ → **[Existing Projects](docs/existing-projects.md)**: adopt OpenSpec on a brownfield codebase<br>
128
+ → **[Editing a Change](docs/editing-changes.md)**: update artifacts, go back, reconcile manual edits<br>
110
129
  → **[Commands](docs/commands.md)**: slash commands & skills<br>
111
130
  → **[CLI](docs/cli.md)**: terminal reference<br>
131
+ → **[Stores](docs/stores-beta/user-guide.md)**: plan in a separate repo, shared across your team (beta)<br>
112
132
  → **[Supported Tools](docs/supported-tools.md)**: tool integrations & install paths<br>
113
133
  → **[Concepts](docs/concepts.md)**: how it all fits<br>
114
134
  → **[Multi-Language](docs/multi-language.md)**: multi-language support<br>
115
- → **[Customization](docs/customization.md)**: make it yours
135
+ → **[Customization](docs/customization.md)**: make it yours<br>
136
+ → **[FAQ](docs/faq.md)** · **[Troubleshooting](docs/troubleshooting.md)** · **[Glossary](docs/glossary.md)**: quick help
116
137
 
117
138
 
118
139
  ## Community schemas
@@ -1,5 +1,10 @@
1
1
  import { Command } from 'commander';
2
2
  declare const program: Command;
3
+ /**
4
+ * Get the full command path for nested commands.
5
+ * For example: 'change show' -> 'change:show'
6
+ */
7
+ export declare function getCommandPath(command: Command): string;
3
8
  export { program };
4
9
  export declare function runCli(argv?: string[]): void;
5
10
  //# sourceMappingURL=index.d.ts.map
package/dist/cli/index.js CHANGED
@@ -1,4 +1,5 @@
1
- import { Command } from 'commander';
1
+ import { asStatus } from '../commands/shared-output.js';
2
+ import { Command, Option } from 'commander';
2
3
  import { createRequire } from 'module';
3
4
  import ora from 'ora';
4
5
  import path from 'path';
@@ -9,6 +10,7 @@ import { UpdateCommand } from '../core/update.js';
9
10
  import { ListCommand } from '../core/list.js';
10
11
  import { ArchiveCommand } from '../core/archive.js';
11
12
  import { ViewCommand } from '../core/view.js';
13
+ import { resolveRootForCommand, toRootOutput } from '../core/root-selection.js';
12
14
  import { registerSpecCommand } from '../commands/spec.js';
13
15
  import { ChangeCommand } from '../commands/change.js';
14
16
  import { ValidateCommand } from '../commands/validate.js';
@@ -17,12 +19,37 @@ import { CompletionCommand } from '../commands/completion.js';
17
19
  import { FeedbackCommand } from '../commands/feedback.js';
18
20
  import { registerConfigCommand } from '../commands/config.js';
19
21
  import { registerSchemaCommand } from '../commands/schema.js';
20
- import { registerWorkspaceCommand, runWorkspaceUpdateForRoot, } from '../commands/workspace.js';
21
- import { registerContextStoreCommand } from '../commands/context-store.js';
22
- import { registerInitiativeCommand } from '../commands/initiative.js';
23
- import { findWorkspaceRoot } from '../core/workspace/index.js';
24
- import { statusCommand, instructionsCommand, applyInstructionsCommand, templatesCommand, schemasCommand, newChangeCommand, setChangeCommand, DEFAULT_SCHEMA, } from '../commands/workflow/index.js';
22
+ import { registerStoreCommand } from '../commands/store.js';
23
+ import { registerDoctorCommand } from '../commands/doctor.js';
24
+ import { registerContextCommand } from '../commands/context.js';
25
+ import { registerWorksetCommand } from '../commands/workset.js';
26
+ import { statusCommand, instructionsCommand, applyInstructionsCommand, templatesCommand, schemasCommand, newChangeCommand, DEFAULT_SCHEMA, } from '../commands/workflow/index.js';
25
27
  import { maybeShowTelemetryNotice, trackCommand, shutdown } from '../telemetry/index.js';
28
+ import { COMMON_FLAGS } from '../core/completions/shared-flags.js';
29
+ const STORE_OPTION_DESCRIPTION = COMMON_FLAGS.store.description;
30
+ // Deliberate rejection path: --store-path stays registered (hidden) so the
31
+ // resolver can explain that registering the path is the supported route,
32
+ // instead of Commander emitting a generic unknown-option error (or, for
33
+ // `show`, silently ignoring it via allowUnknownOption).
34
+ function hiddenStorePathOption() {
35
+ return new Option('--store-path <path>', 'Not supported; register the path with "openspec store register <path>" and use --store <id>').hideHelp();
36
+ }
37
+ function failWithError(error, json) {
38
+ // The agent contract: every --json failure leaves exactly one JSON
39
+ // document on stdout (the command's null-shape plus a status array).
40
+ if (json?.enabled) {
41
+ console.log(JSON.stringify({ ...(json.payload ?? {}), status: [asStatus(error, json.fallbackCode ?? 'command_error')] }, null, 2));
42
+ process.exitCode = 1;
43
+ return;
44
+ }
45
+ ora().fail(`Error: ${error.message}`);
46
+ // Resolution and store errors carry a pasteable fix - never drop it.
47
+ const fix = error.diagnostic?.fix;
48
+ if (fix) {
49
+ console.error(`Fix: ${fix}`);
50
+ }
51
+ process.exitCode = process.exitCode ?? 1;
52
+ }
26
53
  const program = new Command();
27
54
  const require = createRequire(import.meta.url);
28
55
  const { version } = require('../../package.json');
@@ -30,7 +57,7 @@ const { version } = require('../../package.json');
30
57
  * Get the full command path for nested commands.
31
58
  * For example: 'change show' -> 'change:show'
32
59
  */
33
- function getCommandPath(command) {
60
+ export function getCommandPath(command) {
34
61
  const names = [];
35
62
  let current = command;
36
63
  while (current) {
@@ -107,8 +134,7 @@ program
107
134
  await initCommand.execute(targetPath);
108
135
  }
109
136
  catch (error) {
110
- console.log(); // Empty line for spacing
111
- ora().fail(`Error: ${error.message}`);
137
+ failWithError(error);
112
138
  process.exit(1);
113
139
  }
114
140
  });
@@ -129,8 +155,7 @@ program
129
155
  await initCommand.execute('.');
130
156
  }
131
157
  catch (error) {
132
- console.log();
133
- ora().fail(`Error: ${error.message}`);
158
+ failWithError(error);
134
159
  process.exit(1);
135
160
  }
136
161
  });
@@ -140,18 +165,11 @@ program
140
165
  .option('--force', 'Force update even when tools are up to date')
141
166
  .action(async (targetPath = '.', options) => {
142
167
  try {
143
- const resolvedPath = path.resolve(targetPath);
144
- const workspaceRoot = await findWorkspaceRoot(resolvedPath);
145
- if (workspaceRoot) {
146
- await runWorkspaceUpdateForRoot(workspaceRoot, { force: options?.force });
147
- return;
148
- }
149
168
  const updateCommand = new UpdateCommand({ force: options?.force });
150
- await updateCommand.execute(resolvedPath);
169
+ await updateCommand.execute(targetPath);
151
170
  }
152
171
  catch (error) {
153
- console.log(); // Empty line for spacing
154
- ora().fail(`Error: ${error.message}`);
172
+ failWithError(error);
155
173
  process.exit(1);
156
174
  }
157
175
  });
@@ -162,16 +180,32 @@ program
162
180
  .option('--changes', 'List changes explicitly (default)')
163
181
  .option('--sort <order>', 'Sort order: "recent" (default) or "name"', 'recent')
164
182
  .option('--json', 'Output as JSON (for programmatic use)')
183
+ .option('--store <id>', STORE_OPTION_DESCRIPTION)
184
+ .addOption(hiddenStorePathOption())
165
185
  .action(async (options) => {
166
186
  try {
187
+ const root = await resolveRootForCommand(options ?? {}, {
188
+ json: options?.json,
189
+ failurePayload: options?.specs ? { specs: [], root: null } : { changes: [], root: null },
190
+ });
191
+ if (!root) {
192
+ return;
193
+ }
167
194
  const listCommand = new ListCommand();
168
195
  const mode = options?.specs ? 'specs' : 'changes';
169
196
  const sort = options?.sort === 'name' ? 'name' : 'recent';
170
- await listCommand.execute('.', mode, { sort, json: options?.json });
197
+ await listCommand.execute(root.path, mode, {
198
+ sort,
199
+ json: options?.json,
200
+ ...(options?.json ? { root: toRootOutput(root) } : {}),
201
+ });
171
202
  }
172
203
  catch (error) {
173
- console.log(); // Empty line for spacing
174
- ora().fail(`Error: ${error.message}`);
204
+ failWithError(error, {
205
+ enabled: options?.json,
206
+ payload: options?.specs ? { specs: [], root: null } : { changes: [], root: null },
207
+ fallbackCode: 'list_error',
208
+ });
175
209
  process.exit(1);
176
210
  }
177
211
  });
@@ -184,8 +218,7 @@ program
184
218
  await viewCommand.execute('.');
185
219
  }
186
220
  catch (error) {
187
- console.log(); // Empty line for spacing
188
- ora().fail(`Error: ${error.message}`);
221
+ failWithError(error);
189
222
  process.exit(1);
190
223
  }
191
224
  });
@@ -255,23 +288,26 @@ program
255
288
  .option('-y, --yes', 'Skip confirmation prompts')
256
289
  .option('--skip-specs', 'Skip spec update operations (useful for infrastructure, tooling, or doc-only changes)')
257
290
  .option('--no-validate', 'Skip validation (not recommended, requires confirmation)')
291
+ .option('--json', 'Output as JSON (non-interactive)')
292
+ .option('--store <id>', STORE_OPTION_DESCRIPTION)
293
+ .addOption(hiddenStorePathOption())
258
294
  .action(async (changeName, options) => {
259
295
  try {
260
296
  const archiveCommand = new ArchiveCommand();
261
297
  await archiveCommand.execute(changeName, options);
262
298
  }
263
299
  catch (error) {
264
- console.log(); // Empty line for spacing
265
- ora().fail(`Error: ${error.message}`);
300
+ failWithError(error);
266
301
  process.exit(1);
267
302
  }
268
303
  });
269
304
  registerSpecCommand(program);
270
305
  registerConfigCommand(program);
271
306
  registerSchemaCommand(program);
272
- registerWorkspaceCommand(program);
273
- registerContextStoreCommand(program);
274
- registerInitiativeCommand(program);
307
+ registerStoreCommand(program);
308
+ registerDoctorCommand(program);
309
+ registerContextCommand(program);
310
+ registerWorksetCommand(program);
275
311
  // Top-level validate command
276
312
  program
277
313
  .command('validate [item-name]')
@@ -284,14 +320,15 @@ program
284
320
  .option('--json', 'Output validation results as JSON')
285
321
  .option('--concurrency <n>', 'Max concurrent validations (defaults to env OPENSPEC_CONCURRENCY or 6)')
286
322
  .option('--no-interactive', 'Disable interactive prompts')
323
+ .option('--store <id>', STORE_OPTION_DESCRIPTION)
324
+ .addOption(hiddenStorePathOption())
287
325
  .action(async (itemName, options) => {
288
326
  try {
289
327
  const validateCommand = new ValidateCommand();
290
328
  await validateCommand.execute(itemName, options);
291
329
  }
292
330
  catch (error) {
293
- console.log();
294
- ora().fail(`Error: ${error.message}`);
331
+ failWithError(error, { enabled: options?.json, fallbackCode: 'validate_error' });
295
332
  process.exit(1);
296
333
  }
297
334
  });
@@ -309,6 +346,10 @@ program
309
346
  .option('--requirements', 'JSON only: Show only requirements (exclude scenarios)')
310
347
  .option('--no-scenarios', 'JSON only: Exclude scenario content')
311
348
  .option('-r, --requirement <id>', 'JSON only: Show specific requirement by ID (1-based)')
349
+ .option('--store <id>', STORE_OPTION_DESCRIPTION)
350
+ // Explicit registration required: allowUnknownOption would otherwise
351
+ // silently swallow --store-path instead of rejecting it deliberately.
352
+ .addOption(hiddenStorePathOption())
312
353
  // allow unknown options to pass-through to underlying command implementation
313
354
  .allowUnknownOption(true)
314
355
  .action(async (itemName, options) => {
@@ -317,8 +358,7 @@ program
317
358
  await showCommand.execute(itemName, options ?? {});
318
359
  }
319
360
  catch (error) {
320
- console.log();
321
- ora().fail(`Error: ${error.message}`);
361
+ failWithError(error, { enabled: options?.json, fallbackCode: 'show_error' });
322
362
  process.exit(1);
323
363
  }
324
364
  });
@@ -333,8 +373,7 @@ program
333
373
  await feedbackCommand.execute(message, options);
334
374
  }
335
375
  catch (error) {
336
- console.log();
337
- ora().fail(`Error: ${error.message}`);
376
+ failWithError(error);
338
377
  process.exit(1);
339
378
  }
340
379
  });
@@ -351,8 +390,7 @@ completionCmd
351
390
  await completionCommand.generate({ shell });
352
391
  }
353
392
  catch (error) {
354
- console.log();
355
- ora().fail(`Error: ${error.message}`);
393
+ failWithError(error);
356
394
  process.exit(1);
357
395
  }
358
396
  });
@@ -366,8 +404,7 @@ completionCmd
366
404
  await completionCommand.install({ shell, verbose: options?.verbose });
367
405
  }
368
406
  catch (error) {
369
- console.log();
370
- ora().fail(`Error: ${error.message}`);
407
+ failWithError(error);
371
408
  process.exit(1);
372
409
  }
373
410
  });
@@ -381,8 +418,7 @@ completionCmd
381
418
  await completionCommand.uninstall({ shell, yes: options?.yes });
382
419
  }
383
420
  catch (error) {
384
- console.log();
385
- ora().fail(`Error: ${error.message}`);
421
+ failWithError(error);
386
422
  process.exit(1);
387
423
  }
388
424
  });
@@ -410,13 +446,14 @@ program
410
446
  .option('--change <id>', 'Change name to show status for')
411
447
  .option('--schema <name>', 'Schema override (auto-detected from config.yaml)')
412
448
  .option('--json', 'Output as JSON')
449
+ .option('--store <id>', STORE_OPTION_DESCRIPTION)
450
+ .addOption(hiddenStorePathOption())
413
451
  .action(async (options) => {
414
452
  try {
415
453
  await statusCommand(options);
416
454
  }
417
455
  catch (error) {
418
- console.log();
419
- ora().fail(`Error: ${error.message}`);
456
+ failWithError(error, { enabled: options.json, fallbackCode: 'change_error' });
420
457
  process.exit(1);
421
458
  }
422
459
  });
@@ -427,6 +464,8 @@ program
427
464
  .option('--change <id>', 'Change name')
428
465
  .option('--schema <name>', 'Schema override (auto-detected from config.yaml)')
429
466
  .option('--json', 'Output as JSON')
467
+ .option('--store <id>', STORE_OPTION_DESCRIPTION)
468
+ .addOption(hiddenStorePathOption())
430
469
  .action(async (artifactId, options) => {
431
470
  try {
432
471
  // Special case: "apply" is not an artifact, but a command to get apply instructions
@@ -438,8 +477,7 @@ program
438
477
  }
439
478
  }
440
479
  catch (error) {
441
- console.log();
442
- ora().fail(`Error: ${error.message}`);
480
+ failWithError(error, { enabled: options.json, fallbackCode: 'change_error' });
443
481
  process.exit(1);
444
482
  }
445
483
  });
@@ -454,8 +492,7 @@ program
454
492
  await templatesCommand(options);
455
493
  }
456
494
  catch (error) {
457
- console.log();
458
- ora().fail(`Error: ${error.message}`);
495
+ failWithError(error);
459
496
  process.exit(1);
460
497
  }
461
498
  });
@@ -469,8 +506,7 @@ program
469
506
  await schemasCommand(options);
470
507
  }
471
508
  catch (error) {
472
- console.log();
473
- ora().fail(`Error: ${error.message}`);
509
+ failWithError(error);
474
510
  process.exit(1);
475
511
  }
476
512
  });
@@ -480,39 +516,21 @@ newCmd
480
516
  .command('change <name>')
481
517
  .description('Create a new change directory')
482
518
  .option('--description <text>', 'Description to add to README.md')
483
- .option('--goal <text>', 'Workspace product goal to store with the change')
484
- .option('--areas <names>', 'Comma-separated affected workspace link names')
485
- .option('--initiative <id>', 'Link the repo-local change to an initiative')
486
- .option('--store <id>', 'Context store id for --initiative')
487
- .option('--store-path <path>', 'Existing local context store root for --initiative')
519
+ .option('--goal <text>', 'Optional goal metadata to store with the change')
488
520
  .option('--schema <name>', `Workflow schema to use (default: ${DEFAULT_SCHEMA})`)
489
521
  .option('--json', 'Output as JSON')
522
+ .option('--store <id>', STORE_OPTION_DESCRIPTION)
523
+ .addOption(hiddenStorePathOption())
524
+ // Removed options kept registered (hidden) so users get a deliberate
525
+ // explanation instead of a generic unknown-option error.
526
+ .addOption(new Option('--initiative <id>', 'No longer supported').hideHelp())
527
+ .addOption(new Option('--areas <names>', 'No longer supported').hideHelp())
490
528
  .action(async (name, options) => {
491
529
  try {
492
530
  await newChangeCommand(name, options);
493
531
  }
494
532
  catch (error) {
495
- console.log();
496
- ora().fail(`Error: ${error.message}`);
497
- process.exit(1);
498
- }
499
- });
500
- // Set command group
501
- const setCmd = program.command('set').description('Set checked-in OpenSpec metadata');
502
- setCmd
503
- .command('change <name>')
504
- .description('Set repo-local change metadata')
505
- .option('--initiative <id>', 'Link the repo-local change to an initiative')
506
- .option('--store <id>', 'Context store id for --initiative')
507
- .option('--store-path <path>', 'Existing local context store root for --initiative')
508
- .option('--json', 'Output as JSON')
509
- .action(async (name, options) => {
510
- try {
511
- await setChangeCommand(name, options);
512
- }
513
- catch (error) {
514
- console.log();
515
- ora().fail(`Error: ${error.message}`);
533
+ failWithError(error);
516
534
  process.exit(1);
517
535
  }
518
536
  });
@@ -1,6 +1,9 @@
1
+ import type { RootOutput } from '../core/root-selection.js';
1
2
  export declare class ChangeCommand {
2
3
  private converter;
3
- constructor();
4
+ private rootPath?;
5
+ constructor(rootPath?: string);
6
+ private getChangesPath;
4
7
  /**
5
8
  * Show a change proposal.
6
9
  * - Text mode: raw markdown passthrough (no filters)
@@ -12,6 +15,7 @@ export declare class ChangeCommand {
12
15
  requirementsOnly?: boolean;
13
16
  deltasOnly?: boolean;
14
17
  noInteractive?: boolean;
18
+ rootOutput?: RootOutput;
15
19
  }): Promise<void>;
16
20
  /**
17
21
  * List active changes.
@@ -11,8 +11,15 @@ const TASK_PATTERN = /^[-*]\s+\[[\sx]\]/i;
11
11
  const COMPLETED_TASK_PATTERN = /^[-*]\s+\[x\]/i;
12
12
  export class ChangeCommand {
13
13
  converter;
14
- constructor() {
14
+ rootPath;
15
+ // rootPath is set only by root-aware callers (top-level `show`); the
16
+ // deprecated noun-form commands stay cwd-based.
17
+ constructor(rootPath) {
15
18
  this.converter = new JsonConverter();
19
+ this.rootPath = rootPath;
20
+ }
21
+ getChangesPath() {
22
+ return path.join(this.rootPath ?? process.cwd(), 'openspec', 'changes');
16
23
  }
17
24
  /**
18
25
  * Show a change proposal.
@@ -21,7 +28,7 @@ export class ChangeCommand {
21
28
  * Note: --requirements-only is deprecated alias for --deltas-only
22
29
  */
23
30
  async show(changeName, options) {
24
- const changesPath = path.join(process.cwd(), 'openspec', 'changes');
31
+ const changesPath = this.getChangesPath();
25
32
  if (!changeName) {
26
33
  const canPrompt = isInteractive(options);
27
34
  const changes = await this.getActiveChanges(changesPath);
@@ -62,19 +69,14 @@ export class ChangeCommand {
62
69
  const title = this.extractTitle(contentForTitle, changeName);
63
70
  const id = parsed.name;
64
71
  const deltas = parsed.deltas || [];
65
- if (options.requirementsOnly || options.deltasOnly) {
66
- const output = { id, title, deltaCount: deltas.length, deltas };
67
- console.log(JSON.stringify(output, null, 2));
68
- }
69
- else {
70
- const output = {
71
- id,
72
- title,
73
- deltaCount: deltas.length,
74
- deltas,
75
- };
76
- console.log(JSON.stringify(output, null, 2));
77
- }
72
+ const output = {
73
+ id,
74
+ title,
75
+ deltaCount: deltas.length,
76
+ deltas,
77
+ ...(options.rootOutput ? { root: options.rootOutput } : {}),
78
+ };
79
+ console.log(JSON.stringify(output, null, 2));
78
80
  }
79
81
  else {
80
82
  const content = await fs.readFile(proposalPath, 'utf-8');
@@ -6,7 +6,7 @@ import { getNestedValue, setNestedValue, deleteNestedValue, coerceValue, formatV
6
6
  import { CORE_WORKFLOWS, ALL_WORKFLOWS, getProfileWorkflows } from '../core/profiles.js';
7
7
  import { OPENSPEC_DIR_NAME } from '../core/config.js';
8
8
  import { hasProjectConfigDrift } from '../core/profile-sync-drift.js';
9
- import { findWorkspaceRoot, hasWorkspaceSkillProfileDrift, readOptionalWorkspaceViewState, } from '../core/workspace/index.js';
9
+ import { isPromptCancellationError } from './shared-output.js';
10
10
  const WORKFLOW_PROMPT_META = {
11
11
  propose: {
12
12
  name: 'Propose change',
@@ -53,10 +53,6 @@ const WORKFLOW_PROMPT_META = {
53
53
  description: 'Guided onboarding flow for OpenSpec',
54
54
  },
55
55
  };
56
- function isPromptCancellationError(error) {
57
- return (error instanceof Error &&
58
- (error.name === 'ExitPromptError' || error.message.includes('force closed the prompt with SIGINT')));
59
- }
60
56
  /**
61
57
  * Resolve the effective current profile state from global config defaults.
62
58
  */
@@ -133,16 +129,6 @@ export function diffProfileState(before, after) {
133
129
  lines,
134
130
  };
135
131
  }
136
- async function resolveWorkspaceConfigProfileContext(cwd = process.cwd()) {
137
- const workspaceRoot = await findWorkspaceRoot(cwd);
138
- if (!workspaceRoot) {
139
- return null;
140
- }
141
- return {
142
- root: workspaceRoot,
143
- commandCwd: cwd,
144
- };
145
- }
146
132
  function maybeWarnProjectConfigDrift(projectDir, state, colorize) {
147
133
  const openspecDir = path.join(projectDir, OPENSPEC_DIR_NAME);
148
134
  if (!fs.existsSync(openspecDir)) {
@@ -153,28 +139,7 @@ function maybeWarnProjectConfigDrift(projectDir, state, colorize) {
153
139
  }
154
140
  console.log(colorize('Warning: Global config is not applied to this project. Run `openspec update` to sync.'));
155
141
  }
156
- async function maybeWarnConfigDrift(state, colorize) {
157
- const workspaceContext = await resolveWorkspaceConfigProfileContext();
158
- if (workspaceContext) {
159
- let viewState = null;
160
- try {
161
- viewState = await readOptionalWorkspaceViewState(workspaceContext.root);
162
- }
163
- catch {
164
- return;
165
- }
166
- if (hasWorkspaceSkillProfileDrift(viewState)) {
167
- console.log(colorize('Warning: Workspace-local agent skills are out of sync with the active global profile. Run `openspec workspace update` to sync.'));
168
- }
169
- return;
170
- }
171
- maybeWarnProjectConfigDrift(process.cwd(), state, colorize);
172
- }
173
- function printConfigProfileApplyGuidance(workspaceContext) {
174
- if (workspaceContext) {
175
- console.log('Config updated. Run `openspec workspace update` to apply it to workspace-local skills.');
176
- return;
177
- }
142
+ function printConfigProfileApplyGuidance() {
178
143
  console.log('Config updated. Run `openspec update` in your projects to apply.');
179
144
  }
180
145
  /**
@@ -418,8 +383,7 @@ export function registerConfigCommand(program) {
418
383
  config.workflows = [...CORE_WORKFLOWS];
419
384
  // Preserve delivery setting
420
385
  saveGlobalConfig(config);
421
- const workspaceContext = await resolveWorkspaceConfigProfileContext();
422
- printConfigProfileApplyGuidance(workspaceContext);
386
+ printConfigProfileApplyGuidance();
423
387
  return;
424
388
  }
425
389
  if (preset) {
@@ -472,7 +436,7 @@ export function registerConfigCommand(program) {
472
436
  });
473
437
  if (action === 'keep') {
474
438
  console.log('No config changes.');
475
- await maybeWarnConfigDrift(currentState, chalk.yellow);
439
+ maybeWarnProjectConfigDrift(process.cwd(), currentState, chalk.yellow);
476
440
  return;
477
441
  }
478
442
  const nextState = {
@@ -541,7 +505,7 @@ export function registerConfigCommand(program) {
541
505
  const diff = diffProfileState(currentState, nextState);
542
506
  if (!diff.hasChanges) {
543
507
  console.log('No config changes.');
544
- await maybeWarnConfigDrift(nextState, chalk.yellow);
508
+ maybeWarnProjectConfigDrift(process.cwd(), nextState, chalk.yellow);
545
509
  return;
546
510
  }
547
511
  console.log(chalk.bold('\nConfig changes:'));
@@ -553,29 +517,6 @@ export function registerConfigCommand(program) {
553
517
  config.delivery = nextState.delivery;
554
518
  config.workflows = nextState.workflows;
555
519
  saveGlobalConfig(config);
556
- const workspaceContext = await resolveWorkspaceConfigProfileContext();
557
- if (workspaceContext) {
558
- const applyNow = await confirm({
559
- message: 'Apply changes to this workspace now?',
560
- default: true,
561
- });
562
- if (applyNow) {
563
- try {
564
- execSync('npx openspec workspace update', {
565
- stdio: 'inherit',
566
- cwd: workspaceContext.commandCwd,
567
- });
568
- console.log('Run `openspec workspace update` in your other workspaces to apply.');
569
- }
570
- catch {
571
- console.error('`openspec workspace update` failed. Please run it manually to apply the profile changes.');
572
- process.exitCode = 1;
573
- }
574
- return;
575
- }
576
- printConfigProfileApplyGuidance(workspaceContext);
577
- return;
578
- }
579
520
  // Check if inside an OpenSpec project
580
521
  const projectDir = process.cwd();
581
522
  const openspecDir = path.join(projectDir, OPENSPEC_DIR_NAME);
@@ -596,7 +537,7 @@ export function registerConfigCommand(program) {
596
537
  return;
597
538
  }
598
539
  }
599
- printConfigProfileApplyGuidance(null);
540
+ printConfigProfileApplyGuidance();
600
541
  }
601
542
  catch (error) {
602
543
  if (isPromptCancellationError(error)) {
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerContextCommand(program: Command): void;
3
+ //# sourceMappingURL=context.d.ts.map