@exodus/openspec 1.2.3 → 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core/init.js CHANGED
@@ -407,7 +407,8 @@ export class InitCommand {
407
407
  const generatedCommands = generateCommands(commandContents, adapter);
408
408
  for (const cmd of generatedCommands) {
409
409
  const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(projectPath, cmd.path);
410
- await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
410
+ const commandContent = cliTransformer ? cliTransformer(cmd.fileContent) : cmd.fileContent;
411
+ await FileSystemUtils.writeFile(commandFile, commandContent);
411
412
  }
412
413
  }
413
414
  else {
@@ -4,6 +4,7 @@ interface ListOptions {
4
4
  }
5
5
  export declare class ListCommand {
6
6
  execute(targetPath?: string, mode?: 'changes' | 'specs', options?: ListOptions): Promise<void>;
7
+ private executeWorkspaceChanges;
7
8
  }
8
9
  export {};
9
10
  //# sourceMappingURL=list.d.ts.map
package/dist/core/list.js CHANGED
@@ -4,6 +4,7 @@ import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progre
4
4
  import { readFileSync } from 'fs';
5
5
  import { join } from 'path';
6
6
  import { MarkdownParser } from './parsers/markdown-parser.js';
7
+ import { readWorkspaceConfig } from './workspace.js';
7
8
  /**
8
9
  * Get the most recent modification time of any file in a directory (recursive).
9
10
  * Falls back to the directory's own mtime if no files are found.
@@ -63,6 +64,11 @@ export class ListCommand {
63
64
  async execute(targetPath = '.', mode = 'changes', options = {}) {
64
65
  const { sort = 'recent', json = false } = options;
65
66
  if (mode === 'changes') {
67
+ const workspace = readWorkspaceConfig(targetPath);
68
+ if (workspace) {
69
+ await this.executeWorkspaceChanges(targetPath, workspace, { sort, json });
70
+ return;
71
+ }
66
72
  const changesDir = path.join(targetPath, 'openspec', 'changes');
67
73
  // Check if changes directory exists
68
74
  try {
@@ -167,5 +173,73 @@ export class ListCommand {
167
173
  console.log(`${padding}${padded} requirements ${spec.requirementCount}`);
168
174
  }
169
175
  }
176
+ async executeWorkspaceChanges(targetPath, workspace, options) {
177
+ const changes = [];
178
+ const collectChanges = async (dir, scope) => {
179
+ try {
180
+ await fs.access(dir);
181
+ const entries = await fs.readdir(dir, { withFileTypes: true });
182
+ const changeDirs = entries
183
+ .filter(e => e.isDirectory() && e.name !== 'archive')
184
+ .map(e => e.name);
185
+ for (const changeDir of changeDirs) {
186
+ const progress = await getTaskProgressForChange(dir, changeDir);
187
+ const changePath = path.join(dir, changeDir);
188
+ const lastModified = await getLastModified(changePath);
189
+ changes.push({ name: changeDir, scope, completedTasks: progress.completed, totalTasks: progress.total, lastModified });
190
+ }
191
+ }
192
+ catch {
193
+ // directory doesn't exist — skip
194
+ }
195
+ };
196
+ await collectChanges(path.join(targetPath, 'openspec', 'changes'), null);
197
+ for (const scope of workspace.scopes) {
198
+ const scopeRoot = path.resolve(targetPath, scope.path);
199
+ await collectChanges(path.join(scopeRoot, 'openspec', 'changes'), scope.name);
200
+ }
201
+ if (changes.length === 0) {
202
+ if (options.json) {
203
+ console.log(JSON.stringify({ changes: [] }));
204
+ }
205
+ else {
206
+ console.log('No active changes found.');
207
+ }
208
+ return;
209
+ }
210
+ if (options.sort === 'recent') {
211
+ changes.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
212
+ }
213
+ else {
214
+ changes.sort((a, b) => {
215
+ const scopeA = a.scope ?? '';
216
+ const scopeB = b.scope ?? '';
217
+ return scopeA !== scopeB ? scopeA.localeCompare(scopeB) : a.name.localeCompare(b.name);
218
+ });
219
+ }
220
+ if (options.json) {
221
+ const jsonOutput = changes.map(c => ({
222
+ name: c.name,
223
+ scope: c.scope,
224
+ completedTasks: c.completedTasks,
225
+ totalTasks: c.totalTasks,
226
+ lastModified: c.lastModified.toISOString(),
227
+ status: c.totalTasks === 0 ? 'no-tasks' : c.completedTasks === c.totalTasks ? 'complete' : 'in-progress',
228
+ }));
229
+ console.log(JSON.stringify({ changes: jsonOutput }, null, 2));
230
+ return;
231
+ }
232
+ console.log('Changes:');
233
+ const padding = ' ';
234
+ const nameWidth = Math.max(...changes.map(c => c.name.length));
235
+ const scopeWidth = Math.max(...changes.map(c => (c.scope ?? 'root').length)) + 2;
236
+ for (const change of changes) {
237
+ const scopeLabel = `[${change.scope ?? 'root'}]`.padEnd(scopeWidth);
238
+ const paddedName = change.name.padEnd(nameWidth);
239
+ const status = formatTaskStatus({ total: change.totalTasks, completed: change.completedTasks });
240
+ const timeAgo = formatRelativeTime(change.lastModified);
241
+ console.log(`${padding}${scopeLabel} ${paddedName} ${status.padEnd(12)} ${timeAgo}`);
242
+ }
243
+ }
170
244
  }
171
245
  //# sourceMappingURL=list.js.map
@@ -12,7 +12,23 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig
12
12
 
13
13
  1. **Get active changes**
14
14
 
15
- Run \`openspec list --json\` to get all active changes.
15
+ Check for a workspace manifest:
16
+ \`\`\`bash
17
+ cat openspec/workspace.yaml 2>/dev/null
18
+ \`\`\`
19
+
20
+ If workspace.yaml exists, list changes across all scopes:
21
+ \`\`\`bash
22
+ (cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
23
+ ls openspec/changes/ 2>/dev/null # umbrella changes at root
24
+ \`\`\`
25
+
26
+ Otherwise:
27
+ \`\`\`bash
28
+ openspec list --json
29
+ \`\`\`
30
+
31
+ Aggregate all results. In workspace mode, track which scope each change belongs to.
16
32
 
17
33
  If no active changes exist, inform user and stop.
18
34
 
@@ -258,7 +274,23 @@ This skill allows you to batch-archive changes, handling spec conflicts intellig
258
274
 
259
275
  1. **Get active changes**
260
276
 
261
- Run \`openspec list --json\` to get all active changes.
277
+ Check for a workspace manifest:
278
+ \`\`\`bash
279
+ cat openspec/workspace.yaml 2>/dev/null
280
+ \`\`\`
281
+
282
+ If workspace.yaml exists, list changes across all scopes:
283
+ \`\`\`bash
284
+ (cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
285
+ ls openspec/changes/ 2>/dev/null # umbrella changes at root
286
+ \`\`\`
287
+
288
+ Otherwise:
289
+ \`\`\`bash
290
+ openspec list --json
291
+ \`\`\`
292
+
293
+ Aggregate all results. In workspace mode, track which scope each change belongs to.
262
294
 
263
295
  If no active changes exist, inform user and stop.
264
296
 
@@ -17,21 +17,37 @@ export function getFfChangeSkillTemplate() {
17
17
 
18
18
  **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
19
19
 
20
- 2. **Create the change directory**
20
+ 2. **Detect workspace and select scope**
21
+
22
+ Check for a workspace manifest:
23
+ \`\`\`bash
24
+ cat openspec/workspace.yaml 2>/dev/null
25
+ \`\`\`
26
+
27
+ If workspace.yaml exists, ask which scope this change belongs to (show the list of scopes from workspace.yaml). Store the selected scope's path as \`<workspace>\`.
28
+
29
+ If no workspace.yaml (single-project), set \`<workspace>\` to the current directory.
30
+
31
+ All subsequent \`openspec\` commands must run as:
32
+ \`\`\`bash
33
+ (cd <workspace> && openspec ...)
34
+ \`\`\`
35
+
36
+ 3. **Create the change directory**
21
37
  \`\`\`bash
22
- openspec new change "<name>"
38
+ (cd <workspace> && openspec new change "<name>")
23
39
  \`\`\`
24
- This creates a scaffolded change at \`openspec/changes/<name>/\`.
40
+ This creates a scaffolded change at \`<workspace>/openspec/changes/<name>/\`.
25
41
 
26
- 3. **Get the artifact build order**
42
+ 4. **Get the artifact build order**
27
43
  \`\`\`bash
28
- openspec status --change "<name>" --json
44
+ (cd <workspace> && openspec status --change "<name>" --json)
29
45
  \`\`\`
30
46
  Parse the JSON to get:
31
47
  - \`applyRequires\`: array of artifact IDs needed before implementation (e.g., \`["tasks"]\`)
32
48
  - \`artifacts\`: list of all artifacts with their status and dependencies
33
49
 
34
- 4. **Create artifacts in sequence until apply-ready**
50
+ 5. **Create artifacts in sequence until apply-ready**
35
51
 
36
52
  Use the **TodoWrite tool** to track progress through the artifacts.
37
53
 
@@ -40,7 +56,7 @@ export function getFfChangeSkillTemplate() {
40
56
  a. **For each artifact that is \`ready\` (dependencies satisfied)**:
41
57
  - Get instructions:
42
58
  \`\`\`bash
43
- openspec instructions <artifact-id> --change "<name>" --json
59
+ (cd <workspace> && openspec instructions <artifact-id> --change "<name>" --json)
44
60
  \`\`\`
45
61
  - The instructions JSON includes:
46
62
  - \`context\`: Project background (constraints for you - do NOT include in output)
@@ -55,7 +71,7 @@ export function getFfChangeSkillTemplate() {
55
71
  - Show brief progress: "✓ Created <artifact-id>"
56
72
 
57
73
  b. **Continue until all \`applyRequires\` artifacts are complete**
58
- - After creating each artifact, re-run \`openspec status --change "<name>" --json\`
74
+ - After creating each artifact, re-run \`(cd <workspace> && openspec status --change "<name>" --json)\`
59
75
  - Check if every artifact ID in \`applyRequires\` has \`status: "done"\` in the artifacts array
60
76
  - Stop when all \`applyRequires\` artifacts are done
61
77
 
@@ -63,15 +79,15 @@ export function getFfChangeSkillTemplate() {
63
79
  - Use **AskUserQuestion tool** to clarify
64
80
  - Then continue with creation
65
81
 
66
- 5. **Show final status**
82
+ 6. **Show final status**
67
83
  \`\`\`bash
68
- openspec status --change "<name>"
84
+ (cd <workspace> && openspec status --change "<name>")
69
85
  \`\`\`
70
86
 
71
87
  **Output**
72
88
 
73
89
  After completing all artifacts, summarize:
74
- - Change name and location
90
+ - Change name and location (including scope if in a workspace)
75
91
  - List of artifacts created with brief descriptions
76
92
  - What's ready: "All artifacts created! Ready for implementation."
77
93
  - Prompt: "Run \`/opsx:apply\` or ask me to implement to start working on the tasks."
@@ -118,21 +134,37 @@ export function getOpsxFfCommandTemplate() {
118
134
 
119
135
  **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
120
136
 
121
- 2. **Create the change directory**
137
+ 2. **Detect workspace and select scope**
138
+
139
+ Check for a workspace manifest:
140
+ \`\`\`bash
141
+ cat openspec/workspace.yaml 2>/dev/null
142
+ \`\`\`
143
+
144
+ If workspace.yaml exists, ask which scope this change belongs to (show the list of scopes from workspace.yaml). Store the selected scope's path as \`<workspace>\`.
145
+
146
+ If no workspace.yaml (single-project), set \`<workspace>\` to the current directory.
147
+
148
+ All subsequent \`openspec\` commands must run as:
149
+ \`\`\`bash
150
+ (cd <workspace> && openspec ...)
151
+ \`\`\`
152
+
153
+ 3. **Create the change directory**
122
154
  \`\`\`bash
123
- openspec new change "<name>"
155
+ (cd <workspace> && openspec new change "<name>")
124
156
  \`\`\`
125
- This creates a scaffolded change at \`openspec/changes/<name>/\`.
157
+ This creates a scaffolded change at \`<workspace>/openspec/changes/<name>/\`.
126
158
 
127
- 3. **Get the artifact build order**
159
+ 4. **Get the artifact build order**
128
160
  \`\`\`bash
129
- openspec status --change "<name>" --json
161
+ (cd <workspace> && openspec status --change "<name>" --json)
130
162
  \`\`\`
131
163
  Parse the JSON to get:
132
164
  - \`applyRequires\`: array of artifact IDs needed before implementation (e.g., \`["tasks"]\`)
133
165
  - \`artifacts\`: list of all artifacts with their status and dependencies
134
166
 
135
- 4. **Create artifacts in sequence until apply-ready**
167
+ 5. **Create artifacts in sequence until apply-ready**
136
168
 
137
169
  Use the **TodoWrite tool** to track progress through the artifacts.
138
170
 
@@ -141,7 +173,7 @@ export function getOpsxFfCommandTemplate() {
141
173
  a. **For each artifact that is \`ready\` (dependencies satisfied)**:
142
174
  - Get instructions:
143
175
  \`\`\`bash
144
- openspec instructions <artifact-id> --change "<name>" --json
176
+ (cd <workspace> && openspec instructions <artifact-id> --change "<name>" --json)
145
177
  \`\`\`
146
178
  - The instructions JSON includes:
147
179
  - \`context\`: Project background (constraints for you - do NOT include in output)
@@ -156,7 +188,7 @@ export function getOpsxFfCommandTemplate() {
156
188
  - Show brief progress: "✓ Created <artifact-id>"
157
189
 
158
190
  b. **Continue until all \`applyRequires\` artifacts are complete**
159
- - After creating each artifact, re-run \`openspec status --change "<name>" --json\`
191
+ - After creating each artifact, re-run \`(cd <workspace> && openspec status --change "<name>" --json)\`
160
192
  - Check if every artifact ID in \`applyRequires\` has \`status: "done"\` in the artifacts array
161
193
  - Stop when all \`applyRequires\` artifacts are done
162
194
 
@@ -164,15 +196,15 @@ export function getOpsxFfCommandTemplate() {
164
196
  - Use **AskUserQuestion tool** to clarify
165
197
  - Then continue with creation
166
198
 
167
- 5. **Show final status**
199
+ 6. **Show final status**
168
200
  \`\`\`bash
169
- openspec status --change "<name>"
201
+ (cd <workspace> && openspec status --change "<name>")
170
202
  \`\`\`
171
203
 
172
204
  **Output**
173
205
 
174
206
  After completing all artifacts, summarize:
175
- - Change name and location
207
+ - Change name and location (including scope if in a workspace)
176
208
  - List of artifacts created with brief descriptions
177
209
  - What's ready: "All artifacts created! Ready for implementation."
178
210
  - Prompt: "Run \`/opsx:apply\` to start implementing."
@@ -27,33 +27,49 @@ export function getNewChangeSkillTemplate() {
27
27
 
28
28
  **Otherwise**: Omit \`--schema\` to use the default.
29
29
 
30
- 3. **Create the change directory**
30
+ 3. **Detect workspace and select scope**
31
+
32
+ Check for a workspace manifest:
33
+ \`\`\`bash
34
+ cat openspec/workspace.yaml 2>/dev/null
35
+ \`\`\`
36
+
37
+ If workspace.yaml exists, ask which scope this change belongs to (show the list of scopes from workspace.yaml). Store the selected scope's path as \`<workspace>\`.
38
+
39
+ If no workspace.yaml (single-project), set \`<workspace>\` to the current directory.
40
+
41
+ All subsequent \`openspec\` commands must run as:
42
+ \`\`\`bash
43
+ (cd <workspace> && openspec ...)
44
+ \`\`\`
45
+
46
+ 4. **Create the change directory**
31
47
  \`\`\`bash
32
- openspec new change "<name>"
48
+ (cd <workspace> && openspec new change "<name>")
33
49
  \`\`\`
34
50
  Add \`--schema <name>\` only if the user requested a specific workflow.
35
- This creates a scaffolded change at \`openspec/changes/<name>/\` with the selected schema.
51
+ This creates a scaffolded change at \`<workspace>/openspec/changes/<name>/\` with the selected schema.
36
52
 
37
- 4. **Show the artifact status**
53
+ 5. **Show the artifact status**
38
54
  \`\`\`bash
39
- openspec status --change "<name>"
55
+ (cd <workspace> && openspec status --change "<name>")
40
56
  \`\`\`
41
57
  This shows which artifacts need to be created and which are ready (dependencies satisfied).
42
58
 
43
- 5. **Get instructions for the first artifact**
59
+ 6. **Get instructions for the first artifact**
44
60
  The first artifact depends on the schema (e.g., \`proposal\` for spec-driven).
45
61
  Check the status output to find the first artifact with status "ready".
46
62
  \`\`\`bash
47
- openspec instructions <first-artifact-id> --change "<name>"
63
+ (cd <workspace> && openspec instructions <first-artifact-id> --change "<name>")
48
64
  \`\`\`
49
65
  This outputs the template and context for creating the first artifact.
50
66
 
51
- 6. **STOP and wait for user direction**
67
+ 7. **STOP and wait for user direction**
52
68
 
53
69
  **Output**
54
70
 
55
71
  After completing the steps, summarize:
56
- - Change name and location
72
+ - Change name and location (including scope if in a workspace)
57
73
  - Schema/workflow being used and its artifact sequence
58
74
  - Current status (0/N artifacts complete)
59
75
  - The template for the first artifact
@@ -101,32 +117,48 @@ export function getOpsxNewCommandTemplate() {
101
117
 
102
118
  **Otherwise**: Omit \`--schema\` to use the default.
103
119
 
104
- 3. **Create the change directory**
120
+ 3. **Detect workspace and select scope**
121
+
122
+ Check for a workspace manifest:
123
+ \`\`\`bash
124
+ cat openspec/workspace.yaml 2>/dev/null
125
+ \`\`\`
126
+
127
+ If workspace.yaml exists, ask which scope this change belongs to (show the list of scopes from workspace.yaml). Store the selected scope's path as \`<workspace>\`.
128
+
129
+ If no workspace.yaml (single-project), set \`<workspace>\` to the current directory.
130
+
131
+ All subsequent \`openspec\` commands must run as:
132
+ \`\`\`bash
133
+ (cd <workspace> && openspec ...)
134
+ \`\`\`
135
+
136
+ 4. **Create the change directory**
105
137
  \`\`\`bash
106
- openspec new change "<name>"
138
+ (cd <workspace> && openspec new change "<name>")
107
139
  \`\`\`
108
140
  Add \`--schema <name>\` only if the user requested a specific workflow.
109
- This creates a scaffolded change at \`openspec/changes/<name>/\` with the selected schema.
141
+ This creates a scaffolded change at \`<workspace>/openspec/changes/<name>/\` with the selected schema.
110
142
 
111
- 4. **Show the artifact status**
143
+ 5. **Show the artifact status**
112
144
  \`\`\`bash
113
- openspec status --change "<name>"
145
+ (cd <workspace> && openspec status --change "<name>")
114
146
  \`\`\`
115
147
  This shows which artifacts need to be created and which are ready (dependencies satisfied).
116
148
 
117
- 5. **Get instructions for the first artifact**
149
+ 6. **Get instructions for the first artifact**
118
150
  The first artifact depends on the schema. Check the status output to find the first artifact with status "ready".
119
151
  \`\`\`bash
120
- openspec instructions <first-artifact-id> --change "<name>"
152
+ (cd <workspace> && openspec instructions <first-artifact-id> --change "<name>")
121
153
  \`\`\`
122
154
  This outputs the template and context for creating the first artifact.
123
155
 
124
- 6. **STOP and wait for user direction**
156
+ 7. **STOP and wait for user direction**
125
157
 
126
158
  **Output**
127
159
 
128
160
  After completing the steps, summarize:
129
- - Change name and location
161
+ - Change name and location (including scope if in a workspace)
130
162
  - Schema/workflow being used and its artifact sequence
131
163
  - Current status (0/N artifacts complete)
132
164
  - The template for the first artifact
@@ -12,7 +12,24 @@ This is an **agent-driven** operation - you will read delta specs and directly e
12
12
 
13
13
  1. **If no change name provided, prompt for selection**
14
14
 
15
- Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select.
15
+ Check for a workspace manifest:
16
+ \`\`\`bash
17
+ cat openspec/workspace.yaml 2>/dev/null
18
+ \`\`\`
19
+
20
+ If workspace.yaml exists, list changes across all scopes:
21
+ \`\`\`bash
22
+ (cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
23
+ ls openspec/changes/ 2>/dev/null # umbrella changes at root
24
+ \`\`\`
25
+
26
+ Otherwise:
27
+ \`\`\`bash
28
+ openspec list --json
29
+ \`\`\`
30
+
31
+ Aggregate all results. Use the **AskUserQuestion tool** to let the user select.
32
+ In workspace mode, show which scope each change belongs to.
16
33
 
17
34
  Show changes that have delta specs (under \`specs/\` directory).
18
35
 
@@ -150,7 +167,24 @@ This is an **agent-driven** operation - you will read delta specs and directly e
150
167
 
151
168
  1. **If no change name provided, prompt for selection**
152
169
 
153
- Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select.
170
+ Check for a workspace manifest:
171
+ \`\`\`bash
172
+ cat openspec/workspace.yaml 2>/dev/null
173
+ \`\`\`
174
+
175
+ If workspace.yaml exists, list changes across all scopes:
176
+ \`\`\`bash
177
+ (cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
178
+ ls openspec/changes/ 2>/dev/null # umbrella changes at root
179
+ \`\`\`
180
+
181
+ Otherwise:
182
+ \`\`\`bash
183
+ openspec list --json
184
+ \`\`\`
185
+
186
+ Aggregate all results. Use the **AskUserQuestion tool** to let the user select.
187
+ In workspace mode, show which scope each change belongs to.
154
188
 
155
189
  Show changes that have delta specs (under \`specs/\` directory).
156
190
 
@@ -10,7 +10,24 @@ export function getVerifyChangeSkillTemplate() {
10
10
 
11
11
  1. **If no change name provided, prompt for selection**
12
12
 
13
- Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select.
13
+ Check for a workspace manifest:
14
+ \`\`\`bash
15
+ cat openspec/workspace.yaml 2>/dev/null
16
+ \`\`\`
17
+
18
+ If workspace.yaml exists, list changes across all scopes:
19
+ \`\`\`bash
20
+ (cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
21
+ ls openspec/changes/ 2>/dev/null # umbrella changes at root
22
+ \`\`\`
23
+
24
+ Otherwise:
25
+ \`\`\`bash
26
+ openspec list --json
27
+ \`\`\`
28
+
29
+ Aggregate all results. Use the **AskUserQuestion tool** to let the user select.
30
+ In workspace mode, show which scope each change belongs to.
14
31
 
15
32
  Show changes that have implementation tasks (tasks artifact exists).
16
33
  Include the schema used for each change if available.
@@ -178,7 +195,24 @@ export function getOpsxVerifyCommandTemplate() {
178
195
 
179
196
  1. **If no change name provided, prompt for selection**
180
197
 
181
- Run \`openspec list --json\` to get available changes. Use the **AskUserQuestion tool** to let the user select.
198
+ Check for a workspace manifest:
199
+ \`\`\`bash
200
+ cat openspec/workspace.yaml 2>/dev/null
201
+ \`\`\`
202
+
203
+ If workspace.yaml exists, list changes across all scopes:
204
+ \`\`\`bash
205
+ (cd <scope.path> && openspec list --json) # for each scope in workspace.yaml
206
+ ls openspec/changes/ 2>/dev/null # umbrella changes at root
207
+ \`\`\`
208
+
209
+ Otherwise:
210
+ \`\`\`bash
211
+ openspec list --json
212
+ \`\`\`
213
+
214
+ Aggregate all results. Use the **AskUserQuestion tool** to let the user select.
215
+ In workspace mode, show which scope each change belongs to.
182
216
 
183
217
  Show changes that have implementation tasks (tasks artifact exists).
184
218
  Include the schema used for each change if available.
@@ -152,7 +152,8 @@ export class UpdateCommand {
152
152
  const generatedCommands = generateCommands(commandContents, adapter);
153
153
  for (const cmd of generatedCommands) {
154
154
  const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(resolvedProjectPath, cmd.path);
155
- await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
155
+ const commandContent = cliTransformer ? cliTransformer(cmd.fileContent) : cmd.fileContent;
156
+ await FileSystemUtils.writeFile(commandFile, commandContent);
156
157
  }
157
158
  removedDeselectedCommandCount += await this.removeUnselectedCommandFiles(resolvedProjectPath, toolId, desiredWorkflows);
158
159
  }
@@ -527,7 +528,8 @@ export class UpdateCommand {
527
528
  const generatedCommands = generateCommands(commandContents, adapter);
528
529
  for (const cmd of generatedCommands) {
529
530
  const commandFile = path.isAbsolute(cmd.path) ? cmd.path : path.join(projectPath, cmd.path);
530
- await FileSystemUtils.writeFile(commandFile, cmd.fileContent);
531
+ const commandContent = legacyCliTransformer ? legacyCliTransformer(cmd.fileContent) : cmd.fileContent;
532
+ await FileSystemUtils.writeFile(commandFile, commandContent);
531
533
  }
532
534
  }
533
535
  }
@@ -1,4 +1,11 @@
1
1
  export declare function getActiveChangeIds(root?: string): Promise<string[]>;
2
2
  export declare function getSpecIds(root?: string): Promise<string[]>;
3
3
  export declare function getArchivedChangeIds(root?: string): Promise<string[]>;
4
+ export interface ScopedChangeId {
5
+ id: string;
6
+ scope: string | null;
7
+ scopeRoot: string;
8
+ }
9
+ export declare function getActiveChangeIdsAcrossWorkspace(root?: string): Promise<ScopedChangeId[]>;
10
+ export declare function getArchivedChangeIdsAcrossWorkspace(root?: string): Promise<ScopedChangeId[]>;
4
11
  //# sourceMappingURL=item-discovery.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import { promises as fs } from 'fs';
2
2
  import path from 'path';
3
+ import { readWorkspaceConfig } from '../core/workspace.js';
3
4
  export async function getActiveChangeIds(root = process.cwd()) {
4
5
  const changesPath = path.join(root, 'openspec', 'changes');
5
6
  try {
@@ -69,4 +70,60 @@ export async function getArchivedChangeIds(root = process.cwd()) {
69
70
  return [];
70
71
  }
71
72
  }
73
+ export async function getActiveChangeIdsAcrossWorkspace(root = process.cwd()) {
74
+ const workspace = readWorkspaceConfig(root);
75
+ if (!workspace) {
76
+ const ids = await getActiveChangeIds(root);
77
+ return ids.map(id => ({ id, scope: null, scopeRoot: root }));
78
+ }
79
+ const results = [];
80
+ const umbrellaIds = await getActiveChangeIds(root);
81
+ for (const id of umbrellaIds) {
82
+ results.push({ id, scope: null, scopeRoot: root });
83
+ }
84
+ for (const scope of workspace.scopes) {
85
+ const scopeRoot = path.resolve(root, scope.path);
86
+ const ids = await getActiveChangeIds(scopeRoot);
87
+ for (const id of ids) {
88
+ results.push({ id, scope: scope.name, scopeRoot });
89
+ }
90
+ }
91
+ return results.sort((a, b) => {
92
+ if (a.scope === b.scope)
93
+ return a.id.localeCompare(b.id);
94
+ if (a.scope === null)
95
+ return -1;
96
+ if (b.scope === null)
97
+ return 1;
98
+ return a.scope.localeCompare(b.scope);
99
+ });
100
+ }
101
+ export async function getArchivedChangeIdsAcrossWorkspace(root = process.cwd()) {
102
+ const workspace = readWorkspaceConfig(root);
103
+ if (!workspace) {
104
+ const ids = await getArchivedChangeIds(root);
105
+ return ids.map(id => ({ id, scope: null, scopeRoot: root }));
106
+ }
107
+ const results = [];
108
+ const umbrellaIds = await getArchivedChangeIds(root);
109
+ for (const id of umbrellaIds) {
110
+ results.push({ id, scope: null, scopeRoot: root });
111
+ }
112
+ for (const scope of workspace.scopes) {
113
+ const scopeRoot = path.resolve(root, scope.path);
114
+ const ids = await getArchivedChangeIds(scopeRoot);
115
+ for (const id of ids) {
116
+ results.push({ id, scope: scope.name, scopeRoot });
117
+ }
118
+ }
119
+ return results.sort((a, b) => {
120
+ if (a.scope === b.scope)
121
+ return a.id.localeCompare(b.id);
122
+ if (a.scope === null)
123
+ return -1;
124
+ if (b.scope === null)
125
+ return 1;
126
+ return a.scope.localeCompare(b.scope);
127
+ });
128
+ }
72
129
  //# sourceMappingURL=item-discovery.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exodus/openspec",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "AI-native system for spec-driven development",
5
5
  "keywords": [
6
6
  "openspec",