Skip to content

validate accepts a delta spec.md directly under specs/ that archive silently drops #1385

Description

@mc856

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions