@fission-ai/openspec 1.11.0 → 1.13.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 +3 -14
- package/dist/cli/index.js +1 -0
- package/dist/commands/change.js +6 -5
- package/dist/commands/validate.d.ts +57 -0
- package/dist/commands/validate.js +99 -13
- package/dist/commands/workflow/instructions.js +140 -5
- package/dist/commands/workflow/shared.d.ts +8 -0
- package/dist/core/command-generation/adapters/codeassistant.d.ts +15 -0
- package/dist/core/command-generation/adapters/codeassistant.js +29 -0
- package/dist/core/command-generation/adapters/index.d.ts +1 -0
- package/dist/core/command-generation/adapters/index.js +1 -0
- package/dist/core/command-generation/registry.js +2 -0
- package/dist/core/completions/command-registry.js +6 -0
- package/dist/core/config.js +1 -0
- package/dist/core/init.d.ts +1 -0
- package/dist/core/init.js +41 -34
- package/dist/core/onboarding-commands.d.ts +11 -1
- package/dist/core/onboarding-commands.js +26 -0
- package/dist/core/openspec-root.d.ts +1 -0
- package/dist/core/openspec-root.js +10 -2
- package/dist/core/parsers/requirement-blocks.js +77 -31
- package/dist/core/shared/ide-restart.d.ts +26 -0
- package/dist/core/shared/ide-restart.js +42 -0
- package/dist/core/shared/index.d.ts +1 -0
- package/dist/core/shared/index.js +1 -0
- package/dist/core/shared/tool-detection.js +28 -1
- package/dist/core/specs-apply.js +184 -8
- package/dist/core/templates/workflows/explore.js +45 -0
- package/dist/core/templates/workflows/ff-change.js +8 -0
- package/dist/core/templates/workflows/propose.js +40 -12
- package/dist/core/update.d.ts +22 -0
- package/dist/core/update.js +55 -17
- package/dist/core/validation/validator.d.ts +12 -3
- package/dist/core/validation/validator.js +74 -3
- package/dist/utils/command-references.js +7 -8
- package/package.json +3 -3
- package/schemas/spec-driven/schema.yaml +14 -2
package/dist/core/update.js
CHANGED
|
@@ -13,12 +13,12 @@ import { FileSystemUtils } from '../utils/file-system.js';
|
|
|
13
13
|
import { getSkillReferenceTransformer, getTransformerForTool, transformToSkillReferences } from '../utils/command-references.js';
|
|
14
14
|
import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js';
|
|
15
15
|
import { generateCommands, CommandAdapterRegistry, } from './command-generation/index.js';
|
|
16
|
-
import { getToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, } from './shared/index.js';
|
|
16
|
+
import { getToolVersionStatus, getSkillTemplates, getCommandContents, generateSkillContent, getToolsWithSkillsDir, hasGlobalSkillTarget, resolveToolSkillsDir, toolSupportsSkills, formatIdeRestart, } from './shared/index.js';
|
|
17
17
|
import { detectLegacyArtifacts, cleanupLegacyArtifacts, formatDeferredGlobalPromptSummary, formatCleanupSummary, formatDetectionSummary, getLegacyGlobalPromptMatches, getLegacyWorkflowIdsForTool, getToolsFromLegacyArtifacts, omitGlobalLegacyPromptFiles, omitToolLegacyArtifacts, pickGlobalLegacyPromptFiles, } from './legacy-cleanup.js';
|
|
18
18
|
import { isInteractive } from '../utils/interactive.js';
|
|
19
19
|
import { getGlobalConfig } from './global-config.js';
|
|
20
20
|
import { getProfileWorkflows, ALL_WORKFLOWS, CORE_WORKFLOWS } from './profiles.js';
|
|
21
|
-
import { getOnboardingCommands } from './onboarding-commands.js';
|
|
21
|
+
import { formatOptionalWorkflowsNote, getOnboardingCommands } from './onboarding-commands.js';
|
|
22
22
|
import { getAvailableTools } from './available-tools.js';
|
|
23
23
|
import { WORKFLOW_TO_SKILL_DIR, getConfiguredToolsForProfileSync, getToolsNeedingProfileSync, } from './profile-sync-drift.js';
|
|
24
24
|
import { scanInstalledWorkflows as scanInstalledWorkflowsShared, migrateIfNeeded as migrateIfNeededShared, findLegacyToolMigrations, migrateLegacyToolDirs, describeLegacyMigration, legacyMigrationNotice, keptInPlaceNotice, hasMovableContent, } from './migration.js';
|
|
@@ -134,8 +134,7 @@ export class UpdateCommand {
|
|
|
134
134
|
await this.syncCopilotCloudFiles(resolvedProjectPath, configuredAndNewTools);
|
|
135
135
|
// Still check for new tool directories and extra workflows
|
|
136
136
|
this.detectNewTools(resolvedProjectPath, configuredTools);
|
|
137
|
-
this.
|
|
138
|
-
this.displayMissingCoreWorkflowsNote(profile, desiredWorkflows);
|
|
137
|
+
this.displayProfileNotes(resolvedProjectPath, configuredTools, desiredWorkflows, profile, delivery);
|
|
139
138
|
this.displaySetupNotes(configuredTools);
|
|
140
139
|
return;
|
|
141
140
|
}
|
|
@@ -316,9 +315,8 @@ export class UpdateCommand {
|
|
|
316
315
|
await this.syncCopilotCloudFiles(resolvedProjectPath, configuredAndNewTools);
|
|
317
316
|
// 13. Detect new tool directories not currently configured
|
|
318
317
|
this.detectNewTools(resolvedProjectPath, configuredAndNewTools);
|
|
319
|
-
// 14. Display
|
|
320
|
-
this.
|
|
321
|
-
this.displayMissingCoreWorkflowsNote(profile, desiredWorkflows);
|
|
318
|
+
// 14. Display the profile notes
|
|
319
|
+
this.displayProfileNotes(resolvedProjectPath, configuredAndNewTools, desiredWorkflows, profile, delivery);
|
|
322
320
|
this.displaySetupNotes(configuredAndNewTools);
|
|
323
321
|
// 15. List affected tools
|
|
324
322
|
if (updatedTools.length > 0) {
|
|
@@ -327,14 +325,9 @@ export class UpdateCommand {
|
|
|
327
325
|
}
|
|
328
326
|
console.log();
|
|
329
327
|
const affectedToolIds = [...new Set([...newlyConfiguredTools, ...updatedToolIds])];
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
(shouldGenerateCommandsForTool(toolId, delivery) ||
|
|
334
|
-
shouldGenerateSkillsForTool(toolId, delivery)));
|
|
335
|
-
});
|
|
336
|
-
if (shouldRestartIde) {
|
|
337
|
-
console.log(chalk.dim('Restart your IDE for changes to take effect.'));
|
|
328
|
+
const restartHint = formatIdeRestart(affectedToolIds, delivery);
|
|
329
|
+
if (restartHint) {
|
|
330
|
+
console.log(chalk.dim(restartHint));
|
|
338
331
|
}
|
|
339
332
|
if (failedTools.length > 0) {
|
|
340
333
|
throw new Error(`OpenSpec update failed for: ${failedTools.map((tool) => tool.name).join(', ')}`);
|
|
@@ -438,6 +431,18 @@ export class UpdateCommand {
|
|
|
438
431
|
console.log(chalk.yellow(`Detected new ${toolNoun}: ${newToolNames.join(', ')}. Run 'openspec init' to add ${pronoun}.`));
|
|
439
432
|
}
|
|
440
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* Prints the profile notes, in order, with one pointer at
|
|
436
|
+
* `openspec config profile` rather than three.
|
|
437
|
+
*
|
|
438
|
+
* Every note is evaluated: reading them as one short-circuited `||` chain
|
|
439
|
+
* would swallow whichever ran second.
|
|
440
|
+
*/
|
|
441
|
+
displayProfileNotes(projectPath, configuredTools, desiredWorkflows, profile, delivery) {
|
|
442
|
+
const printedExtraNote = this.displayExtraWorkflowsNote(projectPath, configuredTools, desiredWorkflows ?? []);
|
|
443
|
+
const printedMissingCoreNote = this.displayMissingCoreWorkflowsNote(profile, desiredWorkflows);
|
|
444
|
+
this.displayOptionalWorkflowsNote(configuredTools, desiredWorkflows, delivery, printedExtraNote || printedMissingCoreNote);
|
|
445
|
+
}
|
|
441
446
|
/**
|
|
442
447
|
* Displays a note about extra workflows installed that aren't in the current profile.
|
|
443
448
|
*/
|
|
@@ -447,7 +452,9 @@ export class UpdateCommand {
|
|
|
447
452
|
const extraWorkflows = installedWorkflows.filter((w) => !profileSet.has(w));
|
|
448
453
|
if (extraWorkflows.length > 0) {
|
|
449
454
|
console.log(chalk.dim(`Note: ${extraWorkflows.length} extra workflows not in profile (use \`openspec config profile\` to manage)`));
|
|
455
|
+
return true;
|
|
450
456
|
}
|
|
457
|
+
return false;
|
|
451
458
|
}
|
|
452
459
|
/**
|
|
453
460
|
* Point out core workflows a custom profile is missing, so releases that
|
|
@@ -456,17 +463,48 @@ export class UpdateCommand {
|
|
|
456
463
|
*/
|
|
457
464
|
displayMissingCoreWorkflowsNote(profile, workflows) {
|
|
458
465
|
if (profile !== 'custom' || !workflows) {
|
|
459
|
-
return;
|
|
466
|
+
return false;
|
|
460
467
|
}
|
|
461
468
|
const workflowSet = new Set(workflows);
|
|
462
469
|
const missing = CORE_WORKFLOWS.filter((workflow) => !workflowSet.has(workflow));
|
|
463
470
|
if (missing.length === 0) {
|
|
464
|
-
return;
|
|
471
|
+
return false;
|
|
465
472
|
}
|
|
466
473
|
const label = missing.length === 1 ? 'workflow' : 'workflows';
|
|
467
474
|
const pronoun = missing.length === 1 ? 'it' : 'them';
|
|
468
475
|
console.log(chalk.dim(`Note: Your custom profile is missing ${missing.length} core ${label}: ${missing.join(', ')}`));
|
|
469
476
|
console.log(chalk.dim(`Run \`openspec config profile\` to add ${pronoun}, or \`openspec config profile core\` to use the core set.`));
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Fallback pointer to the workflows the profile leaves out.
|
|
481
|
+
*
|
|
482
|
+
* `update` already points at `openspec config profile` when files drift from
|
|
483
|
+
* the profile, and when a custom profile is missing core workflows. Neither
|
|
484
|
+
* fires for the default `core` profile, so the user `update` is most likely
|
|
485
|
+
* to be helping — the one who ran it because a command they read about never
|
|
486
|
+
* appeared — learns nothing (#1076). This covers that gap.
|
|
487
|
+
*
|
|
488
|
+
* Silent when another note already pointed at the same command, and when no
|
|
489
|
+
* configured tool can receive a workflow surface under the active delivery:
|
|
490
|
+
* adding workflows would write nothing there.
|
|
491
|
+
*/
|
|
492
|
+
displayOptionalWorkflowsNote(configuredTools, workflows, delivery, alreadyPointedAtProfileConfig) {
|
|
493
|
+
if (alreadyPointedAtProfileConfig || !workflows) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const anyToolHasASurface = configuredTools.some((toolId) => shouldGenerateSkillsForTool(toolId, delivery) ||
|
|
497
|
+
shouldGenerateCommandsForTool(toolId, delivery));
|
|
498
|
+
if (!anyToolHasASurface) {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const note = formatOptionalWorkflowsNote(workflows);
|
|
502
|
+
if (!note) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
for (const line of note) {
|
|
506
|
+
console.log(chalk.dim(line));
|
|
507
|
+
}
|
|
470
508
|
}
|
|
471
509
|
/**
|
|
472
510
|
* Removes skill directories for workflows when delivery changed to commands-only.
|
|
@@ -19,9 +19,10 @@ export declare class Validator {
|
|
|
19
19
|
* - No duplicates within sections; no cross-section conflicts per spec
|
|
20
20
|
*
|
|
21
21
|
* When `options.mainSpecsDir` is given, MODIFIED blocks are also checked
|
|
22
|
-
* against the current main specs for
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* against the current main specs for scenario loss (#1477), and merge
|
|
23
|
+
* conflicts are reported as INFO without changing the verdict (#1112).
|
|
24
|
+
* When `options.projectRoot` is given, the schema's tracked task files are
|
|
25
|
+
* checked for ambiguous numbering (#1520). Omitting either
|
|
25
26
|
* option keeps existing library and archive callers behaving as before.
|
|
26
27
|
*/
|
|
27
28
|
validateChangeDeltaSpecs(changeDir: string, options?: {
|
|
@@ -47,6 +48,14 @@ export declare class Validator {
|
|
|
47
48
|
private applyChangeRules;
|
|
48
49
|
private enrichTopLevelError;
|
|
49
50
|
private extractNameFromPath;
|
|
51
|
+
/**
|
|
52
|
+
* Dry-run archive's merge builder without writing its result. Reusing the
|
|
53
|
+
* builder preserves its already-synced delta rules instead of duplicating them.
|
|
54
|
+
* INFO leaves the verdict unchanged: a missing target can be a typo or a
|
|
55
|
+
* requirement introduced by a sibling change that has not archived yet.
|
|
56
|
+
* This does not run archive's later merged-spec validation or retirement checks.
|
|
57
|
+
*/
|
|
58
|
+
private findArchiveBlockers;
|
|
50
59
|
private createReport;
|
|
51
60
|
isValid(report: ValidationReport): boolean;
|
|
52
61
|
private extractRequirementText;
|
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { SpecSchema, ChangeSchema } from '../schemas/index.js';
|
|
4
4
|
import { MarkdownParser } from '../parsers/markdown-parser.js';
|
|
5
5
|
import { ChangeParser } from '../parsers/change-parser.js';
|
|
6
|
+
import { findSpecUpdates, buildUpdatedSpec } from '../specs-apply.js';
|
|
6
7
|
import { MIN_PURPOSE_LENGTH, MAX_REQUIREMENT_TEXT_LENGTH, VALIDATION_MESSAGES } from './constants.js';
|
|
7
8
|
import { parseDeltaSpec, foldRequirementName, normalizeRequirementName, extractRequirementsSection, findMissingCurrentScenarios, } from '../parsers/requirement-blocks.js';
|
|
8
9
|
import { extractRequirementBody as extractRequirementBodyShared, containsShallOrMust as containsShallOrMustShared, countScenarios as countScenariosShared, } from '../parsers/requirement-text.js';
|
|
@@ -112,9 +113,10 @@ export class Validator {
|
|
|
112
113
|
* - No duplicates within sections; no cross-section conflicts per spec
|
|
113
114
|
*
|
|
114
115
|
* When `options.mainSpecsDir` is given, MODIFIED blocks are also checked
|
|
115
|
-
* against the current main specs for
|
|
116
|
-
*
|
|
117
|
-
*
|
|
116
|
+
* against the current main specs for scenario loss (#1477), and merge
|
|
117
|
+
* conflicts are reported as INFO without changing the verdict (#1112).
|
|
118
|
+
* When `options.projectRoot` is given, the schema's tracked task files are
|
|
119
|
+
* checked for ambiguous numbering (#1520). Omitting either
|
|
118
120
|
* option keeps existing library and archive callers behaving as before.
|
|
119
121
|
*/
|
|
120
122
|
async validateChangeDeltaSpecs(changeDir, options = {}) {
|
|
@@ -330,6 +332,20 @@ export class Validator {
|
|
|
330
332
|
}
|
|
331
333
|
}
|
|
332
334
|
}
|
|
335
|
+
// Reuse archive's merge builder to report conflicts with the main specs.
|
|
336
|
+
// Keep structural errors and scenario loss in their existing diagnostics.
|
|
337
|
+
if (options.mainSpecsDir) {
|
|
338
|
+
issues.push(...(await this.findArchiveBlockers(changeDir, options.mainSpecsDir, [
|
|
339
|
+
...issues.filter((issue) => issue.level === 'ERROR').map((issue) => issue.path),
|
|
340
|
+
// Collected in the loop above but not turned into issues until
|
|
341
|
+
// after this try block, so they are invisible to the filter. A
|
|
342
|
+
// delta with no parsed sections has nothing for the merge to
|
|
343
|
+
// apply, which it reports as a failure of its own - on top of the
|
|
344
|
+
// error that actually names the mistake.
|
|
345
|
+
...missingHeaderSpecs,
|
|
346
|
+
...emptySectionSpecs.map((spec) => spec.path),
|
|
347
|
+
])));
|
|
348
|
+
}
|
|
333
349
|
}
|
|
334
350
|
catch (error) {
|
|
335
351
|
// A missing specs dir (or a stray `specs` file) means no deltas;
|
|
@@ -671,6 +687,61 @@ export class Validator {
|
|
|
671
687
|
const dotIndex = fileName.lastIndexOf('.');
|
|
672
688
|
return dotIndex > 0 ? fileName.slice(0, dotIndex) : fileName;
|
|
673
689
|
}
|
|
690
|
+
/**
|
|
691
|
+
* Dry-run archive's merge builder without writing its result. Reusing the
|
|
692
|
+
* builder preserves its already-synced delta rules instead of duplicating them.
|
|
693
|
+
* INFO leaves the verdict unchanged: a missing target can be a typo or a
|
|
694
|
+
* requirement introduced by a sibling change that has not archived yet.
|
|
695
|
+
* This does not run archive's later merged-spec validation or retirement checks.
|
|
696
|
+
*/
|
|
697
|
+
async findArchiveBlockers(changeDir, mainSpecsDir, alreadyReportedPaths) {
|
|
698
|
+
const alreadyReported = new Set(alreadyReportedPaths);
|
|
699
|
+
// Only ever reaches a generated skeleton's placeholder Purpose, which this
|
|
700
|
+
// dry run discards.
|
|
701
|
+
const changeName = path.basename(changeDir);
|
|
702
|
+
const issues = [];
|
|
703
|
+
let updates;
|
|
704
|
+
try {
|
|
705
|
+
updates = await findSpecUpdates(changeDir, mainSpecsDir);
|
|
706
|
+
}
|
|
707
|
+
catch (error) {
|
|
708
|
+
// An incomplete advisory check must not discard the validation report.
|
|
709
|
+
// Source discovery already ran above; archive retains its own path guards.
|
|
710
|
+
return [{
|
|
711
|
+
level: 'INFO',
|
|
712
|
+
path: 'specs',
|
|
713
|
+
message: `Could not check archive merge conflicts: ${error instanceof Error ? error.message : String(error)}`,
|
|
714
|
+
}];
|
|
715
|
+
}
|
|
716
|
+
for (const update of updates) {
|
|
717
|
+
// discoverSpecFiles builds both this id and the entryPath the checks
|
|
718
|
+
// above report under, from the same walk.
|
|
719
|
+
const entryPath = FileSystemUtils.toPosixPath(`${update.id}/spec.md`);
|
|
720
|
+
// A delta those checks already rejected would be reported twice, the
|
|
721
|
+
// second time in archive's wording rather than the wording that names
|
|
722
|
+
// the actual mistake.
|
|
723
|
+
if (alreadyReported.has(entryPath))
|
|
724
|
+
continue;
|
|
725
|
+
try {
|
|
726
|
+
await buildUpdatedSpec(update, changeName, { silent: true });
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
// Only the thrown preconditions, which carry no errno. A filesystem
|
|
730
|
+
// error says nothing about whether the delta applies, and `validate
|
|
731
|
+
// --all` reads six changes at once, so a transient EMFILE would report
|
|
732
|
+
// a collision that is not there - the same reason the scenario-loss
|
|
733
|
+
// check above reads only the codes that mean the file is unusable.
|
|
734
|
+
if (error?.code !== undefined)
|
|
735
|
+
continue;
|
|
736
|
+
issues.push({
|
|
737
|
+
level: 'INFO',
|
|
738
|
+
path: entryPath,
|
|
739
|
+
message: `Archive would refuse this delta: ${error instanceof Error ? error.message : String(error)}`,
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return issues;
|
|
744
|
+
}
|
|
674
745
|
createReport(issues) {
|
|
675
746
|
const errors = issues.filter(i => i.level === 'ERROR').length;
|
|
676
747
|
const warnings = issues.filter(i => i.level === 'WARNING').length;
|
|
@@ -60,15 +60,14 @@ const SKILL_INVOCATION_PREFIX = {
|
|
|
60
60
|
codex: '$',
|
|
61
61
|
};
|
|
62
62
|
/**
|
|
63
|
-
* Tools
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* type a command their CLI does not register.
|
|
63
|
+
* Tools with no documented slash invocation for skills: use automatic
|
|
64
|
+
* matching or natural-language prompts instead. SourceCraft Code Assistant
|
|
65
|
+
* supports separate command files, but its skills use description matching.
|
|
66
|
+
* Rovo Dev's `/skills` only manages skills (see docs/supported-tools.md).
|
|
67
|
+
* Skill references for these tools are spelled as prose ("the openspec-propose
|
|
68
|
+
* skill") so skills-only delivery does not advertise unregistered commands.
|
|
70
69
|
*/
|
|
71
|
-
const NATURAL_LANGUAGE_SKILL_TOOLS = new Set(['rovodev']);
|
|
70
|
+
const NATURAL_LANGUAGE_SKILL_TOOLS = new Set(['rovodev', 'codeassistant']);
|
|
72
71
|
/**
|
|
73
72
|
* Whether a tool references skills by natural language rather than a slash
|
|
74
73
|
* command (see NATURAL_LANGUAGE_SKILL_TOOLS).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fission-ai/openspec",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "AI-native system for spec-driven development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openspec",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"node": ">=20.19.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@changesets/changelog-github": "^0.
|
|
45
|
-
"@changesets/cli": "^
|
|
44
|
+
"@changesets/changelog-github": "^1.0.0",
|
|
45
|
+
"@changesets/cli": "^3.0.1",
|
|
46
46
|
"@types/node": "^20.19.43",
|
|
47
47
|
"@vitest/ui": "^3.2.6",
|
|
48
48
|
"eslint": "^10.5.0",
|
|
@@ -18,7 +18,19 @@ artifacts:
|
|
|
18
18
|
- **Impact**: Affected code, APIs, dependencies, or systems.
|
|
19
19
|
|
|
20
20
|
IMPORTANT: The Capabilities section is critical. It creates the contract between
|
|
21
|
-
proposal and specs phases. Research existing specs before filling this in
|
|
21
|
+
proposal and specs phases. Research existing specs before filling this in:
|
|
22
|
+
run `openspec list --specs` for the project's capability inventory, then
|
|
23
|
+
`openspec show "<spec-id>" --type spec --json --no-scenarios` for any that
|
|
24
|
+
look related - that returns a capability's purpose and requirement texts
|
|
25
|
+
without pulling whole spec files into context. Append `--store "<id>"` to
|
|
26
|
+
both commands only for a registered standalone store, and keep `--type
|
|
27
|
+
spec`: a change and a spec sharing a name is otherwise an ambiguous-item
|
|
28
|
+
error. `openspec list` without `--specs` lists in-flight changes, not
|
|
29
|
+
specs - it never shows what the project already covers. Reuse an existing
|
|
30
|
+
capability's exact path instead of introducing a near-duplicate name.
|
|
31
|
+
The filtered read is only an overview. Before deciding what is already
|
|
32
|
+
covered or what should change, read each relevant spec in full, including
|
|
33
|
+
scenarios, with `openspec show "<spec-id>" --type spec` (same `--store` rule).
|
|
22
34
|
Each capability listed here will need a corresponding spec file.
|
|
23
35
|
|
|
24
36
|
Every change must either declare at least one capability (new or
|
|
@@ -63,7 +75,7 @@ artifacts:
|
|
|
63
75
|
`<capability-path>` is the spec directory relative to `specs/` (for example,
|
|
64
76
|
`user-auth` or `identity/user-auth`). Preserve the full path:
|
|
65
77
|
- New capabilities: use the exact path from the proposal at `specs/<capability-path>/spec.md`. Any path segment newly introduced in the proposal must be kebab-case. Follow the project's existing organization; do not add a new domain level when the project uses a flat layout.
|
|
66
|
-
- Modified capabilities: use the exact existing path from `openspec/specs/<capability-path>/` when creating the delta at `specs/<capability-path>/spec.md`. Do not move or rename the capability.
|
|
78
|
+
- Modified capabilities: use the exact existing path from `openspec/specs/<capability-path>/` when creating the delta at `specs/<capability-path>/spec.md`. Run `openspec list --specs` to confirm that path before writing the delta, appending `--store "<id>"` only for a registered standalone store - a mistyped or invented path targets a capability that does not exist rather than the one you meant. Do not move or rename the capability.
|
|
67
79
|
|
|
68
80
|
There must be at least one spec file unless the change's `.openspec.yaml`
|
|
69
81
|
sets `skip_specs: true` (no spec-level behavior change) - `openspec validate`
|