@fission-ai/openspec 1.14.0 → 1.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/config.d.ts +8 -0
- package/dist/cli/commands/config.js +88 -0
- package/dist/cli/commands/context.d.ts +3 -0
- package/dist/cli/commands/context.js +20 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.js +18 -0
- package/dist/cli/commands/schema.d.ts +6 -0
- package/dist/cli/commands/schema.js +57 -0
- package/dist/cli/commands/spec.d.ts +3 -0
- package/dist/cli/commands/spec.js +42 -0
- package/dist/cli/commands/store.d.ts +3 -0
- package/dist/cli/commands/store.js +130 -0
- package/dist/cli/commands/workset.d.ts +3 -0
- package/dist/cli/commands/workset.js +96 -0
- package/dist/cli/index.js +68 -43
- package/dist/commands/config.d.ts +16 -7
- package/dist/commands/config.js +342 -395
- package/dist/commands/context.d.ts +8 -2
- package/dist/commands/context.js +22 -37
- package/dist/commands/doctor.d.ts +6 -7
- package/dist/commands/doctor.js +14 -27
- package/dist/commands/schema.d.ts +19 -5
- package/dist/commands/schema.js +662 -702
- package/dist/commands/spec.d.ts +13 -4
- package/dist/commands/spec.js +91 -120
- package/dist/commands/store.d.ts +27 -2
- package/dist/commands/store.js +1 -120
- package/dist/commands/workflow/default-schema.d.ts +2 -0
- package/dist/commands/workflow/default-schema.js +4 -0
- package/dist/commands/workflow/instructions.js +1 -1
- package/dist/commands/workflow/shared.d.ts +1 -1
- package/dist/commands/workflow/shared.js +1 -1
- package/dist/commands/workset.d.ts +22 -2
- package/dist/commands/workset.js +1 -88
- package/dist/core/archive.js +13 -5
- package/dist/core/completion-tip.js +3 -1
- package/dist/core/config-schema.d.ts +1 -1
- package/dist/core/templates/workflows/apply-change.js +4 -3
- package/dist/core/templates/workflows/continue-change.js +17 -0
- package/dist/core/templates/workflows/verify-change.js +12 -12
- package/dist/core/validation/validator.js +12 -1
- package/dist/core/view.js +2 -26
- package/package.json +1 -1
- package/schemas/spec-driven/schema.yaml +28 -4
package/dist/commands/spec.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { program } from 'commander';
|
|
2
1
|
import type { RootOutput } from '../core/root-selection.js';
|
|
3
|
-
interface ShowOptions {
|
|
2
|
+
export interface ShowOptions {
|
|
4
3
|
json?: boolean;
|
|
5
4
|
requirements?: boolean;
|
|
6
5
|
scenarios?: boolean;
|
|
@@ -14,6 +13,16 @@ export declare class SpecCommand {
|
|
|
14
13
|
constructor(rootPath?: string);
|
|
15
14
|
show(specId?: string, options?: ShowOptions): Promise<void>;
|
|
16
15
|
}
|
|
17
|
-
export declare function
|
|
18
|
-
|
|
16
|
+
export declare function specShowCommand(specId: string | undefined, options: ShowOptions & {
|
|
17
|
+
noInteractive?: boolean;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
export declare function specListCommand(options: {
|
|
20
|
+
json?: boolean;
|
|
21
|
+
long?: boolean;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
export declare function specValidateCommand(specId: string | undefined, options: {
|
|
24
|
+
strict?: boolean;
|
|
25
|
+
json?: boolean;
|
|
26
|
+
noInteractive?: boolean;
|
|
27
|
+
}): Promise<void>;
|
|
19
28
|
//# sourceMappingURL=spec.d.ts.map
|
package/dist/commands/spec.js
CHANGED
|
@@ -119,139 +119,110 @@ export class SpecCommand {
|
|
|
119
119
|
printSpecTextRaw(this.specsDir, specPath);
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
-
export function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
.
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
console.error(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
.option('--no-interactive', 'Disable interactive prompts')
|
|
138
|
-
.action(async (specId, options) => {
|
|
139
|
-
try {
|
|
140
|
-
const cmd = new SpecCommand();
|
|
141
|
-
await cmd.show(specId, options);
|
|
122
|
+
export async function specShowCommand(specId, options) {
|
|
123
|
+
try {
|
|
124
|
+
const cmd = new SpecCommand();
|
|
125
|
+
await cmd.show(specId, options);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
export async function specListCommand(options) {
|
|
133
|
+
try {
|
|
134
|
+
if (!existsSync(SPECS_DIR)) {
|
|
135
|
+
console.log('No items found');
|
|
136
|
+
return;
|
|
142
137
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
const discovered = await discoverSpecFiles(SPECS_DIR);
|
|
139
|
+
const specs = discovered
|
|
140
|
+
.map(({ id, specFile }) => {
|
|
141
|
+
try {
|
|
142
|
+
assertSpecPath(SPECS_DIR, specFile);
|
|
143
|
+
const spec = parseSpecFromFile(SPECS_DIR, specFile, id);
|
|
144
|
+
return {
|
|
145
|
+
id,
|
|
146
|
+
title: spec.name,
|
|
147
|
+
requirementCount: spec.requirements.length
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return {
|
|
152
|
+
id,
|
|
153
|
+
title: id,
|
|
154
|
+
requirementCount: 0
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
.sort((a, b) => a.id.localeCompare(b.id));
|
|
159
|
+
if (options.json) {
|
|
160
|
+
console.log(JSON.stringify(specs, null, 2));
|
|
146
161
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.command('list')
|
|
150
|
-
.description('List all available specifications')
|
|
151
|
-
.option('--json', 'Output as JSON')
|
|
152
|
-
.option('--long', 'Show id and title with counts')
|
|
153
|
-
.action(async (options) => {
|
|
154
|
-
try {
|
|
155
|
-
if (!existsSync(SPECS_DIR)) {
|
|
162
|
+
else {
|
|
163
|
+
if (specs.length === 0) {
|
|
156
164
|
console.log('No items found');
|
|
157
165
|
return;
|
|
158
166
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
assertSpecPath(SPECS_DIR, specFile);
|
|
164
|
-
const spec = parseSpecFromFile(SPECS_DIR, specFile, id);
|
|
165
|
-
return {
|
|
166
|
-
id,
|
|
167
|
-
title: spec.name,
|
|
168
|
-
requirementCount: spec.requirements.length
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
return {
|
|
173
|
-
id,
|
|
174
|
-
title: id,
|
|
175
|
-
requirementCount: 0
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
})
|
|
179
|
-
.sort((a, b) => a.id.localeCompare(b.id));
|
|
180
|
-
if (options.json) {
|
|
181
|
-
console.log(JSON.stringify(specs, null, 2));
|
|
167
|
+
if (!options.long) {
|
|
168
|
+
specs.forEach(spec => console.log(spec.id));
|
|
169
|
+
return;
|
|
182
170
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
171
|
+
specs.forEach(spec => {
|
|
172
|
+
console.log(`${spec.id}: ${spec.title} [requirements ${spec.requirementCount}]`);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
178
|
+
process.exitCode = 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
export async function specValidateCommand(specId, options) {
|
|
182
|
+
try {
|
|
183
|
+
if (!specId) {
|
|
184
|
+
const canPrompt = isInteractive(options);
|
|
185
|
+
const specIds = await getSpecIds();
|
|
186
|
+
if (canPrompt && specIds.length > 0) {
|
|
187
|
+
const { select } = await import('@inquirer/prompts');
|
|
188
|
+
specId = await select({
|
|
189
|
+
message: 'Select a spec to validate',
|
|
190
|
+
choices: specIds.map(id => ({ name: id, value: id })),
|
|
194
191
|
});
|
|
195
192
|
}
|
|
193
|
+
else {
|
|
194
|
+
throw new Error('Missing required argument <spec-id>');
|
|
195
|
+
}
|
|
196
196
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
const specPath = join(SPECS_DIR, specId, 'spec.md');
|
|
198
|
+
assertSpecPath(SPECS_DIR, specPath);
|
|
199
|
+
if (!existsSync(specPath)) {
|
|
200
|
+
throw new Error(`Spec '${specId}' not found at openspec/specs/${specId}/spec.md`);
|
|
200
201
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
.
|
|
204
|
-
.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (!specId) {
|
|
211
|
-
const canPrompt = isInteractive(options);
|
|
212
|
-
const specIds = await getSpecIds();
|
|
213
|
-
if (canPrompt && specIds.length > 0) {
|
|
214
|
-
const { select } = await import('@inquirer/prompts');
|
|
215
|
-
specId = await select({
|
|
216
|
-
message: 'Select a spec to validate',
|
|
217
|
-
choices: specIds.map(id => ({ name: id, value: id })),
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
throw new Error('Missing required argument <spec-id>');
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
const specPath = join(SPECS_DIR, specId, 'spec.md');
|
|
225
|
-
assertSpecPath(SPECS_DIR, specPath);
|
|
226
|
-
if (!existsSync(specPath)) {
|
|
227
|
-
throw new Error(`Spec '${specId}' not found at openspec/specs/${specId}/spec.md`);
|
|
228
|
-
}
|
|
229
|
-
const validator = new Validator(options.strict);
|
|
230
|
-
assertSpecPath(SPECS_DIR, specPath);
|
|
231
|
-
const report = await validator.validateSpec(specPath);
|
|
232
|
-
if (options.json) {
|
|
233
|
-
console.log(JSON.stringify(report, null, 2));
|
|
202
|
+
const validator = new Validator(options.strict);
|
|
203
|
+
assertSpecPath(SPECS_DIR, specPath);
|
|
204
|
+
const report = await validator.validateSpec(specPath);
|
|
205
|
+
if (options.json) {
|
|
206
|
+
console.log(JSON.stringify(report, null, 2));
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
if (report.valid) {
|
|
210
|
+
console.log(`Specification '${specId}' is valid`);
|
|
234
211
|
}
|
|
235
212
|
else {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
console.error(
|
|
241
|
-
|
|
242
|
-
const label = issue.level === 'ERROR' ? 'ERROR' : issue.level;
|
|
243
|
-
const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
|
|
244
|
-
console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
|
|
245
|
-
});
|
|
246
|
-
}
|
|
213
|
+
console.error(`Specification '${specId}' has issues`);
|
|
214
|
+
report.issues.forEach(issue => {
|
|
215
|
+
const label = issue.level === 'ERROR' ? 'ERROR' : issue.level;
|
|
216
|
+
const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
|
|
217
|
+
console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
|
|
218
|
+
});
|
|
247
219
|
}
|
|
248
|
-
process.exitCode = report.valid ? 0 : 1;
|
|
249
|
-
}
|
|
250
|
-
catch (error) {
|
|
251
|
-
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
252
|
-
process.exitCode = 1;
|
|
253
220
|
}
|
|
254
|
-
|
|
255
|
-
|
|
221
|
+
process.exitCode = report.valid ? 0 : 1;
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
225
|
+
process.exitCode = 1;
|
|
226
|
+
}
|
|
256
227
|
}
|
|
257
228
|
//# sourceMappingURL=spec.js.map
|
package/dist/commands/store.d.ts
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export interface StoreSetupOptions {
|
|
2
|
+
path?: string;
|
|
3
|
+
initGit?: boolean;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
remote?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface StoreRegisterOptions {
|
|
8
|
+
id?: string;
|
|
9
|
+
yes?: boolean;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface StoreRemoveOptions {
|
|
13
|
+
yes?: boolean;
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface StoreJsonOptions {
|
|
17
|
+
json?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare class StoreCommand {
|
|
20
|
+
setup(id: string | undefined, options?: StoreSetupOptions): Promise<void>;
|
|
21
|
+
register(inputPath: string | undefined, options?: StoreRegisterOptions): Promise<void>;
|
|
22
|
+
unregister(id: string, options?: StoreJsonOptions): Promise<void>;
|
|
23
|
+
remove(id: string, options?: StoreRemoveOptions): Promise<void>;
|
|
24
|
+
list(options?: StoreJsonOptions): Promise<void>;
|
|
25
|
+
doctor(id: string | undefined, options?: StoreJsonOptions): Promise<void>;
|
|
26
|
+
private handleFailure;
|
|
27
|
+
}
|
|
3
28
|
//# sourceMappingURL=store.d.ts.map
|
package/dist/commands/store.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as os from 'node:os';
|
|
2
2
|
import { asErrorMessage, emitFailure, printJson } from './shared-output.js';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
-
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
|
|
5
4
|
import { StoreError, doctorStores, listStores, prepareStoreSetup, prepareStoreCleanup, registerExistingStore, removeStore, resolveSetupGitEnabled, setupPreparedStore, unregisterStore, validateStoreId, } from '../core/store/index.js';
|
|
6
5
|
import { isInteractive } from '../utils/interactive.js';
|
|
7
6
|
function toStoreOutput(store) {
|
|
@@ -318,7 +317,7 @@ function printDoctorHuman(payload) {
|
|
|
318
317
|
}
|
|
319
318
|
}
|
|
320
319
|
}
|
|
321
|
-
class StoreCommand {
|
|
320
|
+
export class StoreCommand {
|
|
322
321
|
async setup(id, options = {}) {
|
|
323
322
|
try {
|
|
324
323
|
const setupInput = await resolveSetupInput(id, options);
|
|
@@ -429,122 +428,4 @@ class StoreCommand {
|
|
|
429
428
|
emitFailure(json, payload, error, 'store_error');
|
|
430
429
|
}
|
|
431
430
|
}
|
|
432
|
-
export function registerStoreCommand(program) {
|
|
433
|
-
const storeCommand = new StoreCommand();
|
|
434
|
-
// One source for the locked group one-liner: the completions registry
|
|
435
|
-
// entry, which shell completion scripts also consume.
|
|
436
|
-
const storeGroupDescription = COMMAND_REGISTRY.find((entry) => entry.name === 'store')?.description ??
|
|
437
|
-
'Create and manage stores - standalone OpenSpec repos you register on this machine';
|
|
438
|
-
const store = program.command('store').description(storeGroupDescription);
|
|
439
|
-
store
|
|
440
|
-
.command('setup [id]')
|
|
441
|
-
.description('Create and register a local store')
|
|
442
|
-
.option('--path <path>', 'Folder where the store should live (for example ~/openspec/<id>)')
|
|
443
|
-
.option('--init-git', 'Initialize a Git repository with an initial commit (default)')
|
|
444
|
-
.option('--no-init-git', 'Skip every Git action: no init, no initial commit')
|
|
445
|
-
.option('--remote <url>', 'Canonical clone source recorded in store.yaml')
|
|
446
|
-
.option('--json', 'Output as JSON')
|
|
447
|
-
.action(async (id, options) => {
|
|
448
|
-
await storeCommand.setup(id, options);
|
|
449
|
-
});
|
|
450
|
-
store
|
|
451
|
-
.command('register [path]')
|
|
452
|
-
.description('Register an existing local store')
|
|
453
|
-
.option('--id <id>', 'Store id; defaults to metadata or folder name')
|
|
454
|
-
.option('--yes', 'Confirm creating store identity metadata for a healthy OpenSpec root')
|
|
455
|
-
.option('--json', 'Output as JSON')
|
|
456
|
-
.action(async (inputPath, options) => {
|
|
457
|
-
await storeCommand.register(inputPath, options);
|
|
458
|
-
});
|
|
459
|
-
store
|
|
460
|
-
.command('unregister <id>')
|
|
461
|
-
.description('Forget a local store registration without deleting files')
|
|
462
|
-
.option('--json', 'Output as JSON')
|
|
463
|
-
.action(async (id, options) => {
|
|
464
|
-
await storeCommand.unregister(id, options);
|
|
465
|
-
});
|
|
466
|
-
store
|
|
467
|
-
.command('remove <id>')
|
|
468
|
-
.description('Forget a local store registration and delete its local folder')
|
|
469
|
-
.option('--yes', 'Confirm local store folder deletion')
|
|
470
|
-
.option('--json', 'Output as JSON')
|
|
471
|
-
.action(async (id, options) => {
|
|
472
|
-
await storeCommand.remove(id, options);
|
|
473
|
-
});
|
|
474
|
-
store
|
|
475
|
-
.command('list')
|
|
476
|
-
.alias('ls')
|
|
477
|
-
.description('List locally registered stores')
|
|
478
|
-
.option('--json', 'Output as JSON')
|
|
479
|
-
.action(async (options) => {
|
|
480
|
-
await storeCommand.list(options);
|
|
481
|
-
});
|
|
482
|
-
store
|
|
483
|
-
.command('doctor [id]')
|
|
484
|
-
.description('Check local store registration and metadata')
|
|
485
|
-
.option('--json', 'Output as JSON')
|
|
486
|
-
.action(async (id, options) => {
|
|
487
|
-
await storeCommand.doctor(id, options);
|
|
488
|
-
});
|
|
489
|
-
const lifecycleRedirects = new Set(COMMAND_REGISTRY.filter((entry) => entry.flags.some((flag) => flag.name === 'store') ||
|
|
490
|
-
(entry.subcommands ?? []).some((subcommand) => subcommand.flags.some((flag) => flag.name === 'store'))).map((entry) => entry.name));
|
|
491
|
-
const storeSubcommandsLine = store.commands
|
|
492
|
-
.map((subcommand) => {
|
|
493
|
-
const aliases = subcommand.aliases();
|
|
494
|
-
return aliases.length > 0 ? `${subcommand.name()} (${aliases.join(', ')})` : subcommand.name();
|
|
495
|
-
})
|
|
496
|
-
.join(', ');
|
|
497
|
-
// One group action owns missing AND unknown subcommands. Known
|
|
498
|
-
// subcommands dispatch above; everything else — including a bare
|
|
499
|
-
// `store --json` with no operand — lands here, so the handler owns the
|
|
500
|
-
// entire message and exit path (same text for human and --json). The
|
|
501
|
-
// permissive flags route unknown operands/options here instead of
|
|
502
|
-
// letting Commander emit a raw error before the action runs. We detect
|
|
503
|
-
// `--json` in the residual args rather than declaring a group option,
|
|
504
|
-
// which would otherwise shadow each subcommand's own `--json` flag.
|
|
505
|
-
store.allowExcessArguments(true);
|
|
506
|
-
store.allowUnknownOption(true);
|
|
507
|
-
store.action(() => {
|
|
508
|
-
const operands = store.args;
|
|
509
|
-
// Flag values are indistinguishable from operands without a full
|
|
510
|
-
// parse, so the verbatim echo only applies to plain-operand input.
|
|
511
|
-
const attempted = operands.filter((operand) => !operand.startsWith('-'));
|
|
512
|
-
const hasFlagLikeToken = operands.some((operand) => operand.startsWith('-'));
|
|
513
|
-
// The agent contract: --json failures emit one JSON document.
|
|
514
|
-
if (operands.includes('--json')) {
|
|
515
|
-
const message = attempted.length > 0
|
|
516
|
-
? `Unknown command '${attempted[0]}' for 'openspec store'. Store subcommands: ${storeSubcommandsLine}.`
|
|
517
|
-
: `Missing subcommand for 'openspec store'. Store subcommands: ${storeSubcommandsLine}.`;
|
|
518
|
-
printJson({
|
|
519
|
-
status: [
|
|
520
|
-
{
|
|
521
|
-
severity: 'error',
|
|
522
|
-
code: 'unknown_store_subcommand',
|
|
523
|
-
message,
|
|
524
|
-
fix: 'Run a store subcommand, or use the lifecycle command with --store <id>.',
|
|
525
|
-
},
|
|
526
|
-
],
|
|
527
|
-
});
|
|
528
|
-
process.exitCode = 1;
|
|
529
|
-
return;
|
|
530
|
-
}
|
|
531
|
-
let example = 'openspec new change <change-id> --store <id>';
|
|
532
|
-
if (!hasFlagLikeToken && attempted.length > 0 && lifecycleRedirects.has(attempted[0])) {
|
|
533
|
-
if (attempted[0] === 'new') {
|
|
534
|
-
const changeId = attempted[1] === 'change' && attempted[2] ? attempted[2] : '<change-id>';
|
|
535
|
-
example = `openspec new change ${changeId} --store <id>`;
|
|
536
|
-
}
|
|
537
|
-
else {
|
|
538
|
-
example = `openspec ${attempted.join(' ')} --store <id>`;
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
console.error(attempted.length > 0
|
|
542
|
-
? `Error: unknown command '${attempted[0]}' for 'openspec store'.`
|
|
543
|
-
: "Error: missing subcommand for 'openspec store'.");
|
|
544
|
-
console.error(`Store subcommands manage store registration: ${storeSubcommandsLine}.`);
|
|
545
|
-
console.error('To create or work on a change in a store, use the normal command with --store, for example:');
|
|
546
|
-
console.error(` ${example}`);
|
|
547
|
-
process.exitCode = 1;
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
431
|
//# sourceMappingURL=store.js.map
|
|
@@ -483,7 +483,7 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
483
483
|
remaining === 0 &&
|
|
484
484
|
total > 0) {
|
|
485
485
|
state = 'all_done';
|
|
486
|
-
instruction = 'All tasks are complete
|
|
486
|
+
instruction = 'All tracked tasks are complete.\nReview or verify the change as appropriate before archiving.';
|
|
487
487
|
}
|
|
488
488
|
else if (!tracksFile) {
|
|
489
489
|
// No tracking file configured in schema - ready to apply
|
|
@@ -60,7 +60,7 @@ export interface ArchiveInstructions {
|
|
|
60
60
|
/** Current advisory guidance for archive. */
|
|
61
61
|
operationGuidance?: string[];
|
|
62
62
|
}
|
|
63
|
-
export
|
|
63
|
+
export { DEFAULT_SCHEMA } from './default-schema.js';
|
|
64
64
|
export declare function printJson(payload: unknown): void;
|
|
65
65
|
export declare function statusFromError(error: unknown): ChangeCommandStatus;
|
|
66
66
|
/**
|
|
@@ -13,7 +13,7 @@ import { isRootSelectionError } from '../../core/root-selection.js';
|
|
|
13
13
|
// -----------------------------------------------------------------------------
|
|
14
14
|
// Constants
|
|
15
15
|
// -----------------------------------------------------------------------------
|
|
16
|
-
export
|
|
16
|
+
export { DEFAULT_SCHEMA } from './default-schema.js';
|
|
17
17
|
// -----------------------------------------------------------------------------
|
|
18
18
|
// Utility Functions
|
|
19
19
|
// -----------------------------------------------------------------------------
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import type { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
2
|
import { type LaunchCommand } from '../core/openers.js';
|
|
3
|
+
export interface WorksetCreateOptions {
|
|
4
|
+
member?: string[];
|
|
5
|
+
tool?: string;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface WorksetOpenOptions {
|
|
9
|
+
tool?: string;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface WorksetRemoveOptions {
|
|
13
|
+
yes?: boolean;
|
|
14
|
+
json?: boolean;
|
|
15
|
+
}
|
|
4
16
|
interface LaunchResult {
|
|
5
17
|
code: number | null;
|
|
6
18
|
signal: NodeJS.Signals | null;
|
|
@@ -20,6 +32,14 @@ export interface LaunchOptions {
|
|
|
20
32
|
export declare function launchOpenerCommand(command: LaunchCommand, options?: LaunchOptions): Promise<LaunchResult>;
|
|
21
33
|
/** 130 for SIGINT, 143 for SIGTERM - the shell's 128+n convention. */
|
|
22
34
|
export declare function exitCodeForLaunch(result: LaunchResult): number;
|
|
23
|
-
export declare
|
|
35
|
+
export declare class WorksetCommand {
|
|
36
|
+
create(name: string | undefined, options?: WorksetCreateOptions): Promise<void>;
|
|
37
|
+
private composeFromFlags;
|
|
38
|
+
list(options?: {
|
|
39
|
+
json?: boolean;
|
|
40
|
+
}): Promise<void>;
|
|
41
|
+
open(name: string, options?: WorksetOpenOptions): Promise<void>;
|
|
42
|
+
remove(name: string, options?: WorksetRemoveOptions): Promise<void>;
|
|
43
|
+
}
|
|
24
44
|
export {};
|
|
25
45
|
//# sourceMappingURL=workset.d.ts.map
|
package/dist/commands/workset.js
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as os from 'node:os';
|
|
10
10
|
import { createRequire } from 'node:module';
|
|
11
|
-
import { Option } from 'commander';
|
|
12
11
|
import { buildWorksetCodeWorkspaceJson, getWorkset, getWorksetCodeWorkspacePath, listWorksets, readWorksetsState, removeWorkset, updateWorksetsState, validateWorksetName, withWorkset, withWorksetsLock, worksetNotFoundError, } from '../core/worksets.js';
|
|
13
12
|
import { buildLaunchCommand, findOpener, isOpenerCommandAvailable, isOpenerEnabled, listOpenerChoices, mergeOpenerTable, } from '../core/openers.js';
|
|
14
13
|
import { pathIsDirectory, writeFileAtomically } from '../core/file-state.js';
|
|
@@ -18,7 +17,6 @@ import { isInteractive } from '../utils/interactive.js';
|
|
|
18
17
|
import { asErrorMessage, emitFailure, isPromptCancellationError, printJson, } from './shared-output.js';
|
|
19
18
|
import { finalizeWorkset, firstInstalledAlternative, formatMemberRows, noToolInstalledError, resolveMemberFlags, toolUnavailableError, toolUnknownError, } from './workset-input.js';
|
|
20
19
|
import { composeInteractively, confirmRemoveInteractively, promptOpenNow, promptToolFromChoices, } from './workset-prompts.js';
|
|
21
|
-
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
|
|
22
20
|
// cross-spawn is CJS with no types and only `workset open` needs it -
|
|
23
21
|
// loaded lazily so every other CLI invocation skips its module graph.
|
|
24
22
|
let cachedSpawn;
|
|
@@ -93,7 +91,7 @@ export function exitCodeForLaunch(result) {
|
|
|
93
91
|
}
|
|
94
92
|
return result.code ?? 0;
|
|
95
93
|
}
|
|
96
|
-
class WorksetCommand {
|
|
94
|
+
export class WorksetCommand {
|
|
97
95
|
async create(name, options = {}) {
|
|
98
96
|
try {
|
|
99
97
|
const interactive = !options.json && isInteractive();
|
|
@@ -358,89 +356,4 @@ class WorksetCommand {
|
|
|
358
356
|
}
|
|
359
357
|
}
|
|
360
358
|
}
|
|
361
|
-
function collectMember(value, previous) {
|
|
362
|
-
return [...previous, value];
|
|
363
|
-
}
|
|
364
|
-
export function registerWorksetCommand(program) {
|
|
365
|
-
const worksetCommand = new WorksetCommand();
|
|
366
|
-
const groupDescription = COMMAND_REGISTRY.find((entry) => entry.name === 'workset')?.description ??
|
|
367
|
-
'Compose, keep, and open personal working views (purely local)';
|
|
368
|
-
const workset = program.command('workset').description(groupDescription);
|
|
369
|
-
// Parsed at the group level so `openspec workset --json` keeps the
|
|
370
|
-
// one-JSON-document contract instead of a raw Commander error. The
|
|
371
|
-
// parent option matches anywhere; actions read optsWithGlobals().
|
|
372
|
-
workset.addOption(new Option('--json', 'Output as JSON').hideHelp());
|
|
373
|
-
workset
|
|
374
|
-
.command('create [name]')
|
|
375
|
-
.description('Compose and save a named working view of folders you choose')
|
|
376
|
-
.option('--member <member>', 'Member folder as <path> or <name>=<path>; repeatable, first is the primary', collectMember, [])
|
|
377
|
-
.option('--tool <id>', 'Preferred tool to open this workset with')
|
|
378
|
-
.option('--json', 'Output as JSON')
|
|
379
|
-
.action(async (name, _options, command) => {
|
|
380
|
-
await worksetCommand.create(name, command.optsWithGlobals());
|
|
381
|
-
});
|
|
382
|
-
workset
|
|
383
|
-
.command('list')
|
|
384
|
-
.alias('ls')
|
|
385
|
-
.description('Show saved worksets with their members')
|
|
386
|
-
.option('--json', 'Output as JSON')
|
|
387
|
-
.action(async (_options, command) => {
|
|
388
|
-
await worksetCommand.list(command.optsWithGlobals());
|
|
389
|
-
});
|
|
390
|
-
workset
|
|
391
|
-
.command('open <name>')
|
|
392
|
-
.description('Open a saved workset in your tool (editor window or agent session)')
|
|
393
|
-
.option('--tool <id>', 'Open with this tool just this once')
|
|
394
|
-
.addOption(
|
|
395
|
-
// Parsed so Commander never owns the error; rejected in the
|
|
396
|
-
// action with one JSON document. Hidden because help should not
|
|
397
|
-
// advertise a mode that only rejects.
|
|
398
|
-
new Option('--json', 'Not supported for open').hideHelp())
|
|
399
|
-
.action(async (name, _options, command) => {
|
|
400
|
-
await worksetCommand.open(name, command.optsWithGlobals());
|
|
401
|
-
});
|
|
402
|
-
workset
|
|
403
|
-
.command('remove <name>')
|
|
404
|
-
.description('Delete a saved workset (member folders are never touched)')
|
|
405
|
-
.option('--yes', 'Confirm removal non-interactively')
|
|
406
|
-
.option('--json', 'Output as JSON')
|
|
407
|
-
.action(async (name, _options, command) => {
|
|
408
|
-
await worksetCommand.remove(name, command.optsWithGlobals());
|
|
409
|
-
});
|
|
410
|
-
const subcommandsLine = workset.commands
|
|
411
|
-
.map((subcommand) => {
|
|
412
|
-
const aliases = subcommand.aliases();
|
|
413
|
-
return aliases.length > 0
|
|
414
|
-
? `${subcommand.name()} (${aliases.join(', ')})`
|
|
415
|
-
: subcommand.name();
|
|
416
|
-
})
|
|
417
|
-
.join(', ');
|
|
418
|
-
// One handler owns missing AND unknown subcommands: known
|
|
419
|
-
// subcommands dispatch above; everything else lands in this action
|
|
420
|
-
// (allowExcessArguments routes the unknown operand here), keeping
|
|
421
|
-
// the one-JSON-document contract for `--json` probes.
|
|
422
|
-
workset.allowExcessArguments(true);
|
|
423
|
-
workset.action(() => {
|
|
424
|
-
const attempted = workset.args.filter((operand) => !operand.startsWith('-'));
|
|
425
|
-
const message = attempted.length > 0
|
|
426
|
-
? `Unknown command '${attempted[0]}' for 'openspec workset'. Workset subcommands: ${subcommandsLine}.`
|
|
427
|
-
: `Missing subcommand for 'openspec workset'. Workset subcommands: ${subcommandsLine}.`;
|
|
428
|
-
if (workset.opts().json) {
|
|
429
|
-
printJson({
|
|
430
|
-
status: [
|
|
431
|
-
{
|
|
432
|
-
severity: 'error',
|
|
433
|
-
code: 'unknown_workset_subcommand',
|
|
434
|
-
message,
|
|
435
|
-
fix: 'Run one of the workset subcommands.',
|
|
436
|
-
},
|
|
437
|
-
],
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
else {
|
|
441
|
-
console.error(`Error: ${message}`);
|
|
442
|
-
}
|
|
443
|
-
process.exitCode = 1;
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
359
|
//# sourceMappingURL=workset.js.map
|