@fission-ai/openspec 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -3
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +94 -76
- package/dist/commands/change.d.ts +5 -1
- package/dist/commands/change.js +17 -15
- package/dist/commands/config.js +6 -65
- package/dist/commands/context.d.ts +3 -0
- package/dist/commands/context.js +155 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +163 -0
- package/dist/commands/shared-gather.d.ts +14 -0
- package/dist/commands/shared-gather.js +31 -0
- package/dist/commands/shared-output.d.ts +18 -0
- package/dist/commands/shared-output.js +61 -0
- package/dist/commands/show.d.ts +11 -6
- package/dist/commands/show.js +70 -25
- package/dist/commands/spec.d.ts +5 -1
- package/dist/commands/spec.js +15 -4
- package/dist/commands/store.d.ts +3 -0
- package/dist/commands/store.js +547 -0
- package/dist/commands/validate.d.ts +2 -0
- package/dist/commands/validate.js +75 -39
- package/dist/commands/workflow/index.d.ts +0 -2
- package/dist/commands/workflow/index.js +0 -1
- package/dist/commands/workflow/instructions.d.ts +11 -1
- package/dist/commands/workflow/instructions.js +71 -22
- package/dist/commands/workflow/new-change.d.ts +6 -3
- package/dist/commands/workflow/new-change.js +42 -77
- package/dist/commands/workflow/shared.d.ts +15 -3
- package/dist/commands/workflow/shared.js +20 -3
- package/dist/commands/workflow/status.d.ts +2 -0
- package/dist/commands/workflow/status.js +19 -17
- package/dist/commands/workset-input.d.ts +19 -0
- package/dist/commands/workset-input.js +112 -0
- package/dist/commands/workset-prompts.d.ts +12 -0
- package/dist/commands/workset-prompts.js +143 -0
- package/dist/commands/workset.d.ts +25 -0
- package/dist/commands/workset.js +446 -0
- package/dist/core/archive.d.ts +17 -6
- package/dist/core/archive.js +209 -56
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +18 -13
- package/dist/core/artifact-graph/instruction-loader.js +8 -31
- package/dist/core/change-metadata/schema.d.ts +1 -0
- package/dist/core/change-metadata/schema.js +4 -2
- package/dist/core/change-status-policy.d.ts +5 -18
- package/dist/core/change-status-policy.js +2 -37
- package/dist/core/command-generation/adapters/bob.js +1 -14
- package/dist/core/command-generation/adapters/claude.js +1 -14
- package/dist/core/command-generation/adapters/cursor.js +1 -14
- package/dist/core/command-generation/adapters/pi.js +1 -14
- package/dist/core/command-generation/adapters/windsurf.js +1 -14
- package/dist/core/command-generation/yaml.d.ts +22 -0
- package/dist/core/command-generation/yaml.js +38 -0
- package/dist/core/completions/command-registry.js +87 -289
- package/dist/core/completions/generators/zsh-generator.js +3 -1
- package/dist/core/completions/installers/bash-installer.js +3 -0
- package/dist/core/completions/installers/fish-installer.js +8 -0
- package/dist/core/completions/installers/powershell-installer.js +30 -2
- package/dist/core/completions/installers/zsh-installer.js +3 -0
- package/dist/core/completions/shared-flags.d.ts +1 -0
- package/dist/core/completions/shared-flags.js +5 -0
- package/dist/core/config-schema.d.ts +2 -1
- package/dist/core/config-schema.js +26 -0
- package/dist/core/file-state.d.ts +36 -0
- package/dist/core/file-state.js +112 -0
- package/dist/core/global-config.d.ts +2 -0
- package/dist/core/id.d.ts +17 -0
- package/dist/core/id.js +30 -0
- package/dist/core/index.d.ts +3 -3
- package/dist/core/index.js +3 -3
- package/dist/core/init.js +24 -4
- package/dist/core/list.d.ts +2 -0
- package/dist/core/list.js +19 -5
- package/dist/core/openers.d.ts +77 -0
- package/dist/core/openers.js +251 -0
- package/dist/core/openspec-root.d.ts +45 -0
- package/dist/core/openspec-root.js +192 -0
- package/dist/core/planning-home.d.ts +1 -6
- package/dist/core/planning-home.js +2 -43
- package/dist/core/project-config.d.ts +44 -1
- package/dist/core/project-config.js +160 -10
- package/dist/core/references.d.ts +63 -0
- package/dist/core/references.js +310 -0
- package/dist/core/relationship-health.d.ts +65 -0
- package/dist/core/relationship-health.js +64 -0
- package/dist/core/root-selection.d.ts +122 -0
- package/dist/core/root-selection.js +337 -0
- package/dist/core/specs-apply.d.ts +6 -1
- package/dist/core/specs-apply.js +6 -4
- package/dist/core/store/errors.d.ts +20 -0
- package/dist/core/{context-store → store}/errors.js +3 -3
- package/dist/core/store/foundation.d.ts +56 -0
- package/dist/core/store/foundation.js +251 -0
- package/dist/core/store/git.d.ts +23 -0
- package/dist/core/store/git.js +137 -0
- package/dist/core/{context-store → store}/index.d.ts +0 -1
- package/dist/core/{context-store → store}/index.js +0 -1
- package/dist/core/store/operations.d.ts +114 -0
- package/dist/core/store/operations.js +783 -0
- package/dist/core/store/registry.d.ts +58 -0
- package/dist/core/store/registry.js +275 -0
- package/dist/core/templates/workflows/apply-change.js +5 -4
- package/dist/core/templates/workflows/archive-change.js +5 -4
- package/dist/core/templates/workflows/bulk-archive-change.js +5 -4
- package/dist/core/templates/workflows/continue-change.js +7 -2
- package/dist/core/templates/workflows/explore.js +5 -0
- package/dist/core/templates/workflows/ff-change.js +5 -0
- package/dist/core/templates/workflows/new-change.js +5 -0
- package/dist/core/templates/workflows/onboard.js +4 -1
- package/dist/core/templates/workflows/propose.js +5 -0
- package/dist/core/templates/workflows/store-selection.d.ts +8 -0
- package/dist/core/templates/workflows/store-selection.js +8 -0
- package/dist/core/templates/workflows/sync-specs.js +5 -4
- package/dist/core/templates/workflows/verify-change.js +5 -4
- package/dist/core/working-set.d.ts +47 -0
- package/dist/core/working-set.js +43 -0
- package/dist/core/worksets.d.ts +75 -0
- package/dist/core/worksets.js +245 -0
- package/dist/core/zod-issues.d.ts +4 -0
- package/dist/core/zod-issues.js +10 -0
- package/dist/utils/change-metadata.d.ts +1 -0
- package/dist/utils/change-metadata.js +1 -1
- package/dist/utils/change-utils.js +15 -0
- package/dist/utils/file-system.js +36 -17
- package/package.json +6 -6
- package/dist/commands/context-store.d.ts +0 -3
- package/dist/commands/context-store.js +0 -475
- package/dist/commands/initiative.d.ts +0 -13
- package/dist/commands/initiative.js +0 -318
- package/dist/commands/workflow/initiative-link.d.ts +0 -24
- package/dist/commands/workflow/initiative-link.js +0 -47
- package/dist/commands/workflow/set-change.d.ts +0 -13
- package/dist/commands/workflow/set-change.js +0 -87
- package/dist/commands/workspace/context-status.d.ts +0 -4
- package/dist/commands/workspace/context-status.js +0 -59
- package/dist/commands/workspace/open-target-selection.d.ts +0 -13
- package/dist/commands/workspace/open-target-selection.js +0 -146
- package/dist/commands/workspace/open-view.d.ts +0 -62
- package/dist/commands/workspace/open-view.js +0 -249
- package/dist/commands/workspace/open.d.ts +0 -37
- package/dist/commands/workspace/open.js +0 -110
- package/dist/commands/workspace/opener-selection.d.ts +0 -11
- package/dist/commands/workspace/opener-selection.js +0 -98
- package/dist/commands/workspace/operations.d.ts +0 -29
- package/dist/commands/workspace/operations.js +0 -543
- package/dist/commands/workspace/prompt-theme.d.ts +0 -29
- package/dist/commands/workspace/prompt-theme.js +0 -24
- package/dist/commands/workspace/registration.d.ts +0 -13
- package/dist/commands/workspace/registration.js +0 -84
- package/dist/commands/workspace/selection.d.ts +0 -9
- package/dist/commands/workspace/selection.js +0 -115
- package/dist/commands/workspace/setup-prompts.d.ts +0 -13
- package/dist/commands/workspace/setup-prompts.js +0 -121
- package/dist/commands/workspace/types.d.ts +0 -103
- package/dist/commands/workspace/types.js +0 -36
- package/dist/commands/workspace.d.ts +0 -6
- package/dist/commands/workspace.js +0 -587
- package/dist/core/collections/index.d.ts +0 -3
- package/dist/core/collections/index.js +0 -3
- package/dist/core/collections/initiatives/collection.d.ts +0 -4
- package/dist/core/collections/initiatives/collection.js +0 -17
- package/dist/core/collections/initiatives/index.d.ts +0 -6
- package/dist/core/collections/initiatives/index.js +0 -6
- package/dist/core/collections/initiatives/operations.d.ts +0 -49
- package/dist/core/collections/initiatives/operations.js +0 -175
- package/dist/core/collections/initiatives/resolution.d.ts +0 -87
- package/dist/core/collections/initiatives/resolution.js +0 -374
- package/dist/core/collections/initiatives/schema.d.ts +0 -41
- package/dist/core/collections/initiatives/schema.js +0 -134
- package/dist/core/collections/initiatives/templates.d.ts +0 -12
- package/dist/core/collections/initiatives/templates.js +0 -90
- package/dist/core/collections/runtime.d.ts +0 -46
- package/dist/core/collections/runtime.js +0 -194
- package/dist/core/context-store/binding.d.ts +0 -53
- package/dist/core/context-store/binding.js +0 -197
- package/dist/core/context-store/errors.d.ts +0 -20
- package/dist/core/context-store/foundation.d.ts +0 -55
- package/dist/core/context-store/foundation.js +0 -321
- package/dist/core/context-store/operations.d.ts +0 -85
- package/dist/core/context-store/operations.js +0 -528
- package/dist/core/context-store/registry.d.ts +0 -45
- package/dist/core/context-store/registry.js +0 -229
- package/dist/core/workspace/foundation.d.ts +0 -67
- package/dist/core/workspace/foundation.js +0 -295
- package/dist/core/workspace/index.d.ts +0 -8
- package/dist/core/workspace/index.js +0 -8
- package/dist/core/workspace/legacy-state.d.ts +0 -28
- package/dist/core/workspace/legacy-state.js +0 -200
- package/dist/core/workspace/link-input.d.ts +0 -9
- package/dist/core/workspace/link-input.js +0 -32
- package/dist/core/workspace/open-surface.d.ts +0 -45
- package/dist/core/workspace/open-surface.js +0 -215
- package/dist/core/workspace/openers.d.ts +0 -21
- package/dist/core/workspace/openers.js +0 -124
- package/dist/core/workspace/registry.d.ts +0 -24
- package/dist/core/workspace/registry.js +0 -146
- package/dist/core/workspace/skills.d.ts +0 -57
- package/dist/core/workspace/skills.js +0 -334
- package/dist/core/workspace/state-io.d.ts +0 -10
- package/dist/core/workspace/state-io.js +0 -119
- package/schemas/workspace-planning/schema.yaml +0 -72
- package/schemas/workspace-planning/templates/design.md +0 -33
- package/schemas/workspace-planning/templates/proposal.md +0 -28
- package/schemas/workspace-planning/templates/spec.md +0 -9
- package/schemas/workspace-planning/templates/tasks.md +0 -15
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { Validator } from '../core/validation/validator.js';
|
|
4
|
+
import { resolveRootForCommand, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../core/root-selection.js';
|
|
4
5
|
import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
|
|
5
6
|
import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
|
|
6
7
|
import { nearestMatches } from '../utils/match.js';
|
|
7
8
|
export class ValidateCommand {
|
|
8
9
|
async execute(itemName, options = {}) {
|
|
10
|
+
const root = await resolveRootForCommand(options, { json: options.json });
|
|
11
|
+
if (!root) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
9
14
|
const interactive = isInteractive(options);
|
|
10
15
|
// Handle bulk flags first
|
|
11
16
|
if (options.all || options.changes || options.specs) {
|
|
12
|
-
await this.runBulkValidation({
|
|
17
|
+
await this.runBulkValidation(root, {
|
|
13
18
|
changes: !!options.all || !!options.changes,
|
|
14
19
|
specs: !!options.all || !!options.specs,
|
|
15
20
|
}, { strict: !!options.strict, json: !!options.json, concurrency: options.concurrency, noInteractive: resolveNoInteractive(options) });
|
|
@@ -18,16 +23,16 @@ export class ValidateCommand {
|
|
|
18
23
|
// No item and no flags
|
|
19
24
|
if (!itemName) {
|
|
20
25
|
if (interactive) {
|
|
21
|
-
await this.runInteractiveSelector({ strict: !!options.strict, json: !!options.json, concurrency: options.concurrency });
|
|
26
|
+
await this.runInteractiveSelector(root, { strict: !!options.strict, json: !!options.json, concurrency: options.concurrency });
|
|
22
27
|
return;
|
|
23
28
|
}
|
|
24
|
-
this.printNonInteractiveHint();
|
|
29
|
+
this.printNonInteractiveHint(root);
|
|
25
30
|
process.exitCode = 1;
|
|
26
31
|
return;
|
|
27
32
|
}
|
|
28
33
|
// Direct item validation with type detection or override
|
|
29
34
|
const typeOverride = this.normalizeType(options.type);
|
|
30
|
-
await this.validateDirectItem(itemName, { typeOverride, strict: !!options.strict, json: !!options.json });
|
|
35
|
+
await this.validateDirectItem(root, itemName, { typeOverride, strict: !!options.strict, json: !!options.json });
|
|
31
36
|
}
|
|
32
37
|
normalizeType(value) {
|
|
33
38
|
if (!value)
|
|
@@ -37,7 +42,7 @@ export class ValidateCommand {
|
|
|
37
42
|
return v;
|
|
38
43
|
return undefined;
|
|
39
44
|
}
|
|
40
|
-
async runInteractiveSelector(opts) {
|
|
45
|
+
async runInteractiveSelector(root, opts) {
|
|
41
46
|
const { select } = await import('@inquirer/prompts');
|
|
42
47
|
const choice = await select({
|
|
43
48
|
message: 'What would you like to validate?',
|
|
@@ -49,13 +54,13 @@ export class ValidateCommand {
|
|
|
49
54
|
],
|
|
50
55
|
});
|
|
51
56
|
if (choice === 'all')
|
|
52
|
-
return this.runBulkValidation({ changes: true, specs: true }, opts);
|
|
57
|
+
return this.runBulkValidation(root, { changes: true, specs: true }, opts);
|
|
53
58
|
if (choice === 'changes')
|
|
54
|
-
return this.runBulkValidation({ changes: true, specs: false }, opts);
|
|
59
|
+
return this.runBulkValidation(root, { changes: true, specs: false }, opts);
|
|
55
60
|
if (choice === 'specs')
|
|
56
|
-
return this.runBulkValidation({ changes: false, specs: true }, opts);
|
|
61
|
+
return this.runBulkValidation(root, { changes: false, specs: true }, opts);
|
|
57
62
|
// one
|
|
58
|
-
const [changes, specs] = await Promise.all([getActiveChangeIds(), getSpecIds()]);
|
|
63
|
+
const [changes, specs] = await Promise.all([getActiveChangeIds(root.path), getSpecIds(root.path)]);
|
|
59
64
|
const items = [];
|
|
60
65
|
items.push(...changes.map(id => ({ name: `change/${id}`, value: { type: 'change', id } })));
|
|
61
66
|
items.push(...specs.map(id => ({ name: `spec/${id}`, value: { type: 'spec', id } })));
|
|
@@ -65,59 +70,85 @@ export class ValidateCommand {
|
|
|
65
70
|
return;
|
|
66
71
|
}
|
|
67
72
|
const picked = await select({ message: 'Pick an item', choices: items });
|
|
68
|
-
await this.validateByType(picked.type, picked.id, opts);
|
|
73
|
+
await this.validateByType(root, picked.type, picked.id, opts);
|
|
69
74
|
}
|
|
70
|
-
printNonInteractiveHint() {
|
|
75
|
+
printNonInteractiveHint(root) {
|
|
71
76
|
console.error('Nothing to validate. Try one of:');
|
|
72
|
-
console.error('
|
|
73
|
-
console.error('
|
|
74
|
-
console.error('
|
|
75
|
-
console.error('
|
|
77
|
+
console.error(` ${withStoreFlag(root, 'openspec validate --all')}`);
|
|
78
|
+
console.error(` ${withStoreFlag(root, 'openspec validate --changes')}`);
|
|
79
|
+
console.error(` ${withStoreFlag(root, 'openspec validate --specs')}`);
|
|
80
|
+
console.error(` ${withStoreFlag(root, 'openspec validate <item-name>')}`);
|
|
76
81
|
console.error('Or run in an interactive terminal.');
|
|
77
82
|
}
|
|
78
|
-
async validateDirectItem(itemName, opts) {
|
|
79
|
-
const [changes, specs] = await Promise.all([getActiveChangeIds(), getSpecIds()]);
|
|
83
|
+
async validateDirectItem(root, itemName, opts) {
|
|
84
|
+
const [changes, specs] = await Promise.all([getActiveChangeIds(root.path), getSpecIds(root.path)]);
|
|
80
85
|
const isChange = changes.includes(itemName);
|
|
81
86
|
const isSpec = specs.includes(itemName);
|
|
82
87
|
const type = opts.typeOverride ?? (isChange ? 'change' : isSpec ? 'spec' : undefined);
|
|
83
88
|
if (!type) {
|
|
84
|
-
console.error(`Unknown item '${itemName}'`);
|
|
85
89
|
const suggestions = nearestMatches(itemName, [...changes, ...specs]);
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
const message = suggestions.length
|
|
91
|
+
? `Unknown item '${itemName}'. Did you mean: ${suggestions.join(', ')}?`
|
|
92
|
+
: `Unknown item '${itemName}'.`;
|
|
93
|
+
if (opts.json) {
|
|
94
|
+
console.log(JSON.stringify({ status: [{ severity: 'error', code: 'unknown_item', message }] }, null, 2));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.error(message);
|
|
98
|
+
}
|
|
88
99
|
process.exitCode = 1;
|
|
89
100
|
return;
|
|
90
101
|
}
|
|
91
102
|
if (!opts.typeOverride && isChange && isSpec) {
|
|
103
|
+
if (opts.json) {
|
|
104
|
+
console.log(JSON.stringify({
|
|
105
|
+
status: [
|
|
106
|
+
{
|
|
107
|
+
severity: 'error',
|
|
108
|
+
code: 'ambiguous_item',
|
|
109
|
+
message: `Ambiguous item '${itemName}' matches both a change and a spec.`,
|
|
110
|
+
fix: 'Pass --type change|spec.',
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
}, null, 2));
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
92
117
|
console.error(`Ambiguous item '${itemName}' matches both a change and a spec.`);
|
|
93
|
-
|
|
118
|
+
// The noun-form commands are cwd-based and cannot reach a selected store.
|
|
119
|
+
if (isStoreSelectedRoot(root)) {
|
|
120
|
+
console.error('Pass --type change|spec.');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.error('Pass --type change|spec, or use: openspec change validate / openspec spec validate');
|
|
124
|
+
}
|
|
94
125
|
process.exitCode = 1;
|
|
95
126
|
return;
|
|
96
127
|
}
|
|
97
|
-
await this.validateByType(type, itemName, opts);
|
|
128
|
+
await this.validateByType(root, type, itemName, opts);
|
|
98
129
|
}
|
|
99
|
-
async validateByType(type, id, opts) {
|
|
130
|
+
async validateByType(root, type, id, opts) {
|
|
100
131
|
const validator = new Validator(opts.strict);
|
|
101
132
|
if (type === 'change') {
|
|
102
|
-
const changeDir = path.join(
|
|
133
|
+
const changeDir = path.join(root.changesDir, id);
|
|
103
134
|
const start = Date.now();
|
|
104
135
|
const report = await validator.validateChangeDeltaSpecs(changeDir);
|
|
105
136
|
const durationMs = Date.now() - start;
|
|
106
|
-
this.printReport('change', id, report, durationMs, opts.json);
|
|
137
|
+
this.printReport('change', id, report, durationMs, opts.json, root);
|
|
107
138
|
// Non-zero exit if invalid (keeps enriched output test semantics)
|
|
108
139
|
process.exitCode = report.valid ? 0 : 1;
|
|
109
140
|
return;
|
|
110
141
|
}
|
|
111
|
-
const file = path.join(
|
|
142
|
+
const file = path.join(root.specsDir, id, 'spec.md');
|
|
112
143
|
const start = Date.now();
|
|
113
144
|
const report = await validator.validateSpec(file);
|
|
114
145
|
const durationMs = Date.now() - start;
|
|
115
|
-
this.printReport('spec', id, report, durationMs, opts.json);
|
|
146
|
+
this.printReport('spec', id, report, durationMs, opts.json, root);
|
|
116
147
|
process.exitCode = report.valid ? 0 : 1;
|
|
117
148
|
}
|
|
118
|
-
printReport(type, id, report, durationMs, json) {
|
|
149
|
+
printReport(type, id, report, durationMs, json, root) {
|
|
119
150
|
if (json) {
|
|
120
|
-
const out = { items: [{ id, type, valid: report.valid, issues: report.issues, durationMs }], summary: { totals: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 }, byType: { [type]: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 } } }, version: '1.0' };
|
|
151
|
+
const out = { items: [{ id, type, valid: report.valid, issues: report.issues, durationMs }], summary: { totals: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 }, byType: { [type]: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 } } }, version: '1.0', root: toRootOutput(root) };
|
|
121
152
|
console.log(JSON.stringify(out, null, 2));
|
|
122
153
|
return;
|
|
123
154
|
}
|
|
@@ -131,15 +162,15 @@ export class ValidateCommand {
|
|
|
131
162
|
const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
|
|
132
163
|
console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
|
|
133
164
|
}
|
|
134
|
-
this.printNextSteps(type);
|
|
165
|
+
this.printNextSteps(type, id, root);
|
|
135
166
|
}
|
|
136
167
|
}
|
|
137
|
-
printNextSteps(type) {
|
|
168
|
+
printNextSteps(type, id, root) {
|
|
138
169
|
const bullets = [];
|
|
139
170
|
if (type === 'change') {
|
|
140
171
|
bullets.push('- Ensure change has deltas in specs/: use headers ## ADDED/MODIFIED/REMOVED/RENAMED Requirements');
|
|
141
172
|
bullets.push('- Each requirement MUST include at least one #### Scenario: block');
|
|
142
|
-
bullets.push(
|
|
173
|
+
bullets.push(`- Debug parsed deltas: ${withStoreFlag(root, `openspec show ${id} --json --deltas-only`)}`);
|
|
143
174
|
}
|
|
144
175
|
else {
|
|
145
176
|
bullets.push('- Ensure spec includes ## Purpose and ## Requirements sections');
|
|
@@ -149,11 +180,11 @@ export class ValidateCommand {
|
|
|
149
180
|
console.error('Next steps:');
|
|
150
181
|
bullets.forEach(b => console.error(` ${b}`));
|
|
151
182
|
}
|
|
152
|
-
async runBulkValidation(scope, opts) {
|
|
183
|
+
async runBulkValidation(root, scope, opts) {
|
|
153
184
|
const spinner = !opts.json && !opts.noInteractive ? ora('Validating...').start() : undefined;
|
|
154
185
|
const [changeIds, specIds] = await Promise.all([
|
|
155
|
-
scope.changes ? getActiveChangeIds() : Promise.resolve([]),
|
|
156
|
-
scope.specs ? getSpecIds() : Promise.resolve([]),
|
|
186
|
+
scope.changes ? getActiveChangeIds(root.path) : Promise.resolve([]),
|
|
187
|
+
scope.specs ? getSpecIds(root.path) : Promise.resolve([]),
|
|
157
188
|
]);
|
|
158
189
|
const DEFAULT_CONCURRENCY = 6;
|
|
159
190
|
const maxSuggestions = 5; // used by nearestMatches
|
|
@@ -163,7 +194,7 @@ export class ValidateCommand {
|
|
|
163
194
|
for (const id of changeIds) {
|
|
164
195
|
queue.push(async () => {
|
|
165
196
|
const start = Date.now();
|
|
166
|
-
const changeDir = path.join(
|
|
197
|
+
const changeDir = path.join(root.changesDir, id);
|
|
167
198
|
const report = await validator.validateChangeDeltaSpecs(changeDir);
|
|
168
199
|
const durationMs = Date.now() - start;
|
|
169
200
|
return { id, type: 'change', valid: report.valid, issues: report.issues, durationMs };
|
|
@@ -172,7 +203,7 @@ export class ValidateCommand {
|
|
|
172
203
|
for (const id of specIds) {
|
|
173
204
|
queue.push(async () => {
|
|
174
205
|
const start = Date.now();
|
|
175
|
-
const file = path.join(
|
|
206
|
+
const file = path.join(root.specsDir, id, 'spec.md');
|
|
176
207
|
const report = await validator.validateSpec(file);
|
|
177
208
|
const durationMs = Date.now() - start;
|
|
178
209
|
return { id, type: 'spec', valid: report.valid, issues: report.issues, durationMs };
|
|
@@ -188,7 +219,7 @@ export class ValidateCommand {
|
|
|
188
219
|
},
|
|
189
220
|
};
|
|
190
221
|
if (opts.json) {
|
|
191
|
-
const out = { items: [], summary, version: '1.0' };
|
|
222
|
+
const out = { items: [], summary, version: '1.0', root: toRootOutput(root) };
|
|
192
223
|
console.log(JSON.stringify(out, null, 2));
|
|
193
224
|
}
|
|
194
225
|
else {
|
|
@@ -245,7 +276,7 @@ export class ValidateCommand {
|
|
|
245
276
|
},
|
|
246
277
|
};
|
|
247
278
|
if (opts.json) {
|
|
248
|
-
const out = { items: results, summary, version: '1.0' };
|
|
279
|
+
const out = { items: results, summary, version: '1.0', root: toRootOutput(root) };
|
|
249
280
|
console.log(JSON.stringify(out, null, 2));
|
|
250
281
|
}
|
|
251
282
|
else {
|
|
@@ -256,6 +287,11 @@ export class ValidateCommand {
|
|
|
256
287
|
console.error(`✗ ${res.type}/${res.id}`);
|
|
257
288
|
}
|
|
258
289
|
console.log(`Totals: ${summary.totals.passed} passed, ${summary.totals.failed} failed (${summary.totals.items} items)`);
|
|
290
|
+
const firstFailure = results.find((res) => !res.valid);
|
|
291
|
+
if (firstFailure) {
|
|
292
|
+
const storeFlag = isStoreSelectedRoot(root) ? ` --store ${root.storeId}` : '';
|
|
293
|
+
console.log(`Details: openspec validate ${firstFailure.id} --type ${firstFailure.type}${storeFlag}`);
|
|
294
|
+
}
|
|
259
295
|
}
|
|
260
296
|
process.exitCode = failed > 0 ? 1 : 0;
|
|
261
297
|
}
|
|
@@ -13,7 +13,5 @@ export { schemasCommand } from './schemas.js';
|
|
|
13
13
|
export type { SchemasOptions } from './schemas.js';
|
|
14
14
|
export { newChangeCommand } from './new-change.js';
|
|
15
15
|
export type { NewChangeOptions } from './new-change.js';
|
|
16
|
-
export { setChangeCommand } from './set-change.js';
|
|
17
|
-
export type { SetChangeOptions } from './set-change.js';
|
|
18
16
|
export { DEFAULT_SCHEMA } from './shared.js';
|
|
19
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -8,6 +8,5 @@ export { instructionsCommand, applyInstructionsCommand } from './instructions.js
|
|
|
8
8
|
export { templatesCommand } from './templates.js';
|
|
9
9
|
export { schemasCommand } from './schemas.js';
|
|
10
10
|
export { newChangeCommand } from './new-change.js';
|
|
11
|
-
export { setChangeCommand } from './set-change.js';
|
|
12
11
|
export { DEFAULT_SCHEMA } from './shared.js';
|
|
13
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -5,25 +5,35 @@
|
|
|
5
5
|
* Includes both artifact instructions and apply instructions.
|
|
6
6
|
*/
|
|
7
7
|
import { type ArtifactInstructions } from '../../core/artifact-graph/index.js';
|
|
8
|
+
import { type PlanningHome } from '../../core/planning-home.js';
|
|
9
|
+
import { type ReferenceIndexEntry } from '../../core/references.js';
|
|
8
10
|
import { type ApplyInstructions } from './shared.js';
|
|
9
11
|
export interface InstructionsOptions {
|
|
10
12
|
change?: string;
|
|
11
13
|
schema?: string;
|
|
14
|
+
store?: string;
|
|
15
|
+
storePath?: string;
|
|
12
16
|
json?: boolean;
|
|
13
17
|
}
|
|
14
18
|
export interface ApplyInstructionsOptions {
|
|
15
19
|
change?: string;
|
|
16
20
|
schema?: string;
|
|
21
|
+
store?: string;
|
|
22
|
+
storePath?: string;
|
|
17
23
|
json?: boolean;
|
|
18
24
|
}
|
|
19
25
|
export declare function instructionsCommand(artifactId: string | undefined, options: InstructionsOptions): Promise<void>;
|
|
20
26
|
export declare function printInstructionsText(instructions: ArtifactInstructions, isBlocked: boolean): void;
|
|
27
|
+
export interface GenerateApplyInstructionsOptions {
|
|
28
|
+
planningHome?: PlanningHome;
|
|
29
|
+
references?: ReferenceIndexEntry[];
|
|
30
|
+
}
|
|
21
31
|
/**
|
|
22
32
|
* Generates apply instructions for implementing tasks from a change.
|
|
23
33
|
* Schema-aware: reads apply phase configuration from schema to determine
|
|
24
34
|
* required artifacts, tracking file, and instruction.
|
|
25
35
|
*/
|
|
26
|
-
export declare function generateApplyInstructions(projectRoot: string, changeName: string, schemaName?: string,
|
|
36
|
+
export declare function generateApplyInstructions(projectRoot: string, changeName: string, schemaName?: string, options?: GenerateApplyInstructionsOptions): Promise<ApplyInstructions>;
|
|
27
37
|
export declare function applyInstructionsCommand(options: ApplyInstructionsOptions): Promise<void>;
|
|
28
38
|
export declare function printApplyInstructionsText(instructions: ApplyInstructions): void;
|
|
29
39
|
//# sourceMappingURL=instructions.d.ts.map
|
|
@@ -8,17 +8,49 @@ import ora from 'ora';
|
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import { loadChangeContext, generateInstructions, resolveSchema, resolveArtifactOutputs, } from '../../core/artifact-graph/index.js';
|
|
11
|
-
import { getChangeDir, resolveCurrentPlanningHomeSync } from '../../core/planning-home.js';
|
|
11
|
+
import { getChangeDir, resolveCurrentPlanningHomeSync, } from '../../core/planning-home.js';
|
|
12
|
+
import { resolveRootForCommand, withStoreFlag, toPlanningHome, toRootOutput, } from '../../core/root-selection.js';
|
|
13
|
+
import { assembleReferenceIndex, renderReferencedStoresBlock, renderReferencedStoresSection, } from '../../core/references.js';
|
|
14
|
+
import { readRegistrySnapshot } from '../../core/store/registry.js';
|
|
15
|
+
import { readProjectConfig } from '../../core/project-config.js';
|
|
12
16
|
import { validateChangeExists, validateSchemaExists, } from './shared.js';
|
|
13
17
|
// -----------------------------------------------------------------------------
|
|
14
18
|
// Artifact Instructions Command
|
|
15
19
|
// -----------------------------------------------------------------------------
|
|
20
|
+
/**
|
|
21
|
+
* Reads the resolved root's config once, assembles the referenced-store
|
|
22
|
+
* index when references are declared, and resolves the config path for
|
|
23
|
+
* fix text. Shared by both instruction surfaces.
|
|
24
|
+
*/
|
|
25
|
+
async function loadRootConfigContext(root) {
|
|
26
|
+
// readProjectConfig never throws: missing/unparseable configs are null.
|
|
27
|
+
const projectConfig = readProjectConfig(root.path);
|
|
28
|
+
// One registry read serves every relationship consumer in this
|
|
29
|
+
// output so it never carries a torn snapshot.
|
|
30
|
+
const snapshot = await readRegistrySnapshot();
|
|
31
|
+
const registryEntries = snapshot.entries;
|
|
32
|
+
const declared = projectConfig?.references ?? [];
|
|
33
|
+
const index = declared.length > 0
|
|
34
|
+
? await assembleReferenceIndex({ references: declared, resolvedRoot: root, registryEntries })
|
|
35
|
+
: [];
|
|
36
|
+
// Omitted, not empty: an index emptied by self-reference omission must
|
|
37
|
+
// look identical to an undeclared one in JSON.
|
|
38
|
+
return {
|
|
39
|
+
projectConfig,
|
|
40
|
+
references: index.length > 0 ? index : undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
16
43
|
export async function instructionsCommand(artifactId, options) {
|
|
44
|
+
// Resolve (and banner) before the spinner starts so stderr stays readable.
|
|
45
|
+
const root = await resolveRootForCommand(options, { json: options.json });
|
|
46
|
+
if (!root) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
17
49
|
const spinner = options.json ? undefined : ora('Generating instructions...').start();
|
|
18
50
|
try {
|
|
19
|
-
const planningHome =
|
|
20
|
-
const projectRoot =
|
|
21
|
-
const changeName = await validateChangeExists(options.change, projectRoot,
|
|
51
|
+
const planningHome = toPlanningHome(root);
|
|
52
|
+
const projectRoot = root.path;
|
|
53
|
+
const changeName = await validateChangeExists(options.change, projectRoot, root.changesDir, { newChangeHint: withStoreFlag(root, 'openspec new change <name>') });
|
|
22
54
|
// Validate schema if explicitly provided
|
|
23
55
|
if (options.schema) {
|
|
24
56
|
validateSchemaExists(options.schema, projectRoot);
|
|
@@ -39,11 +71,15 @@ export async function instructionsCommand(artifactId, options) {
|
|
|
39
71
|
const validIds = context.graph.getAllArtifacts().map((a) => a.id);
|
|
40
72
|
throw new Error(`Artifact '${artifactId}' not found in schema '${context.schemaName}'. Valid artifacts:\n ${validIds.join('\n ')}`);
|
|
41
73
|
}
|
|
42
|
-
const
|
|
74
|
+
const { projectConfig, references } = await loadRootConfigContext(root);
|
|
75
|
+
const instructions = generateInstructions(context, artifactId, projectRoot, {
|
|
76
|
+
projectConfig,
|
|
77
|
+
references,
|
|
78
|
+
});
|
|
43
79
|
const isBlocked = instructions.dependencies.some((d) => !d.done);
|
|
44
80
|
spinner?.stop();
|
|
45
81
|
if (options.json) {
|
|
46
|
-
console.log(JSON.stringify(instructions, null, 2));
|
|
82
|
+
console.log(JSON.stringify({ ...instructions, root: toRootOutput(root) }, null, 2));
|
|
47
83
|
return;
|
|
48
84
|
}
|
|
49
85
|
printInstructionsText(instructions, isBlocked);
|
|
@@ -54,14 +90,10 @@ export async function instructionsCommand(artifactId, options) {
|
|
|
54
90
|
}
|
|
55
91
|
}
|
|
56
92
|
export function printInstructionsText(instructions, isBlocked) {
|
|
57
|
-
const { artifactId, changeName, schemaName, changeDir,
|
|
93
|
+
const { artifactId, changeName, schemaName, changeDir, resolvedOutputPath, description, instruction, context, rules, template, dependencies, unlocks, } = instructions;
|
|
58
94
|
// Opening tag
|
|
59
95
|
console.log(`<artifact id="${artifactId}" change="${changeName}" schema="${schemaName}">`);
|
|
60
96
|
console.log();
|
|
61
|
-
if (initiative) {
|
|
62
|
-
console.log(`<initiative store="${initiative.store}" id="${initiative.id}" />`);
|
|
63
|
-
console.log();
|
|
64
|
-
}
|
|
65
97
|
// Warning for blocked artifacts
|
|
66
98
|
if (isBlocked) {
|
|
67
99
|
const missing = dependencies.filter((d) => !d.done).map((d) => d.id);
|
|
@@ -85,6 +117,11 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
85
117
|
console.log('</project_context>');
|
|
86
118
|
console.log();
|
|
87
119
|
}
|
|
120
|
+
// Referenced-store index (read-only upstream context)
|
|
121
|
+
if (instructions.references && instructions.references.length > 0) {
|
|
122
|
+
console.log(renderReferencedStoresBlock(instructions.references));
|
|
123
|
+
console.log();
|
|
124
|
+
}
|
|
88
125
|
// Rules (AI constraint - do not include in output)
|
|
89
126
|
if (rules && rules.length > 0) {
|
|
90
127
|
console.log('<rules>');
|
|
@@ -175,7 +212,9 @@ function parseTasksFile(content) {
|
|
|
175
212
|
* Schema-aware: reads apply phase configuration from schema to determine
|
|
176
213
|
* required artifacts, tracking file, and instruction.
|
|
177
214
|
*/
|
|
178
|
-
export async function generateApplyInstructions(projectRoot, changeName, schemaName,
|
|
215
|
+
export async function generateApplyInstructions(projectRoot, changeName, schemaName, options = {}) {
|
|
216
|
+
const planningHome = options.planningHome ?? resolveCurrentPlanningHomeSync({ startPath: projectRoot });
|
|
217
|
+
const references = options.references;
|
|
179
218
|
// loadChangeContext will auto-detect schema from metadata if not provided
|
|
180
219
|
const context = loadChangeContext(projectRoot, changeName, schemaName, {
|
|
181
220
|
changeDir: getChangeDir(planningHome, changeName),
|
|
@@ -257,30 +296,39 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
257
296
|
changeName,
|
|
258
297
|
changeDir,
|
|
259
298
|
schemaName: context.schemaName,
|
|
260
|
-
...(context.initiative ? { initiative: context.initiative } : {}),
|
|
261
299
|
contextFiles,
|
|
262
300
|
progress: { total, complete, remaining },
|
|
263
301
|
tasks,
|
|
264
302
|
state,
|
|
265
303
|
missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
|
|
266
304
|
instruction,
|
|
305
|
+
...(references !== undefined ? { references } : {}),
|
|
267
306
|
};
|
|
268
307
|
}
|
|
269
308
|
export async function applyInstructionsCommand(options) {
|
|
309
|
+
// Resolve (and banner) before the spinner starts so stderr stays readable.
|
|
310
|
+
const root = await resolveRootForCommand(options, { json: options.json });
|
|
311
|
+
if (!root) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
270
314
|
const spinner = options.json ? undefined : ora('Generating apply instructions...').start();
|
|
271
315
|
try {
|
|
272
|
-
const planningHome =
|
|
273
|
-
const projectRoot =
|
|
274
|
-
const changeName = await validateChangeExists(options.change, projectRoot,
|
|
316
|
+
const planningHome = toPlanningHome(root);
|
|
317
|
+
const projectRoot = root.path;
|
|
318
|
+
const changeName = await validateChangeExists(options.change, projectRoot, root.changesDir, { newChangeHint: withStoreFlag(root, 'openspec new change <name>') });
|
|
275
319
|
// Validate schema if explicitly provided
|
|
276
320
|
if (options.schema) {
|
|
277
321
|
validateSchemaExists(options.schema, projectRoot);
|
|
278
322
|
}
|
|
279
323
|
// generateApplyInstructions uses loadChangeContext which auto-detects schema
|
|
280
|
-
const
|
|
324
|
+
const { references } = await loadRootConfigContext(root);
|
|
325
|
+
const instructions = await generateApplyInstructions(projectRoot, changeName, options.schema, {
|
|
326
|
+
planningHome,
|
|
327
|
+
references,
|
|
328
|
+
});
|
|
281
329
|
spinner?.stop();
|
|
282
330
|
if (options.json) {
|
|
283
|
-
console.log(JSON.stringify(instructions, null, 2));
|
|
331
|
+
console.log(JSON.stringify({ ...instructions, root: toRootOutput(root) }, null, 2));
|
|
284
332
|
return;
|
|
285
333
|
}
|
|
286
334
|
printApplyInstructionsText(instructions);
|
|
@@ -291,13 +339,14 @@ export async function applyInstructionsCommand(options) {
|
|
|
291
339
|
}
|
|
292
340
|
}
|
|
293
341
|
export function printApplyInstructionsText(instructions) {
|
|
294
|
-
const { changeName, schemaName,
|
|
342
|
+
const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction } = instructions;
|
|
295
343
|
console.log(`## Apply: ${changeName}`);
|
|
296
344
|
console.log(`Schema: ${schemaName}`);
|
|
297
|
-
if (initiative) {
|
|
298
|
-
console.log(`Initiative: ${initiative.store}/${initiative.id}`);
|
|
299
|
-
}
|
|
300
345
|
console.log();
|
|
346
|
+
if (instructions.references && instructions.references.length > 0) {
|
|
347
|
+
console.log(renderReferencedStoresSection(instructions.references));
|
|
348
|
+
console.log();
|
|
349
|
+
}
|
|
301
350
|
// Warning for blocked state
|
|
302
351
|
if (state === 'blocked' && missingArtifacts) {
|
|
303
352
|
console.log('### ⚠️ Blocked');
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* New Change Command
|
|
3
3
|
*
|
|
4
|
-
* Creates a new change directory with optional description and schema
|
|
4
|
+
* Creates a new change directory with optional description and schema in the
|
|
5
|
+
* resolved OpenSpec root. `--store <id>` selects a registered store's
|
|
6
|
+
* root; initiative linking and workspace affected areas are no longer part of
|
|
7
|
+
* this command.
|
|
5
8
|
*/
|
|
6
9
|
export interface NewChangeOptions {
|
|
7
10
|
description?: string;
|
|
8
11
|
goal?: string;
|
|
9
|
-
areas?: string;
|
|
10
12
|
schema?: string;
|
|
11
|
-
initiative?: string;
|
|
12
13
|
store?: string;
|
|
13
14
|
storePath?: string;
|
|
15
|
+
initiative?: string;
|
|
16
|
+
areas?: string;
|
|
14
17
|
json?: boolean;
|
|
15
18
|
}
|
|
16
19
|
export declare function newChangeCommand(name: string | undefined, options: NewChangeOptions): Promise<void>;
|