Summary
A change's delta specs are discovered by two walkers that disagree on one layout: a spec.md placed directly at the change's specs/ root, with no capability folder.
Validator.findDeltaSpecFiles (src/core/validation/validator.ts) collects every spec.md under specs/, including one at the root, so openspec validate <change> (even --strict) passes and counts its deltas.
discoverSpecFiles (src/utils/spec-discovery.ts), used by the change parser, show, and the archive/apply merge path, only accepts spec.md inside a capability folder, so the same file yields deltas: 0 and is never merged.
Net effect: a change that validates clean can be archived while its delta never reaches openspec/specs/. Archive does print a non-blocking ⚠ ("Change must have at least one delta"), but the change is still moved to archive/ and the requirement is dropped from the merge.
#1355 converged parse/show/apply/archive/list onto the shared discoverSpecFiles helper and fixed the nested-layout half of this class; the root-level divergence predates it. The validator's own recursive walker (from #1280) is the one remaining discovery path with different rules — it also includes dot-directories, which discoverSpecFiles skips.
Reproduction
On current main (9acddcd), CLI v1.6.0:
mkdir -p repro/openspec/changes/add-metrics/specs repro/openspec/specs
cd repro
cat > openspec/changes/add-metrics/proposal.md <<'EOF'
# Add metrics
## Why
We need request metrics to debug production latency reports.
## What Changes
- Add a metrics requirement
EOF
printf '# Tasks\n\n- [x] 1.1 Implement metrics\n' > openspec/changes/add-metrics/tasks.md
# delta spec directly under specs/ - no capability folder
cat > openspec/changes/add-metrics/specs/spec.md <<'EOF'
## ADDED Requirements
### Requirement: Request metrics
The system SHALL record request metrics.
#### Scenario: Request is counted
- **WHEN** a request completes
- **THEN** a counter is incremented
EOF
openspec validate add-metrics --strict
# -> Change 'add-metrics' is valid
openspec show add-metrics --json | jq '.deltas | length'
# -> 0
openspec archive add-metrics --yes
# -> non-blocking ⚠ "Change must have at least one delta", then:
# -> Change 'add-metrics' archived as '2026-07-19-add-metrics'
grep -r "Request metrics" openspec/specs/
# -> no match: the delta was never merged
Expected: validate and archive agree on this layout — either it fails validation, or it merges like any other delta.
Actual: validate passes, archive completes, the requirement is silently dropped.
Possible directions
Either walker could become the canonical one: teach findDeltaSpecFiles the same rules as discoverSpecFiles (a root-level spec.md becomes a validation error pointing at the capability-folder convention), or let discoverSpecFiles accept the root-level file and merge it. Maintainers' call — happy to pick up whichever direction you prefer.
Summary
A change's delta specs are discovered by two walkers that disagree on one layout: a
spec.mdplaced directly at the change'sspecs/root, with no capability folder.Validator.findDeltaSpecFiles(src/core/validation/validator.ts) collects everyspec.mdunderspecs/, including one at the root, soopenspec validate <change>(even--strict) passes and counts its deltas.discoverSpecFiles(src/utils/spec-discovery.ts), used by the change parser,show, and the archive/apply merge path, only acceptsspec.mdinside a capability folder, so the same file yieldsdeltas: 0and is never merged.Net effect: a change that validates clean can be archived while its delta never reaches
openspec/specs/. Archive does print a non-blocking ⚠ ("Change must have at least one delta"), but the change is still moved toarchive/and the requirement is dropped from the merge.#1355 converged parse/show/apply/archive/list onto the shared
discoverSpecFileshelper and fixed the nested-layout half of this class; the root-level divergence predates it. The validator's own recursive walker (from #1280) is the one remaining discovery path with different rules — it also includes dot-directories, whichdiscoverSpecFilesskips.Reproduction
On current main (9acddcd), CLI v1.6.0:
Expected: validate and archive agree on this layout — either it fails validation, or it merges like any other delta.
Actual: validate passes, archive completes, the requirement is silently dropped.
Possible directions
Either walker could become the canonical one: teach
findDeltaSpecFilesthe same rules asdiscoverSpecFiles(a root-levelspec.mdbecomes a validation error pointing at the capability-folder convention), or letdiscoverSpecFilesaccept the root-level file and merge it. Maintainers' call — happy to pick up whichever direction you prefer.