@fission-ai/openspec 1.6.0 → 1.8.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 +31 -2
- package/dist/cli/index.js +79 -8
- package/dist/commands/change.d.ts +0 -1
- package/dist/commands/change.js +105 -51
- package/dist/commands/config.js +14 -8
- package/dist/commands/doctor.js +9 -4
- package/dist/commands/feedback.js +74 -23
- package/dist/commands/schema.js +110 -31
- package/dist/commands/show.js +5 -2
- package/dist/commands/spec.js +49 -29
- package/dist/commands/validate.js +20 -5
- package/dist/commands/workflow/index.d.ts +1 -1
- package/dist/commands/workflow/index.js +1 -1
- package/dist/commands/workflow/instructions.d.ts +7 -1
- package/dist/commands/workflow/instructions.js +121 -33
- package/dist/commands/workflow/shared.d.ts +13 -2
- package/dist/commands/workflow/shared.js +34 -4
- package/dist/commands/workflow/status.js +9 -4
- package/dist/commands/workflow/templates.js +15 -5
- package/dist/core/archive.d.ts +14 -1
- package/dist/core/archive.js +1332 -155
- package/dist/core/artifact-graph/graph.d.ts +16 -0
- package/dist/core/artifact-graph/graph.js +30 -6
- package/dist/core/artifact-graph/index.d.ts +1 -1
- package/dist/core/artifact-graph/index.js +1 -1
- package/dist/core/artifact-graph/instruction-loader.d.ts +30 -3
- package/dist/core/artifact-graph/instruction-loader.js +70 -12
- package/dist/core/artifact-graph/outputs.d.ts +1 -0
- package/dist/core/artifact-graph/outputs.js +67 -5
- package/dist/core/artifact-graph/resolver.d.ts +14 -0
- package/dist/core/artifact-graph/resolver.js +69 -15
- package/dist/core/artifact-graph/types.js +21 -3
- package/dist/core/available-tools.d.ts +2 -2
- package/dist/core/available-tools.js +14 -3
- package/dist/core/change-metadata/schema.d.ts +2 -0
- package/dist/core/change-metadata/schema.js +13 -0
- package/dist/core/change-status-policy.d.ts +1 -1
- package/dist/core/change-status-policy.js +2 -2
- package/dist/core/command-generation/adapters/amazon-q.d.ts +4 -0
- package/dist/core/command-generation/adapters/amazon-q.js +7 -1
- package/dist/core/command-generation/adapters/antigravity.js +2 -1
- package/dist/core/command-generation/adapters/auggie.js +2 -1
- package/dist/core/command-generation/adapters/bob.d.ts +5 -1
- package/dist/core/command-generation/adapters/bob.js +6 -5
- package/dist/core/command-generation/adapters/claude.js +1 -8
- package/dist/core/command-generation/adapters/codebuddy.js +3 -2
- package/dist/core/command-generation/adapters/continue.js +3 -2
- package/dist/core/command-generation/adapters/costrict.js +2 -1
- package/dist/core/command-generation/adapters/crush.js +5 -5
- package/dist/core/command-generation/adapters/cursor.js +2 -2
- package/dist/core/command-generation/adapters/devin.d.ts +19 -0
- package/dist/core/command-generation/adapters/devin.js +36 -0
- package/dist/core/command-generation/adapters/factory.js +2 -1
- package/dist/core/command-generation/adapters/gemini.js +37 -2
- package/dist/core/command-generation/adapters/github-copilot.js +2 -1
- package/dist/core/command-generation/adapters/iflow.js +5 -4
- package/dist/core/command-generation/adapters/index.d.ts +2 -2
- package/dist/core/command-generation/adapters/index.js +2 -2
- package/dist/core/command-generation/adapters/junie.js +2 -1
- package/dist/core/command-generation/adapters/kiro.js +2 -1
- package/dist/core/command-generation/adapters/lingma.js +5 -5
- package/dist/core/command-generation/adapters/oh-my-pi.d.ts +2 -2
- package/dist/core/command-generation/adapters/oh-my-pi.js +3 -5
- package/dist/core/command-generation/adapters/opencode.js +3 -5
- package/dist/core/command-generation/adapters/pi.d.ts +2 -2
- package/dist/core/command-generation/adapters/pi.js +3 -6
- package/dist/core/command-generation/adapters/qoder.js +5 -5
- package/dist/core/command-generation/adapters/qwen.d.ts +7 -3
- package/dist/core/command-generation/adapters/qwen.js +12 -7
- package/dist/core/command-generation/adapters/roocode.d.ts +4 -4
- package/dist/core/command-generation/adapters/roocode.js +4 -4
- package/dist/core/command-generation/adapters/trae.js +1 -21
- package/dist/core/command-generation/adapters/zcode.d.ts +16 -0
- package/dist/core/command-generation/adapters/zcode.js +33 -0
- package/dist/core/command-generation/generator.d.ts +8 -0
- package/dist/core/command-generation/generator.js +15 -1
- package/dist/core/command-generation/index.d.ts +1 -1
- package/dist/core/command-generation/index.js +1 -1
- package/dist/core/command-generation/invocation.d.ts +73 -0
- package/dist/core/command-generation/invocation.js +77 -0
- package/dist/core/command-generation/registry.js +4 -4
- package/dist/core/command-generation/types.d.ts +9 -2
- package/dist/core/command-generation/yaml.d.ts +20 -6
- package/dist/core/command-generation/yaml.js +31 -21
- package/dist/core/command-surface.d.ts +15 -0
- package/dist/core/command-surface.js +33 -0
- package/dist/core/completions/command-registry.js +16 -2
- package/dist/core/completions/generators/powershell-generator.js +15 -8
- package/dist/core/completions/installers/zsh-installer.d.ts +9 -0
- package/dist/core/completions/installers/zsh-installer.js +20 -4
- package/dist/core/config-prompts.d.ts +1 -1
- package/dist/core/config-prompts.js +14 -1
- package/dist/core/config-schema.d.ts +10 -1
- package/dist/core/config-schema.js +67 -1
- package/dist/core/config.d.ts +15 -0
- package/dist/core/config.js +45 -5
- package/dist/core/file-state.js +56 -24
- package/dist/core/github-copilot/cloud-agent.d.ts +95 -0
- package/dist/core/github-copilot/cloud-agent.js +497 -0
- package/dist/core/global-config.d.ts +16 -0
- package/dist/core/init.d.ts +58 -0
- package/dist/core/init.js +450 -69
- package/dist/core/legacy-cleanup.d.ts +59 -0
- package/dist/core/legacy-cleanup.js +253 -24
- package/dist/core/list.js +5 -7
- package/dist/core/migration.d.ts +73 -1
- package/dist/core/migration.js +360 -11
- package/dist/core/onboarding-commands.d.ts +30 -0
- package/dist/core/onboarding-commands.js +38 -0
- package/dist/core/parsers/change-parser.d.ts +17 -2
- package/dist/core/parsers/change-parser.js +33 -20
- package/dist/core/parsers/code-fence.d.ts +15 -0
- package/dist/core/parsers/code-fence.js +49 -0
- package/dist/core/parsers/markdown-parser.js +2 -1
- package/dist/core/parsers/requirement-blocks.d.ts +18 -0
- package/dist/core/parsers/requirement-blocks.js +120 -35
- package/dist/core/parsers/requirement-text.d.ts +1 -8
- package/dist/core/parsers/requirement-text.js +4 -42
- package/dist/core/parsers/spec-structure.d.ts +1 -1
- package/dist/core/parsers/spec-structure.js +20 -30
- package/dist/core/profile-sync-drift.d.ts +0 -8
- package/dist/core/profile-sync-drift.js +48 -49
- package/dist/core/project-config.d.ts +28 -6
- package/dist/core/project-config.js +108 -8
- package/dist/core/references.js +42 -4
- package/dist/core/relationship-health.d.ts +8 -0
- package/dist/core/relationship-health.js +13 -0
- package/dist/core/root-selection.d.ts +9 -6
- package/dist/core/root-selection.js +42 -5
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/skill-content-equivalence.d.ts +7 -0
- package/dist/core/shared/skill-content-equivalence.js +41 -0
- package/dist/core/shared/skill-paths.d.ts +13 -0
- package/dist/core/shared/skill-paths.js +23 -0
- package/dist/core/shared/tool-detection.d.ts +25 -5
- package/dist/core/shared/tool-detection.js +166 -36
- package/dist/core/shared-skill-target.d.ts +16 -0
- package/dist/core/shared-skill-target.js +156 -0
- package/dist/core/specs-apply.d.ts +80 -33
- package/dist/core/specs-apply.js +659 -171
- package/dist/core/store/git.d.ts +13 -0
- package/dist/core/store/git.js +24 -0
- package/dist/core/templates/skill-templates.d.ts +1 -1
- package/dist/core/templates/skill-templates.js +1 -1
- package/dist/core/templates/workflows/apply-change.d.ts +10 -0
- package/dist/core/templates/workflows/apply-change.js +48 -158
- package/dist/core/templates/workflows/archive-change.js +170 -42
- package/dist/core/templates/workflows/bulk-archive-change.js +256 -75
- package/dist/core/templates/workflows/continue-change.js +38 -46
- package/dist/core/templates/workflows/explore.js +54 -18
- package/dist/core/templates/workflows/ff-change.js +46 -30
- package/dist/core/templates/workflows/new-change.js +2 -2
- package/dist/core/templates/workflows/onboard.js +13 -7
- package/dist/core/templates/workflows/propose.js +116 -46
- package/dist/core/templates/workflows/store-selection.d.ts +1 -1
- package/dist/core/templates/workflows/store-selection.js +1 -1
- package/dist/core/templates/workflows/sync-specs.js +260 -32
- package/dist/core/templates/workflows/update-change.js +26 -16
- package/dist/core/templates/workflows/verify-change.js +16 -10
- package/dist/core/update.d.ts +40 -5
- package/dist/core/update.js +389 -93
- package/dist/core/validation/constants.d.ts +4 -1
- package/dist/core/validation/constants.js +4 -1
- package/dist/core/validation/validator.d.ts +22 -8
- package/dist/core/validation/validator.js +222 -50
- package/dist/core/version-check.d.ts +152 -0
- package/dist/core/version-check.js +731 -0
- package/dist/core/view.js +12 -17
- package/dist/prompts/searchable-multi-select.js +1 -1
- package/dist/telemetry/config.d.ts +3 -4
- package/dist/telemetry/index.d.ts +10 -5
- package/dist/telemetry/index.js +75 -41
- package/dist/ui/welcome-screen.d.ts +9 -1
- package/dist/ui/welcome-screen.js +91 -38
- package/dist/utils/change-metadata.d.ts +43 -0
- package/dist/utils/change-metadata.js +123 -7
- package/dist/utils/change-utils.d.ts +10 -4
- package/dist/utils/change-utils.js +20 -13
- package/dist/utils/ci.d.ts +12 -0
- package/dist/utils/ci.js +16 -0
- package/dist/utils/command-references.d.ts +92 -6
- package/dist/utils/command-references.js +180 -7
- package/dist/utils/date.d.ts +8 -0
- package/dist/utils/date.js +13 -0
- package/dist/utils/file-system.d.ts +10 -0
- package/dist/utils/file-system.js +70 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/interactive.d.ts +23 -0
- package/dist/utils/interactive.js +32 -0
- package/dist/utils/item-discovery.d.ts +19 -0
- package/dist/utils/item-discovery.js +30 -48
- package/dist/utils/shell-detection.d.ts +1 -1
- package/dist/utils/shell-detection.js +64 -11
- package/dist/utils/spec-discovery.d.ts +40 -0
- package/dist/utils/spec-discovery.js +115 -0
- package/dist/utils/task-progress.d.ts +17 -0
- package/dist/utils/task-progress.js +41 -13
- package/package.json +14 -12
- package/schemas/spec-driven/schema.yaml +67 -10
- package/schemas/spec-driven/templates/design.md +2 -2
- package/schemas/spec-driven/templates/proposal.md +10 -4
- package/schemas/spec-driven/templates/spec.md +3 -0
- package/dist/core/command-generation/adapters/codex.d.ts +0 -16
- package/dist/core/command-generation/adapters/codex.js +0 -39
- package/dist/core/command-generation/adapters/windsurf.d.ts +0 -14
- package/dist/core/command-generation/adapters/windsurf.js +0 -38
|
@@ -105,37 +105,88 @@ function displayFormattedFeedback(title, body) {
|
|
|
105
105
|
console.log('--- END FEEDBACK ---\n');
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
|
-
*
|
|
108
|
+
* Check whether gh refused the issue because the repository does not define
|
|
109
|
+
* the label. gh resolves label names before creating the issue, so this
|
|
110
|
+
* failure means no issue was created.
|
|
111
|
+
*
|
|
112
|
+
* Only gh's stderr is inspected. The error message also embeds the command
|
|
113
|
+
* line, which carries the user's own feedback text.
|
|
114
|
+
*/
|
|
115
|
+
function isMissingLabelError(error) {
|
|
116
|
+
return /could not add label/i.test(error?.stderr?.toString() ?? '');
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Report a gh CLI failure and exit, preserving gh's exit code.
|
|
120
|
+
*
|
|
121
|
+
* gh failed after the user already typed their feedback (issues disabled,
|
|
122
|
+
* network, rate limit, ...), so show the same manual-submission path the
|
|
123
|
+
* missing-gh and unauthenticated flows get instead of discarding the text.
|
|
124
|
+
*/
|
|
125
|
+
function reportGhFailure(error, title, body) {
|
|
126
|
+
// Display the error output from gh CLI
|
|
127
|
+
if (error.stderr) {
|
|
128
|
+
console.error(error.stderr.toString());
|
|
129
|
+
}
|
|
130
|
+
else if (error.message) {
|
|
131
|
+
console.error(error.message);
|
|
132
|
+
}
|
|
133
|
+
displayFormattedFeedback(title, body);
|
|
134
|
+
const manualUrl = generateManualSubmissionUrl(title, body);
|
|
135
|
+
console.log('Please submit your feedback manually:');
|
|
136
|
+
console.log(manualUrl);
|
|
137
|
+
// Exit with the same code as gh CLI
|
|
138
|
+
process.exit(error.status ?? 1);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create the feedback issue via gh CLI
|
|
109
142
|
* Uses execFileSync to prevent shell injection vulnerabilities
|
|
110
143
|
*/
|
|
144
|
+
function createIssue(title, body, labels) {
|
|
145
|
+
const args = [
|
|
146
|
+
'issue',
|
|
147
|
+
'create',
|
|
148
|
+
'--repo',
|
|
149
|
+
'Fission-AI/OpenSpec',
|
|
150
|
+
'--title',
|
|
151
|
+
title,
|
|
152
|
+
'--body',
|
|
153
|
+
body,
|
|
154
|
+
];
|
|
155
|
+
for (const label of labels) {
|
|
156
|
+
args.push('--label', label);
|
|
157
|
+
}
|
|
158
|
+
const result = execFileSync('gh', args, { encoding: 'utf-8', stdio: 'pipe' });
|
|
159
|
+
return result.trim();
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Submit feedback via gh CLI
|
|
163
|
+
*/
|
|
111
164
|
function submitViaGhCli(title, body) {
|
|
165
|
+
let issueUrl;
|
|
166
|
+
let labelApplied = true;
|
|
112
167
|
try {
|
|
113
|
-
|
|
114
|
-
'issue',
|
|
115
|
-
'create',
|
|
116
|
-
'--repo',
|
|
117
|
-
'Fission-AI/OpenSpec',
|
|
118
|
-
'--title',
|
|
119
|
-
title,
|
|
120
|
-
'--body',
|
|
121
|
-
body,
|
|
122
|
-
'--label',
|
|
123
|
-
'feedback',
|
|
124
|
-
], { encoding: 'utf-8', stdio: 'pipe' });
|
|
125
|
-
const issueUrl = result.trim();
|
|
126
|
-
console.log(`\n✓ Feedback submitted successfully!`);
|
|
127
|
-
console.log(`Issue URL: ${issueUrl}\n`);
|
|
168
|
+
issueUrl = createIssue(title, body, ['feedback']);
|
|
128
169
|
}
|
|
129
170
|
catch (error) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
171
|
+
if (!isMissingLabelError(error)) {
|
|
172
|
+
reportGhFailure(error, title, body);
|
|
173
|
+
return;
|
|
133
174
|
}
|
|
134
|
-
|
|
135
|
-
|
|
175
|
+
// The repository does not define the 'feedback' label. Nothing was
|
|
176
|
+
// created, so retry unlabeled rather than dropping the feedback.
|
|
177
|
+
try {
|
|
178
|
+
issueUrl = createIssue(title, body, []);
|
|
179
|
+
labelApplied = false;
|
|
136
180
|
}
|
|
137
|
-
|
|
138
|
-
|
|
181
|
+
catch (retryError) {
|
|
182
|
+
reportGhFailure(retryError, title, body);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
console.log(`\n✓ Feedback submitted successfully!`);
|
|
187
|
+
console.log(`Issue URL: ${issueUrl}\n`);
|
|
188
|
+
if (!labelApplied) {
|
|
189
|
+
console.log("Note: created without the 'feedback' label because the repository does not define it.\n");
|
|
139
190
|
}
|
|
140
191
|
}
|
|
141
192
|
/**
|
package/dist/commands/schema.js
CHANGED
|
@@ -2,8 +2,9 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { stringify as stringifyYaml } from 'yaml';
|
|
5
|
-
import { getSchemaDir, getProjectSchemasDir, getUserSchemasDir, getPackageSchemasDir, listSchemas, } from '../core/artifact-graph/resolver.js';
|
|
5
|
+
import { getSchemaDir, getProjectSchemasDir, getUserSchemasDir, getPackageSchemasDir, isSchemaDir, listSchemas, } from '../core/artifact-graph/resolver.js';
|
|
6
6
|
import { parseSchema, SchemaValidationError } from '../core/artifact-graph/schema.js';
|
|
7
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
7
8
|
/**
|
|
8
9
|
* Check all three locations for a schema and return which ones exist.
|
|
9
10
|
*/
|
|
@@ -129,21 +130,30 @@ function validateSchema(schemaDir, verbose = false) {
|
|
|
129
130
|
}
|
|
130
131
|
return { valid: false, issues };
|
|
131
132
|
}
|
|
132
|
-
// Check template files exist
|
|
133
|
-
// Templates can be in schemaDir directly or in a templates/ subdirectory
|
|
133
|
+
// Check template files exist in the same directory used at runtime.
|
|
134
134
|
if (verbose) {
|
|
135
135
|
console.log(' Checking template files...');
|
|
136
136
|
}
|
|
137
137
|
for (const artifact of schema.artifacts) {
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
if (!fs.existsSync(templatePathInTemplates) && !fs.existsSync(templatePathInRoot)) {
|
|
138
|
+
const templatesDir = path.join(schemaDir, 'templates');
|
|
139
|
+
const existingTemplatePath = path.join(templatesDir, artifact.template);
|
|
140
|
+
if (!fs.existsSync(existingTemplatePath)) {
|
|
142
141
|
issues.push({
|
|
143
142
|
level: 'error',
|
|
144
143
|
path: `artifacts.${artifact.id}.template`,
|
|
145
144
|
message: `Template file '${artifact.template}' not found for artifact '${artifact.id}'`,
|
|
146
145
|
});
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
FileSystemUtils.assertPathWithin(templatesDir, existingTemplatePath);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
issues.push({
|
|
153
|
+
level: 'error',
|
|
154
|
+
path: `artifacts.${artifact.id}.template`,
|
|
155
|
+
message: `Template file '${artifact.template}' points outside the schema templates directory`,
|
|
156
|
+
});
|
|
147
157
|
}
|
|
148
158
|
}
|
|
149
159
|
// Dependency graph validation is already done by parseSchema
|
|
@@ -162,19 +172,73 @@ function isValidSchemaName(name) {
|
|
|
162
172
|
/**
|
|
163
173
|
* Copy a directory recursively.
|
|
164
174
|
*/
|
|
165
|
-
function
|
|
175
|
+
function resolveSchemaCopyPath(allowedRoot, sourcePath) {
|
|
176
|
+
try {
|
|
177
|
+
const canonicalRoot = fs.realpathSync(allowedRoot);
|
|
178
|
+
const canonicalPath = fs.realpathSync(sourcePath);
|
|
179
|
+
FileSystemUtils.assertPathWithin(canonicalRoot, canonicalPath);
|
|
180
|
+
return canonicalPath;
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
184
|
+
throw new Error(`Cannot fork schema with linked or unsupported entry: ${sourcePath}: ${detail}`, { cause: error });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function copyDirRecursive(src, dest, allowedRoot = src, ancestors = new Set()) {
|
|
188
|
+
const canonicalSrc = resolveSchemaCopyPath(allowedRoot, src);
|
|
189
|
+
if (ancestors.has(canonicalSrc)) {
|
|
190
|
+
throw new Error(`Cannot fork schema with a linked directory cycle: ${src}`);
|
|
191
|
+
}
|
|
192
|
+
ancestors.add(canonicalSrc);
|
|
166
193
|
fs.mkdirSync(dest, { recursive: true });
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
194
|
+
try {
|
|
195
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
196
|
+
for (const entry of entries) {
|
|
197
|
+
const srcPath = path.join(src, entry.name);
|
|
198
|
+
const destPath = path.join(dest, entry.name);
|
|
199
|
+
const canonicalEntry = resolveSchemaCopyPath(allowedRoot, srcPath);
|
|
200
|
+
const stats = fs.statSync(canonicalEntry);
|
|
201
|
+
if (stats.isDirectory()) {
|
|
202
|
+
copyDirRecursive(canonicalEntry, destPath, allowedRoot, ancestors);
|
|
203
|
+
}
|
|
204
|
+
else if (stats.isFile()) {
|
|
205
|
+
// Dereference confined links so the fork is an independent schema.
|
|
206
|
+
fs.copyFileSync(canonicalEntry, destPath);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
throw new Error(`Cannot fork schema with linked or unsupported entry: ${srcPath}`);
|
|
210
|
+
}
|
|
173
211
|
}
|
|
174
|
-
|
|
175
|
-
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
ancestors.delete(canonicalSrc);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Verifies a schema tree before replacing or creating the fork destination.
|
|
219
|
+
*/
|
|
220
|
+
function assertSchemaTreeCanBeCopied(src, allowedRoot = src, ancestors = new Set()) {
|
|
221
|
+
const canonicalSrc = resolveSchemaCopyPath(allowedRoot, src);
|
|
222
|
+
if (ancestors.has(canonicalSrc)) {
|
|
223
|
+
throw new Error(`Cannot fork schema with a linked directory cycle: ${src}`);
|
|
224
|
+
}
|
|
225
|
+
ancestors.add(canonicalSrc);
|
|
226
|
+
try {
|
|
227
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
228
|
+
const entryPath = path.join(src, entry.name);
|
|
229
|
+
const canonicalEntry = resolveSchemaCopyPath(allowedRoot, entryPath);
|
|
230
|
+
const stats = fs.statSync(canonicalEntry);
|
|
231
|
+
if (stats.isDirectory()) {
|
|
232
|
+
assertSchemaTreeCanBeCopied(canonicalEntry, allowedRoot, ancestors);
|
|
233
|
+
}
|
|
234
|
+
else if (!stats.isFile()) {
|
|
235
|
+
throw new Error(`Cannot fork schema with linked or unsupported entry: ${entryPath}`);
|
|
236
|
+
}
|
|
176
237
|
}
|
|
177
238
|
}
|
|
239
|
+
finally {
|
|
240
|
+
ancestors.delete(canonicalSrc);
|
|
241
|
+
}
|
|
178
242
|
}
|
|
179
243
|
/**
|
|
180
244
|
* Default artifacts with descriptions for schema init.
|
|
@@ -339,7 +403,7 @@ export function registerSchemaCommand(program) {
|
|
|
339
403
|
const schemaResults = [];
|
|
340
404
|
let anyInvalid = false;
|
|
341
405
|
for (const entry of entries) {
|
|
342
|
-
if (!entry
|
|
406
|
+
if (!isSchemaDir(projectSchemasDir, entry))
|
|
343
407
|
continue;
|
|
344
408
|
const schemaDir = path.join(projectSchemasDir, entry.name);
|
|
345
409
|
const schemaPath = path.join(schemaDir, 'schema.yaml');
|
|
@@ -378,9 +442,9 @@ export function registerSchemaCommand(program) {
|
|
|
378
442
|
console.log(` ${issue.level}: ${issue.message}`);
|
|
379
443
|
}
|
|
380
444
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
445
|
+
}
|
|
446
|
+
if (anyInvalid) {
|
|
447
|
+
process.exitCode = 1;
|
|
384
448
|
}
|
|
385
449
|
return;
|
|
386
450
|
}
|
|
@@ -423,9 +487,11 @@ export function registerSchemaCommand(program) {
|
|
|
423
487
|
for (const issue of result.issues) {
|
|
424
488
|
console.log(` ${issue.level}: ${issue.message}`);
|
|
425
489
|
}
|
|
426
|
-
process.exitCode = 1;
|
|
427
490
|
}
|
|
428
491
|
}
|
|
492
|
+
if (!result.valid) {
|
|
493
|
+
process.exitCode = 1;
|
|
494
|
+
}
|
|
429
495
|
}
|
|
430
496
|
catch (error) {
|
|
431
497
|
if (options?.json) {
|
|
@@ -487,6 +553,9 @@ export function registerSchemaCommand(program) {
|
|
|
487
553
|
// Determine source location
|
|
488
554
|
const sourceResolution = getSchemaResolution(source, projectRoot);
|
|
489
555
|
const sourceLocation = sourceResolution?.source || 'package';
|
|
556
|
+
// Validate the complete source before a forced fork removes anything.
|
|
557
|
+
const trustedSourceDir = fs.realpathSync(sourceDir);
|
|
558
|
+
assertSchemaTreeCanBeCopied(trustedSourceDir);
|
|
490
559
|
// Check destination
|
|
491
560
|
const destinationDir = path.join(getProjectSchemasDir(projectRoot), destinationName);
|
|
492
561
|
if (fs.existsSync(destinationDir)) {
|
|
@@ -513,7 +582,7 @@ export function registerSchemaCommand(program) {
|
|
|
513
582
|
// Copy schema
|
|
514
583
|
if (spinner)
|
|
515
584
|
spinner.start(`Forking '${source}' to '${destinationName}'...`);
|
|
516
|
-
copyDirRecursive(
|
|
585
|
+
copyDirRecursive(trustedSourceDir, destinationDir);
|
|
517
586
|
// Update name in schema.yaml
|
|
518
587
|
const destSchemaPath = path.join(destinationDir, 'schema.yaml');
|
|
519
588
|
const schemaContent = fs.readFileSync(destSchemaPath, 'utf-8');
|
|
@@ -584,8 +653,9 @@ export function registerSchemaCommand(program) {
|
|
|
584
653
|
return;
|
|
585
654
|
}
|
|
586
655
|
const schemaDir = path.join(getProjectSchemasDir(projectRoot), name);
|
|
587
|
-
// Check
|
|
588
|
-
|
|
656
|
+
// Check overwrite permission without mutating the destination
|
|
657
|
+
const schemaExists = fs.existsSync(schemaDir);
|
|
658
|
+
if (schemaExists) {
|
|
589
659
|
if (!options?.force) {
|
|
590
660
|
if (options?.json) {
|
|
591
661
|
console.log(JSON.stringify({
|
|
@@ -601,9 +671,6 @@ export function registerSchemaCommand(program) {
|
|
|
601
671
|
process.exitCode = 1;
|
|
602
672
|
return;
|
|
603
673
|
}
|
|
604
|
-
if (spinner)
|
|
605
|
-
spinner.start(`Removing existing schema '${name}'...`);
|
|
606
|
-
fs.rmSync(schemaDir, { recursive: true });
|
|
607
674
|
}
|
|
608
675
|
// Determine artifacts and description
|
|
609
676
|
let description;
|
|
@@ -625,6 +692,12 @@ export function registerSchemaCommand(program) {
|
|
|
625
692
|
}));
|
|
626
693
|
selectedArtifactIds = await checkbox({
|
|
627
694
|
message: 'Select artifacts to include:',
|
|
695
|
+
theme: {
|
|
696
|
+
icon: {
|
|
697
|
+
checked: '[x]',
|
|
698
|
+
unchecked: '[ ]',
|
|
699
|
+
},
|
|
700
|
+
},
|
|
628
701
|
choices: artifactChoices,
|
|
629
702
|
});
|
|
630
703
|
if (selectedArtifactIds.length === 0) {
|
|
@@ -673,10 +746,6 @@ export function registerSchemaCommand(program) {
|
|
|
673
746
|
selectedArtifactIds = DEFAULT_ARTIFACTS.map((a) => a.id);
|
|
674
747
|
}
|
|
675
748
|
}
|
|
676
|
-
// Create schema directory
|
|
677
|
-
if (spinner)
|
|
678
|
-
spinner.start(`Creating schema '${name}'...`);
|
|
679
|
-
fs.mkdirSync(schemaDir, { recursive: true });
|
|
680
749
|
// Build artifacts array with proper dependencies
|
|
681
750
|
const selectedArtifacts = selectedArtifactIds.map((id) => {
|
|
682
751
|
const template = DEFAULT_ARTIFACTS.find((a) => a.id === id);
|
|
@@ -718,6 +787,16 @@ export function registerSchemaCommand(program) {
|
|
|
718
787
|
tracks: 'tasks.md',
|
|
719
788
|
};
|
|
720
789
|
}
|
|
790
|
+
// Replace only after all inputs have been collected and validated
|
|
791
|
+
if (schemaExists) {
|
|
792
|
+
if (spinner)
|
|
793
|
+
spinner.start(`Removing existing schema '${name}'...`);
|
|
794
|
+
fs.rmSync(schemaDir, { recursive: true });
|
|
795
|
+
}
|
|
796
|
+
// Create schema directory
|
|
797
|
+
if (spinner)
|
|
798
|
+
spinner.start(`Creating schema '${name}'...`);
|
|
799
|
+
fs.mkdirSync(schemaDir, { recursive: true });
|
|
721
800
|
fs.writeFileSync(path.join(schemaDir, 'schema.yaml'), stringifyYaml(schema));
|
|
722
801
|
// Create template files in templates/ subdirectory (standard location)
|
|
723
802
|
const templatesDir = path.join(schemaDir, 'templates');
|
package/dist/commands/show.js
CHANGED
|
@@ -157,14 +157,17 @@ export class ShowCommand {
|
|
|
157
157
|
}
|
|
158
158
|
warnIrrelevantFlags(type, options) {
|
|
159
159
|
const irrelevant = [];
|
|
160
|
+
// --no-scenarios makes commander default `scenarios` to true, so its
|
|
161
|
+
// presence alone does not mean the user passed it — only false does.
|
|
162
|
+
const isUserProvided = (k) => k === 'scenarios' ? options[k] === false : k in options;
|
|
160
163
|
if (type === 'change') {
|
|
161
164
|
for (const k of SPEC_FLAG_KEYS)
|
|
162
|
-
if (k
|
|
165
|
+
if (isUserProvided(k))
|
|
163
166
|
irrelevant.push(k);
|
|
164
167
|
}
|
|
165
168
|
else {
|
|
166
169
|
for (const k of CHANGE_FLAG_KEYS)
|
|
167
|
-
if (k
|
|
170
|
+
if (isUserProvided(k))
|
|
168
171
|
irrelevant.push(k);
|
|
169
172
|
}
|
|
170
173
|
if (irrelevant.length > 0) {
|
package/dist/commands/spec.js
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import { join } from 'path';
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import path, { join } from 'path';
|
|
3
3
|
import { MarkdownParser } from '../core/parsers/markdown-parser.js';
|
|
4
4
|
import { Validator } from '../core/validation/validator.js';
|
|
5
5
|
import { isInteractive } from '../utils/interactive.js';
|
|
6
6
|
import { getSpecIds } from '../utils/item-discovery.js';
|
|
7
|
+
import { discoverSpecFiles } from '../utils/spec-discovery.js';
|
|
8
|
+
import { FileSystemUtils } from '../utils/file-system.js';
|
|
7
9
|
const SPECS_DIR = 'openspec/specs';
|
|
8
|
-
function
|
|
10
|
+
function assertSpecPath(specsDir, specPath) {
|
|
11
|
+
const relativePath = path.relative(path.resolve(specsDir), path.resolve(specPath));
|
|
12
|
+
if (relativePath === '..' ||
|
|
13
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
14
|
+
path.isAbsolute(relativePath)) {
|
|
15
|
+
throw new Error(`Path is outside the allowed directory: ${specPath}`);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
// Preserve confined spec.md links, including links to a sibling capability.
|
|
19
|
+
FileSystemUtils.assertPathWithin(specsDir, specPath);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// A capability directory may intentionally be a monorepo symlink. Treat it
|
|
23
|
+
// as the trust root while still rejecting a link outside that capability.
|
|
24
|
+
FileSystemUtils.assertPathWithin(path.dirname(specPath), specPath);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function parseSpecFromFile(specsDir, specPath, specId) {
|
|
28
|
+
assertSpecPath(specsDir, specPath);
|
|
9
29
|
const content = readFileSync(specPath, 'utf-8');
|
|
10
30
|
const parser = new MarkdownParser(content);
|
|
11
31
|
return parser.parseSpec(specId);
|
|
@@ -40,7 +60,8 @@ function filterSpec(spec, options) {
|
|
|
40
60
|
* Print the raw markdown content for a spec file without any formatting.
|
|
41
61
|
* Raw-first behavior ensures text mode is a passthrough for deterministic output.
|
|
42
62
|
*/
|
|
43
|
-
function printSpecTextRaw(specPath) {
|
|
63
|
+
function printSpecTextRaw(specsDir, specPath) {
|
|
64
|
+
assertSpecPath(specsDir, specPath);
|
|
44
65
|
const content = readFileSync(specPath, 'utf-8');
|
|
45
66
|
console.log(content);
|
|
46
67
|
}
|
|
@@ -69,6 +90,7 @@ export class SpecCommand {
|
|
|
69
90
|
}
|
|
70
91
|
}
|
|
71
92
|
const specPath = join(this.specsDir, specId, 'spec.md');
|
|
93
|
+
assertSpecPath(this.specsDir, specPath);
|
|
72
94
|
if (!existsSync(specPath)) {
|
|
73
95
|
// Root-aware callers get the absolute path; the cwd-based noun form
|
|
74
96
|
// keeps its historical forward-slash relative message on all platforms.
|
|
@@ -79,7 +101,7 @@ export class SpecCommand {
|
|
|
79
101
|
if (options.requirements && options.requirement) {
|
|
80
102
|
throw new Error('Options --requirements and --requirement cannot be used together');
|
|
81
103
|
}
|
|
82
|
-
const parsed = parseSpecFromFile(specPath, specId);
|
|
104
|
+
const parsed = parseSpecFromFile(this.specsDir, specPath, specId);
|
|
83
105
|
const filtered = filterSpec(parsed, options);
|
|
84
106
|
const output = {
|
|
85
107
|
id: specId,
|
|
@@ -93,7 +115,7 @@ export class SpecCommand {
|
|
|
93
115
|
console.log(JSON.stringify(output, null, 2));
|
|
94
116
|
return;
|
|
95
117
|
}
|
|
96
|
-
printSpecTextRaw(specPath);
|
|
118
|
+
printSpecTextRaw(this.specsDir, specPath);
|
|
97
119
|
}
|
|
98
120
|
}
|
|
99
121
|
export function registerSpecCommand(rootProgram) {
|
|
@@ -127,36 +149,32 @@ export function registerSpecCommand(rootProgram) {
|
|
|
127
149
|
.description('List all available specifications')
|
|
128
150
|
.option('--json', 'Output as JSON')
|
|
129
151
|
.option('--long', 'Show id and title with counts')
|
|
130
|
-
.action((options) => {
|
|
152
|
+
.action(async (options) => {
|
|
131
153
|
try {
|
|
132
154
|
if (!existsSync(SPECS_DIR)) {
|
|
133
155
|
console.log('No items found');
|
|
134
156
|
return;
|
|
135
157
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
.map(
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
};
|
|
155
|
-
}
|
|
158
|
+
const discovered = await discoverSpecFiles(SPECS_DIR);
|
|
159
|
+
const specs = discovered
|
|
160
|
+
.map(({ id, specFile }) => {
|
|
161
|
+
try {
|
|
162
|
+
assertSpecPath(SPECS_DIR, specFile);
|
|
163
|
+
const spec = parseSpecFromFile(SPECS_DIR, specFile, id);
|
|
164
|
+
return {
|
|
165
|
+
id,
|
|
166
|
+
title: spec.name,
|
|
167
|
+
requirementCount: spec.requirements.length
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return {
|
|
172
|
+
id,
|
|
173
|
+
title: id,
|
|
174
|
+
requirementCount: 0
|
|
175
|
+
};
|
|
156
176
|
}
|
|
157
|
-
return null;
|
|
158
177
|
})
|
|
159
|
-
.filter((spec) => spec !== null)
|
|
160
178
|
.sort((a, b) => a.id.localeCompare(b.id));
|
|
161
179
|
if (options.json) {
|
|
162
180
|
console.log(JSON.stringify(specs, null, 2));
|
|
@@ -203,10 +221,12 @@ export function registerSpecCommand(rootProgram) {
|
|
|
203
221
|
}
|
|
204
222
|
}
|
|
205
223
|
const specPath = join(SPECS_DIR, specId, 'spec.md');
|
|
224
|
+
assertSpecPath(SPECS_DIR, specPath);
|
|
206
225
|
if (!existsSync(specPath)) {
|
|
207
226
|
throw new Error(`Spec '${specId}' not found at openspec/specs/${specId}/spec.md`);
|
|
208
227
|
}
|
|
209
228
|
const validator = new Validator(options.strict);
|
|
229
|
+
assertSpecPath(SPECS_DIR, specPath);
|
|
210
230
|
const report = await validator.validateSpec(specPath);
|
|
211
231
|
if (options.json) {
|
|
212
232
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { Validator } from '../core/validation/validator.js';
|
|
4
|
+
import { VALIDATION_MESSAGES } from '../core/validation/constants.js';
|
|
4
5
|
import { resolveRootForCommand, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../core/root-selection.js';
|
|
5
6
|
import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
|
|
6
7
|
import { getSpecIds } from '../utils/item-discovery.js';
|
|
@@ -144,7 +145,7 @@ export class ValidateCommand {
|
|
|
144
145
|
if (type === 'change') {
|
|
145
146
|
const changeDir = path.join(root.changesDir, id);
|
|
146
147
|
const start = Date.now();
|
|
147
|
-
const report = await validator.validateChangeDeltaSpecs(changeDir);
|
|
148
|
+
const report = await validator.validateChangeDeltaSpecs(changeDir, { mainSpecsDir: root.specsDir });
|
|
148
149
|
const durationMs = Date.now() - start;
|
|
149
150
|
this.printReport('change', id, report, durationMs, opts.json, root);
|
|
150
151
|
// Non-zero exit if invalid (keeps enriched output test semantics)
|
|
@@ -174,12 +175,26 @@ export class ValidateCommand {
|
|
|
174
175
|
const prefix = issue.level === 'ERROR' ? '✗' : issue.level === 'WARNING' ? '⚠' : 'ℹ';
|
|
175
176
|
console.error(`${prefix} [${label}] ${issue.path}: ${issue.message}`);
|
|
176
177
|
}
|
|
177
|
-
this.printNextSteps(type, id, root);
|
|
178
|
+
this.printNextSteps(type, id, root, report.issues);
|
|
178
179
|
}
|
|
179
180
|
}
|
|
180
|
-
printNextSteps(type, id, root) {
|
|
181
|
+
printNextSteps(type, id, root, issues = []) {
|
|
181
182
|
const bullets = [];
|
|
182
|
-
|
|
183
|
+
// The delta-authoring bullets contradict a marker-related error ("add
|
|
184
|
+
// deltas" vs "remove skip_specs or the files"), so branch on the exact
|
|
185
|
+
// marker messages - the generic no-deltas guidance also mentions
|
|
186
|
+
// skip_specs, which must not trigger this.
|
|
187
|
+
const conflictIssue = issues.some(i => i.message.includes(VALIDATION_MESSAGES.CHANGE_SKIP_SPECS_CONFLICT));
|
|
188
|
+
const invalidMarkerIssue = issues.some(i => i.message.includes(VALIDATION_MESSAGES.CHANGE_SKIP_SPECS_INVALID_METADATA));
|
|
189
|
+
if (type === 'change' && conflictIssue) {
|
|
190
|
+
bullets.push('- This change declares skip_specs (no spec deltas): delete the files under specs/, or remove skip_specs from .openspec.yaml if requirements do change');
|
|
191
|
+
bullets.push('- skip_specs is only honored when .openspec.yaml is valid change metadata (schema: <name> naming a known schema is required)');
|
|
192
|
+
}
|
|
193
|
+
else if (type === 'change' && invalidMarkerIssue) {
|
|
194
|
+
bullets.push('- Fix .openspec.yaml so the skip_specs marker can be honored (schema: <name> naming a known schema is required)');
|
|
195
|
+
bullets.push('- Or remove skip_specs from .openspec.yaml and add delta specs instead');
|
|
196
|
+
}
|
|
197
|
+
else if (type === 'change') {
|
|
183
198
|
bullets.push('- Ensure change has deltas in specs/: use headers ## ADDED/MODIFIED/REMOVED/RENAMED Requirements');
|
|
184
199
|
bullets.push('- Each requirement MUST include at least one #### Scenario: block');
|
|
185
200
|
bullets.push(`- Debug parsed deltas: ${withStoreFlag(root, `openspec show ${id} --json --deltas-only`)}`);
|
|
@@ -207,7 +222,7 @@ export class ValidateCommand {
|
|
|
207
222
|
queue.push(async () => {
|
|
208
223
|
const start = Date.now();
|
|
209
224
|
const changeDir = path.join(root.changesDir, id);
|
|
210
|
-
const report = await validator.validateChangeDeltaSpecs(changeDir);
|
|
225
|
+
const report = await validator.validateChangeDeltaSpecs(changeDir, { mainSpecsDir: root.specsDir });
|
|
211
226
|
const durationMs = Date.now() - start;
|
|
212
227
|
return { id, type: 'change', valid: report.valid, issues: report.issues, durationMs };
|
|
213
228
|
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export { statusCommand } from './status.js';
|
|
7
7
|
export type { StatusOptions } from './status.js';
|
|
8
|
-
export { instructionsCommand, applyInstructionsCommand } from './instructions.js';
|
|
8
|
+
export { instructionsCommand, applyInstructionsCommand, archiveInstructionsCommand, } from './instructions.js';
|
|
9
9
|
export type { InstructionsOptions } from './instructions.js';
|
|
10
10
|
export { templatesCommand } from './templates.js';
|
|
11
11
|
export type { TemplatesOptions } from './templates.js';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Commands for the artifact-driven workflow: status, instructions, templates, schemas, new change.
|
|
5
5
|
*/
|
|
6
6
|
export { statusCommand } from './status.js';
|
|
7
|
-
export { instructionsCommand, applyInstructionsCommand } from './instructions.js';
|
|
7
|
+
export { instructionsCommand, applyInstructionsCommand, archiveInstructionsCommand, } 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';
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import { type ArtifactInstructions } from '../../core/artifact-graph/index.js';
|
|
8
8
|
import { type PlanningHome } from '../../core/planning-home.js';
|
|
9
9
|
import { type ReferenceIndexEntry } from '../../core/references.js';
|
|
10
|
-
import { type
|
|
10
|
+
import { type ProjectConfig } from '../../core/project-config.js';
|
|
11
|
+
import { type ApplyInstructions, type ArchiveInstructions } from './shared.js';
|
|
11
12
|
export interface InstructionsOptions {
|
|
12
13
|
change?: string;
|
|
13
14
|
schema?: string;
|
|
@@ -22,11 +23,13 @@ export interface ApplyInstructionsOptions {
|
|
|
22
23
|
storePath?: string;
|
|
23
24
|
json?: boolean;
|
|
24
25
|
}
|
|
26
|
+
export type ArchiveInstructionsOptions = ApplyInstructionsOptions;
|
|
25
27
|
export declare function instructionsCommand(artifactId: string | undefined, options: InstructionsOptions): Promise<void>;
|
|
26
28
|
export declare function printInstructionsText(instructions: ArtifactInstructions, isBlocked: boolean): void;
|
|
27
29
|
export interface GenerateApplyInstructionsOptions {
|
|
28
30
|
planningHome?: PlanningHome;
|
|
29
31
|
references?: ReferenceIndexEntry[];
|
|
32
|
+
projectConfig?: ProjectConfig | null;
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
35
|
* Generates apply instructions for implementing tasks from a change.
|
|
@@ -36,4 +39,7 @@ export interface GenerateApplyInstructionsOptions {
|
|
|
36
39
|
export declare function generateApplyInstructions(projectRoot: string, changeName: string, schemaName?: string, options?: GenerateApplyInstructionsOptions): Promise<ApplyInstructions>;
|
|
37
40
|
export declare function applyInstructionsCommand(options: ApplyInstructionsOptions): Promise<void>;
|
|
38
41
|
export declare function printApplyInstructionsText(instructions: ApplyInstructions): void;
|
|
42
|
+
export declare function generateArchiveInstructions(changeName: string, projectConfig: ProjectConfig | null): ArchiveInstructions;
|
|
43
|
+
export declare function archiveInstructionsCommand(options: ArchiveInstructionsOptions): Promise<void>;
|
|
44
|
+
export declare function printArchiveInstructionsText(instructions: ArchiveInstructions): void;
|
|
39
45
|
//# sourceMappingURL=instructions.d.ts.map
|