@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
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
import ora from 'ora';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
|
-
import { loadChangeContext, generateInstructions, resolveSchema, resolveArtifactOutputs, } from '../../core/artifact-graph/index.js';
|
|
10
|
+
import { loadChangeContext, generateInstructions, resolveSchema, resolveArtifactOutputPath, resolveArtifactOutputs, } from '../../core/artifact-graph/index.js';
|
|
11
11
|
import { getChangeDir, resolveCurrentPlanningHomeSync, } from '../../core/planning-home.js';
|
|
12
12
|
import { resolveRootForCommand, withStoreFlag, toPlanningHome, toRootOutput, } from '../../core/root-selection.js';
|
|
13
13
|
import { assembleReferenceIndex, renderReferencedStoresBlock, renderReferencedStoresSection, } from '../../core/references.js';
|
|
14
14
|
import { readRegistrySnapshot } from '../../core/store/registry.js';
|
|
15
|
-
import { readProjectConfig } from '../../core/project-config.js';
|
|
15
|
+
import { loadOperationInputs, readProjectConfig, } from '../../core/project-config.js';
|
|
16
16
|
import { validateChangeExists, validateSchemaExists, } from './shared.js';
|
|
17
|
+
import { parseTaskLines } from '../../utils/task-progress.js';
|
|
17
18
|
// -----------------------------------------------------------------------------
|
|
18
19
|
// Artifact Instructions Command
|
|
19
20
|
// -----------------------------------------------------------------------------
|
|
@@ -55,10 +56,12 @@ export async function instructionsCommand(artifactId, options) {
|
|
|
55
56
|
if (options.schema) {
|
|
56
57
|
validateSchemaExists(options.schema, projectRoot);
|
|
57
58
|
}
|
|
59
|
+
const { projectConfig, references } = await loadRootConfigContext(root);
|
|
58
60
|
// loadChangeContext will auto-detect schema from metadata if not provided
|
|
59
61
|
const context = loadChangeContext(projectRoot, changeName, options.schema, {
|
|
60
62
|
changeDir: getChangeDir(planningHome, changeName),
|
|
61
63
|
planningHome,
|
|
64
|
+
projectConfig,
|
|
62
65
|
});
|
|
63
66
|
if (!artifactId) {
|
|
64
67
|
spinner?.stop();
|
|
@@ -71,7 +74,6 @@ export async function instructionsCommand(artifactId, options) {
|
|
|
71
74
|
const validIds = context.graph.getAllArtifacts().map((a) => a.id);
|
|
72
75
|
throw new Error(`Artifact '${artifactId}' not found in schema '${context.schemaName}'. Valid artifacts:\n ${validIds.join('\n ')}`);
|
|
73
76
|
}
|
|
74
|
-
const { projectConfig, references } = await loadRootConfigContext(root);
|
|
75
77
|
const instructions = generateInstructions(context, artifactId, projectRoot, {
|
|
76
78
|
projectConfig,
|
|
77
79
|
references,
|
|
@@ -94,6 +96,17 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
94
96
|
// Opening tag
|
|
95
97
|
console.log(`<artifact id="${artifactId}" change="${changeName}" schema="${schemaName}">`);
|
|
96
98
|
console.log();
|
|
99
|
+
// Artifacts skipped via skip_specs get no creation directive: emitting the
|
|
100
|
+
// task/template anyway would prompt an agent to write spec files that
|
|
101
|
+
// validate then rejects as conflicting with the marker.
|
|
102
|
+
if (instructions.skipped) {
|
|
103
|
+
console.log('<warning>');
|
|
104
|
+
console.log(instructions.warning ?? 'This artifact is skipped (skip_specs is set in .openspec.yaml).');
|
|
105
|
+
console.log('</warning>');
|
|
106
|
+
console.log();
|
|
107
|
+
console.log('</artifact>');
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
97
110
|
// Warning for blocked artifacts
|
|
98
111
|
if (isBlocked) {
|
|
99
112
|
const missing = dependencies.filter((d) => !d.done).map((d) => d.id);
|
|
@@ -135,9 +148,18 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
135
148
|
// Dependencies (files to read for context)
|
|
136
149
|
if (dependencies.length > 0) {
|
|
137
150
|
console.log('<dependencies>');
|
|
138
|
-
console.log('Read these files
|
|
151
|
+
console.log('Read the current contents of these files before creating this artifact (re-read them from disk even if you saw them earlier - they may have been edited):');
|
|
139
152
|
console.log();
|
|
140
153
|
for (const dep of dependencies) {
|
|
154
|
+
// A dependency satisfied via skip_specs has no files by design: telling
|
|
155
|
+
// the agent to read them (or calling them "done") would send it hunting
|
|
156
|
+
// for spec files that must not exist.
|
|
157
|
+
if (dep.skipped) {
|
|
158
|
+
console.log(`<dependency id="${dep.id}" status="skipped">`);
|
|
159
|
+
console.log(` <description>Skipped: the change declares skip_specs, so this artifact has no files to read.</description>`);
|
|
160
|
+
console.log('</dependency>');
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
141
163
|
const status = dep.done ? 'done' : 'missing';
|
|
142
164
|
const fullPath = path.join(changeDir, dep.path);
|
|
143
165
|
console.log(`<dependency id="${dep.id}" status="${status}">`);
|
|
@@ -185,25 +207,26 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
185
207
|
// Apply Instructions Command
|
|
186
208
|
// -----------------------------------------------------------------------------
|
|
187
209
|
/**
|
|
188
|
-
*
|
|
210
|
+
* Turns parsed task lines into the listed task items.
|
|
211
|
+
*
|
|
212
|
+
* A checkbox with no text after it is left out of the list: this is work for an
|
|
213
|
+
* agent to act on and tick off, and a bare `- [ ]` gives it nothing to match.
|
|
214
|
+
* It still counts toward progress, which is taken from every parsed line, so
|
|
215
|
+
* this list can be shorter than the totals beside it but never disagrees with
|
|
216
|
+
* `openspec list` or archive about how much work is left. An empty list is also
|
|
217
|
+
* what puts apply in its "nothing to work on" state, so a file of nothing but
|
|
218
|
+
* text-less checkboxes asks to be rewritten instead of being called done.
|
|
189
219
|
*/
|
|
190
|
-
function
|
|
220
|
+
function toTaskItems(parsed) {
|
|
191
221
|
const tasks = [];
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const description = checkboxMatch[2].trim();
|
|
201
|
-
tasks.push({
|
|
202
|
-
id: `${taskIndex}`,
|
|
203
|
-
description,
|
|
204
|
-
done,
|
|
205
|
-
});
|
|
206
|
-
}
|
|
222
|
+
for (const task of parsed) {
|
|
223
|
+
if (task.description.length === 0)
|
|
224
|
+
continue;
|
|
225
|
+
tasks.push({
|
|
226
|
+
id: `${tasks.length + 1}`,
|
|
227
|
+
description: task.description,
|
|
228
|
+
done: task.done,
|
|
229
|
+
});
|
|
207
230
|
}
|
|
208
231
|
return tasks;
|
|
209
232
|
}
|
|
@@ -219,6 +242,7 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
219
242
|
const context = loadChangeContext(projectRoot, changeName, schemaName, {
|
|
220
243
|
changeDir: getChangeDir(planningHome, changeName),
|
|
221
244
|
planningHome,
|
|
245
|
+
projectConfig: options.projectConfig,
|
|
222
246
|
});
|
|
223
247
|
const changeDir = context.changeDir;
|
|
224
248
|
// Get the full schema to access the apply phase configuration
|
|
@@ -229,9 +253,15 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
229
253
|
const requiredArtifactIds = applyConfig?.requires ?? schema.artifacts.map((a) => a.id);
|
|
230
254
|
const tracksFile = applyConfig?.tracks ?? null;
|
|
231
255
|
const schemaInstruction = applyConfig?.instruction ?? null;
|
|
232
|
-
|
|
256
|
+
const operationInputs = loadOperationInputs(options.projectConfig ?? null, 'apply');
|
|
257
|
+
// Check which required artifacts are missing. Artifacts the change skips
|
|
258
|
+
// via skip_specs count as present - their files must not exist, and
|
|
259
|
+
// status already reports them complete, so apply cannot block on them.
|
|
233
260
|
const missingArtifacts = [];
|
|
234
261
|
for (const artifactId of requiredArtifactIds) {
|
|
262
|
+
if (context.skippedArtifacts?.has(artifactId)) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
235
265
|
const artifact = schema.artifacts.find((a) => a.id === artifactId);
|
|
236
266
|
if (artifact && resolveArtifactOutputs(changeDir, artifact.generates).length === 0) {
|
|
237
267
|
missingArtifacts.push(artifactId);
|
|
@@ -246,19 +276,21 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
246
276
|
}
|
|
247
277
|
}
|
|
248
278
|
// Parse tasks if tracking file exists
|
|
249
|
-
let
|
|
279
|
+
let parsedTasks = [];
|
|
250
280
|
let tracksFileExists = false;
|
|
251
281
|
if (tracksFile) {
|
|
252
|
-
const tracksPath =
|
|
282
|
+
const tracksPath = resolveArtifactOutputPath(changeDir, tracksFile);
|
|
253
283
|
tracksFileExists = fs.existsSync(tracksPath);
|
|
254
284
|
if (tracksFileExists) {
|
|
255
285
|
const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
|
|
256
|
-
|
|
286
|
+
parsedTasks = parseTaskLines(tasksContent);
|
|
257
287
|
}
|
|
258
288
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
289
|
+
const tasks = toTaskItems(parsedTasks);
|
|
290
|
+
// Calculate progress over every checkbox in the file, listed or not, so these
|
|
291
|
+
// numbers match `openspec list` and archive's incomplete-task check.
|
|
292
|
+
const total = parsedTasks.length;
|
|
293
|
+
const complete = parsedTasks.filter((task) => task.done).length;
|
|
262
294
|
const remaining = total - complete;
|
|
263
295
|
// Determine state and instruction
|
|
264
296
|
let state;
|
|
@@ -273,11 +305,12 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
273
305
|
state = 'blocked';
|
|
274
306
|
instruction = `The ${tracksFilename} file is missing and must be created.\nUse openspec-continue-change to generate the tracking file.`;
|
|
275
307
|
}
|
|
276
|
-
else if (tracksFile && tracksFileExists &&
|
|
277
|
-
// Tracking file exists but
|
|
308
|
+
else if (tracksFile && tracksFileExists && tasks.length === 0) {
|
|
309
|
+
// Tracking file exists but lists nothing an agent can work on: either no
|
|
310
|
+
// checkboxes at all, or only checkboxes with no text after them.
|
|
278
311
|
const tracksFilename = path.basename(tracksFile);
|
|
279
312
|
state = 'blocked';
|
|
280
|
-
instruction = `The ${tracksFilename} file exists but contains no tasks.\nAdd tasks to ${tracksFilename} or regenerate it with openspec-continue-change.`;
|
|
313
|
+
instruction = `The ${tracksFilename} file exists but contains no tasks to work on.\nAdd tasks to ${tracksFilename} or regenerate it with openspec-continue-change.`;
|
|
281
314
|
}
|
|
282
315
|
else if (tracksFile && remaining === 0 && total > 0) {
|
|
283
316
|
state = 'all_done';
|
|
@@ -303,6 +336,7 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
303
336
|
missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
|
|
304
337
|
instruction,
|
|
305
338
|
...(references !== undefined ? { references } : {}),
|
|
339
|
+
...operationInputs,
|
|
306
340
|
};
|
|
307
341
|
}
|
|
308
342
|
export async function applyInstructionsCommand(options) {
|
|
@@ -320,11 +354,13 @@ export async function applyInstructionsCommand(options) {
|
|
|
320
354
|
if (options.schema) {
|
|
321
355
|
validateSchemaExists(options.schema, projectRoot);
|
|
322
356
|
}
|
|
323
|
-
//
|
|
324
|
-
|
|
357
|
+
// One parsed config snapshot supplies schema fallback, references, context,
|
|
358
|
+
// and operation guidance for this command.
|
|
359
|
+
const { projectConfig, references } = await loadRootConfigContext(root);
|
|
325
360
|
const instructions = await generateApplyInstructions(projectRoot, changeName, options.schema, {
|
|
326
361
|
planningHome,
|
|
327
362
|
references,
|
|
363
|
+
projectConfig,
|
|
328
364
|
});
|
|
329
365
|
spinner?.stop();
|
|
330
366
|
if (options.json) {
|
|
@@ -389,5 +425,57 @@ export function printApplyInstructionsText(instructions) {
|
|
|
389
425
|
// Instruction
|
|
390
426
|
console.log('### Instruction');
|
|
391
427
|
console.log(instruction);
|
|
428
|
+
console.log();
|
|
429
|
+
printOperationInputsText(instructions);
|
|
430
|
+
}
|
|
431
|
+
export function generateArchiveInstructions(changeName, projectConfig) {
|
|
432
|
+
return {
|
|
433
|
+
changeName,
|
|
434
|
+
...loadOperationInputs(projectConfig, 'archive'),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
export async function archiveInstructionsCommand(options) {
|
|
438
|
+
const root = await resolveRootForCommand(options, { json: options.json });
|
|
439
|
+
if (!root) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const spinner = options.json ? undefined : ora('Loading archive inputs...').start();
|
|
443
|
+
try {
|
|
444
|
+
const changeName = await validateChangeExists(options.change, root.path, root.changesDir, { newChangeHint: withStoreFlag(root, 'openspec new change <name>') });
|
|
445
|
+
const projectConfig = readProjectConfig(root.path);
|
|
446
|
+
const instructions = generateArchiveInstructions(changeName, projectConfig);
|
|
447
|
+
spinner?.stop();
|
|
448
|
+
if (options.json) {
|
|
449
|
+
console.log(JSON.stringify({ ...instructions, root: toRootOutput(root) }, null, 2));
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
printArchiveInstructionsText(instructions);
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
spinner?.stop();
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
export function printArchiveInstructionsText(instructions) {
|
|
460
|
+
console.log(`## Archive Inputs: ${instructions.changeName}`);
|
|
461
|
+
console.log();
|
|
462
|
+
printOperationInputsText(instructions);
|
|
463
|
+
}
|
|
464
|
+
function printOperationInputsText(inputs) {
|
|
465
|
+
if (inputs.context) {
|
|
466
|
+
console.log('### Project Context (required instruction input)');
|
|
467
|
+
console.log(inputs.context);
|
|
468
|
+
console.log();
|
|
469
|
+
}
|
|
470
|
+
if (inputs.operationGuidance && inputs.operationGuidance.length > 0) {
|
|
471
|
+
console.log('### Operation Guidance (advisory)');
|
|
472
|
+
for (const guidance of inputs.operationGuidance) {
|
|
473
|
+
console.log(`- ${guidance}`);
|
|
474
|
+
}
|
|
475
|
+
console.log();
|
|
476
|
+
}
|
|
477
|
+
if (!inputs.context && !inputs.operationGuidance) {
|
|
478
|
+
console.log('No project context or operation guidance configured.');
|
|
479
|
+
}
|
|
392
480
|
}
|
|
393
481
|
//# sourceMappingURL=instructions.js.map
|
|
@@ -33,6 +33,17 @@ export interface ApplyInstructions {
|
|
|
33
33
|
instruction: string;
|
|
34
34
|
/** Referenced-store index (read-only upstream context; omitted when none declared) */
|
|
35
35
|
references?: ReferenceIndexEntry[];
|
|
36
|
+
/** Current project background from the selected root. */
|
|
37
|
+
context?: string;
|
|
38
|
+
/** Current advisory guidance for apply. */
|
|
39
|
+
operationGuidance?: string[];
|
|
40
|
+
}
|
|
41
|
+
export interface ArchiveInstructions {
|
|
42
|
+
changeName: string;
|
|
43
|
+
/** Current project background from the selected root. */
|
|
44
|
+
context?: string;
|
|
45
|
+
/** Current advisory guidance for archive. */
|
|
46
|
+
operationGuidance?: string[];
|
|
36
47
|
}
|
|
37
48
|
export declare const DEFAULT_SCHEMA = "spec-driven";
|
|
38
49
|
export declare function printJson(payload: unknown): void;
|
|
@@ -44,11 +55,11 @@ export declare function isColorDisabled(): boolean;
|
|
|
44
55
|
/**
|
|
45
56
|
* Gets the color function based on status.
|
|
46
57
|
*/
|
|
47
|
-
export declare function getStatusColor(status: 'done' | 'ready' | 'blocked'): (text: string) => string;
|
|
58
|
+
export declare function getStatusColor(status: 'done' | 'skipped' | 'ready' | 'blocked'): (text: string) => string;
|
|
48
59
|
/**
|
|
49
60
|
* Gets the status indicator for an artifact.
|
|
50
61
|
*/
|
|
51
|
-
export declare function getStatusIndicator(status: 'done' | 'ready' | 'blocked'): string;
|
|
62
|
+
export declare function getStatusIndicator(status: 'done' | 'skipped' | 'ready' | 'blocked'): string;
|
|
52
63
|
/**
|
|
53
64
|
* Returns the list of available change directory names under openspec/changes/.
|
|
54
65
|
* Excludes the archive directory and hidden directories.
|
|
@@ -9,7 +9,6 @@ import path from 'path';
|
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import { getSchemaDir, listSchemas } from '../../core/artifact-graph/index.js';
|
|
11
11
|
import { isRootSelectionError } from '../../core/root-selection.js';
|
|
12
|
-
import { validateChangeName } from '../../utils/change-utils.js';
|
|
13
12
|
// -----------------------------------------------------------------------------
|
|
14
13
|
// Constants
|
|
15
14
|
// -----------------------------------------------------------------------------
|
|
@@ -46,6 +45,8 @@ export function getStatusColor(status) {
|
|
|
46
45
|
switch (status) {
|
|
47
46
|
case 'done':
|
|
48
47
|
return chalk.green;
|
|
48
|
+
case 'skipped':
|
|
49
|
+
return chalk.gray;
|
|
49
50
|
case 'ready':
|
|
50
51
|
return chalk.yellow;
|
|
51
52
|
case 'blocked':
|
|
@@ -60,6 +61,8 @@ export function getStatusIndicator(status) {
|
|
|
60
61
|
switch (status) {
|
|
61
62
|
case 'done':
|
|
62
63
|
return color('[x]');
|
|
64
|
+
case 'skipped':
|
|
65
|
+
return color('[~]');
|
|
63
66
|
case 'ready':
|
|
64
67
|
return color('[ ]');
|
|
65
68
|
case 'blocked':
|
|
@@ -84,6 +87,33 @@ export async function getAvailableChanges(projectRoot, changesDir = path.join(pr
|
|
|
84
87
|
throw error;
|
|
85
88
|
}
|
|
86
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Validates a change name used to look up an existing change directory.
|
|
92
|
+
* Lookup accepts any directory name that `getAvailableChanges` could return
|
|
93
|
+
* (the kebab-case convention in `validateChangeName` applies at creation
|
|
94
|
+
* time only); it only rejects names that would escape the changes directory
|
|
95
|
+
* or address entries `getAvailableChanges` excludes (hidden dirs, archive).
|
|
96
|
+
*
|
|
97
|
+
* @returns An error message, or undefined if the name is safe to look up
|
|
98
|
+
*/
|
|
99
|
+
function validateChangeLookupName(changeName) {
|
|
100
|
+
if (changeName === '.' || changeName === '..') {
|
|
101
|
+
return 'Change name cannot be a relative path segment';
|
|
102
|
+
}
|
|
103
|
+
if (changeName.includes('/') || changeName.includes('\\')) {
|
|
104
|
+
return 'Change name cannot contain path separators';
|
|
105
|
+
}
|
|
106
|
+
if (changeName.includes('\0')) {
|
|
107
|
+
return 'Change name cannot contain null characters';
|
|
108
|
+
}
|
|
109
|
+
if (changeName.startsWith('.')) {
|
|
110
|
+
return 'Change name cannot start with a dot';
|
|
111
|
+
}
|
|
112
|
+
if (changeName === 'archive') {
|
|
113
|
+
return "'archive' is reserved for archived changes";
|
|
114
|
+
}
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
87
117
|
/**
|
|
88
118
|
* Validates that a change exists and returns available changes if not.
|
|
89
119
|
* Checks directory existence directly to support scaffolded changes (without proposal.md).
|
|
@@ -100,9 +130,9 @@ export async function validateChangeExists(changeName, projectRoot, changesDir =
|
|
|
100
130
|
throw new Error(`Missing required option --change. Available changes:\n ${available.join('\n ')}`);
|
|
101
131
|
}
|
|
102
132
|
// Validate change name format to prevent path traversal
|
|
103
|
-
const
|
|
104
|
-
if (
|
|
105
|
-
throw new Error(`Invalid change name '${changeName}': ${
|
|
133
|
+
const lookupError = validateChangeLookupName(changeName);
|
|
134
|
+
if (lookupError) {
|
|
135
|
+
throw new Error(`Invalid change name '${changeName}': ${lookupError}`);
|
|
106
136
|
}
|
|
107
137
|
// Check directory existence directly
|
|
108
138
|
const changePath = path.join(changesDir, changeName);
|
|
@@ -67,26 +67,31 @@ export async function statusCommand(options) {
|
|
|
67
67
|
}
|
|
68
68
|
export function printStatusText(status) {
|
|
69
69
|
const doneCount = status.artifacts.filter((a) => a.status === 'done').length;
|
|
70
|
-
const
|
|
70
|
+
const skippedCount = status.artifacts.filter((a) => a.status === 'skipped').length;
|
|
71
|
+
const total = status.artifacts.length - skippedCount;
|
|
71
72
|
console.log(`Change: ${status.changeName}`);
|
|
72
73
|
console.log(`Schema: ${status.schemaName}`);
|
|
73
74
|
if (status.changeRoot) {
|
|
74
75
|
console.log(`Change root: ${status.changeRoot}`);
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
+
const skippedSuffix = skippedCount > 0 ? ` (${skippedCount} skipped)` : '';
|
|
78
|
+
console.log(`Progress: ${doneCount}/${total} artifacts complete${skippedSuffix}`);
|
|
77
79
|
console.log();
|
|
78
80
|
for (const artifact of status.artifacts) {
|
|
79
81
|
const indicator = getStatusIndicator(artifact.status);
|
|
80
82
|
const color = getStatusColor(artifact.status);
|
|
81
83
|
let line = `${indicator} ${artifact.id}`;
|
|
84
|
+
if (artifact.status === 'skipped') {
|
|
85
|
+
line += color(' (skipped: change declares skip_specs)');
|
|
86
|
+
}
|
|
82
87
|
if (artifact.status === 'blocked' && artifact.missingDeps && artifact.missingDeps.length > 0) {
|
|
83
88
|
line += color(` (blocked by: ${artifact.missingDeps.join(', ')})`);
|
|
84
89
|
}
|
|
85
90
|
console.log(line);
|
|
86
91
|
}
|
|
87
|
-
if (status.
|
|
92
|
+
if (status.isPlanningComplete) {
|
|
88
93
|
console.log();
|
|
89
|
-
console.log(chalk.green('All artifacts complete!'));
|
|
94
|
+
console.log(chalk.green('All planning artifacts complete!'));
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
//# sourceMappingURL=status.js.map
|
|
@@ -39,11 +39,21 @@ export async function templatesCommand(options) {
|
|
|
39
39
|
else {
|
|
40
40
|
source = 'package';
|
|
41
41
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
templatePath
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
const templatesDir = path.join(schemaDir, 'templates');
|
|
43
|
+
const templates = graph.getAllArtifacts().map((artifact) => {
|
|
44
|
+
const templatePath = path.join(templatesDir, artifact.template);
|
|
45
|
+
try {
|
|
46
|
+
FileSystemUtils.assertPathWithin(templatesDir, templatePath);
|
|
47
|
+
return {
|
|
48
|
+
artifactId: artifact.id,
|
|
49
|
+
templatePath: FileSystemUtils.canonicalizeExistingPath(templatePath),
|
|
50
|
+
source,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`Template '${artifact.template}' for artifact '${artifact.id}' points outside the schema templates directory`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
47
57
|
spinner?.stop();
|
|
48
58
|
if (options.json) {
|
|
49
59
|
const output = {};
|
package/dist/core/archive.d.ts
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when the ONLY thing wrong with a rebuilt spec is that it has no
|
|
3
|
+
* requirements. That is the exact failure retiring a capability replaces
|
|
4
|
+
* (#1302); anything else means the spec is broken in a way the author still has
|
|
5
|
+
* to fix, so archive must abort exactly as it always did instead of retiring.
|
|
6
|
+
*
|
|
7
|
+
* Asking the validator - rather than counting requirement blocks a second time -
|
|
8
|
+
* is what makes "this spec could not have been written anyway" true by
|
|
9
|
+
* construction. The two counts genuinely disagree: `MarkdownParser` accepts any
|
|
10
|
+
* `###` heading under `## Requirements` as a requirement, while the delta block
|
|
11
|
+
* parser only indexes canonical `### Requirement:` headers and sweeps the rest
|
|
12
|
+
* into the preamble, which survives into the rebuilt spec.
|
|
13
|
+
*/
|
|
14
|
+
export declare function isRetirableSpec(specName: string, rebuilt: string): Promise<boolean>;
|
|
1
15
|
export interface ArchiveOptions {
|
|
2
16
|
yes?: boolean;
|
|
3
17
|
skipSpecs?: boolean;
|
|
@@ -17,6 +31,5 @@ export declare class ArchiveCommand {
|
|
|
17
31
|
*/
|
|
18
32
|
private run;
|
|
19
33
|
private selectChange;
|
|
20
|
-
private getArchiveDate;
|
|
21
34
|
}
|
|
22
35
|
//# sourceMappingURL=archive.d.ts.map
|