@fission-ai/openspec 1.10.0 → 1.12.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.
- package/README.md +1 -0
- package/dist/cli/index.js +12 -2
- package/dist/commands/change.d.ts +24 -0
- package/dist/commands/change.js +229 -5
- package/dist/commands/schema.d.ts +5 -0
- package/dist/commands/schema.js +201 -35
- package/dist/commands/show.js +1 -1
- package/dist/commands/validate.d.ts +57 -0
- package/dist/commands/validate.js +99 -13
- package/dist/commands/workflow/index.d.ts +1 -1
- package/dist/commands/workflow/index.js +1 -1
- package/dist/commands/workflow/status.d.ts +2 -0
- package/dist/commands/workflow/status.js +76 -12
- package/dist/core/artifact-graph/resolver.js +15 -9
- package/dist/core/available-tools.js +16 -1
- package/dist/core/command-generation/adapters/antigravity.d.ts +1 -1
- package/dist/core/command-generation/adapters/antigravity.js +2 -2
- package/dist/core/command-generation/adapters/codeassistant.d.ts +15 -0
- package/dist/core/command-generation/adapters/codeassistant.js +29 -0
- package/dist/core/command-generation/adapters/index.d.ts +1 -0
- package/dist/core/command-generation/adapters/index.js +1 -0
- package/dist/core/command-generation/registry.js +2 -0
- package/dist/core/completions/command-registry.js +21 -0
- package/dist/core/completions/generators/fish-generator.d.ts +12 -0
- package/dist/core/completions/generators/fish-generator.js +79 -47
- package/dist/core/completions/templates/fish-templates.d.ts +1 -1
- package/dist/core/completions/templates/fish-templates.js +107 -10
- package/dist/core/completions/types.d.ts +4 -0
- package/dist/core/config.js +7 -1
- package/dist/core/init.d.ts +1 -0
- package/dist/core/init.js +71 -65
- package/dist/core/legacy-cleanup.js +6 -0
- package/dist/core/migration.js +42 -12
- package/dist/core/openspec-root.d.ts +1 -0
- package/dist/core/openspec-root.js +10 -2
- package/dist/core/parsers/requirement-blocks.d.ts +1 -0
- package/dist/core/parsers/requirement-blocks.js +2 -0
- package/dist/core/shared/ide-restart.d.ts +26 -0
- package/dist/core/shared/ide-restart.js +42 -0
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/tool-detection.js +9 -3
- package/dist/core/shared-skill-target.d.ts +13 -0
- package/dist/core/shared-skill-target.js +43 -1
- package/dist/core/specs-apply.js +22 -9
- package/dist/core/templates/workflows/explore.js +108 -73
- package/dist/core/templates/workflows/ff-change.js +8 -0
- package/dist/core/templates/workflows/propose.js +8 -0
- package/dist/core/update.js +28 -31
- package/dist/core/validation/constants.d.ts +3 -0
- package/dist/core/validation/constants.js +12 -0
- package/dist/core/validation/purpose-placeholder.d.ts +45 -0
- package/dist/core/validation/purpose-placeholder.js +117 -0
- package/dist/core/validation/validator.d.ts +12 -3
- package/dist/core/validation/validator.js +90 -4
- package/dist/utils/command-references.js +7 -8
- package/dist/utils/requirement-diff.d.ts +42 -0
- package/dist/utils/requirement-diff.js +61 -0
- package/package.json +4 -3
package/dist/commands/show.js
CHANGED
|
@@ -4,7 +4,7 @@ import { resolveRootForCommand, toRootOutput, withStoreFlag, isStoreSelectedRoot
|
|
|
4
4
|
import { ChangeCommand } from './change.js';
|
|
5
5
|
import { SpecCommand } from './spec.js';
|
|
6
6
|
import { nearestMatches } from '../utils/match.js';
|
|
7
|
-
const CHANGE_FLAG_KEYS = new Set(['deltasOnly', 'requirementsOnly']);
|
|
7
|
+
const CHANGE_FLAG_KEYS = new Set(['deltasOnly', 'requirementsOnly', 'diff']);
|
|
8
8
|
const SPEC_FLAG_KEYS = new Set(['requirements', 'scenarios', 'requirement']);
|
|
9
9
|
export class ShowCommand {
|
|
10
10
|
async execute(itemName, options = {}) {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { toRootOutput } from '../core/root-selection.js';
|
|
2
|
+
type ItemType = 'change' | 'spec';
|
|
1
3
|
interface ExecuteOptions {
|
|
2
4
|
all?: boolean;
|
|
3
5
|
changes?: boolean;
|
|
4
6
|
specs?: boolean;
|
|
5
7
|
archived?: boolean;
|
|
8
|
+
report?: string;
|
|
6
9
|
type?: string;
|
|
7
10
|
strict?: boolean;
|
|
8
11
|
json?: boolean;
|
|
@@ -12,6 +15,58 @@ interface ExecuteOptions {
|
|
|
12
15
|
store?: string;
|
|
13
16
|
storePath?: string;
|
|
14
17
|
}
|
|
18
|
+
interface BulkItemResult {
|
|
19
|
+
id: string;
|
|
20
|
+
type: ItemType;
|
|
21
|
+
valid: boolean;
|
|
22
|
+
issues: {
|
|
23
|
+
level: 'ERROR' | 'WARNING' | 'INFO';
|
|
24
|
+
path: string;
|
|
25
|
+
message: string;
|
|
26
|
+
}[];
|
|
27
|
+
durationMs: number;
|
|
28
|
+
}
|
|
29
|
+
type BulkScope = 'all' | 'changes' | 'specs' | 'archived';
|
|
30
|
+
interface BulkValidationResult<T extends BulkItemResult = BulkItemResult> {
|
|
31
|
+
items: T[];
|
|
32
|
+
summary: {
|
|
33
|
+
totals: {
|
|
34
|
+
items: number;
|
|
35
|
+
passed: number;
|
|
36
|
+
failed: number;
|
|
37
|
+
};
|
|
38
|
+
byType: Partial<Record<ItemType, {
|
|
39
|
+
items: number;
|
|
40
|
+
passed: number;
|
|
41
|
+
failed: number;
|
|
42
|
+
}>>;
|
|
43
|
+
};
|
|
44
|
+
root: ReturnType<typeof toRootOutput>;
|
|
45
|
+
}
|
|
46
|
+
/** Findings are a distinct report, not a partial full-v1 items collection. */
|
|
47
|
+
export declare function projectValidationFindings<T extends BulkItemResult>(full: BulkValidationResult<T>, scope: BulkScope): {
|
|
48
|
+
report: {
|
|
49
|
+
kind: "validation-findings";
|
|
50
|
+
version: "1.0";
|
|
51
|
+
scope: BulkScope;
|
|
52
|
+
returnedItems: number;
|
|
53
|
+
totalItems: number;
|
|
54
|
+
};
|
|
55
|
+
itemFindings: T[];
|
|
56
|
+
summary: {
|
|
57
|
+
totals: {
|
|
58
|
+
items: number;
|
|
59
|
+
passed: number;
|
|
60
|
+
failed: number;
|
|
61
|
+
};
|
|
62
|
+
byType: Partial<Record<ItemType, {
|
|
63
|
+
items: number;
|
|
64
|
+
passed: number;
|
|
65
|
+
failed: number;
|
|
66
|
+
}>>;
|
|
67
|
+
};
|
|
68
|
+
root: import("../core/root-selection.js").RootOutput;
|
|
69
|
+
};
|
|
15
70
|
export declare class ValidateCommand {
|
|
16
71
|
execute(itemName: string | undefined, options?: ExecuteOptions): Promise<void>;
|
|
17
72
|
private normalizeType;
|
|
@@ -29,6 +84,8 @@ export declare class ValidateCommand {
|
|
|
29
84
|
private validateByType;
|
|
30
85
|
private printReport;
|
|
31
86
|
private printNextSteps;
|
|
87
|
+
private printFindingsReport;
|
|
88
|
+
private printBulkDetails;
|
|
32
89
|
private runBulkValidation;
|
|
33
90
|
/**
|
|
34
91
|
* Lists archived change ids from the resolved root's archive directory,
|
|
@@ -10,9 +10,54 @@ import { nearestMatches } from '../utils/match.js';
|
|
|
10
10
|
import { promises as fs } from 'fs';
|
|
11
11
|
import { getTaskProgressDetailForChange } from '../utils/task-progress.js';
|
|
12
12
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
13
|
+
/** Findings are a distinct report, not a partial full-v1 items collection. */
|
|
14
|
+
export function projectValidationFindings(full, scope) {
|
|
15
|
+
const itemFindings = full.items.filter(item => item.issues.length > 0);
|
|
16
|
+
return {
|
|
17
|
+
report: {
|
|
18
|
+
kind: 'validation-findings',
|
|
19
|
+
version: '1.0',
|
|
20
|
+
scope,
|
|
21
|
+
returnedItems: itemFindings.length,
|
|
22
|
+
totalItems: full.summary.totals.items,
|
|
23
|
+
},
|
|
24
|
+
itemFindings,
|
|
25
|
+
summary: full.summary,
|
|
26
|
+
root: full.root,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
13
29
|
export class ValidateCommand {
|
|
14
30
|
async execute(itemName, options = {}) {
|
|
15
31
|
const bulk = options.all || options.changes || options.specs;
|
|
32
|
+
let findingsScope;
|
|
33
|
+
if (options.report !== undefined) {
|
|
34
|
+
const message = options.report !== 'full' && options.report !== 'findings'
|
|
35
|
+
? `Unknown validation report '${options.report}'.`
|
|
36
|
+
: itemName !== undefined
|
|
37
|
+
? 'A validation report cannot be combined with an item name.'
|
|
38
|
+
: options.archived && bulk
|
|
39
|
+
? 'A validation report cannot combine archived and active scopes.'
|
|
40
|
+
: !options.archived && !bulk
|
|
41
|
+
? 'A validation report requires an explicit bulk scope.'
|
|
42
|
+
: undefined;
|
|
43
|
+
if (message) {
|
|
44
|
+
const fix = 'Use --report full|findings with --all, --changes, --specs, or --archived, without an item name. Do not combine archived and active scopes.';
|
|
45
|
+
if (options.json) {
|
|
46
|
+
console.log(JSON.stringify({ status: [{ severity: 'error', code: 'invalid_validation_report_request', message, fix }] }, null, 2));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.error(`Error: ${message}`);
|
|
50
|
+
console.error(`Fix: ${fix}`);
|
|
51
|
+
}
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (options.report === 'findings') {
|
|
56
|
+
findingsScope = options.archived ? 'archived'
|
|
57
|
+
: options.all || (options.changes && options.specs) ? 'all'
|
|
58
|
+
: options.changes ? 'changes' : 'specs';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
16
61
|
const root = await resolveRootForCommand(options, {
|
|
17
62
|
json: options.json,
|
|
18
63
|
...(bulk ? { allowImplicitRoot: false } : {}),
|
|
@@ -29,6 +74,7 @@ export class ValidateCommand {
|
|
|
29
74
|
await this.runArchivedTaskValidation(root, {
|
|
30
75
|
json: !!options.json,
|
|
31
76
|
noInteractive: resolveNoInteractive(options),
|
|
77
|
+
findingsScope,
|
|
32
78
|
});
|
|
33
79
|
return;
|
|
34
80
|
}
|
|
@@ -37,7 +83,7 @@ export class ValidateCommand {
|
|
|
37
83
|
await this.runBulkValidation(root, {
|
|
38
84
|
changes: !!options.all || !!options.changes,
|
|
39
85
|
specs: !!options.all || !!options.specs,
|
|
40
|
-
}, { strict: !!options.strict, json: !!options.json, concurrency: options.concurrency, noInteractive: resolveNoInteractive(options) });
|
|
86
|
+
}, { strict: !!options.strict, json: !!options.json, concurrency: options.concurrency, noInteractive: resolveNoInteractive(options), findingsScope });
|
|
41
87
|
return;
|
|
42
88
|
}
|
|
43
89
|
// No item and no flags
|
|
@@ -191,11 +237,12 @@ export class ValidateCommand {
|
|
|
191
237
|
}
|
|
192
238
|
else {
|
|
193
239
|
console.error(`${type === 'change' ? 'Change' : 'Specification'} '${id}' has issues`);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
240
|
+
}
|
|
241
|
+
for (const issue of report.issues) {
|
|
242
|
+
const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
|
|
243
|
+
console.error(`${prefix} [${issue.level}] ${issue.path}: ${issue.message}`);
|
|
244
|
+
}
|
|
245
|
+
if (!report.valid) {
|
|
199
246
|
this.printNextSteps(type, id, root, report.issues);
|
|
200
247
|
}
|
|
201
248
|
}
|
|
@@ -228,6 +275,34 @@ export class ValidateCommand {
|
|
|
228
275
|
console.error('Next steps:');
|
|
229
276
|
bullets.forEach(b => console.error(` ${b}`));
|
|
230
277
|
}
|
|
278
|
+
printFindingsReport(full, scope, json, root) {
|
|
279
|
+
const findings = projectValidationFindings(full, scope);
|
|
280
|
+
if (json) {
|
|
281
|
+
console.log(JSON.stringify(findings, null, 2));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
console.log(`Scope: ${scope} (${findings.report.totalItems} items)`);
|
|
285
|
+
if (findings.itemFindings.length === 0) {
|
|
286
|
+
console.log('No item findings.');
|
|
287
|
+
}
|
|
288
|
+
for (const item of findings.itemFindings) {
|
|
289
|
+
console.error(`${item.type}/${item.id}`);
|
|
290
|
+
for (const issue of item.issues) {
|
|
291
|
+
console.error(` [${issue.level}] ${issue.path}: ${issue.message}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const totals = findings.summary.totals;
|
|
295
|
+
console.log(`Totals: ${totals.passed} passed, ${totals.failed} failed (${totals.items} items)`);
|
|
296
|
+
if (scope !== 'archived')
|
|
297
|
+
this.printBulkDetails(full.items, root);
|
|
298
|
+
}
|
|
299
|
+
printBulkDetails(results, root) {
|
|
300
|
+
const firstFailure = results.find((res) => !res.valid);
|
|
301
|
+
if (firstFailure) {
|
|
302
|
+
const storeFlag = isStoreSelectedRoot(root) ? ` --store ${root.storeId}` : '';
|
|
303
|
+
console.log(`Details: openspec validate ${firstFailure.id} --type ${firstFailure.type}${storeFlag}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
231
306
|
async runBulkValidation(root, scope, opts) {
|
|
232
307
|
const spinner = !opts.json && !opts.noInteractive ? ora('Validating...').start() : undefined;
|
|
233
308
|
const [changeIds, specIds] = await Promise.all([
|
|
@@ -269,7 +344,10 @@ export class ValidateCommand {
|
|
|
269
344
|
...(scope.specs ? { spec: { items: 0, passed: 0, failed: 0 } } : {}),
|
|
270
345
|
},
|
|
271
346
|
};
|
|
272
|
-
if (opts.
|
|
347
|
+
if (opts.findingsScope) {
|
|
348
|
+
this.printFindingsReport({ items: [], summary, root: toRootOutput(root) }, opts.findingsScope, opts.json, root);
|
|
349
|
+
}
|
|
350
|
+
else if (opts.json) {
|
|
273
351
|
const out = { items: [], summary, version: '1.0', root: toRootOutput(root) };
|
|
274
352
|
console.log(JSON.stringify(out, null, 2));
|
|
275
353
|
}
|
|
@@ -326,7 +404,10 @@ export class ValidateCommand {
|
|
|
326
404
|
...(scope.specs ? { spec: summarizeType(results, 'spec') } : {}),
|
|
327
405
|
},
|
|
328
406
|
};
|
|
329
|
-
if (opts.
|
|
407
|
+
if (opts.findingsScope) {
|
|
408
|
+
this.printFindingsReport({ items: results, summary, root: toRootOutput(root) }, opts.findingsScope, opts.json, root);
|
|
409
|
+
}
|
|
410
|
+
else if (opts.json) {
|
|
330
411
|
const out = { items: results, summary, version: '1.0', root: toRootOutput(root) };
|
|
331
412
|
console.log(JSON.stringify(out, null, 2));
|
|
332
413
|
}
|
|
@@ -336,13 +417,13 @@ export class ValidateCommand {
|
|
|
336
417
|
console.log(`✓ ${res.type}/${res.id}`);
|
|
337
418
|
else
|
|
338
419
|
console.error(`✗ ${res.type}/${res.id}`);
|
|
420
|
+
for (const issue of res.issues) {
|
|
421
|
+
const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
|
|
422
|
+
console.error(` ${prefix} [${issue.level}] ${issue.path}: ${issue.message}`);
|
|
423
|
+
}
|
|
339
424
|
}
|
|
340
425
|
console.log(`Totals: ${summary.totals.passed} passed, ${summary.totals.failed} failed (${summary.totals.items} items)`);
|
|
341
|
-
|
|
342
|
-
if (firstFailure) {
|
|
343
|
-
const storeFlag = isStoreSelectedRoot(root) ? ` --store ${root.storeId}` : '';
|
|
344
|
-
console.log(`Details: openspec validate ${firstFailure.id} --type ${firstFailure.type}${storeFlag}`);
|
|
345
|
-
}
|
|
426
|
+
this.printBulkDetails(results, root);
|
|
346
427
|
}
|
|
347
428
|
process.exitCode = failed > 0 ? 1 : 0;
|
|
348
429
|
}
|
|
@@ -438,6 +519,11 @@ export class ValidateCommand {
|
|
|
438
519
|
totals: { items: results.length, passed, failed },
|
|
439
520
|
byType: { change: summarizeType(results, 'change') },
|
|
440
521
|
};
|
|
522
|
+
if (opts.findingsScope) {
|
|
523
|
+
this.printFindingsReport({ items: results, summary, root: toRootOutput(root) }, opts.findingsScope, opts.json, root);
|
|
524
|
+
process.exitCode = failed > 0 ? 1 : 0;
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
441
527
|
if (opts.json) {
|
|
442
528
|
const out = { items: results, summary, version: '1.0', root: toRootOutput(root) };
|
|
443
529
|
console.log(JSON.stringify(out, null, 2));
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Commands for the artifact-driven workflow: status, instructions, templates, schemas, new change.
|
|
5
5
|
*/
|
|
6
|
-
export { statusCommand } from './status.js';
|
|
6
|
+
export { statusCommand, BATCH_STATUS_FAILURE_PAYLOAD } from './status.js';
|
|
7
7
|
export type { StatusOptions } from './status.js';
|
|
8
8
|
export { instructionsCommand, applyInstructionsCommand, archiveInstructionsCommand, } from './instructions.js';
|
|
9
9
|
export type { InstructionsOptions } from './instructions.js';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Commands for the artifact-driven workflow: status, instructions, templates, schemas, new change.
|
|
5
5
|
*/
|
|
6
|
-
export { statusCommand } from './status.js';
|
|
6
|
+
export { statusCommand, BATCH_STATUS_FAILURE_PAYLOAD } from './status.js';
|
|
7
7
|
export { instructionsCommand, applyInstructionsCommand, archiveInstructionsCommand, } from './instructions.js';
|
|
8
8
|
export { templatesCommand } from './templates.js';
|
|
9
9
|
export { schemasCommand } from './schemas.js';
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
import { type ChangeStatus } from '../../core/artifact-graph/index.js';
|
|
7
7
|
export interface StatusOptions {
|
|
8
8
|
change?: string;
|
|
9
|
+
all?: boolean;
|
|
9
10
|
schema?: string;
|
|
10
11
|
store?: string;
|
|
11
12
|
storePath?: string;
|
|
12
13
|
json?: boolean;
|
|
13
14
|
}
|
|
15
|
+
export declare const BATCH_STATUS_FAILURE_PAYLOAD: Record<string, unknown>;
|
|
14
16
|
export declare function statusCommand(options: StatusOptions): Promise<void>;
|
|
15
17
|
export declare function printStatusText(status: ChangeStatus): void;
|
|
16
18
|
//# sourceMappingURL=status.d.ts.map
|
|
@@ -8,14 +8,26 @@ import chalk from 'chalk';
|
|
|
8
8
|
import { getChangeDir } from '../../core/planning-home.js';
|
|
9
9
|
import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../../core/root-selection.js';
|
|
10
10
|
import { loadChangeContext, formatChangeStatus, } from '../../core/artifact-graph/index.js';
|
|
11
|
+
import { asStatus } from '../shared-output.js';
|
|
11
12
|
import { validateChangeExists, validateSchemaExists, getAvailableChanges, getStatusIndicator, getStatusColor, } from './shared.js';
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// The --all --json failure null-shape. Root-selection failures (handled in
|
|
14
|
+
// resolveRootForCommand) and thrown errors (caught by the CLI wrapper) must
|
|
15
|
+
// emit the same shape, so both call sites reference this one constant.
|
|
16
|
+
export const BATCH_STATUS_FAILURE_PAYLOAD = {
|
|
17
|
+
changes: [],
|
|
18
|
+
root: null,
|
|
19
|
+
};
|
|
15
20
|
export async function statusCommand(options) {
|
|
21
|
+
if (options.all && options.change) {
|
|
22
|
+
throw new Error('The --all and --change options are mutually exclusive.');
|
|
23
|
+
}
|
|
16
24
|
// The root resolves (and the store banner prints) before the spinner starts
|
|
17
|
-
// so the two do not fight over stderr.
|
|
18
|
-
|
|
25
|
+
// so the two do not fight over stderr. The batch null-shape rides along so
|
|
26
|
+
// a root-selection failure under --all --json still carries `changes: []`.
|
|
27
|
+
const root = await resolveRootForCommand(options, {
|
|
28
|
+
json: options.json,
|
|
29
|
+
failurePayload: options.all ? BATCH_STATUS_FAILURE_PAYLOAD : undefined,
|
|
30
|
+
});
|
|
19
31
|
if (!root) {
|
|
20
32
|
return;
|
|
21
33
|
}
|
|
@@ -25,9 +37,20 @@ export async function statusCommand(options) {
|
|
|
25
37
|
const projectRoot = root.path;
|
|
26
38
|
const rootOutput = toRootOutput(root);
|
|
27
39
|
const newChangeHint = withStoreFlag(root, 'openspec new change <name>');
|
|
40
|
+
// Single definition of "load one change's status" so the batch and
|
|
41
|
+
// single-change payloads can never drift apart.
|
|
42
|
+
const loadStatus = (changeName) => formatChangeStatus(loadChangeContext(projectRoot, changeName, options.schema, {
|
|
43
|
+
changeDir: getChangeDir(planningHome, changeName),
|
|
44
|
+
planningHome,
|
|
45
|
+
}), isStoreSelectedRoot(root) ? { storeId: root.storeId } : {});
|
|
28
46
|
// Handle no-changes case gracefully — status is informational,
|
|
29
47
|
// so "no changes" is a valid state, not an error.
|
|
30
48
|
if (!options.change) {
|
|
49
|
+
// Validate before the no-changes early return so a bogus --schema
|
|
50
|
+
// fails the same way whether or not any change exists yet.
|
|
51
|
+
if (options.all && options.schema) {
|
|
52
|
+
validateSchemaExists(options.schema, projectRoot);
|
|
53
|
+
}
|
|
31
54
|
const available = await getAvailableChanges(projectRoot, root.changesDir);
|
|
32
55
|
if (available.length === 0) {
|
|
33
56
|
spinner?.stop();
|
|
@@ -38,9 +61,54 @@ export async function statusCommand(options) {
|
|
|
38
61
|
console.log(`No active changes. Create one with: ${newChangeHint}`);
|
|
39
62
|
return;
|
|
40
63
|
}
|
|
41
|
-
|
|
64
|
+
if (options.all) {
|
|
65
|
+
// readdir order is platform-dependent; sort for deterministic output,
|
|
66
|
+
// with the same comparator validate --all uses so the two batch
|
|
67
|
+
// commands order a given change set identically.
|
|
68
|
+
const entries = [];
|
|
69
|
+
for (const changeName of available.sort((a, b) => a.localeCompare(b))) {
|
|
70
|
+
try {
|
|
71
|
+
entries.push(loadStatus(changeName));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// One malformed change must not blank the sweep; carry its
|
|
75
|
+
// diagnostic in place and keep going.
|
|
76
|
+
entries.push({ changeName, status: [asStatus(error, 'change_error')] });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
spinner?.stop();
|
|
80
|
+
const failed = entries.some((entry) => !('artifacts' in entry));
|
|
81
|
+
if (options.json) {
|
|
82
|
+
console.log(JSON.stringify({ changes: entries, root: rootOutput }, null, 2));
|
|
83
|
+
if (failed) {
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
entries.forEach((entry, index) => {
|
|
89
|
+
if (index > 0) {
|
|
90
|
+
console.log();
|
|
91
|
+
}
|
|
92
|
+
if ('artifacts' in entry) {
|
|
93
|
+
printStatusText(entry);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log(chalk.red(`✗ ${entry.changeName}: ${entry.status[0]?.message}`));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
// A partial load is still a failed command in both output modes;
|
|
100
|
+
// JSON callers can parse the complete envelope independently of
|
|
101
|
+
// the process exit code.
|
|
102
|
+
if (failed) {
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// Changes exist but neither --change nor --all provided. Name --all
|
|
108
|
+
// here too: it is the other way to answer this prompt, and a caller
|
|
109
|
+
// who wants every change should not have to find it in --help.
|
|
42
110
|
spinner?.stop();
|
|
43
|
-
throw new Error(`Missing required option --change. Available changes:\n ${available.join('\n ')}`);
|
|
111
|
+
throw new Error(`Missing required option --change (or --all for every active change). Available changes:\n ${available.join('\n ')}`);
|
|
44
112
|
}
|
|
45
113
|
const changeName = await validateChangeExists(options.change, projectRoot, root.changesDir, { newChangeHint });
|
|
46
114
|
// Validate schema if explicitly provided
|
|
@@ -48,11 +116,7 @@ export async function statusCommand(options) {
|
|
|
48
116
|
validateSchemaExists(options.schema, projectRoot);
|
|
49
117
|
}
|
|
50
118
|
// loadChangeContext will auto-detect schema from metadata if not provided
|
|
51
|
-
const
|
|
52
|
-
changeDir: getChangeDir(planningHome, changeName),
|
|
53
|
-
planningHome,
|
|
54
|
-
});
|
|
55
|
-
const status = formatChangeStatus(context, isStoreSelectedRoot(root) ? { storeId: root.storeId } : {});
|
|
119
|
+
const status = loadStatus(changeName);
|
|
56
120
|
spinner?.stop();
|
|
57
121
|
if (options.json) {
|
|
58
122
|
console.log(JSON.stringify({ ...status, root: rootOutput }, null, 2));
|
|
@@ -53,18 +53,24 @@ export function getProjectSchemasDir(projectRoot) {
|
|
|
53
53
|
* @param entry - The directory entry from `fs.readdirSync(..., { withFileTypes: true })`
|
|
54
54
|
*/
|
|
55
55
|
/**
|
|
56
|
-
* Directories `schema fork`
|
|
57
|
-
* place: a staging copy (`.fork-staging-<rand
|
|
58
|
-
* backup of the previous
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
56
|
+
* Directories `schema fork` and `schema init` create transiently while swapping
|
|
57
|
+
* a schema into place: a staging copy (`.fork-staging-<rand>` /
|
|
58
|
+
* `.init-staging-<rand>`, created via mkdtemp) and a backup of the previous
|
|
59
|
+
* destination (`<name>.fork-backup-<pid>-<ts>` /
|
|
60
|
+
* `<name>.init-backup-<pid>-<ts>`). Either can briefly coexist with real
|
|
61
|
+
* schemas in the schemas dir, and a backup outlives the run when its cleanup is
|
|
62
|
+
* blocked, so discovery must never surface them. Real schema names are
|
|
63
|
+
* kebab-case (no dots), so excluding these dot-bearing temp names can never
|
|
64
|
+
* hide a legitimate schema.
|
|
62
65
|
*/
|
|
63
|
-
function
|
|
64
|
-
return name.startsWith('.fork-staging-') ||
|
|
66
|
+
function isOwnedTransientSchemaDir(name) {
|
|
67
|
+
return (name.startsWith('.fork-staging-') ||
|
|
68
|
+
name.includes('.fork-backup-') ||
|
|
69
|
+
name.startsWith('.init-staging-') ||
|
|
70
|
+
name.includes('.init-backup-'));
|
|
65
71
|
}
|
|
66
72
|
export function isSchemaDir(parentDir, entry) {
|
|
67
|
-
if (
|
|
73
|
+
if (isOwnedTransientSchemaDir(entry.name)) {
|
|
68
74
|
return false;
|
|
69
75
|
}
|
|
70
76
|
if (entry.isDirectory()) {
|
|
@@ -49,6 +49,21 @@ export function getAvailableTools(projectPath) {
|
|
|
49
49
|
}
|
|
50
50
|
});
|
|
51
51
|
const activeProjectTools = new Set(reconcileSharedSkillTargets(projectPath, available.filter((tool) => tool.skillsDir)).map((tool) => tool.value));
|
|
52
|
-
|
|
52
|
+
const hasIndependentDetectionPath = (tool) => (tool.detectionPaths ?? []).some((detectionPath) => {
|
|
53
|
+
// Skill roots still go through managed-content reconciliation below;
|
|
54
|
+
// their mere existence is not an independent tool signal.
|
|
55
|
+
if (detectionPath.endsWith('/skills'))
|
|
56
|
+
return false;
|
|
57
|
+
try {
|
|
58
|
+
fs.statSync(path.join(projectPath, detectionPath));
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return available.filter((tool) => tool.globalSkillsDir ||
|
|
66
|
+
hasIndependentDetectionPath(tool) ||
|
|
67
|
+
activeProjectTools.has(tool.value));
|
|
53
68
|
}
|
|
54
69
|
//# sourceMappingURL=available-tools.js.map
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type { ToolCommandAdapter } from '../types.js';
|
|
7
7
|
/**
|
|
8
8
|
* Antigravity adapter for command generation.
|
|
9
|
-
* File path: .
|
|
9
|
+
* File path: .agents/workflows/opsx-<id>.md
|
|
10
10
|
* Frontmatter: description
|
|
11
11
|
*/
|
|
12
12
|
export declare const antigravityAdapter: ToolCommandAdapter;
|
|
@@ -7,13 +7,13 @@ import path from 'path';
|
|
|
7
7
|
import { escapeYamlValue } from '../yaml.js';
|
|
8
8
|
/**
|
|
9
9
|
* Antigravity adapter for command generation.
|
|
10
|
-
* File path: .
|
|
10
|
+
* File path: .agents/workflows/opsx-<id>.md
|
|
11
11
|
* Frontmatter: description
|
|
12
12
|
*/
|
|
13
13
|
export const antigravityAdapter = {
|
|
14
14
|
toolId: 'antigravity',
|
|
15
15
|
getFilePath(commandId) {
|
|
16
|
-
return path.join('.
|
|
16
|
+
return path.join('.agents', 'workflows', `opsx-${commandId}.md`);
|
|
17
17
|
},
|
|
18
18
|
formatFile(content) {
|
|
19
19
|
return `---
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SourceCraft Code Assistant Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for the SourceCraft Code Assistant VS Code extension.
|
|
5
|
+
*
|
|
6
|
+
* @see https://sourcecraft.dev/portal/docs/en/code-assistant/operations/agent/slash-commands
|
|
7
|
+
*/
|
|
8
|
+
import type { ToolCommandAdapter } from '../types.js';
|
|
9
|
+
/**
|
|
10
|
+
* SourceCraft Code Assistant adapter for command generation.
|
|
11
|
+
* File path: .codeassistant/commands/opsx-<id>.md
|
|
12
|
+
* Format: YAML frontmatter with description
|
|
13
|
+
*/
|
|
14
|
+
export declare const codeassistantAdapter: ToolCommandAdapter;
|
|
15
|
+
//# sourceMappingURL=codeassistant.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SourceCraft Code Assistant Command Adapter
|
|
3
|
+
*
|
|
4
|
+
* Formats commands for the SourceCraft Code Assistant VS Code extension.
|
|
5
|
+
*
|
|
6
|
+
* @see https://sourcecraft.dev/portal/docs/en/code-assistant/operations/agent/slash-commands
|
|
7
|
+
*/
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { escapeYamlValue } from '../yaml.js';
|
|
10
|
+
/**
|
|
11
|
+
* SourceCraft Code Assistant adapter for command generation.
|
|
12
|
+
* File path: .codeassistant/commands/opsx-<id>.md
|
|
13
|
+
* Format: YAML frontmatter with description
|
|
14
|
+
*/
|
|
15
|
+
export const codeassistantAdapter = {
|
|
16
|
+
toolId: 'codeassistant',
|
|
17
|
+
getFilePath(commandId) {
|
|
18
|
+
return path.join('.codeassistant', 'commands', `opsx-${commandId}.md`);
|
|
19
|
+
},
|
|
20
|
+
formatFile(content) {
|
|
21
|
+
return `---
|
|
22
|
+
description: ${escapeYamlValue(content.description)}
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
${content.body}
|
|
26
|
+
`;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=codeassistant.js.map
|
|
@@ -26,6 +26,7 @@ export { kiroAdapter } from './kiro.js';
|
|
|
26
26
|
export { ohMyPiAdapter } from './oh-my-pi.js';
|
|
27
27
|
export { opencodeAdapter } from './opencode.js';
|
|
28
28
|
export { piAdapter } from './pi.js';
|
|
29
|
+
export { codeassistantAdapter } from './codeassistant.js';
|
|
29
30
|
export { qoderAdapter } from './qoder.js';
|
|
30
31
|
export { lingmaAdapter } from './lingma.js';
|
|
31
32
|
export { qwenAdapter } from './qwen.js';
|
|
@@ -26,6 +26,7 @@ export { kiroAdapter } from './kiro.js';
|
|
|
26
26
|
export { ohMyPiAdapter } from './oh-my-pi.js';
|
|
27
27
|
export { opencodeAdapter } from './opencode.js';
|
|
28
28
|
export { piAdapter } from './pi.js';
|
|
29
|
+
export { codeassistantAdapter } from './codeassistant.js';
|
|
29
30
|
export { qoderAdapter } from './qoder.js';
|
|
30
31
|
export { lingmaAdapter } from './lingma.js';
|
|
31
32
|
export { qwenAdapter } from './qwen.js';
|
|
@@ -27,6 +27,7 @@ import { kiroAdapter } from './adapters/kiro.js';
|
|
|
27
27
|
import { ohMyPiAdapter } from './adapters/oh-my-pi.js';
|
|
28
28
|
import { opencodeAdapter } from './adapters/opencode.js';
|
|
29
29
|
import { piAdapter } from './adapters/pi.js';
|
|
30
|
+
import { codeassistantAdapter } from './adapters/codeassistant.js';
|
|
30
31
|
import { qoderAdapter } from './adapters/qoder.js';
|
|
31
32
|
import { lingmaAdapter } from './adapters/lingma.js';
|
|
32
33
|
import { qwenAdapter } from './adapters/qwen.js';
|
|
@@ -63,6 +64,7 @@ export class CommandAdapterRegistry {
|
|
|
63
64
|
CommandAdapterRegistry.register(ohMyPiAdapter);
|
|
64
65
|
CommandAdapterRegistry.register(opencodeAdapter);
|
|
65
66
|
CommandAdapterRegistry.register(piAdapter);
|
|
67
|
+
CommandAdapterRegistry.register(codeassistantAdapter);
|
|
66
68
|
CommandAdapterRegistry.register(qoderAdapter);
|
|
67
69
|
CommandAdapterRegistry.register(lingmaAdapter);
|
|
68
70
|
CommandAdapterRegistry.register(qwenAdapter);
|
|
@@ -106,6 +106,12 @@ export const COMMAND_REGISTRY = [
|
|
|
106
106
|
name: 'archived',
|
|
107
107
|
description: 'Validate that archived changes have all tasks completed (for pre-commit linting)',
|
|
108
108
|
},
|
|
109
|
+
{
|
|
110
|
+
name: 'report',
|
|
111
|
+
description: 'Select bulk report content',
|
|
112
|
+
takesValue: true,
|
|
113
|
+
values: ['full', 'findings'],
|
|
114
|
+
},
|
|
109
115
|
COMMON_FLAGS.type,
|
|
110
116
|
COMMON_FLAGS.strict,
|
|
111
117
|
COMMON_FLAGS.jsonValidation,
|
|
@@ -136,6 +142,10 @@ export const COMMAND_REGISTRY = [
|
|
|
136
142
|
name: 'requirements-only',
|
|
137
143
|
description: 'Alias for --deltas-only (deprecated, change-specific)',
|
|
138
144
|
},
|
|
145
|
+
{
|
|
146
|
+
name: 'diff',
|
|
147
|
+
description: 'Show per-requirement diffs for delta specs (change-specific)',
|
|
148
|
+
},
|
|
139
149
|
{
|
|
140
150
|
name: 'requirements',
|
|
141
151
|
description: 'Show only requirements, exclude scenarios (JSON only, spec-specific)',
|
|
@@ -189,6 +199,10 @@ export const COMMAND_REGISTRY = [
|
|
|
189
199
|
description: 'Change name to show status for',
|
|
190
200
|
takesValue: true,
|
|
191
201
|
},
|
|
202
|
+
{
|
|
203
|
+
name: 'all',
|
|
204
|
+
description: 'Show status for all active changes',
|
|
205
|
+
},
|
|
192
206
|
{
|
|
193
207
|
name: 'schema',
|
|
194
208
|
description: 'Schema override',
|
|
@@ -285,6 +299,7 @@ export const COMMAND_REGISTRY = [
|
|
|
285
299
|
name: 'path',
|
|
286
300
|
description: 'Directory to use for the store',
|
|
287
301
|
takesValue: true,
|
|
302
|
+
completionType: 'path',
|
|
288
303
|
},
|
|
289
304
|
{
|
|
290
305
|
name: 'init-git',
|
|
@@ -377,6 +392,7 @@ export const COMMAND_REGISTRY = [
|
|
|
377
392
|
name: 'code-workspace',
|
|
378
393
|
description: 'Also write a VS Code workspace file for the set',
|
|
379
394
|
takesValue: true,
|
|
395
|
+
completionType: 'path',
|
|
380
396
|
},
|
|
381
397
|
{
|
|
382
398
|
name: 'force',
|
|
@@ -407,6 +423,7 @@ export const COMMAND_REGISTRY = [
|
|
|
407
423
|
name: 'member',
|
|
408
424
|
description: 'Member folder as <path> or <name>=<path>; repeatable, first is the primary',
|
|
409
425
|
takesValue: true,
|
|
426
|
+
completionType: 'path',
|
|
410
427
|
},
|
|
411
428
|
{
|
|
412
429
|
name: 'tool',
|
|
@@ -488,6 +505,10 @@ export const COMMAND_REGISTRY = [
|
|
|
488
505
|
name: 'requirements-only',
|
|
489
506
|
description: 'Alias for --deltas-only (deprecated)',
|
|
490
507
|
},
|
|
508
|
+
{
|
|
509
|
+
name: 'diff',
|
|
510
|
+
description: 'Show per-requirement diffs for delta specs',
|
|
511
|
+
},
|
|
491
512
|
COMMON_FLAGS.noInteractive,
|
|
492
513
|
],
|
|
493
514
|
},
|