You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`specd.local.yaml` today is a full config replacement — `specd.yaml` is not read at all when the local file is present. This makes any local customisation expensive: you must duplicate the entire config just to change one field.
This is a general problem, independent of any specific feature (e.g. worktree support). Teams that want to override a single workspace path, tweak a storage directory, or adjust a hook for local development have no option other than maintaining a full copy.
Proposed cascade model
Activation by presence
There is no explicit activation mechanism — if a file matching the pattern exists on disk, it is applied. No flags, no env vars, no state files.
Default cascade (no explicit --config)
specd discovers and merges all present files in this fixed order:
specd.yaml
→ specd.*.yaml (all present named variants, in alphabetical order)
→ specd.local.yaml (local override)
→ specd.local.*.yaml (all present local named variants, in alphabetical order)
Each layer merges on top of the previous. Having specd.local.prueba.yaml on disk is enough for it to be applied — no opt-in required.
Standalone local config
If specd.local.yaml does not opt in to the cascade (no extends or equivalent), it is treated as a complete, self-contained base. The cascade then continues only from that point:
specd.local.yaml
→ specd.local.*.yaml (all present local named variants, in alphabetical order)
This preserves the current behaviour for projects using specd.local.yaml as a full replacement.
Explicit file selection (--config)
When a specific file is passed, only that file — and anything in its own extends chain — is used. Discovery and the presence-based cascade are skipped entirely.
# only specd.local.prueba.yaml and whatever it extends
specd --config specd.local.prueba.yaml <command>
Multiple explicit files
Allow passing multiple files whose layers are merged in declaration order, for scripting and CI:
Scalar fields — later layers override earlier ones.
Objects (workspaces, storage sections) — deep merge. A later layer overrides individual keys within an object without replacing the whole object.
New keys — if a later layer introduces a key not present in any previous layer (e.g. a new workspace, a new plugin), it is additive.
Arrays (workflow, context, plugins) — additive by default; items declared in a later layer are appended to the base.
Removing array items: remove with YAML AST selectors
To remove items inherited from a previous layer, use the remove key. Each entry uses the same YAML AST selector model already defined in specs/core/delta-format/spec.md and used in deltaValidations — type, where, matches, contentMatches, etc. — applied against the YAML AST of the config arrays. No new selector vocabulary is introduced.
remove is applied before the current layer's additions: first delete matching items from the accumulated base, then append the new items declared in this layer.
Optional id field on array items
All items that can appear in arrays support an optional id field: context entries (both file and instruction), workflow hook entries (both run and instruction), and plugins.
The id is only necessary when the item may need to be referenced or removed by a downstream layer — it documents extensibility intent. Items without id can still be targeted via other where fields (e.g. file, name, run).
# specd.yamlcontext:
- id: bootstrap # removable by id in downstream layersfile: specd-bootstrap.md
- instruction: 'Always prefer editing existing files over creating new ones.'# no id — identifiable by content via contentMatches if neededworkflow:
- step: archivinghooks:
post:
- id: notify-team # removable by idrun: 'pnpm run notify-team'
id values must be unique within their array.
Gitignore convention
Files committed to the repo:
specd.yaml
specd.*.yaml
Files always gitignored (added by specd init):
specd.local.yaml
specd.local.*.yaml
Notes
The current constraint — specd.local.yaml without cascade opt-in is a complete, standalone config — is preserved. No breaking change for existing setups.
The remove selector engine reuses the existing YAML AST selector infrastructure — no new parsing or matching logic needed.
Problem
`specd.local.yaml` today is a full config replacement — `specd.yaml` is not read at all when the local file is present. This makes any local customisation expensive: you must duplicate the entire config just to change one field.
This is a general problem, independent of any specific feature (e.g. worktree support). Teams that want to override a single workspace path, tweak a storage directory, or adjust a hook for local development have no option other than maintaining a full copy.
Proposed cascade model
Activation by presence
There is no explicit activation mechanism — if a file matching the pattern exists on disk, it is applied. No flags, no env vars, no state files.
Default cascade (no explicit
--config)specd discovers and merges all present files in this fixed order:
Each layer merges on top of the previous. Having
specd.local.prueba.yamlon disk is enough for it to be applied — no opt-in required.Standalone local config
If
specd.local.yamldoes not opt in to the cascade (noextendsor equivalent), it is treated as a complete, self-contained base. The cascade then continues only from that point:This preserves the current behaviour for projects using
specd.local.yamlas a full replacement.Explicit file selection (
--config)When a specific file is passed, only that file — and anything in its own
extendschain — is used. Discovery and the presence-based cascade are skipped entirely.Multiple explicit files
Allow passing multiple files whose layers are merged in declaration order, for scripting and CI:
Merge semantics
workflow,context,plugins) — additive by default; items declared in a later layer are appended to the base.Removing array items:
removewith YAML AST selectorsTo remove items inherited from a previous layer, use the
removekey. Each entry uses the same YAML AST selector model already defined inspecs/core/delta-format/spec.mdand used indeltaValidations—type,where,matches,contentMatches, etc. — applied against the YAML AST of the config arrays. No new selector vocabulary is introduced.removeis applied before the current layer's additions: first delete matching items from the accumulated base, then append the new items declared in this layer.Optional
idfield on array itemsAll items that can appear in arrays support an optional
idfield:contextentries (bothfileandinstruction), workflow hook entries (bothrunandinstruction), and plugins.The
idis only necessary when the item may need to be referenced or removed by a downstream layer — it documents extensibility intent. Items withoutidcan still be targeted via otherwherefields (e.g.file,name,run).idvalues must be unique within their array.Gitignore convention
Files committed to the repo:
specd.yamlspecd.*.yamlFiles always gitignored (added by
specd init):specd.local.yamlspecd.local.*.yamlNotes
specd.local.yamlwithout cascade opt-in is a complete, standalone config — is preserved. No breaking change for existing setups.removeselector engine reuses the existing YAML AST selector infrastructure — no new parsing or matching logic needed.