@fission-ai/openspec 1.13.2 → 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 +106 -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 +92 -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 +32 -12
- package/dist/commands/workflow/shared.d.ts +3 -1
- package/dist/commands/workflow/shared.js +1 -1
- package/dist/commands/workflow/status.js +15 -2
- package/dist/commands/workset.d.ts +22 -2
- package/dist/commands/workset.js +1 -88
- package/dist/core/archive.js +28 -7
- package/dist/core/artifact-graph/instruction-loader.d.ts +7 -0
- package/dist/core/artifact-graph/instruction-loader.js +18 -1
- package/dist/core/change-metadata/schema.d.ts +3 -0
- package/dist/core/change-metadata/schema.js +10 -0
- package/dist/core/change-status-policy.d.ts +8 -0
- package/dist/core/change-status-policy.js +33 -2
- package/dist/core/command-generation/adapters/atomcode.d.ts +24 -0
- package/dist/core/command-generation/adapters/atomcode.js +46 -0
- package/dist/core/command-generation/adapters/codestudio.d.ts +13 -0
- package/dist/core/command-generation/adapters/codestudio.js +27 -0
- package/dist/core/command-generation/adapters/easycode.d.ts +17 -0
- package/dist/core/command-generation/adapters/easycode.js +28 -0
- package/dist/core/command-generation/adapters/gemini.js +1 -35
- package/dist/core/command-generation/adapters/gigacode.d.ts +17 -0
- package/dist/core/command-generation/adapters/gigacode.js +31 -0
- package/dist/core/command-generation/adapters/index.d.ts +4 -0
- package/dist/core/command-generation/adapters/index.js +4 -0
- package/dist/core/command-generation/registry.js +8 -0
- package/dist/core/command-generation/toml.d.ts +19 -0
- package/dist/core/command-generation/toml.js +39 -0
- package/dist/core/command-surface.js +1 -1
- package/dist/core/completion-tip.js +3 -1
- package/dist/core/completions/command-registry.js +19 -0
- package/dist/core/completions/installers/zsh-installer.js +4 -2
- package/dist/core/config-prompts.js +6 -6
- package/dist/core/config-schema.d.ts +1 -1
- package/dist/core/config.js +11 -1
- package/dist/core/init.js +35 -11
- package/dist/core/legacy-cleanup.js +11 -7
- package/dist/core/list.d.ts +2 -0
- package/dist/core/list.js +49 -23
- package/dist/core/parsers/markdown-parser.js +5 -0
- package/dist/core/parsers/requirement-blocks.d.ts +6 -0
- package/dist/core/parsers/requirement-blocks.js +9 -2
- package/dist/core/project-config.js +45 -1
- package/dist/core/references.js +3 -1
- package/dist/core/root-selection.d.ts +6 -0
- package/dist/core/root-selection.js +16 -0
- package/dist/core/schemas/base.schema.d.ts +3 -0
- package/dist/core/schemas/base.schema.js +6 -0
- package/dist/core/schemas/change.schema.d.ts +8 -0
- package/dist/core/schemas/spec.schema.d.ts +2 -0
- package/dist/core/templates/workflows/apply-change.js +9 -5
- package/dist/core/templates/workflows/archive-change.js +26 -4
- package/dist/core/templates/workflows/bulk-archive-change.js +6 -2
- package/dist/core/templates/workflows/continue-change.js +17 -0
- package/dist/core/templates/workflows/verify-change.js +12 -12
- package/dist/core/validation/constants.d.ts +1 -1
- package/dist/core/validation/constants.js +1 -1
- package/dist/core/validation/validator.js +21 -2
- package/dist/core/version-check.d.ts +23 -1
- package/dist/core/version-check.js +112 -26
- package/dist/core/view.d.ts +2 -0
- package/dist/core/view.js +36 -2
- package/dist/utils/change-metadata.d.ts +20 -1
- package/dist/utils/change-metadata.js +57 -1
- package/dist/utils/command-references.d.ts +1 -1
- package/dist/utils/command-references.js +2 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/package.json +1 -1
- package/schemas/spec-driven/schema.yaml +29 -4
- package/schemas/spec-driven/templates/proposal.md +6 -3
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
|
@@ -45,6 +45,7 @@ function filterSpec(spec, options) {
|
|
|
45
45
|
const filteredRequirements = (requirementIndex !== undefined
|
|
46
46
|
? [spec.requirements[requirementIndex]]
|
|
47
47
|
: spec.requirements).map(req => ({
|
|
48
|
+
name: req.name,
|
|
48
49
|
text: req.text,
|
|
49
50
|
scenarios: includeScenarios ? req.scenarios : [],
|
|
50
51
|
}));
|
|
@@ -118,139 +119,110 @@ export class SpecCommand {
|
|
|
118
119
|
printSpecTextRaw(this.specsDir, specPath);
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
|
-
export function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
console.error(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
.option('--no-interactive', 'Disable interactive prompts')
|
|
137
|
-
.action(async (specId, options) => {
|
|
138
|
-
try {
|
|
139
|
-
const cmd = new SpecCommand();
|
|
140
|
-
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;
|
|
141
137
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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));
|
|
145
161
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
.command('list')
|
|
149
|
-
.description('List all available specifications')
|
|
150
|
-
.option('--json', 'Output as JSON')
|
|
151
|
-
.option('--long', 'Show id and title with counts')
|
|
152
|
-
.action(async (options) => {
|
|
153
|
-
try {
|
|
154
|
-
if (!existsSync(SPECS_DIR)) {
|
|
162
|
+
else {
|
|
163
|
+
if (specs.length === 0) {
|
|
155
164
|
console.log('No items found');
|
|
156
165
|
return;
|
|
157
166
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
assertSpecPath(SPECS_DIR, specFile);
|
|
163
|
-
const spec = parseSpecFromFile(SPECS_DIR, specFile, id);
|
|
164
|
-
return {
|
|
165
|
-
id,
|
|
166
|
-
title: spec.name,
|
|
167
|
-
requirementCount: spec.requirements.length
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
return {
|
|
172
|
-
id,
|
|
173
|
-
title: id,
|
|
174
|
-
requirementCount: 0
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
.sort((a, b) => a.id.localeCompare(b.id));
|
|
179
|
-
if (options.json) {
|
|
180
|
-
console.log(JSON.stringify(specs, null, 2));
|
|
167
|
+
if (!options.long) {
|
|
168
|
+
specs.forEach(spec => console.log(spec.id));
|
|
169
|
+
return;
|
|
181
170
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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 })),
|
|
193
191
|
});
|
|
194
192
|
}
|
|
193
|
+
else {
|
|
194
|
+
throw new Error('Missing required argument <spec-id>');
|
|
195
|
+
}
|
|
195
196
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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`);
|
|
199
201
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.
|
|
203
|
-
.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (!specId) {
|
|
210
|
-
const canPrompt = isInteractive(options);
|
|
211
|
-
const specIds = await getSpecIds();
|
|
212
|
-
if (canPrompt && specIds.length > 0) {
|
|
213
|
-
const { select } = await import('@inquirer/prompts');
|
|
214
|
-
specId = await select({
|
|
215
|
-
message: 'Select a spec to validate',
|
|
216
|
-
choices: specIds.map(id => ({ name: id, value: id })),
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
throw new Error('Missing required argument <spec-id>');
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const specPath = join(SPECS_DIR, specId, 'spec.md');
|
|
224
|
-
assertSpecPath(SPECS_DIR, specPath);
|
|
225
|
-
if (!existsSync(specPath)) {
|
|
226
|
-
throw new Error(`Spec '${specId}' not found at openspec/specs/${specId}/spec.md`);
|
|
227
|
-
}
|
|
228
|
-
const validator = new Validator(options.strict);
|
|
229
|
-
assertSpecPath(SPECS_DIR, specPath);
|
|
230
|
-
const report = await validator.validateSpec(specPath);
|
|
231
|
-
if (options.json) {
|
|
232
|
-
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`);
|
|
233
211
|
}
|
|
234
212
|
else {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
console.error(
|
|
240
|
-
|
|
241
|
-
const label = issue.level === 'ERROR' ? 'ERROR' : issue.level;
|
|
242
|
-
const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
|
|
243
|
-
console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
|
|
244
|
-
});
|
|
245
|
-
}
|
|
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
|
+
});
|
|
246
219
|
}
|
|
247
|
-
process.exitCode = report.valid ? 0 : 1;
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
251
|
-
process.exitCode = 1;
|
|
252
220
|
}
|
|
253
|
-
|
|
254
|
-
|
|
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
|
+
}
|
|
255
227
|
}
|
|
256
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
|
|
@@ -103,6 +103,14 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
103
103
|
` change="${escapeEnvelopeAttribute(changeName)}"` +
|
|
104
104
|
` schema="${escapeEnvelopeAttribute(schemaName)}">`);
|
|
105
105
|
console.log();
|
|
106
|
+
if (instructions.warnings) {
|
|
107
|
+
for (const warning of instructions.warnings) {
|
|
108
|
+
console.log('<warning>');
|
|
109
|
+
console.log(escapeEnvelopeTags(warning));
|
|
110
|
+
console.log('</warning>');
|
|
111
|
+
console.log();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
106
114
|
// Artifacts skipped via skip_specs get no creation directive: emitting the
|
|
107
115
|
// task/template anyway would prompt an agent to write spec files that
|
|
108
116
|
// validate then rejects as conflicting with the marker.
|
|
@@ -215,9 +223,16 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
215
223
|
// Closing tag
|
|
216
224
|
console.log('</artifact>');
|
|
217
225
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
226
|
+
/** Adds one-based source locations to parsed tasks without changing task parsing. */
|
|
227
|
+
function parseLocatedTasks(content, sourcePath) {
|
|
228
|
+
const tasks = [];
|
|
229
|
+
for (const [index, line] of content.split('\n').entries()) {
|
|
230
|
+
const [task] = parseTaskLines(line);
|
|
231
|
+
if (task)
|
|
232
|
+
tasks.push({ ...task, sourcePath, line: index + 1 });
|
|
233
|
+
}
|
|
234
|
+
return tasks;
|
|
235
|
+
}
|
|
221
236
|
/**
|
|
222
237
|
* Turns parsed task lines into the listed task items.
|
|
223
238
|
*
|
|
@@ -238,6 +253,8 @@ function toTaskItems(parsed) {
|
|
|
238
253
|
id: `${tasks.length + 1}`,
|
|
239
254
|
description: task.description,
|
|
240
255
|
done: task.done,
|
|
256
|
+
sourcePath: task.sourcePath,
|
|
257
|
+
line: task.line,
|
|
241
258
|
});
|
|
242
259
|
}
|
|
243
260
|
return tasks;
|
|
@@ -406,7 +423,7 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
406
423
|
for (const tracksPath of tracksPaths) {
|
|
407
424
|
try {
|
|
408
425
|
const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
|
|
409
|
-
parsedTasks.push(...
|
|
426
|
+
parsedTasks.push(...parseLocatedTasks(tasksContent, tracksPath));
|
|
410
427
|
}
|
|
411
428
|
catch (error) {
|
|
412
429
|
const code = error?.code;
|
|
@@ -466,7 +483,7 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
466
483
|
remaining === 0 &&
|
|
467
484
|
total > 0) {
|
|
468
485
|
state = 'all_done';
|
|
469
|
-
instruction = 'All tasks are complete
|
|
486
|
+
instruction = 'All tracked tasks are complete.\nReview or verify the change as appropriate before archiving.';
|
|
470
487
|
}
|
|
471
488
|
else if (!tracksFile) {
|
|
472
489
|
// No tracking file configured in schema - ready to apply
|
|
@@ -483,13 +500,16 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
483
500
|
.join('\n');
|
|
484
501
|
instruction += `\nTask completion is not verified because tracking evidence was unavailable:\n${unavailableDetails}`;
|
|
485
502
|
}
|
|
486
|
-
const warnings =
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
503
|
+
const warnings = [
|
|
504
|
+
...(context.warnings ?? []),
|
|
505
|
+
...(await collectApplyWarnings({
|
|
506
|
+
state,
|
|
507
|
+
schema,
|
|
508
|
+
changeDir,
|
|
509
|
+
changeName,
|
|
510
|
+
skippedArtifacts: context.skippedArtifacts,
|
|
511
|
+
})),
|
|
512
|
+
];
|
|
493
513
|
return {
|
|
494
514
|
changeName,
|
|
495
515
|
changeDir,
|
|
@@ -16,6 +16,8 @@ export interface TaskItem {
|
|
|
16
16
|
id: string;
|
|
17
17
|
description: string;
|
|
18
18
|
done: boolean;
|
|
19
|
+
sourcePath: string;
|
|
20
|
+
line: number;
|
|
19
21
|
}
|
|
20
22
|
export interface ApplyInstructions {
|
|
21
23
|
changeName: string;
|
|
@@ -58,7 +60,7 @@ export interface ArchiveInstructions {
|
|
|
58
60
|
/** Current advisory guidance for archive. */
|
|
59
61
|
operationGuidance?: string[];
|
|
60
62
|
}
|
|
61
|
-
export
|
|
63
|
+
export { DEFAULT_SCHEMA } from './default-schema.js';
|
|
62
64
|
export declare function printJson(payload: unknown): void;
|
|
63
65
|
export declare function statusFromError(error: unknown): ChangeCommandStatus;
|
|
64
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
|
// -----------------------------------------------------------------------------
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { getChangeDir } from '../../core/planning-home.js';
|
|
9
|
-
import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../../core/root-selection.js';
|
|
9
|
+
import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, findDeclaringProjectRoot, } from '../../core/root-selection.js';
|
|
10
10
|
import { loadChangeContext, formatChangeStatus, } from '../../core/artifact-graph/index.js';
|
|
11
11
|
import { resolveNextStep } from '../../core/change-status-policy.js';
|
|
12
12
|
import { asStatus } from '../shared-output.js';
|
|
@@ -43,12 +43,20 @@ export async function statusCommand(options) {
|
|
|
43
43
|
// `Next:` line, so a store-selected root can never carry `--store` in one
|
|
44
44
|
// and drop it from the other.
|
|
45
45
|
const storeOptions = isStoreSelectedRoot(root) ? { storeId: root.storeId } : {};
|
|
46
|
+
// A store holds planning artifacts only; the project declaring it is
|
|
47
|
+
// where implementation edits go (#2013).
|
|
48
|
+
const implementationRoot = isStoreSelectedRoot(root)
|
|
49
|
+
? findDeclaringProjectRoot(root.storeId)
|
|
50
|
+
: null;
|
|
51
|
+
const statusOptions = implementationRoot
|
|
52
|
+
? { ...storeOptions, implementationRoot }
|
|
53
|
+
: storeOptions;
|
|
46
54
|
// Single definition of "load one change's status" so the batch and
|
|
47
55
|
// single-change payloads can never drift apart.
|
|
48
56
|
const loadStatus = (changeName) => formatChangeStatus(loadChangeContext(projectRoot, changeName, options.schema, {
|
|
49
57
|
changeDir: getChangeDir(planningHome, changeName),
|
|
50
58
|
planningHome,
|
|
51
|
-
}),
|
|
59
|
+
}), statusOptions);
|
|
52
60
|
// Handle no-changes case gracefully — status is informational,
|
|
53
61
|
// so "no changes" is a valid state, not an error.
|
|
54
62
|
if (!options.change) {
|
|
@@ -157,6 +165,11 @@ export function printStatusText(status, options = {}) {
|
|
|
157
165
|
const total = status.artifacts.length - skippedCount;
|
|
158
166
|
console.log(`Change: ${status.changeName}`);
|
|
159
167
|
console.log(`Schema: ${status.schemaName}`);
|
|
168
|
+
if (status.warnings) {
|
|
169
|
+
for (const warning of status.warnings) {
|
|
170
|
+
console.log(chalk.yellow(`Warning: ${warning}`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
160
173
|
if (status.changeRoot) {
|
|
161
174
|
console.log(`Change root: ${status.changeRoot}`);
|
|
162
175
|
}
|
|
@@ -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
|