-
Notifications
You must be signed in to change notification settings - Fork 4.8k
docs(openspec): propose reporting requirements two active changes both claim #1963
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ryandemelo
wants to merge
2
commits into
Fission-AI:main
Choose a base branch
from
ryandemelo:docs/propose-cross-change-overlap
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| schema: spec-driven | ||
| created: 2026-09-23 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| ## Context | ||
|
|
||
| See `proposal.md` for the failure this addresses. Every existing check in `validate` and `archive` takes one change and one base, the main specs as they are now. Nothing reads two active changes side by side, so nothing can see them converge. | ||
|
|
||
| The repository's parallel merge plan (`openspec-parallel-merge-plan.md`) calls this Phase 1: make overlap visible before building anything that acts on it. The open `add-change-stacking-awareness` change plans overlap warnings from a `touches` field that authors fill in by hand. This proposal needs no metadata. It reads the claims from the delta files each change already has, at requirement level, so it cannot drift from what archive will actually apply. The two fit together: declared intent from metadata, observed claims from deltas. | ||
|
|
||
| ## Goals / Non-Goals | ||
|
|
||
| **Goals:** | ||
|
|
||
| - Show every requirement that more than one active change claims, before any of them archives. | ||
| - Give the author enough to judge the overlap without opening files: which changes, which operations, and whether the requirement exists today. | ||
| - Read exactly the files archive will apply, from the root the run selected. | ||
| - Stay out of the way: no new failure, no exit code change, nothing printed when there is nothing to report. | ||
|
|
||
| **Non-Goals:** | ||
|
|
||
| - Ranking overlaps by severity, or saying which archive order works. See decision 1. | ||
| - Detecting overlap at scenario level, or between changes and archived history. | ||
| - Changing any existing check, the findings report, or targeted `validate <item>` output. | ||
| - Deciding whether an overlap is intended. Overlap is often deliberate, such as a stacked pair or sequenced work, and nothing in a delta records intent. | ||
|
|
||
| ## Decisions | ||
|
|
||
| ### 1. Report claims, never a verdict | ||
|
|
||
| The obvious next step is to rank each overlap: this pair cannot both archive, this pair only works in one order. An earlier version of #1698 did that, and it was removed. | ||
|
|
||
| Knowing whether a given archive order aborts means reproducing the preconditions in `specs-apply.ts`, including the cases it treats as already synced rather than as collisions: an ADD of a requirement already present word for word, a rename whose source is gone but whose target is present, a REMOVED whose target is already absent. A second copy of those rules would be free to disagree with the code that does the writing. Tested against real archive runs, the ranked version did disagree. It reported conflicts for pairs that archive cleanly in either order, and in one case recommended the order that actually fails. | ||
|
|
||
| A wrong verdict is worse than none, because it tells an author to rewrite a change that would have archived fine. Ranking needs one applicability check that `archive` and `validate` both call. #1112 shows the same split from the other side, with `validate` passing a MODIFIED whose target does not exist and `archive` refusing it later. That shared check is future work. This proposal stays on the side of the line that cannot be wrong. | ||
|
|
||
| The one piece of context it does add is `inMainSpec`. Two changes editing shared text is a different situation from two changes each proposing a requirement that does not exist yet, and that fact is read directly from the main spec rather than predicted. | ||
|
|
||
| ### 2. Advisory, and never a failure | ||
|
|
||
| Overlap never moves the exit code, and the scan fails in two layers. An unreadable input only removes itself: a change whose delta files cannot be discovered or read is skipped, a main spec that cannot be read counts as holding no requirements, and overlaps among the remaining changes are still reported. Any other error inside the scan is swallowed and the report is empty for that run. Neither case loses information, because every delta the scan reads is also read by the validation of each change, which reports unreadable or malformed files on its own path. Advisory output must never be the thing that fails a run. | ||
|
|
||
| ### 3. Read what archive reads | ||
|
|
||
| Delta files are enumerated with `discoverSpecFiles()`, the same walk `archive` and `specs-apply` use. Nested capability ids such as `platform/session-layout` are included, and nothing is read that archive would ignore. Requirement names are matched with `normalizeRequirementName()`, the function the validator and archive already share. Both paths come from the resolved root, `root.changesDir` and `root.specsDir`, so a `--store` run scans the store it selected rather than a path rebuilt from the project root. | ||
|
|
||
| A requirement is identified by spec id plus normalized name. The same name in two different specs is not an overlap. | ||
|
|
||
| ### 4. Where the report appears | ||
|
|
||
| The scan runs only for bulk validation with changes in scope, and is skipped below two changes, since one change cannot overlap anything. Targeted validation of a single item does not scan. | ||
|
|
||
| In human output the section follows the existing details and prints only when there is at least one overlap: | ||
|
|
||
| ```text | ||
| ⚠ 2 requirements are claimed by more than one active change: | ||
| colors: Widget colors (not in the main spec yet) | ||
| adds-focus ADDED, adds-hover ADDED | ||
| widgets: Widget state (in the main spec) | ||
| adds-focus MODIFIED, adds-hover MODIFIED | ||
| Whichever of these archives second lands on a spec the first one changed; re-read it before archiving. | ||
| ``` | ||
|
|
||
| In JSON, `overlaps` sits beside `items` and `summary` in the full v1 document: | ||
|
|
||
| ```json | ||
| "overlaps": [ | ||
| { | ||
| "specId": "widgets", | ||
| "requirement": "Widget state", | ||
| "inMainSpec": true, | ||
| "claimants": [ | ||
| { "changeId": "adds-focus", "operation": "MODIFIED", "requirement": "Widget state" }, | ||
| { "changeId": "adds-hover", "operation": "MODIFIED", "requirement": "Widget state" } | ||
| ] | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| The field is present, possibly empty, whenever changes are in scope, including an empty scope, so a consumer sees one shape on every path. It is absent for `--specs` and `--archived`. The findings document from `--report findings` carries item findings only by design, so the scan is skipped on that path rather than computed and discarded. | ||
|
|
||
| ### 5. Stable order | ||
|
|
||
| Overlaps are sorted by spec id, then requirement name, and claimants by change id, then operation. Sorting compares code units rather than using `localeCompare`, so the order does not change with the ICU locale of the machine and output can be diffed in CI. | ||
|
|
||
| ### 6. Which changes count as active | ||
|
|
||
| The scan takes the list of change ids the run already resolved and never works out on its own what "active" means. If change state moves into metadata, as #1683 and the `status` discussion on #1813 suggest, only that one call site in `validate` changes. | ||
|
|
||
| ## Risks / Tradeoffs | ||
|
|
||
| - **Noise from deliberate overlap.** A stacked pair will be reported every run. That is the cost of refusing to guess intent. It is one short section, it never fails anything, and it disappears when either change archives. | ||
| - **Requirement level only.** Two changes touching different scenarios of one requirement are reported. That matches how archive applies a MODIFIED block, which replaces the whole requirement. | ||
| - **Cost.** One extra read of each change's delta files and of the main specs those deltas name. Only specs some change claims are read. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| ## Why | ||
|
|
||
| Every check `validate` runs compares one change against the main specs as they are today. When two active changes edit the same requirement, each one is consistent with a spec that neither has landed in yet, so both pass and `validate --changes` reports nothing. The collision only shows up when the first change archives and the second starts failing. By then the second author has built against a base that moved, over a requirement they may never have touched. | ||
|
|
||
| The scenario loss guard stopped this from losing data: archive now refuses a MODIFIED block that would drop scenarios. What is left is timing. The refusal lands late, on the wrong person, after the work is done. #1246 describes the failure, #1669 asks for the overlap to be visible before either change archives, and #1387 reports a working tool built for the same gap outside the CLI. | ||
|
|
||
| ## What Changes | ||
|
|
||
| - When changes are in scope (`--changes` or `--all`), bulk `validate` reports each requirement that two or more active changes claim. | ||
| - Each entry names the spec, the requirement, every claiming change with the operation it applies (`ADDED`, `MODIFIED`, `REMOVED`, `RENAMED_FROM`, `RENAMED_TO`), and whether the main spec holds that requirement today. | ||
| - A RENAMED delta claims both of its names. The old name collides with anyone editing it, and the new name collides with anyone adding it. | ||
| - Human output gets one advisory section after the existing details. JSON output gets an `overlaps` array, present and possibly empty whenever changes are in scope, and absent otherwise. | ||
| - The report is advisory. It never changes the exit code, never fails a run, and draws no conclusion about which change is wrong or which should archive first. | ||
| - `--report findings` keeps its own document unchanged and carries no overlaps. | ||
|
|
||
| ## Capabilities | ||
|
|
||
| ### New Capabilities | ||
|
|
||
| _None._ | ||
|
|
||
| ### Modified Capabilities | ||
|
|
||
| - `cli-validate`: bulk validation reports requirements that more than one active change claims. | ||
|
|
||
| ## Impact | ||
|
|
||
| - **Public CLI:** new output on `validate --changes` and `validate --all` only when an overlap exists. No new flag, no default behavior change, no exit code change. | ||
| - **JSON consumers:** one additive top level field, `overlaps`, on the full v1 document. The `add-validation-findings-report` design names exactly this field as needing an explicit contract decision before it is added, and this proposal is that decision. The findings document is untouched. | ||
| - **Implementation:** a new read only module under `src/core/`, a call site in `src/commands/validate.ts`, docs in `docs-lab/reference/cli.md` and `docs/agent-contract.md` §4.3, tests, and a minor changeset. No new dependency. | ||
| - **Existing implementation:** #1698 implements this proposal as written and is kept rebased on `main`. |
90 changes: 90 additions & 0 deletions
90
openspec/changes/report-cross-change-overlap/specs/cli-validate/spec.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| ## ADDED Requirements | ||
|
|
||
| ### Requirement: Bulk validation SHALL report requirements that more than one active change claims | ||
|
|
||
| When active changes are in scope of a bulk validation (`--changes` or `--all`), the `validate` command SHALL report each requirement that two or more active changes claim in their delta specs. A requirement is identified by its spec id and its normalized name. The report SHALL be advisory: it SHALL NOT change the exit code, SHALL NOT fail the run, and SHALL NOT state which change is wrong or which archive order is correct. | ||
|
|
||
| #### Scenario: Two changes modify the same requirement | ||
|
|
||
| - **WHEN** two active changes each have a MODIFIED block for the same requirement in the same spec | ||
| - **AND** that requirement exists in the main spec | ||
| - **THEN** human output SHALL include one advisory entry naming the spec, the requirement, and `(in the main spec)` | ||
| - **AND** the entry SHALL list both changes with the operation each applies | ||
|
|
||
| #### Scenario: Two changes add the same requirement | ||
|
|
||
| - **WHEN** two active changes each ADD a requirement with the same name to the same spec | ||
| - **AND** the main spec does not hold that requirement | ||
| - **THEN** the entry SHALL say `(not in the main spec yet)` | ||
|
|
||
| #### Scenario: A rename claims both of its names | ||
|
|
||
| - **WHEN** one active change RENAMES a requirement from an old name to a new name | ||
| - **AND** a second active change claims the old name, and a third claims the new name | ||
| - **THEN** the report SHALL include an entry for the old name listing `RENAMED_FROM` beside the second change's operation | ||
| - **AND** an entry for the new name listing `RENAMED_TO` beside the third change's operation | ||
|
|
||
| #### Scenario: One change never overlaps itself | ||
|
|
||
| - **WHEN** a single change is the only claimant of a requirement, including a change whose RENAMED pair names it twice | ||
| - **THEN** no overlap SHALL be reported for that requirement | ||
|
|
||
| #### Scenario: The same name in different specs is not an overlap | ||
|
|
||
| - **WHEN** two active changes claim requirements with the same name in two different specs | ||
| - **THEN** no overlap SHALL be reported | ||
|
|
||
| #### Scenario: Overlap never changes the exit code | ||
|
|
||
| - **WHEN** every change in scope is valid and an overlap exists | ||
| - **THEN** validation SHALL exit 0 | ||
| - **AND WHEN** a change in scope is invalid and an overlap exists | ||
| - **THEN** validation SHALL exit 1 exactly as it would without the overlap | ||
|
|
||
| #### Scenario: Nothing is printed when nothing overlaps | ||
|
|
||
| - **WHEN** no requirement is claimed by more than one active change | ||
| - **THEN** human output SHALL be unchanged from validation without this report | ||
|
|
||
| #### Scenario: JSON output carries an overlaps array whenever changes are in scope | ||
|
|
||
| - **WHEN** a user runs `validate --changes --json` or `validate --all --json` | ||
| - **THEN** the full v1 document SHALL include an `overlaps` array, empty when nothing overlaps and also when the scope holds no changes | ||
| - **AND** each entry SHALL carry `specId`, `requirement`, `inMainSpec`, and `claimants`, where each claimant carries `changeId`, `operation`, and `requirement` | ||
| - **AND** `operation` SHALL be one of `ADDED`, `MODIFIED`, `REMOVED`, `RENAMED_FROM`, `RENAMED_TO` | ||
|
|
||
| #### Scenario: No overlaps field without changes in scope | ||
|
|
||
| - **WHEN** a user runs `validate --specs --json` or `validate --archived --json` | ||
| - **THEN** the document SHALL NOT include an `overlaps` field | ||
|
|
||
| #### Scenario: The findings report is unchanged | ||
|
|
||
| - **WHEN** a user runs bulk validation with `--report findings` | ||
| - **THEN** the findings document SHALL NOT include overlaps | ||
| - **AND** the overlap scan SHALL NOT run | ||
|
|
||
| #### Scenario: Targeted validation does not scan | ||
|
|
||
| - **WHEN** a user validates a single named change | ||
| - **THEN** no overlap report SHALL be produced | ||
|
|
||
| #### Scenario: The scan reads what archive applies | ||
|
|
||
| - **WHEN** a change stores delta specs under nested capability paths | ||
| - **THEN** the scan SHALL find them with the same discovery archive uses and report their nested spec ids | ||
| - **AND WHEN** a run selects a store | ||
| - **THEN** the scan SHALL read that store's changes and main specs | ||
|
|
||
| #### Scenario: An unreadable change is skipped | ||
|
|
||
| - **WHEN** a change's delta files cannot be read during the scan | ||
| - **THEN** the scan SHALL skip that change without an error of its own | ||
| - **AND** overlaps among the remaining changes SHALL still be reported | ||
| - **AND** the change's own validation SHALL report the problem as it does today | ||
|
|
||
| #### Scenario: Output order is stable | ||
|
|
||
| - **WHEN** overlaps are reported | ||
| - **THEN** they SHALL be ordered by spec id and then requirement name, with claimants ordered by change id and then operation | ||
| - **AND** the order SHALL NOT depend on the locale of the machine | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| ## 1. Overlap detection | ||
|
|
||
| - [ ] 1.1 Add a read only module under `src/core/` that turns each delta file into claims (spec id, normalized requirement name, operation), with RENAMED contributing a `RENAMED_FROM` and a `RENAMED_TO` claim, and a repeated claim inside one change dropped | ||
| - [ ] 1.2 Enumerate delta files with `discoverSpecFiles()` and match names with `normalizeRequirementName()`, so the scan sees exactly what archive applies | ||
| - [ ] 1.3 Group claims by spec id and normalized name, keep groups claimed by two or more changes, and set `inMainSpec` from the main spec's current requirements | ||
| - [ ] 1.4 Sort overlaps and claimants by code unit, never by locale | ||
| - [ ] 1.5 Skip an unreadable change, and treat an unreadable main spec as holding no requirements, so overlaps among the remaining changes are still reported | ||
|
|
||
| ## 2. Validate integration | ||
|
|
||
| - [ ] 2.1 Run the scan only for bulk validation with changes in scope, only with two or more changes, and never for `--report findings` | ||
| - [ ] 2.2 Pass `root.changesDir`, `root.specsDir`, and the change ids the run already resolved, so store runs scan the selected store | ||
| - [ ] 2.3 Print the advisory section after the existing details only when an overlap exists | ||
| - [ ] 2.4 Add `overlaps` to the full v1 JSON document whenever changes are in scope, including an empty scope, and leave it out otherwise | ||
| - [ ] 2.5 Swallow any scan error and leave the exit code exactly as validation sets it | ||
|
|
||
| ## 3. Documentation and release tracking | ||
|
|
||
| - [ ] 3.1 Document the section, the JSON fields, and the advisory status in `docs-lab/reference/cli.md` | ||
| - [ ] 3.2 Add `overlaps` to the `validate --json` shape in `docs/agent-contract.md` §4.3 | ||
| - [ ] 3.3 Add a minor changeset | ||
|
|
||
| ## 4. Verification | ||
|
|
||
| - [ ] 4.1 Unit tests for claims, grouping, rename at both ends, same name in different specs, nested spec ids, three claimants on one requirement, and stable order | ||
| - [ ] 4.2 End to end tests through the CLI for human and JSON output, `--specs`, empty and single change scopes, `--report findings`, a selected store, and an unchanged exit code when an overlap exists | ||
| - [ ] 4.3 Run build, lint, type checks, the full suite, and `openspec validate report-cross-change-overlap --strict` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Specify the other scan-failure cases.
This scenario covers unreadable delta files only. The stated scan contract also excludes an unreadable main spec while retaining overlaps from other readable inputs, but clears the run’s overlap report for other scan errors. Add scenarios for both cases so implementations apply the intended behavior consistently.
🤖 Prompt for AI Agents