Skip to content

fix(extensions): resolve core-command dirs via _assets helpers (#3274)#3287

Merged
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/3274-core-command-discovery
Jul 1, 2026
Merged

fix(extensions): resolve core-command dirs via _assets helpers (#3274)#3287
mnriem merged 1 commit into
github:mainfrom
Noor-ul-ain001:fix/3274-core-command-discovery

Conversation

@Noor-ul-ain001

Copy link
Copy Markdown
Contributor

Closes #3274.

Problem

specify_cli.extensions._load_core_command_names() never discovers the bundled command templates. It computed its candidate command dirs with bespoke Path(__file__) arithmetic that was correct when the code lived at src/specify_cli/extensions.py, but the #3014 move into the package (extensions.pyextensions/__init__.py) pushed the file one directory deeper without updating the .parent counts. Both candidates now resolve to non-existent paths:

env computed real
wheel specify_cli/extensions/core_pack/commands specify_cli/core_pack/commands
source src/templates/commands repo-root templates/commands

Neither exists, so commands_dir.is_dir() is always false and every call silently returns _FALLBACK_CORE_COMMAND_NAMES.

Why it matters

Latent today (the fallback happens to equal the 10 real command stems), but the shadowing guard from #1994 that consumes CORE_COMMAND_NAMES now depends entirely on someone hand-editing the fallback whenever a core command is added or removed — which already happened once (converge was manually appended in #3001). A future add/remove that forgets the fallback would silently desync the guard.

This is the same off-by-one class @mnriem fixed for the presets loader, but in the extensions module.

Fix

Delegate path resolution to the canonical _locate_core_pack / _repo_root resolvers in _assets — the same ones the presets and bundle loaders already use. They are anchored to the package root, so discovery survives future module moves. (_assets is stdlib-only with zero internal imports, so no cycle risk.)

Tests

The existing test_core_command_names_match_bundled_templates can't catch this — the fallback equals the live tree, so a dead loader and a working one look identical. Added three tests that point the resolvers at a temp tree with different command names, proving discovery reads from disk:

  • test_load_core_command_names_discovers_from_source_checkout
  • test_load_core_command_names_prefers_wheel_core_pack
  • test_load_core_command_names_falls_back_when_nothing_found

The first two fail on the pre-fix code (verified by reverting the source change).

🤖 Generated with Claude Code

…b#3274)

`_load_core_command_names()` computed its candidate command dirs with
bespoke `Path(__file__)` arithmetic. The github#3014 move of this module from
`specify_cli/extensions.py` to `specify_cli/extensions/__init__.py`
pushed the file one directory deeper but left the `.parent` counts
unchanged, so both candidates resolved to non-existent paths:

  wheel  -> specify_cli/extensions/core_pack/commands (real: specify_cli/core_pack/commands)
  source -> src/templates/commands                    (real: repo-root templates/commands)

Neither exists, so every call silently fell through to
`_FALLBACK_CORE_COMMAND_NAMES`. Discovery is latent-dead: the fallback
happens to equal the real stems today, but the shadowing guard (github#1994)
that depends on it now relies on someone hand-editing the fallback on
every core-command add/remove (as already happened for `converge`, github#3001).

Delegate path resolution to the canonical `_locate_core_pack` /
`_repo_root` resolvers in `_assets` — the same ones the presets and
bundle loaders use. They are anchored to the package root, so discovery
survives future module moves.

Add regression tests that point the resolvers at a temp tree with
*different* command names, proving discovery reads from disk rather than
returning the fallback (they fail on the pre-fix code).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes core-command template discovery for extensions by switching _load_core_command_names() to use the canonical asset path resolvers, preventing future module moves from silently breaking discovery and desyncing the anti-shadowing guard.

Changes:

  • Resolve core command directories via _assets._locate_core_pack() (wheel) and _assets._repo_root() (source checkout) instead of Path(__file__) parent-counting.
  • Add regression tests that prove discovery reads from disk (and that fallback behavior only triggers when nothing is found).
Show a summary per file
File Description
src/specify_cli/extensions/__init__.py Fixes core command discovery by delegating path resolution to _assets helpers and handling missing candidates cleanly.
tests/test_extensions.py Adds targeted tests that distinguish a live loader from a “dead” loader returning the baked-in fallback.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 2/2 changed files
  • Comments generated: 0
  • Review effort level: Low

@mnriem mnriem merged commit f59fd81 into github:main Jul 1, 2026
12 checks passed
@mnriem

mnriem commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet