Commit 22e7699
authored
feat: implement preset wrap strategy (#2189)
* feat: implement strategy: wrap
* fix: resolve merge conflict for strategy wrap correctness
* feat: multi-preset composable wrapping with priority ordering
Implements comment #4 from PR review: multiple installed wrap presets
now compose in priority order rather than overwriting each other.
Key changes:
- PresetResolver.resolve() gains skip_presets flag; resolve_core() wraps
it to skip tier 2, preventing accidental nesting during replay
- _replay_wraps_for_command() recomposed all enabled wrap presets for a
command in ascending priority order (innermost-first) after any
install or remove
- _replay_skill_override() keeps SKILL.md in sync with the recomposed
command body for ai-skills-enabled projects
- install_from_directory() detects strategy: wrap commands, stores
wrap_commands in the registry entry, and calls replay after install
- remove() reads wrap_commands before deletion, removes registry entry
before rmtree so replay sees post-removal state, then replays
remaining wraps or unregisters when none remain
Tests: TestResolveCore (5), TestReplayWrapsForCommand (5),
TestInstallRemoveWrapLifecycle (5), plus 2 skill/alias regression tests
* fix: resolve extension commands via manifest file mapping
PresetResolver.resolve_extension_command_via_manifest() consults each
installed extension.yml to find the actual file declared for a command
name, rather than assuming the file is named <cmd_name>.md. This fixes
_substitute_core_template for extensions like selftest where the manifest
maps speckit.selftest.extension → commands/selftest.md.
Resolution order in _substitute_core_template is now:
1. resolve_core(cmd_name) — project overrides win, then name-based lookup
2. resolve_extension_command_via_manifest(cmd_name) — manifest fallback
3. resolve_core(short_name) — core template short-name fallback
Path traversal guard mirrors the containment check already present in
ExtensionManager to reject absolute paths or paths escaping the extension
root.
* fix: add bundled core_pack as Priority 5 in PresetResolver.resolve()
resolve_core() was returning None for built-in commands (implement,
specify, etc.) because PresetResolver only checked .specify/templates/
commands/ (Priority 4), which is never populated for commands in a
normal project. strategy:wrap presets rely on resolve_core() to fetch
the {CORE_TEMPLATE} body, so the wrap was silently skipped and SKILL.md
was never updated.
Priority 5 now checks core_pack/commands/ (wheel install) or
repo_root/templates/commands/ (source checkout), mirroring the pattern
used by _locate_core_pack() elsewhere.
Updated two tests whose assertions assumed resolve_core() always
returned None when .specify/templates/commands/ was absent.
* fix: harden preset wrap replay removal
* fix: stabilize existing directory error output
* fix: track outermost_pack_id from contributing preset; use Path.parts in tests
- outermost_pack_id now updates alongside outermost_frontmatter inside
the wrap loop, so it reflects the actual last contributing preset
rather than always taking wrap_presets[0] (which may have been skipped)
- Replace str(path) substring checks in TestResolveCore with Path.parts
tuple comparisons for correct behaviour on Windows (CI runs windows-latest)
* fix: guard against non-mapping YAML manifests; apply integration post-processing in replay
- ExtensionManifest._load raises ValidationError for non-dict YAML roots instead of TypeError
- PresetManager._replay_wraps_for_command calls integration.post_process_skill_content,
matching _register_skills behaviour
- PresetResolver skips extensions that raise OSError/TypeError/AttributeError on manifest load
- Tests: non-mapping YAML, OSError manifest skip, and replay integration post-processing1 parent 569d18a commit 22e7699
File tree
10 files changed
+1771
-16
lines changed- presets
- self-test
- commands
- src/specify_cli
- tests
- integrations
10 files changed
+1771
-16
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
116 | 116 | | |
117 | 117 | | |
118 | 118 | | |
119 | | - | |
| 119 | + | |
120 | 120 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
59 | 64 | | |
60 | 65 | | |
61 | 66 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1115 | 1115 | | |
1116 | 1116 | | |
1117 | 1117 | | |
1118 | | - | |
| 1118 | + | |
1119 | 1119 | | |
1120 | 1120 | | |
1121 | 1121 | | |
| |||
1371 | 1371 | | |
1372 | 1372 | | |
1373 | 1373 | | |
1374 | | - | |
1375 | 1374 | | |
1376 | 1375 | | |
1377 | 1376 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
468 | 468 | | |
469 | 469 | | |
470 | 470 | | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
471 | 480 | | |
472 | 481 | | |
473 | 482 | | |
| |||
495 | 504 | | |
496 | 505 | | |
497 | 506 | | |
498 | | - | |
499 | | - | |
500 | | - | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
501 | 510 | | |
| 511 | + | |
| 512 | + | |
502 | 513 | | |
503 | 514 | | |
504 | 515 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
143 | | - | |
| 143 | + | |
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
148 | 153 | | |
149 | 154 | | |
150 | 155 | | |
| |||
0 commit comments