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
{{ message }}
Repository navigation
feat(presets): support regex selectors for command declarations - #4776
This replaces the previous (much larger) version of this PR with the focused change the
maintainer asked for: regex:<pattern> support for preset command declarations only.
Everything else that had accumulated here is split out into subsequent targeted PRs; the
full earlier work is preserved on the branch feat/4659-regex-preset-selectors-full
(2aa5e892) as the source for those follow-ups.
What this does
A preset may use a bounded selector in a provides.templates entry of type command:
The selector is expanded, in declaration order, into the concrete lower-layer commands it
matches. Those concrete names then flow through the existing command resolution,
registration, composition and removal paths.
Matching is against the full logical command name only — never a path, file name or
substring — and only against commands that actually exist in the layers below (enabled
presets, enabled extensions, core, including built-in core). The declaring preset itself,
higher-priority presets and project-local overrides are not used as evidence of existence.
A literal selector string is never used as an output path, a .composed file name or a
registry key: the registry only ever sees concrete names.
A regex: name on a non-command declaration is a validation error, not a silent
no-op.
Entries without the prefix keep exact-name matching and are completely unaffected.
Diagnostics: preset info reports the commands a selector currently matches; preset resolve <name> names the declaration that matched when a layer came from a
selector.
Matching is deterministic (declaration order preserved, overlapping declarations are not
de-duplicated away) and uses the bounded matcher with its existing limits.
Scope: what is deliberately not here
Split into follow-up PRs, each based on its own point in upstream/main:
template and script selectors, and selector support on the extension side;
automatic reconciliation after provider/integration changes (see the known limitation);
historical recovery sweeps and the ownership/ledger redesign beyond the minimal guard;
the install-transaction rework, snapshot/journal machinery and CLI boundary refactors
(design/cli.md, design/shared.md).
Kept deliberately, because the feature materializes command/skill files:
the minimal ownership-based guard on the command/skill write paths, so that a preset
install cannot overwrite artifacts the user has edited. This is a prerequisite for the
feature being safe, not a re-introduction of the wider ownership work.
Known limitations (please review with these in mind)
No automatic broadcast on provider change. A selector is expanded at install/enable
time and the existing reconciliation of concrete names still runs, but there is no new
mechanism that re-expands selectors when a new provider/integration appears. This is the
main user-visible limitation of the reduced scope and the reason the change is small.
Bounded regex subset. The matcher intentionally implements a bounded subset rather
than full Python regex compatibility; the limits and the rejection behaviour are covered
by tests.
No tests were skipped, weakened or marked expected-failure to reach these numbers.
Against the same base, tests/specify_cli/test_file_policy.py shows the same 10
pre-existing failures / 177 passed before and after this change (verified on a pristine
checkout of the base) — i.e. this PR adds none.
Merged against the current main (which has moved past this branch's base) the full suite
is 12423 passed / 98 skipped / 0 failed — the change merges cleanly with the latest
base and does not regress it.
CI note: the workflow runs for a fork PR are in action_required state, i.e. they need a
maintainer to approve them before they execute. No CI result should be assumed for this
revision yet.
WINDOWS-VERIFIED: NO — the fork's Windows CI cannot be triggered from here, so the
Windows job must be treated as unverified for this revision. The earlier branch's Windows
fixes are not part of this PR (they belong with the files this PR does not touch).
AI disclosure
Authored with AI assistance (Hermes Agent); reviewed and verified against the test suite by
a human before submission.
Added command regex selectors to normal layer resolution and composition.
Preserved all matching regex declarations in manifest order.
Fixed the invalid _selectors imports used by diagnostics.
Reused selector expansion for AI-skill registration.
Added selector-aware reconciliation for relevant preset/extension lifecycle changes.
Also added regression coverage for overlapping selectors, command composition strategies, diagnostics, AI-skill registration, and lifecycle state changes.
All previous Copilot review findings have been addressed and the updated test suite is passing. The latest Copilot re-review appears to have failed due to a review error, so the PR is ready for another review when convenient.
Stale artifacts remain when selectors lose all matches
src/specify_cli/extensions/_commands.py:121
This refresh runs only after the extension state has changed, so a selector whose sole lower layer was disabled or removed now expands to nothing. register_enabled_presets_for_agent() never sees the previously materialized concrete name, and its reconciler also skips names with no remaining layers, leaving the old command/SKILL artifact active. Capture the pre-mutation matches (or persisted registrations), reconcile the union with post-mutation matches, and explicitly unregister names that no longer resolve.
Selector diagnostics and expansion run after the preset directory and registry entry are created but before the existing rollback guard. _expand_command_selectors() deliberately raises when artifact enumeration fails, so such a failure leaves an enabled, partially installed preset behind. Move both calls into the guarded installation phase so the established cleanup executes.
Re-enumeration causes false failure after registration
src/specify_cli/presets/_manager.py:507
This repeats artifact enumeration outside the transactional registration block. If inventory changes or becomes unreadable after registration, the command reports installation failure even though the preset and artifacts are already committed. Reuse the concrete command_templates computed for registration.
@mnriem I've addressed the latest Copilot feedback in commit 1ce59c1, including the disabled-extension fallback, include_disabled handling, stale selector tracking/cleanup, and install rollback coverage.
When convenient, could you please re-request the Copilot review? Thanks!
Preserve selector attribution in preset resolve diagnostics
src/specify_cli/presets/command_info.py:170
The linked acceptance criteria also require resolution diagnostics to show which selector matched each concrete resource, but this expansion is only rendered by preset info. preset resolve still receives layers containing only the preset source/path/strategy, so its composition chain cannot distinguish a regex match from an exact declaration or display the selector expression. Please carry selector attribution into resolved layers and render it in the resolution diagnostic as well.
🧠 Review effort: Balanced
Give feedback about Copilot approvals in this survey to enter a drawing for a $150 gift card.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Conventional lower-preset commands remain unresolved, rollback can mask the original error, and lifecycle orchestration violates the documented CLI boundary.
I am so sorry to ask for this and thank you for being so diligent in addressing the feedback. To keep the change focused and reviewable, can we please limit this PR to supporting regex:<pattern> for preset command declarations only, then address the remaining areas in subsequent targeted PRs? I am afraid we won't land this as it has snowballed and diverged from the original intent
A preset may declare `regex:<pattern>` in a `provides.templates` entry of
type `command`. At install/enable time the selector expands, in declaration
order, into the concrete lower-layer commands it matches; those names then
flow through the existing command resolution, registration, tracking and
removal behaviour. Matching is against the full logical command name only,
never a path, file name or substring, and a literal selector is never used
as an output path, composed file name or registry key.
Non-command declarations using a selector are rejected during validation.
Diagnostics: `preset info` reports the commands a selector currently matches
and `preset resolve <name>` attributes selector-contributed layers.
Kept from the earlier work: the self-contained selector engine, the bounded
matching limits with their tests, the minimal ownership-based guard on the
command/skill materialization write paths, and the command-only docs.
Deliberately out of scope, for subsequent targeted PRs: template/script
selectors, extension-side selectors, automatic reconciliation after provider
changes, historical recovery sweeps, and the install-transaction rework.
AI disclosure: this change was authored with AI assistance (Hermes Agent);
all behaviour was reviewed and verified against the test suite by a human.
Hi — thank you for the guidance, and sorry for letting this PR grow well past the original intent (it had reached 69 files).
I have now replaced it with a focused version: regex: support for preset command declarations only, expanding into concrete command names that then flow through the existing resolution and registration paths, with selectors rejected on non-command declarations. It is a single commit and 15 files (+3,446/−115). The remaining areas — template/script selectors, extension-side selectors, reconciliation after provider changes, and the lifecycle/transaction work — will come as separate targeted PRs, each based on its own point in main.
Locally the full suite is green on this revision (12,410 passed, 0 failed), and it also stays green after merging the current main (12,423 passed, 0 failed). The workflow runs are currently action_required, so there is no CI result yet — I would appreciate it if you could approve them.
Could you take another look when you have a chance?
This branch has not been deployed
No deployments
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
triage-can-waitVerdict: valid and in-scope but deprioritized; held behind the evidence gate
3 participants
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.
Closes #4659.
This replaces the previous (much larger) version of this PR with the focused change the
maintainer asked for:
regex:<pattern>support for preset command declarations only.Everything else that had accumulated here is split out into subsequent targeted PRs; the
full earlier work is preserved on the branch
feat/4659-regex-preset-selectors-full(
2aa5e892) as the source for those follow-ups.What this does
A preset may use a bounded selector in a
provides.templatesentry of typecommand:matches. Those concrete names then flow through the existing command resolution,
registration, composition and removal paths.
substring — and only against commands that actually exist in the layers below (enabled
presets, enabled extensions, core, including built-in core). The declaring preset itself,
higher-priority presets and project-local overrides are not used as evidence of existence.
.composedfile name or aregistry key: the registry only ever sees concrete names.
regex:name on a non-commanddeclaration is a validation error, not a silentno-op.
preset inforeports the commands a selector currently matches;preset resolve <name>names the declaration that matched when a layer came from aselector.
de-duplicated away) and uses the bounded matcher with its existing limits.
Scope: what is deliberately not here
Split into follow-up PRs, each based on its own point in
upstream/main:(
design/cli.md,design/shared.md).Kept deliberately, because the feature materializes command/skill files:
install cannot overwrite artifacts the user has edited. This is a prerequisite for the
feature being safe, not a re-introduction of the wider ownership work.
Known limitations (please review with these in mind)
time and the existing reconciliation of concrete names still runs, but there is no new
mechanism that re-expands selectors when a new provider/integration appears. This is the
main user-visible limitation of the reduced scope and the reason the change is small.
than full Python regex compatibility; the limits and the rejection behaviour are covered
by tests.
Tests / evidence
tests/specify_cli/presets/test_regex_selectors.py,test_selector_safety.py,test_selector_resolution_contract.py,test_resolver_candidates.py,test_regex_selector_diagnostics.py— 146 passed.uvx ruff@0.15.0 check src tests: All checks passed;compileallclean;git diff --checkclean.tests/specify_cli/test_file_policy.pyshows the same 10pre-existing failures / 177 passed before and after this change (verified on a pristine
checkout of the base) — i.e. this PR adds none.
main(which has moved past this branch's base) the full suiteis 12423 passed / 98 skipped / 0 failed — the change merges cleanly with the latest
base and does not regress it.
action_requiredstate, i.e. they need amaintainer to approve them before they execute. No CI result should be assumed for this
revision yet.
Windows job must be treated as unverified for this revision. The earlier branch's Windows
fixes are not part of this PR (they belong with the files this PR does not touch).
AI disclosure
Authored with AI assistance (Hermes Agent); reviewed and verified against the test suite by
a human before submission.