@fission-ai/openspec 1.4.1 → 1.6.0-beta.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/README.md +37 -4
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/index.js +95 -95
- package/dist/commands/change.d.ts +5 -2
- package/dist/commands/change.js +23 -55
- 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 +10 -0
- package/dist/commands/validate.js +88 -40
- 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 +220 -68
- 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.d.ts +1 -1
- package/dist/core/command-generation/adapters/claude.js +4 -15
- package/dist/core/command-generation/adapters/cursor.js +1 -14
- package/dist/core/command-generation/adapters/index.d.ts +2 -0
- package/dist/core/command-generation/adapters/index.js +2 -0
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +21 -0
- package/dist/core/command-generation/adapters/oh-my-pi.js +44 -0
- package/dist/core/command-generation/adapters/pi.js +1 -14
- package/dist/core/command-generation/adapters/trae.d.ts +13 -0
- package/dist/core/command-generation/adapters/trae.js +48 -0
- package/dist/core/command-generation/adapters/windsurf.js +1 -14
- package/dist/core/command-generation/registry.js +4 -0
- 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/config.js +1 -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 +27 -6
- package/dist/core/list.d.ts +2 -0
- package/dist/core/list.js +37 -14
- 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 +195 -0
- package/dist/core/parsers/change-parser.js +2 -1
- package/dist/core/parsers/markdown-parser.d.ts +0 -3
- package/dist/core/parsers/markdown-parser.js +5 -59
- package/dist/core/parsers/requirement-blocks.d.ts +11 -0
- package/dist/core/parsers/requirement-blocks.js +42 -8
- package/dist/core/parsers/requirement-text.d.ts +55 -0
- package/dist/core/parsers/requirement-text.js +136 -0
- package/dist/core/planning-home.d.ts +1 -6
- package/dist/core/planning-home.js +2 -43
- package/dist/core/profile-sync-drift.js +1 -0
- package/dist/core/profiles.d.ts +2 -2
- package/dist/core/profiles.js +2 -1
- 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/schemas/base.schema.js +7 -2
- package/dist/core/shared/allowed-tools.d.ts +12 -0
- package/dist/core/shared/allowed-tools.js +12 -0
- package/dist/core/shared/skill-generation.js +5 -1
- package/dist/core/shared/tool-detection.d.ts +2 -2
- package/dist/core/shared/tool-detection.js +2 -0
- package/dist/core/specs-apply.d.ts +6 -1
- package/dist/core/specs-apply.js +41 -5
- 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 +803 -0
- package/dist/core/store/registry.d.ts +58 -0
- package/dist/core/store/registry.js +275 -0
- package/dist/core/templates/skill-templates.d.ts +1 -0
- package/dist/core/templates/skill-templates.js +1 -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/update-change.d.ts +10 -0
- package/dist/core/templates/workflows/update-change.js +167 -0
- package/dist/core/templates/workflows/verify-change.js +5 -4
- package/dist/core/update.js +4 -4
- package/dist/core/validation/validator.d.ts +7 -0
- package/dist/core/validation/validator.js +101 -38
- package/dist/core/view.js +1 -1
- 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/dist/utils/task-progress.d.ts +10 -1
- package/dist/utils/task-progress.js +66 -2
- package/package.json +26 -23
- 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 -8
- package/dist/commands/workspace/selection.js +0 -104
- 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 -5
- package/dist/commands/workspace.js +0 -574
- 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 -121
- 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
package/dist/core/archive.js
CHANGED
|
@@ -3,7 +3,59 @@ import path from 'path';
|
|
|
3
3
|
import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
|
|
4
4
|
import { Validator } from './validation/validator.js';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
+
import { emitStoreRootBanner, isRootSelectionError, resolveOpenSpecRoot, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from './root-selection.js';
|
|
6
7
|
import { findSpecUpdates, buildUpdatedSpec, writeUpdatedSpec, } from './specs-apply.js';
|
|
8
|
+
function isMissingPathError(error) {
|
|
9
|
+
return (typeof error === 'object' &&
|
|
10
|
+
error !== null &&
|
|
11
|
+
'code' in error &&
|
|
12
|
+
error.code === 'ENOENT');
|
|
13
|
+
}
|
|
14
|
+
async function listActiveChangeNames(changesDir) {
|
|
15
|
+
try {
|
|
16
|
+
const entries = await fs.readdir(changesDir, { withFileTypes: true });
|
|
17
|
+
return entries
|
|
18
|
+
.filter((entry) => entry.isDirectory() && entry.name !== 'archive')
|
|
19
|
+
.map((entry) => entry.name)
|
|
20
|
+
.sort();
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (!isMissingPathError(error))
|
|
24
|
+
throw error;
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* JSON mode is non-interactive: any point where the human flow would prompt or
|
|
30
|
+
* print prose instead throws this error, which becomes a machine-readable
|
|
31
|
+
* status entry with a non-zero exit code.
|
|
32
|
+
*/
|
|
33
|
+
class ArchiveBlockedError extends Error {
|
|
34
|
+
diagnostic;
|
|
35
|
+
constructor(code, message, fix) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = 'ArchiveBlockedError';
|
|
38
|
+
this.diagnostic = {
|
|
39
|
+
severity: 'error',
|
|
40
|
+
code,
|
|
41
|
+
message,
|
|
42
|
+
...(fix ? { fix } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function toArchiveDiagnostic(error) {
|
|
47
|
+
if (error instanceof ArchiveBlockedError) {
|
|
48
|
+
return error.diagnostic;
|
|
49
|
+
}
|
|
50
|
+
if (isRootSelectionError(error)) {
|
|
51
|
+
return error.diagnostic;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
severity: 'error',
|
|
55
|
+
code: 'archive_error',
|
|
56
|
+
message: error instanceof Error ? error.message : String(error),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
7
59
|
/**
|
|
8
60
|
* Recursively copy a directory. Used when fs.rename fails (e.g. EPERM on Windows).
|
|
9
61
|
*/
|
|
@@ -44,23 +96,63 @@ async function moveDirectory(src, dest) {
|
|
|
44
96
|
}
|
|
45
97
|
export class ArchiveCommand {
|
|
46
98
|
async execute(changeName, options = {}) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const archiveDir = path.join(changesDir, 'archive');
|
|
50
|
-
const mainSpecsDir = path.join(targetPath, 'openspec', 'specs');
|
|
51
|
-
// Check if changes directory exists
|
|
99
|
+
const json = !!options.json;
|
|
100
|
+
let root;
|
|
52
101
|
try {
|
|
53
|
-
await
|
|
102
|
+
root = await resolveOpenSpecRoot({
|
|
103
|
+
...(options.store !== undefined ? { store: options.store } : {}),
|
|
104
|
+
...(options.storePath !== undefined ? { storePath: options.storePath } : {}),
|
|
105
|
+
});
|
|
54
106
|
}
|
|
55
|
-
catch {
|
|
56
|
-
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (json && isRootSelectionError(error)) {
|
|
109
|
+
this.printJsonFailure(undefined, toArchiveDiagnostic(error));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
throw error;
|
|
57
113
|
}
|
|
114
|
+
if (json) {
|
|
115
|
+
try {
|
|
116
|
+
const result = await this.run(changeName, options, root, true);
|
|
117
|
+
if (!result) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(JSON.stringify({ archive: result, root: toRootOutput(root) }, null, 2));
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
this.printJsonFailure(root, toArchiveDiagnostic(error));
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
emitStoreRootBanner(root);
|
|
128
|
+
await this.run(changeName, options, root, false);
|
|
129
|
+
}
|
|
130
|
+
printJsonFailure(root, diagnostic) {
|
|
131
|
+
console.log(JSON.stringify({
|
|
132
|
+
archive: null,
|
|
133
|
+
...(root ? { root: toRootOutput(root) } : {}),
|
|
134
|
+
status: [diagnostic],
|
|
135
|
+
}, null, 2));
|
|
136
|
+
process.exitCode = 1;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Shared archive flow. In human mode (json=false) prompts and prose match
|
|
140
|
+
* the historical behavior and cancellations return null. In JSON mode no
|
|
141
|
+
* prose reaches stdout and every blocked path throws.
|
|
142
|
+
*/
|
|
143
|
+
async run(changeName, options, root, json) {
|
|
144
|
+
const changesDir = root.changesDir;
|
|
145
|
+
const archiveDir = root.archiveDir;
|
|
146
|
+
const mainSpecsDir = root.specsDir;
|
|
58
147
|
// Get change name interactively if not provided
|
|
59
148
|
if (!changeName) {
|
|
149
|
+
if (json) {
|
|
150
|
+
throw new ArchiveBlockedError('archive_change_name_required', 'A change name is required: archive --json is non-interactive.', withStoreFlag(root, 'openspec archive <change-name> --json'));
|
|
151
|
+
}
|
|
60
152
|
const selectedChange = await this.selectChange(changesDir);
|
|
61
153
|
if (!selectedChange) {
|
|
62
154
|
console.log('No change selected. Aborting.');
|
|
63
|
-
return;
|
|
155
|
+
return null;
|
|
64
156
|
}
|
|
65
157
|
changeName = selectedChange;
|
|
66
158
|
}
|
|
@@ -73,29 +165,34 @@ export class ArchiveCommand {
|
|
|
73
165
|
}
|
|
74
166
|
}
|
|
75
167
|
catch {
|
|
76
|
-
|
|
168
|
+
const available = await listActiveChangeNames(changesDir);
|
|
169
|
+
throw new ArchiveBlockedError('archive_change_not_found', available.length > 0
|
|
170
|
+
? `Change '${changeName}' not found. Available changes: ${available.join(', ')}`
|
|
171
|
+
: `Change '${changeName}' not found. No active changes exist in this root.`);
|
|
77
172
|
}
|
|
78
173
|
const skipValidation = options.validate === false || options.noValidate === true;
|
|
79
174
|
// Validate specs and change before archiving
|
|
80
175
|
if (!skipValidation) {
|
|
81
176
|
const validator = new Validator();
|
|
82
177
|
let hasValidationErrors = false;
|
|
83
|
-
// Validate proposal.md (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
178
|
+
// Validate proposal.md (informative only; human mode prints warnings)
|
|
179
|
+
if (!json) {
|
|
180
|
+
const changeFile = path.join(changeDir, 'proposal.md');
|
|
181
|
+
try {
|
|
182
|
+
await fs.access(changeFile);
|
|
183
|
+
const changeReport = await validator.validateChange(changeFile);
|
|
184
|
+
// Proposal validation is informative only (do not block archive)
|
|
185
|
+
if (!changeReport.valid) {
|
|
186
|
+
console.log(chalk.yellow(`\nProposal warnings in proposal.md (non-blocking):`));
|
|
187
|
+
for (const issue of changeReport.issues) {
|
|
188
|
+
const symbol = issue.level === 'ERROR' ? '⚠' : (issue.level === 'WARNING' ? '⚠' : 'ℹ');
|
|
189
|
+
console.log(chalk.yellow(` ${symbol} ${issue.message}`));
|
|
190
|
+
}
|
|
94
191
|
}
|
|
95
192
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
193
|
+
catch {
|
|
194
|
+
// Change file doesn't exist, skip validation
|
|
195
|
+
}
|
|
99
196
|
}
|
|
100
197
|
// Validate delta-formatted spec files under the change directory if present
|
|
101
198
|
const changeSpecsDir = path.join(changeDir, 'specs');
|
|
@@ -122,21 +219,32 @@ export class ArchiveCommand {
|
|
|
122
219
|
const deltaReport = await validator.validateChangeDeltaSpecs(changeDir);
|
|
123
220
|
if (!deltaReport.valid) {
|
|
124
221
|
hasValidationErrors = true;
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
222
|
+
if (!json) {
|
|
223
|
+
console.log(chalk.red(`\nValidation errors in change delta specs:`));
|
|
224
|
+
for (const issue of deltaReport.issues) {
|
|
225
|
+
if (issue.level === 'ERROR') {
|
|
226
|
+
console.log(chalk.red(` ✗ ${issue.message}`));
|
|
227
|
+
}
|
|
228
|
+
else if (issue.level === 'WARNING') {
|
|
229
|
+
console.log(chalk.yellow(` ⚠ ${issue.message}`));
|
|
230
|
+
}
|
|
132
231
|
}
|
|
133
232
|
}
|
|
134
233
|
}
|
|
135
234
|
}
|
|
136
235
|
if (hasValidationErrors) {
|
|
236
|
+
if (json) {
|
|
237
|
+
throw new ArchiveBlockedError('archive_validation_failed', `Validation failed for change '${changeName}'.`, `Run ${withStoreFlag(root, `openspec validate ${changeName}`)} for details, fix the errors, or rerun with --no-validate.`);
|
|
238
|
+
}
|
|
137
239
|
console.log(chalk.red('\nValidation failed. Please fix the errors before archiving.'));
|
|
138
240
|
console.log(chalk.yellow('To skip validation (not recommended), use --no-validate flag.'));
|
|
139
|
-
|
|
241
|
+
process.exitCode = 1;
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (json) {
|
|
246
|
+
if (!options.yes) {
|
|
247
|
+
throw new ArchiveBlockedError('archive_confirmation_required', 'Skipping validation requires confirmation: rerun with --yes.', withStoreFlag(root, 'openspec archive <change-name> --json --no-validate --yes'));
|
|
140
248
|
}
|
|
141
249
|
}
|
|
142
250
|
else {
|
|
@@ -150,7 +258,7 @@ export class ArchiveCommand {
|
|
|
150
258
|
});
|
|
151
259
|
if (!proceed) {
|
|
152
260
|
console.log('Archive cancelled.');
|
|
153
|
-
return;
|
|
261
|
+
return null;
|
|
154
262
|
}
|
|
155
263
|
}
|
|
156
264
|
else {
|
|
@@ -160,12 +268,19 @@ export class ArchiveCommand {
|
|
|
160
268
|
console.log(chalk.yellow(`Affected files: ${changeDir}`));
|
|
161
269
|
}
|
|
162
270
|
// Show progress and check for incomplete tasks
|
|
163
|
-
const progress = await getTaskProgressForChange(changesDir, changeName);
|
|
164
|
-
|
|
165
|
-
|
|
271
|
+
const progress = await getTaskProgressForChange(changesDir, changeName, path.resolve(changesDir, '..', '..'));
|
|
272
|
+
if (!json) {
|
|
273
|
+
const status = formatTaskStatus(progress);
|
|
274
|
+
console.log(`Task status: ${status}`);
|
|
275
|
+
}
|
|
166
276
|
const incompleteTasks = Math.max(progress.total - progress.completed, 0);
|
|
167
277
|
if (incompleteTasks > 0) {
|
|
168
|
-
if (
|
|
278
|
+
if (json) {
|
|
279
|
+
if (!options.yes) {
|
|
280
|
+
throw new ArchiveBlockedError('archive_tasks_incomplete', `${incompleteTasks} incomplete task(s) found for change '${changeName}'.`, 'Complete the tasks or rerun with --yes.');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else if (!options.yes) {
|
|
169
284
|
const { confirm } = await import('@inquirer/prompts');
|
|
170
285
|
const proceed = await confirm({
|
|
171
286
|
message: `Warning: ${incompleteTasks} incomplete task(s) found. Continue?`,
|
|
@@ -173,7 +288,7 @@ export class ArchiveCommand {
|
|
|
173
288
|
});
|
|
174
289
|
if (!proceed) {
|
|
175
290
|
console.log('Archive cancelled.');
|
|
176
|
-
return;
|
|
291
|
+
return null;
|
|
177
292
|
}
|
|
178
293
|
}
|
|
179
294
|
else {
|
|
@@ -181,21 +296,30 @@ export class ArchiveCommand {
|
|
|
181
296
|
}
|
|
182
297
|
}
|
|
183
298
|
// Handle spec updates unless skipSpecs flag is set
|
|
299
|
+
let specsUpdated = false;
|
|
300
|
+
let totals;
|
|
184
301
|
if (options.skipSpecs) {
|
|
185
|
-
|
|
302
|
+
if (!json) {
|
|
303
|
+
console.log('Skipping spec updates (--skip-specs flag provided).');
|
|
304
|
+
}
|
|
186
305
|
}
|
|
187
306
|
else {
|
|
188
307
|
// Find specs to update
|
|
189
308
|
const specUpdates = await findSpecUpdates(changeDir, mainSpecsDir);
|
|
190
309
|
if (specUpdates.length > 0) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
310
|
+
if (!json) {
|
|
311
|
+
console.log('\nSpecs to update:');
|
|
312
|
+
for (const update of specUpdates) {
|
|
313
|
+
const status = update.exists ? 'update' : 'create';
|
|
314
|
+
const capability = path.basename(path.dirname(update.target));
|
|
315
|
+
console.log(` ${capability}: ${status}`);
|
|
316
|
+
}
|
|
196
317
|
}
|
|
197
318
|
let shouldUpdateSpecs = true;
|
|
198
319
|
if (!options.yes) {
|
|
320
|
+
if (json) {
|
|
321
|
+
throw new ArchiveBlockedError('archive_confirmation_required', `Updating ${specUpdates.length} spec(s) requires confirmation: rerun with --yes.`, withStoreFlag(root, 'openspec archive <change-name> --json --yes'));
|
|
322
|
+
}
|
|
199
323
|
const { confirm } = await import('@inquirer/prompts');
|
|
200
324
|
shouldUpdateSpecs = await confirm({
|
|
201
325
|
message: 'Proceed with spec updates?',
|
|
@@ -210,22 +334,29 @@ export class ArchiveCommand {
|
|
|
210
334
|
const prepared = [];
|
|
211
335
|
try {
|
|
212
336
|
for (const update of specUpdates) {
|
|
213
|
-
const built = await buildUpdatedSpec(update, changeName);
|
|
337
|
+
const built = await buildUpdatedSpec(update, changeName, { silent: json });
|
|
214
338
|
prepared.push({ update, rebuilt: built.rebuilt, counts: built.counts });
|
|
215
339
|
}
|
|
216
340
|
}
|
|
217
341
|
catch (err) {
|
|
342
|
+
if (json) {
|
|
343
|
+
throw new ArchiveBlockedError('archive_spec_update_failed', String(err.message || err), 'Fix the change delta specs and rerun. No files were changed.');
|
|
344
|
+
}
|
|
218
345
|
console.log(String(err.message || err));
|
|
219
346
|
console.log('Aborted. No files were changed.');
|
|
220
|
-
|
|
347
|
+
process.exitCode = 1;
|
|
348
|
+
return null;
|
|
221
349
|
}
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const
|
|
226
|
-
|
|
350
|
+
// Validate every rebuilt spec before writing any of them, so a
|
|
351
|
+
// late validation failure really does leave all targets unchanged.
|
|
352
|
+
if (!skipValidation) {
|
|
353
|
+
for (const p of prepared) {
|
|
354
|
+
const specName = path.basename(path.dirname(p.update.target));
|
|
227
355
|
const report = await new Validator().validateSpecContent(specName, p.rebuilt);
|
|
228
356
|
if (!report.valid) {
|
|
357
|
+
if (json) {
|
|
358
|
+
throw new ArchiveBlockedError('archive_spec_validation_failed', `Rebuilt spec for '${specName}' failed validation. No files were changed.`, `Run ${withStoreFlag(root, `openspec validate ${specName}`)} after fixing the change deltas.`);
|
|
359
|
+
}
|
|
229
360
|
console.log(chalk.red(`\nValidation errors in rebuilt spec for ${specName} (will not write changes):`));
|
|
230
361
|
for (const issue of report.issues) {
|
|
231
362
|
if (issue.level === 'ERROR')
|
|
@@ -234,17 +365,30 @@ export class ArchiveCommand {
|
|
|
234
365
|
console.log(chalk.yellow(` ⚠ ${issue.message}`));
|
|
235
366
|
}
|
|
236
367
|
console.log('Aborted. No files were changed.');
|
|
237
|
-
|
|
368
|
+
process.exitCode = 1;
|
|
369
|
+
return null;
|
|
238
370
|
}
|
|
239
371
|
}
|
|
240
|
-
await writeUpdatedSpec(p.update, p.rebuilt, p.counts);
|
|
241
|
-
totals.added += p.counts.added;
|
|
242
|
-
totals.modified += p.counts.modified;
|
|
243
|
-
totals.removed += p.counts.removed;
|
|
244
|
-
totals.renamed += p.counts.renamed;
|
|
245
372
|
}
|
|
246
|
-
|
|
247
|
-
|
|
373
|
+
// All validations passed; write files and display counts
|
|
374
|
+
const writeTotals = { added: 0, modified: 0, removed: 0, renamed: 0 };
|
|
375
|
+
for (const p of prepared) {
|
|
376
|
+
await writeUpdatedSpec(p.update, p.rebuilt, p.counts, {
|
|
377
|
+
silent: json,
|
|
378
|
+
// Cross-root paths must be absolute when a store is selected.
|
|
379
|
+
...(isStoreSelectedRoot(root) ? { displayPath: p.update.target } : {}),
|
|
380
|
+
});
|
|
381
|
+
writeTotals.added += p.counts.added;
|
|
382
|
+
writeTotals.modified += p.counts.modified;
|
|
383
|
+
writeTotals.removed += p.counts.removed;
|
|
384
|
+
writeTotals.renamed += p.counts.renamed;
|
|
385
|
+
}
|
|
386
|
+
specsUpdated = true;
|
|
387
|
+
totals = writeTotals;
|
|
388
|
+
if (!json) {
|
|
389
|
+
console.log(`Totals: + ${writeTotals.added}, ~ ${writeTotals.modified}, - ${writeTotals.removed}, → ${writeTotals.renamed}`);
|
|
390
|
+
console.log('Specs updated successfully.');
|
|
391
|
+
}
|
|
248
392
|
}
|
|
249
393
|
}
|
|
250
394
|
}
|
|
@@ -252,29 +396,37 @@ export class ArchiveCommand {
|
|
|
252
396
|
const archiveName = `${this.getArchiveDate()}-${changeName}`;
|
|
253
397
|
const archivePath = path.join(archiveDir, archiveName);
|
|
254
398
|
// Check if archive already exists
|
|
399
|
+
let archiveExists = false;
|
|
255
400
|
try {
|
|
256
401
|
await fs.access(archivePath);
|
|
257
|
-
|
|
402
|
+
archiveExists = true;
|
|
258
403
|
}
|
|
259
404
|
catch (error) {
|
|
260
405
|
if (error.code !== 'ENOENT') {
|
|
261
406
|
throw error;
|
|
262
407
|
}
|
|
263
408
|
}
|
|
409
|
+
if (archiveExists) {
|
|
410
|
+
throw new ArchiveBlockedError('archive_target_exists', `Archive '${archiveName}' already exists.`);
|
|
411
|
+
}
|
|
264
412
|
// Create archive directory if needed
|
|
265
413
|
await fs.mkdir(archiveDir, { recursive: true });
|
|
266
414
|
// Move change to archive (uses copy+remove on EPERM/EXDEV, e.g. Windows)
|
|
267
415
|
await moveDirectory(changeDir, archivePath);
|
|
268
|
-
|
|
416
|
+
if (!json) {
|
|
417
|
+
console.log(`Change '${changeName}' archived as '${archiveName}'.`);
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
change: changeName,
|
|
421
|
+
archivedAs: archiveName,
|
|
422
|
+
path: archivePath,
|
|
423
|
+
specsUpdated,
|
|
424
|
+
...(totals ? { totals } : {}),
|
|
425
|
+
};
|
|
269
426
|
}
|
|
270
427
|
async selectChange(changesDir) {
|
|
271
428
|
const { select } = await import('@inquirer/prompts');
|
|
272
|
-
|
|
273
|
-
const entries = await fs.readdir(changesDir, { withFileTypes: true });
|
|
274
|
-
const changeDirs = entries
|
|
275
|
-
.filter(entry => entry.isDirectory() && entry.name !== 'archive')
|
|
276
|
-
.map(entry => entry.name)
|
|
277
|
-
.sort();
|
|
429
|
+
const changeDirs = await listActiveChangeNames(changesDir);
|
|
278
430
|
if (changeDirs.length === 0) {
|
|
279
431
|
console.log('No active changes found.');
|
|
280
432
|
return null;
|
|
@@ -284,7 +436,7 @@ export class ArchiveCommand {
|
|
|
284
436
|
try {
|
|
285
437
|
const progressList = [];
|
|
286
438
|
for (const id of changeDirs) {
|
|
287
|
-
const progress = await getTaskProgressForChange(changesDir, id);
|
|
439
|
+
const progress = await getTaskProgressForChange(changesDir, id, path.resolve(changesDir, '..', '..'));
|
|
288
440
|
const status = formatTaskStatus(progress);
|
|
289
441
|
progressList.push({ id, status });
|
|
290
442
|
}
|
|
@@ -5,5 +5,5 @@ export { detectCompleted } from './state.js';
|
|
|
5
5
|
export { artifactOutputExists, isGlobPattern, resolveArtifactOutputs } from './outputs.js';
|
|
6
6
|
export { resolveSchema, listSchemas, listSchemasWithInfo, getSchemaDir, getPackageSchemasDir, getUserSchemasDir, SchemaLoadError, type SchemaInfo, } from './resolver.js';
|
|
7
7
|
export { loadTemplate, loadChangeContext, generateInstructions, formatChangeStatus, TemplateLoadError, type ChangeContext, type LoadChangeContextOptions, type ArtifactInstructions, type DependencyInfo, type ArtifactStatus, type ChangeStatus, type ArtifactPathSummary, } from './instruction-loader.js';
|
|
8
|
-
export type { PlanningHomeSummary,
|
|
8
|
+
export type { PlanningHomeSummary, ActionContext, } from '../change-status-policy.js';
|
|
9
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ArtifactGraph } from './graph.js';
|
|
2
|
-
import { type ActionContext, type
|
|
2
|
+
import { type ActionContext, type PlanningHomeSummary } from '../change-status-policy.js';
|
|
3
|
+
import { type ProjectConfig } from '../project-config.js';
|
|
4
|
+
import type { ReferenceIndexEntry } from '../references.js';
|
|
3
5
|
import type { PlanningHome } from '../planning-home.js';
|
|
4
|
-
import type { ChangeMetadata
|
|
6
|
+
import type { ChangeMetadata } from '../change-metadata/index.js';
|
|
5
7
|
import type { CompletedSet } from './types.js';
|
|
6
8
|
/**
|
|
7
9
|
* Error thrown when loading a template fails.
|
|
@@ -30,8 +32,6 @@ export interface ChangeContext {
|
|
|
30
32
|
planningHome?: PlanningHome;
|
|
31
33
|
/** Parsed change metadata, when present */
|
|
32
34
|
metadata?: ChangeMetadata;
|
|
33
|
-
/** Stored initiative link, when this change is linked to shared context */
|
|
34
|
-
initiative?: InitiativeLink;
|
|
35
35
|
}
|
|
36
36
|
export interface LoadChangeContextOptions {
|
|
37
37
|
changeDir?: string;
|
|
@@ -51,8 +51,6 @@ export interface ArtifactInstructions {
|
|
|
51
51
|
changeDir: string;
|
|
52
52
|
/** Resolved planning home for this change */
|
|
53
53
|
planningHome?: PlanningHomeSummary;
|
|
54
|
-
/** Stored initiative link, when this change is linked to shared context */
|
|
55
|
-
initiative?: InitiativeLink;
|
|
56
54
|
/** Output path pattern (e.g., "proposal.md") */
|
|
57
55
|
outputPath: string;
|
|
58
56
|
/** Absolute output path or glob pattern resolved under the change directory */
|
|
@@ -67,6 +65,8 @@ export interface ArtifactInstructions {
|
|
|
67
65
|
context: string | undefined;
|
|
68
66
|
/** Artifact-specific rules from config (constraints for AI, not to be included in output) */
|
|
69
67
|
rules: string[] | undefined;
|
|
68
|
+
/** Referenced-store index (read-only upstream context; omitted when no references are declared) */
|
|
69
|
+
references?: ReferenceIndexEntry[];
|
|
70
70
|
/** Template content (structure to follow - this IS the output format) */
|
|
71
71
|
template: string;
|
|
72
72
|
/** Dependencies with completion status and paths */
|
|
@@ -108,16 +108,13 @@ export interface ChangeStatus {
|
|
|
108
108
|
changeName: string;
|
|
109
109
|
/** Schema name */
|
|
110
110
|
schemaName: string;
|
|
111
|
-
/**
|
|
111
|
+
/** Planning home facts (generated skills derive the archive dir
|
|
112
|
+
* from planningHome.changesDir - a published agent contract). */
|
|
112
113
|
planningHome?: PlanningHomeSummary;
|
|
113
|
-
/** Stored initiative link, when this change is linked to shared context */
|
|
114
|
-
initiative?: InitiativeLink;
|
|
115
114
|
/** Full path to the change root */
|
|
116
115
|
changeRoot: string;
|
|
117
116
|
/** Absolute artifact path details keyed by artifact ID */
|
|
118
117
|
artifactPaths: Record<string, ArtifactPathSummary>;
|
|
119
|
-
/** Workspace affected-area summary, when available */
|
|
120
|
-
affectedAreas?: AffectedAreasSummary;
|
|
121
118
|
/** Plain-language next steps for users and agents */
|
|
122
119
|
nextSteps: string[];
|
|
123
120
|
/** Machine-readable action constraints for agents */
|
|
@@ -172,12 +169,20 @@ export declare function loadChangeContext(projectRoot: string, changeName: strin
|
|
|
172
169
|
* @returns Enriched artifact instructions
|
|
173
170
|
* @throws Error if artifact not found
|
|
174
171
|
*/
|
|
175
|
-
export
|
|
172
|
+
export interface GenerateInstructionsOptions {
|
|
173
|
+
/** Pre-read project config; suppresses the internal read (no double read). */
|
|
174
|
+
projectConfig?: ProjectConfig | null;
|
|
175
|
+
/** Referenced-store index assembled at the command boundary. */
|
|
176
|
+
references?: ReferenceIndexEntry[];
|
|
177
|
+
}
|
|
178
|
+
export declare function generateInstructions(context: ChangeContext, artifactId: string, projectRoot?: string, options?: GenerateInstructionsOptions): ArtifactInstructions;
|
|
176
179
|
/**
|
|
177
180
|
* Formats the status of all artifacts in a change.
|
|
178
181
|
*
|
|
179
182
|
* @param context - Change context
|
|
180
183
|
* @returns Formatted change status
|
|
181
184
|
*/
|
|
182
|
-
export declare function formatChangeStatus(context: ChangeContext
|
|
185
|
+
export declare function formatChangeStatus(context: ChangeContext, options?: {
|
|
186
|
+
storeId?: string;
|
|
187
|
+
}): ChangeStatus;
|
|
183
188
|
//# sourceMappingURL=instruction-loader.d.ts.map
|
|
@@ -6,7 +6,7 @@ import { detectCompleted } from './state.js';
|
|
|
6
6
|
import { resolveArtifactOutputs } from './outputs.js';
|
|
7
7
|
import { readChangeMetadata, resolveSchemaForChange } from '../../utils/change-metadata.js';
|
|
8
8
|
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
9
|
-
import { buildActionContext, buildNextSteps,
|
|
9
|
+
import { buildActionContext, buildNextSteps, summarizePlanningHome, } from '../change-status-policy.js';
|
|
10
10
|
import { readProjectConfig, validateConfigRules } from '../project-config.js';
|
|
11
11
|
// Session-level cache for validation warnings (avoid repeating same warnings)
|
|
12
12
|
const shownWarnings = new Set();
|
|
@@ -79,24 +79,9 @@ export function loadChangeContext(projectRoot, changeName, schemaName, options =
|
|
|
79
79
|
projectRoot,
|
|
80
80
|
...(options.planningHome ? { planningHome: options.planningHome } : {}),
|
|
81
81
|
...(metadata ? { metadata } : {}),
|
|
82
|
-
...(metadata?.initiative ? { initiative: metadata.initiative } : {}),
|
|
83
82
|
};
|
|
84
83
|
}
|
|
85
|
-
|
|
86
|
-
* Generates enriched instructions for creating an artifact.
|
|
87
|
-
*
|
|
88
|
-
* Instruction injection order:
|
|
89
|
-
* 1. <context> - Project context from config (if present)
|
|
90
|
-
* 2. <rules> - Artifact-specific rules from config (if present)
|
|
91
|
-
* 3. <template> - Schema's template content
|
|
92
|
-
*
|
|
93
|
-
* @param context - Change context
|
|
94
|
-
* @param artifactId - Artifact ID to generate instructions for
|
|
95
|
-
* @param projectRoot - Project root directory (for reading config)
|
|
96
|
-
* @returns Enriched artifact instructions
|
|
97
|
-
* @throws Error if artifact not found
|
|
98
|
-
*/
|
|
99
|
-
export function generateInstructions(context, artifactId, projectRoot) {
|
|
84
|
+
export function generateInstructions(context, artifactId, projectRoot, options = {}) {
|
|
100
85
|
const artifact = context.graph.getArtifact(artifactId);
|
|
101
86
|
if (!artifact) {
|
|
102
87
|
throw new Error(`Artifact '${artifactId}' not found in schema '${context.schemaName}'`);
|
|
@@ -106,9 +91,9 @@ export function generateInstructions(context, artifactId, projectRoot) {
|
|
|
106
91
|
const unlocks = getUnlockedArtifacts(context.graph, artifactId);
|
|
107
92
|
// Use projectRoot from context if not explicitly provided
|
|
108
93
|
const effectiveProjectRoot = projectRoot ?? context.projectRoot;
|
|
109
|
-
//
|
|
110
|
-
let projectConfig = null;
|
|
111
|
-
if (effectiveProjectRoot) {
|
|
94
|
+
// Use the pre-read config when provided; otherwise read it here.
|
|
95
|
+
let projectConfig = options.projectConfig ?? null;
|
|
96
|
+
if (options.projectConfig === undefined && effectiveProjectRoot) {
|
|
112
97
|
try {
|
|
113
98
|
projectConfig = readProjectConfig(effectiveProjectRoot);
|
|
114
99
|
}
|
|
@@ -138,7 +123,6 @@ export function generateInstructions(context, artifactId, projectRoot) {
|
|
|
138
123
|
schemaName: context.schemaName,
|
|
139
124
|
changeDir: context.changeDir,
|
|
140
125
|
planningHome: summarizePlanningHome(context.planningHome),
|
|
141
|
-
...(context.initiative ? { initiative: context.initiative } : {}),
|
|
142
126
|
outputPath: artifact.generates,
|
|
143
127
|
resolvedOutputPath: path.join(context.changeDir, artifact.generates),
|
|
144
128
|
existingOutputPaths: resolveArtifactOutputs(context.changeDir, artifact.generates),
|
|
@@ -146,6 +130,7 @@ export function generateInstructions(context, artifactId, projectRoot) {
|
|
|
146
130
|
instruction: artifact.instruction,
|
|
147
131
|
context: configContext,
|
|
148
132
|
rules: configRules,
|
|
133
|
+
...(options.references !== undefined ? { references: options.references } : {}),
|
|
149
134
|
template: templateContent,
|
|
150
135
|
dependencies,
|
|
151
136
|
unlocks,
|
|
@@ -183,7 +168,7 @@ function getUnlockedArtifacts(graph, artifactId) {
|
|
|
183
168
|
* @param context - Change context
|
|
184
169
|
* @returns Formatted change status
|
|
185
170
|
*/
|
|
186
|
-
export function formatChangeStatus(context) {
|
|
171
|
+
export function formatChangeStatus(context, options = {}) {
|
|
187
172
|
// Load schema to get apply phase configuration
|
|
188
173
|
const schema = resolveSchema(context.schemaName, context.projectRoot);
|
|
189
174
|
const applyRequires = schema.apply?.requires ?? schema.artifacts.map(a => a.id);
|
|
@@ -222,31 +207,23 @@ export function formatChangeStatus(context) {
|
|
|
222
207
|
const buildOrder = context.graph.getBuildOrder();
|
|
223
208
|
const orderMap = new Map(buildOrder.map((id, idx) => [id, idx]));
|
|
224
209
|
artifactStatuses.sort((a, b) => (orderMap.get(a.id) ?? 0) - (orderMap.get(b.id) ?? 0));
|
|
225
|
-
const affectedAreas = summarizeAffectedAreas({
|
|
226
|
-
planningHome: context.planningHome,
|
|
227
|
-
metadata: context.metadata,
|
|
228
|
-
});
|
|
229
210
|
const isComplete = context.graph.isComplete(context.completed);
|
|
230
211
|
const artifactIds = artifactStatuses.map((artifact) => artifact.id);
|
|
231
212
|
return {
|
|
232
213
|
changeName: context.changeName,
|
|
233
214
|
schemaName: context.schemaName,
|
|
234
215
|
planningHome: summarizePlanningHome(context.planningHome),
|
|
235
|
-
...(context.initiative ? { initiative: context.initiative } : {}),
|
|
236
216
|
changeRoot: context.changeDir,
|
|
237
217
|
artifactPaths,
|
|
238
|
-
affectedAreas,
|
|
239
218
|
isComplete,
|
|
240
219
|
applyRequires,
|
|
241
220
|
nextSteps: buildNextSteps({
|
|
242
221
|
changeName: context.changeName,
|
|
243
|
-
planningHome: context.planningHome,
|
|
244
222
|
artifactStatuses,
|
|
245
|
-
affectedAreas,
|
|
246
223
|
allArtifactsComplete: isComplete,
|
|
224
|
+
...(options.storeId ? { storeId: options.storeId } : {}),
|
|
247
225
|
}),
|
|
248
226
|
actionContext: buildActionContext({
|
|
249
|
-
planningHome: context.planningHome,
|
|
250
227
|
projectRoot: context.projectRoot,
|
|
251
228
|
artifactIds,
|
|
252
229
|
}),
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { isKebabId } from '../id.js';
|
|
3
|
+
export { isKebabId } from '../id.js';
|
|
2
4
|
const KebabIdentifierSchema = (label) => z.string().superRefine((value, ctx) => {
|
|
3
|
-
if (
|
|
5
|
+
if (!isKebabId(value)) {
|
|
4
6
|
ctx.addIssue({
|
|
5
7
|
code: 'custom',
|
|
6
8
|
message: `${label} must be kebab-case with lowercase letters, numbers, and single hyphen separators`,
|
|
@@ -8,7 +10,7 @@ const KebabIdentifierSchema = (label) => z.string().superRefine((value, ctx) =>
|
|
|
8
10
|
}
|
|
9
11
|
});
|
|
10
12
|
export const InitiativeLinkSchema = z.object({
|
|
11
|
-
store: KebabIdentifierSchema('
|
|
13
|
+
store: KebabIdentifierSchema('Store id'),
|
|
12
14
|
id: KebabIdentifierSchema('Initiative id'),
|
|
13
15
|
}).strict();
|
|
14
16
|
// Per-change metadata schema. The schema field is validated against available
|