@fission-ai/openspec 1.13.1 → 1.14.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 +15 -3
- package/dist/cli/index.js +39 -1
- package/dist/commands/spec.js +1 -0
- package/dist/commands/workflow/instructions.js +66 -18
- package/dist/commands/workflow/shared.d.ts +7 -0
- package/dist/commands/workflow/status.js +15 -2
- package/dist/core/archive.js +258 -78
- package/dist/core/artifact-graph/instruction-loader.d.ts +7 -0
- package/dist/core/artifact-graph/instruction-loader.js +18 -1
- package/dist/core/artifact-graph/outputs.d.ts +1 -1
- package/dist/core/artifact-graph/outputs.js +38 -8
- package/dist/core/change-metadata/schema.d.ts +3 -0
- package/dist/core/change-metadata/schema.js +10 -0
- package/dist/core/change-status-policy.d.ts +8 -0
- package/dist/core/change-status-policy.js +33 -2
- package/dist/core/command-generation/adapters/atomcode.d.ts +24 -0
- package/dist/core/command-generation/adapters/atomcode.js +46 -0
- package/dist/core/command-generation/adapters/codestudio.d.ts +13 -0
- package/dist/core/command-generation/adapters/codestudio.js +27 -0
- package/dist/core/command-generation/adapters/continue.js +4 -0
- package/dist/core/command-generation/adapters/easycode.d.ts +17 -0
- package/dist/core/command-generation/adapters/easycode.js +28 -0
- package/dist/core/command-generation/adapters/gemini.js +1 -35
- package/dist/core/command-generation/adapters/gigacode.d.ts +17 -0
- package/dist/core/command-generation/adapters/gigacode.js +31 -0
- package/dist/core/command-generation/adapters/index.d.ts +4 -0
- package/dist/core/command-generation/adapters/index.js +4 -0
- package/dist/core/command-generation/adapters/kilocode.d.ts +1 -1
- package/dist/core/command-generation/adapters/kilocode.js +2 -2
- package/dist/core/command-generation/registry.js +8 -0
- package/dist/core/command-generation/toml.d.ts +19 -0
- package/dist/core/command-generation/toml.js +39 -0
- package/dist/core/command-surface.js +1 -1
- package/dist/core/completions/command-registry.js +19 -0
- package/dist/core/completions/installers/zsh-installer.js +4 -2
- package/dist/core/config-prompts.js +6 -6
- package/dist/core/config.js +11 -1
- package/dist/core/init.js +44 -14
- package/dist/core/legacy-cleanup.d.ts +1 -1
- package/dist/core/legacy-cleanup.js +40 -11
- package/dist/core/list.d.ts +2 -0
- package/dist/core/list.js +49 -23
- package/dist/core/parsers/markdown-parser.js +5 -0
- package/dist/core/parsers/requirement-blocks.d.ts +40 -3
- package/dist/core/parsers/requirement-blocks.js +74 -25
- package/dist/core/project-config.js +45 -1
- package/dist/core/references.js +3 -1
- package/dist/core/root-selection.d.ts +6 -0
- package/dist/core/root-selection.js +16 -0
- package/dist/core/schemas/base.schema.d.ts +3 -0
- package/dist/core/schemas/base.schema.js +6 -0
- package/dist/core/schemas/change.schema.d.ts +8 -0
- package/dist/core/schemas/spec.schema.d.ts +2 -0
- package/dist/core/specs-apply.d.ts +20 -0
- package/dist/core/specs-apply.js +19 -6
- package/dist/core/templates/workflows/apply-change.js +5 -2
- package/dist/core/templates/workflows/archive-change.js +56 -20
- package/dist/core/templates/workflows/bulk-archive-change.js +34 -16
- package/dist/core/templates/workflows/continue-change.js +0 -2
- package/dist/core/templates/workflows/explore.js +2 -2
- package/dist/core/templates/workflows/ff-change.js +2 -2
- package/dist/core/templates/workflows/onboard.js +9 -4
- package/dist/core/templates/workflows/update-change.js +21 -9
- package/dist/core/templates/workflows/verify-change.js +115 -52
- package/dist/core/update.js +20 -7
- package/dist/core/validation/constants.d.ts +1 -1
- package/dist/core/validation/constants.js +1 -1
- package/dist/core/validation/purpose-placeholder.js +24 -9
- package/dist/core/validation/validator.js +15 -6
- package/dist/core/version-check.d.ts +23 -1
- package/dist/core/version-check.js +112 -26
- package/dist/core/view.d.ts +2 -0
- package/dist/core/view.js +62 -4
- package/dist/utils/change-metadata.d.ts +20 -1
- package/dist/utils/change-metadata.js +57 -1
- package/dist/utils/command-references.d.ts +1 -1
- package/dist/utils/command-references.js +2 -2
- package/dist/utils/file-system.js +20 -3
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/line-endings.d.ts +34 -0
- package/dist/utils/line-endings.js +46 -0
- package/package.json +2 -2
- package/schemas/spec-driven/schema.yaml +10 -1
- package/schemas/spec-driven/templates/proposal.md +6 -3
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ Solo, OpenSpec keeps you and your AI honest on a single repo. On a team, the har
|
|
|
122
122
|
|
|
123
123
|
## Quick Start
|
|
124
124
|
|
|
125
|
-
**Requires Node.js 20.19.0 or higher.**
|
|
125
|
+
**Requires Node.js 20.19.0 or higher.** Homebrew installs it as a dependency.
|
|
126
126
|
|
|
127
127
|
Install OpenSpec globally:
|
|
128
128
|
|
|
@@ -130,6 +130,12 @@ Install OpenSpec globally:
|
|
|
130
130
|
npm install -g @fission-ai/openspec@latest
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
Or install the official [Homebrew formula](https://formulae.brew.sh/formula/openspec) on macOS or Linux:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
brew install openspec
|
|
137
|
+
```
|
|
138
|
+
|
|
133
139
|
Then navigate to your project directory and initialize:
|
|
134
140
|
|
|
135
141
|
```bash
|
|
@@ -137,7 +143,7 @@ cd your-project
|
|
|
137
143
|
openspec init
|
|
138
144
|
```
|
|
139
145
|
|
|
140
|
-
> **Want your AI to do it?** Paste the [setup prompt](docs/installation.md#install-with-your-ai-assistant) into your coding assistant — it installs the CLI, runs `openspec init`, and verifies the result.
|
|
146
|
+
> **Want your AI to do it?** Paste the [setup prompt](docs-lab/start/installation.md#install-with-your-ai-assistant) into your coding assistant — it installs the CLI, runs `openspec init`, and verifies the result.
|
|
141
147
|
|
|
142
148
|
Now talk to your AI:
|
|
143
149
|
|
|
@@ -151,7 +157,7 @@ Both are in the default profile. If you want the expanded workflow (`/opsx:new`,
|
|
|
151
157
|
> [!NOTE]
|
|
152
158
|
> Not sure if your tool is supported? [View the full list](docs/supported-tools.md) – we support 30+ tools and growing.
|
|
153
159
|
>
|
|
154
|
-
> Also works with pnpm, yarn, bun, and
|
|
160
|
+
> Also works with Homebrew, pnpm, yarn, bun, and Nix. [See installation options](docs-lab/start/installation.md).
|
|
155
161
|
|
|
156
162
|
## Docs
|
|
157
163
|
|
|
@@ -208,6 +214,12 @@ AI coding assistants are powerful but unpredictable when requirements live only
|
|
|
208
214
|
npm install -g @fission-ai/openspec@latest
|
|
209
215
|
```
|
|
210
216
|
|
|
217
|
+
If you installed OpenSpec with Homebrew:
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
brew upgrade openspec
|
|
221
|
+
```
|
|
222
|
+
|
|
211
223
|
**Refresh agent instructions**
|
|
212
224
|
|
|
213
225
|
Run this inside each project to regenerate AI guidance and ensure the latest slash commands are active:
|
package/dist/cli/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'url';
|
|
|
7
7
|
import { existsSync, promises as fs } from 'fs';
|
|
8
8
|
import { AI_TOOLS, TOOL_ID_ALIASES } from '../core/config.js';
|
|
9
9
|
import { UpdateCommand } from '../core/update.js';
|
|
10
|
-
import { getAvailableCliUpdate, displayCliUpdateNote, shouldOfferUpgrade, getInstallDir, offerCliUpgrade, rerunUpdateWithUpgradedCli, displayUpgradeCommand, isSourceCheckout, } from '../core/version-check.js';
|
|
10
|
+
import { getAvailableCliUpdate, displayCliUpdateNote, shouldOfferUpgrade, getInstallDir, offerCliUpgrade, rerunUpdateWithUpgradedCli, displayUpgradeCommand, isSourceCheckout, checkForCliUpdate, getCliInstallInfo, getCliUpdateCommand, canSelfUpgrade, buildVersionReportLines, } from '../core/version-check.js';
|
|
11
11
|
import { ListCommand } from '../core/list.js';
|
|
12
12
|
import { ArchiveCommand } from '../core/archive.js';
|
|
13
13
|
import { ViewCommand } from '../core/view.js';
|
|
@@ -118,6 +118,37 @@ program
|
|
|
118
118
|
.version(version);
|
|
119
119
|
// Global options
|
|
120
120
|
program.option('--no-color', 'Disable color output');
|
|
121
|
+
program
|
|
122
|
+
.command('version')
|
|
123
|
+
.description('Report the installed OpenSpec version and update availability')
|
|
124
|
+
.option('--json', 'Output as JSON')
|
|
125
|
+
.option('--check', 'Check the registry for a newer version')
|
|
126
|
+
.action(async (options) => {
|
|
127
|
+
const install = getCliInstallInfo();
|
|
128
|
+
const update = options.check ? await checkForCliUpdate() : undefined;
|
|
129
|
+
const command = update?.status === 'available' ? getCliUpdateCommand(install) : null;
|
|
130
|
+
const output = {
|
|
131
|
+
schemaVersion: 1,
|
|
132
|
+
version,
|
|
133
|
+
install,
|
|
134
|
+
...(update
|
|
135
|
+
? {
|
|
136
|
+
update: {
|
|
137
|
+
...update,
|
|
138
|
+
command,
|
|
139
|
+
canSelfUpgrade: update.status === 'available'
|
|
140
|
+
? canSelfUpgrade(install.location, process.cwd())
|
|
141
|
+
: false,
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
: {}),
|
|
145
|
+
};
|
|
146
|
+
if (options.json) {
|
|
147
|
+
console.log(JSON.stringify(output, null, 2));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log(buildVersionReportLines(version, install, update, command).join('\n'));
|
|
151
|
+
});
|
|
121
152
|
// Apply global flags and telemetry before any command runs
|
|
122
153
|
// Note: preAction receives (thisCommand, actionCommand) where:
|
|
123
154
|
// - thisCommand: the command where hook was added (root program)
|
|
@@ -296,12 +327,17 @@ program
|
|
|
296
327
|
.description('List items (changes by default). Use --specs to list specs.')
|
|
297
328
|
.option('--specs', 'List specs instead of changes')
|
|
298
329
|
.option('--changes', 'List changes explicitly (default)')
|
|
330
|
+
.option('--archived', 'Show only archived changes')
|
|
331
|
+
.option('--all', 'Show both active and archived changes')
|
|
299
332
|
.option('--sort <order>', 'Sort order: "recent" (default) or "name"', 'recent')
|
|
300
333
|
.option('--json', 'Output as JSON (for programmatic use)')
|
|
301
334
|
.option('--store <id>', STORE_OPTION_DESCRIPTION)
|
|
302
335
|
.addOption(hiddenStorePathOption())
|
|
303
336
|
.action(async (options) => {
|
|
304
337
|
try {
|
|
338
|
+
if (options?.specs && (options.archived || options.all)) {
|
|
339
|
+
throw new Error('--archived and --all can only be used when listing changes.');
|
|
340
|
+
}
|
|
305
341
|
const root = await resolveRootForCommand(options ?? {}, {
|
|
306
342
|
json: options?.json,
|
|
307
343
|
failurePayload: options?.specs ? { specs: [], root: null } : { changes: [], root: null },
|
|
@@ -318,6 +354,8 @@ program
|
|
|
318
354
|
await listCommand.execute(root.path, mode, {
|
|
319
355
|
sort,
|
|
320
356
|
json: options?.json,
|
|
357
|
+
archived: options?.archived,
|
|
358
|
+
all: options?.all,
|
|
321
359
|
...(options?.json ? { root: toRootOutput(root) } : {}),
|
|
322
360
|
});
|
|
323
361
|
}
|
package/dist/commands/spec.js
CHANGED
|
@@ -45,6 +45,7 @@ function filterSpec(spec, options) {
|
|
|
45
45
|
const filteredRequirements = (requirementIndex !== undefined
|
|
46
46
|
? [spec.requirements[requirementIndex]]
|
|
47
47
|
: spec.requirements).map(req => ({
|
|
48
|
+
name: req.name,
|
|
48
49
|
text: req.text,
|
|
49
50
|
scenarios: includeScenarios ? req.scenarios : [],
|
|
50
51
|
}));
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import ora from 'ora';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
|
-
import { loadChangeContext, generateInstructions, resolveSchema,
|
|
10
|
+
import { loadChangeContext, generateInstructions, resolveSchema, resolveArtifactOutputs, } from '../../core/artifact-graph/index.js';
|
|
11
11
|
import { isSpecsArtifactPath } from '../../core/artifact-graph/outputs.js';
|
|
12
12
|
import { findUnreadDeltaFiles } from '../../utils/spec-discovery.js';
|
|
13
13
|
import { getChangeDir, resolveCurrentPlanningHomeSync, } from '../../core/planning-home.js';
|
|
@@ -103,6 +103,14 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
103
103
|
` change="${escapeEnvelopeAttribute(changeName)}"` +
|
|
104
104
|
` schema="${escapeEnvelopeAttribute(schemaName)}">`);
|
|
105
105
|
console.log();
|
|
106
|
+
if (instructions.warnings) {
|
|
107
|
+
for (const warning of instructions.warnings) {
|
|
108
|
+
console.log('<warning>');
|
|
109
|
+
console.log(escapeEnvelopeTags(warning));
|
|
110
|
+
console.log('</warning>');
|
|
111
|
+
console.log();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
106
114
|
// Artifacts skipped via skip_specs get no creation directive: emitting the
|
|
107
115
|
// task/template anyway would prompt an agent to write spec files that
|
|
108
116
|
// validate then rejects as conflicting with the marker.
|
|
@@ -215,9 +223,16 @@ export function printInstructionsText(instructions, isBlocked) {
|
|
|
215
223
|
// Closing tag
|
|
216
224
|
console.log('</artifact>');
|
|
217
225
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
226
|
+
/** Adds one-based source locations to parsed tasks without changing task parsing. */
|
|
227
|
+
function parseLocatedTasks(content, sourcePath) {
|
|
228
|
+
const tasks = [];
|
|
229
|
+
for (const [index, line] of content.split('\n').entries()) {
|
|
230
|
+
const [task] = parseTaskLines(line);
|
|
231
|
+
if (task)
|
|
232
|
+
tasks.push({ ...task, sourcePath, line: index + 1 });
|
|
233
|
+
}
|
|
234
|
+
return tasks;
|
|
235
|
+
}
|
|
221
236
|
/**
|
|
222
237
|
* Turns parsed task lines into the listed task items.
|
|
223
238
|
*
|
|
@@ -238,6 +253,8 @@ function toTaskItems(parsed) {
|
|
|
238
253
|
id: `${tasks.length + 1}`,
|
|
239
254
|
description: task.description,
|
|
240
255
|
done: task.done,
|
|
256
|
+
sourcePath: task.sourcePath,
|
|
257
|
+
line: task.line,
|
|
241
258
|
});
|
|
242
259
|
}
|
|
243
260
|
return tasks;
|
|
@@ -394,15 +411,28 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
394
411
|
contextFiles[artifact.id] = outputs;
|
|
395
412
|
}
|
|
396
413
|
}
|
|
397
|
-
// Parse
|
|
414
|
+
// Parse every concrete file matched by apply.tracks. A tracking path may be
|
|
415
|
+
// a glob owned by an artifact with any ID, so treating it as one literal
|
|
416
|
+
// path loses task evidence for valid custom schemas.
|
|
398
417
|
let parsedTasks = [];
|
|
418
|
+
const unavailableTrackingFiles = [];
|
|
399
419
|
let tracksFileExists = false;
|
|
400
420
|
if (tracksFile) {
|
|
401
|
-
const
|
|
402
|
-
tracksFileExists =
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
421
|
+
const tracksPaths = resolveArtifactOutputs(changeDir, tracksFile);
|
|
422
|
+
tracksFileExists = tracksPaths.length > 0;
|
|
423
|
+
for (const tracksPath of tracksPaths) {
|
|
424
|
+
try {
|
|
425
|
+
const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
|
|
426
|
+
parsedTasks.push(...parseLocatedTasks(tasksContent, tracksPath));
|
|
427
|
+
}
|
|
428
|
+
catch (error) {
|
|
429
|
+
const code = error?.code;
|
|
430
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
431
|
+
unavailableTrackingFiles.push({
|
|
432
|
+
path: tracksPath,
|
|
433
|
+
reason: code && !message.includes(code) ? `${code}: ${message}` : message,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
406
436
|
}
|
|
407
437
|
}
|
|
408
438
|
const tasks = toTaskItems(parsedTasks);
|
|
@@ -435,6 +465,10 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
435
465
|
`The ${tracksFilename} file is missing and must be created.` +
|
|
436
466
|
`\n${describeArtifactRemedy(changeName, findArtifactIdFor(schema, tracksFile))}`;
|
|
437
467
|
}
|
|
468
|
+
else if (tracksFile && unavailableTrackingFiles.length > 0 && tasks.length === 0) {
|
|
469
|
+
state = 'blocked';
|
|
470
|
+
instruction = 'No readable task descriptions are available.';
|
|
471
|
+
}
|
|
438
472
|
else if (tracksFile && tracksFileExists && tasks.length === 0) {
|
|
439
473
|
// Tracking file exists but lists nothing an agent can work on: either no
|
|
440
474
|
// checkboxes at all, or only checkboxes with no text after them.
|
|
@@ -444,7 +478,10 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
444
478
|
`The ${tracksFilename} file exists but contains no tasks to work on.` +
|
|
445
479
|
`\nAdd tasks to ${tracksFilename}, or rebuild it: ${describeArtifactRemedy(changeName, findArtifactIdFor(schema, tracksFile))}`;
|
|
446
480
|
}
|
|
447
|
-
else if (tracksFile &&
|
|
481
|
+
else if (tracksFile &&
|
|
482
|
+
unavailableTrackingFiles.length === 0 &&
|
|
483
|
+
remaining === 0 &&
|
|
484
|
+
total > 0) {
|
|
448
485
|
state = 'all_done';
|
|
449
486
|
instruction = 'All tasks are complete! This change is ready to be archived.\nConsider running tests and reviewing the changes before archiving.';
|
|
450
487
|
}
|
|
@@ -457,13 +494,22 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
457
494
|
state = 'ready';
|
|
458
495
|
instruction = schemaInstruction?.trim() ?? 'Read context files, work through pending tasks, mark complete as you go.\nPause if you hit blockers or need clarification.';
|
|
459
496
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
497
|
+
if (unavailableTrackingFiles.length > 0) {
|
|
498
|
+
const unavailableDetails = unavailableTrackingFiles
|
|
499
|
+
.map((file) => `- ${file.path}: ${file.reason}`)
|
|
500
|
+
.join('\n');
|
|
501
|
+
instruction += `\nTask completion is not verified because tracking evidence was unavailable:\n${unavailableDetails}`;
|
|
502
|
+
}
|
|
503
|
+
const warnings = [
|
|
504
|
+
...(context.warnings ?? []),
|
|
505
|
+
...(await collectApplyWarnings({
|
|
506
|
+
state,
|
|
507
|
+
schema,
|
|
508
|
+
changeDir,
|
|
509
|
+
changeName,
|
|
510
|
+
skippedArtifacts: context.skippedArtifacts,
|
|
511
|
+
})),
|
|
512
|
+
];
|
|
467
513
|
return {
|
|
468
514
|
changeName,
|
|
469
515
|
changeDir,
|
|
@@ -471,6 +517,8 @@ export async function generateApplyInstructions(projectRoot, changeName, schemaN
|
|
|
471
517
|
contextFiles,
|
|
472
518
|
progress: { total, complete, remaining },
|
|
473
519
|
tasks,
|
|
520
|
+
taskTrackingConfigured: tracksFile !== null,
|
|
521
|
+
...(unavailableTrackingFiles.length > 0 ? { unavailableTrackingFiles } : {}),
|
|
474
522
|
state,
|
|
475
523
|
missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
|
|
476
524
|
...(missingPrerequisites.length > 0 ? { missingPrerequisites } : {}),
|
|
@@ -16,6 +16,8 @@ export interface TaskItem {
|
|
|
16
16
|
id: string;
|
|
17
17
|
description: string;
|
|
18
18
|
done: boolean;
|
|
19
|
+
sourcePath: string;
|
|
20
|
+
line: number;
|
|
19
21
|
}
|
|
20
22
|
export interface ApplyInstructions {
|
|
21
23
|
changeName: string;
|
|
@@ -28,6 +30,11 @@ export interface ApplyInstructions {
|
|
|
28
30
|
remaining: number;
|
|
29
31
|
};
|
|
30
32
|
tasks: TaskItem[];
|
|
33
|
+
taskTrackingConfigured: boolean;
|
|
34
|
+
unavailableTrackingFiles?: Array<{
|
|
35
|
+
path: string;
|
|
36
|
+
reason: string;
|
|
37
|
+
}>;
|
|
31
38
|
state: 'blocked' | 'all_done' | 'ready';
|
|
32
39
|
missingArtifacts?: string[];
|
|
33
40
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { getChangeDir } from '../../core/planning-home.js';
|
|
9
|
-
import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, } from '../../core/root-selection.js';
|
|
9
|
+
import { resolveRootForCommand, toPlanningHome, toRootOutput, withStoreFlag, isStoreSelectedRoot, findDeclaringProjectRoot, } from '../../core/root-selection.js';
|
|
10
10
|
import { loadChangeContext, formatChangeStatus, } from '../../core/artifact-graph/index.js';
|
|
11
11
|
import { resolveNextStep } from '../../core/change-status-policy.js';
|
|
12
12
|
import { asStatus } from '../shared-output.js';
|
|
@@ -43,12 +43,20 @@ export async function statusCommand(options) {
|
|
|
43
43
|
// `Next:` line, so a store-selected root can never carry `--store` in one
|
|
44
44
|
// and drop it from the other.
|
|
45
45
|
const storeOptions = isStoreSelectedRoot(root) ? { storeId: root.storeId } : {};
|
|
46
|
+
// A store holds planning artifacts only; the project declaring it is
|
|
47
|
+
// where implementation edits go (#2013).
|
|
48
|
+
const implementationRoot = isStoreSelectedRoot(root)
|
|
49
|
+
? findDeclaringProjectRoot(root.storeId)
|
|
50
|
+
: null;
|
|
51
|
+
const statusOptions = implementationRoot
|
|
52
|
+
? { ...storeOptions, implementationRoot }
|
|
53
|
+
: storeOptions;
|
|
46
54
|
// Single definition of "load one change's status" so the batch and
|
|
47
55
|
// single-change payloads can never drift apart.
|
|
48
56
|
const loadStatus = (changeName) => formatChangeStatus(loadChangeContext(projectRoot, changeName, options.schema, {
|
|
49
57
|
changeDir: getChangeDir(planningHome, changeName),
|
|
50
58
|
planningHome,
|
|
51
|
-
}),
|
|
59
|
+
}), statusOptions);
|
|
52
60
|
// Handle no-changes case gracefully — status is informational,
|
|
53
61
|
// so "no changes" is a valid state, not an error.
|
|
54
62
|
if (!options.change) {
|
|
@@ -157,6 +165,11 @@ export function printStatusText(status, options = {}) {
|
|
|
157
165
|
const total = status.artifacts.length - skippedCount;
|
|
158
166
|
console.log(`Change: ${status.changeName}`);
|
|
159
167
|
console.log(`Schema: ${status.schemaName}`);
|
|
168
|
+
if (status.warnings) {
|
|
169
|
+
for (const warning of status.warnings) {
|
|
170
|
+
console.log(chalk.yellow(`Warning: ${warning}`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
160
173
|
if (status.changeRoot) {
|
|
161
174
|
console.log(`Change root: ${status.changeRoot}`);
|
|
162
175
|
}
|