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
Closes#4777. Installing an extension in a generic project now registers its commands or skills in the configured --commands-dir. Registration reads persisted integration settings and tracks generated artifacts by hash; removal, disable, and upgrades preserve edited output, core commands, and unrelated files. The core speckit.taskstoissues command is unchanged.
The bundled GitHub extension is now covered by real commands- and skills-layout installation/removal tests, and its migration documentation no longer claims generic is unsupported. This is a shared registration fix, not a GitHub-specific workaround. The bundled extension's documentation change bumps its manifest and catalog version together to 1.0.1 so installed copies can receive the update.
Regression tests fail before the fixes and pass afterward for missing generic invocations, collision/retry, stale hashes after skill upgrades, aliases and flat-command refresh, malformed-settings cleanup and enable rollback, partial-registration cleanup (including config preservation), and extension-update backup/rollback. Both layouts, custom paths, rendering, core coexistence, edited files, and invalid settings are covered.
Testing
Tested locally with uv run specify --help (using this worktree's .venv/bin/specify --help).
Ran existing tests with uv sync && uv run pytest (using this worktree's .venv/bin/python -m pytest): before the upstream merge, the full suite passed on macOS Python 3.13 and 3.14 (8,615 passed, 17 skipped each). On the updated branch, the affected suites passed (321 passed, 36 skipped), and after the manifest/catalog version bump the bundled GitHub + generic suites passed (264 passed, 36 skipped). The GitHub full matrix on the latest commit is pending.
Tested with a sample project through a coding agent. Automated tests create representative generic projects and install the real bundled GitHub extension; manual agent invocation remains outstanding.
CI-equivalent local checks passed: Ruff 0.15.0 (src and tests), ShellCheck at error severity on tracked shell scripts, the Bash portability guard, markdownlint-cli2 0.23.2 on CI's documentation-only globs (58 files after the upstream merge), the committed-requirements pip-audit, the extension version-bump guard against upstream main, and git diff --check. The local PowerShell executable currently crashes with an assembly-load error even when started standalone; tests requiring it cannot be validated again on this host. GitHub CI tests the other operating systems.
Manual test selection and results
Changed files
Affected invocation
Manual result
Generic integration and CLI scaffolding
specify init --integration generic, then /speckit.specify
Not run through a coding agent; automated CLI/project tests passed.
Extension registration and lifecycle
specify extension add github, invoke the generated command or skill, then disable/re-enable/remove
Not run through a coding agent; automated installation/removal tests passed.
AI Disclosure
I did not use AI assistance for this contribution
I did use AI assistance (fill in the disclosure below)
AI disclosure: GitHub Copilot (GPT-6 Sol, interactive mode; reasoning effort not specified) autonomously authored the implementation, regression tests, documentation, commits, review fixes, and this PR description in response to contributor requests. The contributor directed the scope and requested validation; no human line-by-line review or manual agent-invocation test is claimed.
Support generic command and skill registration in configured output directories, track generated artifacts for ownership-aware cleanup, and cover both layouts and failure cases.
Assisted-by: GitHub Copilot (model: GPT-6 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The success condition accepts a partial registration. Manifest validation does not require each declared source file to exist, so an extension with one valid command and one missing/unreadable command writes the first artifact, returns a non-empty list, and is recorded as successfully installed even though a declared invocation is absent. Compare the registered names with every expected primary/alias (or skill primary) and roll back partial artifacts before committing the registry entry.
If resolving the persisted generic directory fails here (for example, integration.json is missing or malformed), the extension was already marked enabled on line 45 and this exception escapes without restoring it. The CLI therefore fails while leaving an enabled extension with no invocation artifact. Roll the registry flag back on registration errors, as the no-artifact branch below already does.
This issue also appears on line 54 of the same file.
Refresh owned commands and aliases, reject incomplete generic installs with artifact rollback, restore disabled state on enable errors, and verify the bundled GitHub extension registers in both layouts. Update its migration documentation and upstream regression expectations.
Assisted-by: GitHub Copilot (model: GPT-6 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep the bundled extension manifest and catalog at 1.0.1 so updated documentation reaches existing installs and the extension version guard passes.
Assisted-by: GitHub Copilot (model: GPT-6 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
On behalf of @mnriem, review round 5352087611 was addressed in c5724ef5: cleanup tolerates invalid optional generic settings, rewritten artifacts receive current ownership digests, output collisions fail before copying an extension, and extension-update backup/rollback uses the project-specific registrar. Regression cases reproduced each issue before the fix; the affected install/update/rollback suites passed (180 tests). Review conversations are left open for the reviewer.
AI involvement: GitHub Copilot (GPT-6 Sol; interactive mode, reasoning effort not specified) autonomously authored the fixes, tests, and this comment. No human line-by-line review is claimed.
On behalf of @mnriem, review round 5352364021 was addressed in cf484c9d, with the bundled-extension version follow-up in 13e46977. Verified-owned generic commands and aliases now regenerate; incomplete registrations reject the install and clean up partial output while preserving kept configs; enable errors restore the disabled flag. After integrating upstream main, the bundled GitHub extension tests were updated to verify actual generic command/skill registration and unchanged core output, and its migration documentation was corrected. The extension manifest and catalog both move to 1.0.1 as required by the version guard.
The affected merged-branch suites passed (321 passed, 36 skipped); after the version bump, bundled GitHub and generic suites passed (264 passed, 36 skipped). Ruff, documentation lint, and the version-bump guard pass locally and in current CI. The remaining full CI matrix is still running. Review conversations remain open for the reviewer.
AI involvement: GitHub Copilot (GPT-6 Sol; interactive mode, reasoning effort not specified) autonomously authored the fixes, tests, documentation, and this comment on @mnriem's behalf. No human line-by-line review or manual agent invocation is claimed.
Retire generated generic artifacts when updating a disabled extension, and require complete hash-owned command or skill registration before enabling. Clean up partial output on failure and cover update success, rollback, retry, and both output layouts.
Assisted-by: GitHub Copilot (model: GPT-6 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Registration failures leave partial artifacts and copied directories
src/specify_cli/extensions/__init__.py:2825
This rollback runs only after both registrars return normally. If an extension has multiple commands and writing a later command/skill raises (for example, an OSError), control skips this block entirely, leaving earlier generated artifacts and the copied extension directory behind with no registry entry. Wrap registration and completeness validation in one cleanup path so exceptional partial registrations are rolled back too.
On behalf of @mnriem, review round 5352588802 is addressed in 6c8bd76d. Updating a disabled generic extension now retires newly generated command or skill artifacts before restoring the disabled registry state. Enabling compares every declared command and alias (or each skill primary) against the current hash-owned output; a partial registration removes its newly created artifacts and restores the disabled flag. Tests reproduce both failures before the fix and verify success, failed-update rollback, collision preservation, and retry afterward in both layouts.
The affected lifecycle suites passed locally (327 passed, 36 skipped with this host's broken PowerShell excluded); Ruff passed. CI will rerun against 6c8bd76d. Review conversations remain open for the reviewer.
AI involvement: GitHub Copilot (GPT-6 Sol; interactive mode, reasoning effort not specified) autonomously authored these fixes, tests, and this comment on @mnriem's behalf. No human line-by-line review or manual agent invocation is claimed.
This generic branch also activates preset skill registration because PresetManager._get_skills_dir() calls resolve_active_skills_dir() (presets/_manager_skills.py:459). Preset removal then cannot resolve the recorded generic owner: _safe_skills_dir_for_agent() rejects it via the static CommandRegistrar.AGENT_CONFIGS check at line 993, leaving the generated/overridden skill behind. Either keep this resolution extension-specific or make preset registration and cleanup consistently project-aware, with a generic preset lifecycle regression test.
Installation guide still incorrectly describes generic add-on support
docs/reference/integrations.md:279
The main installation guide still states the opposite at docs/installation.md:153-155: it says generic does not register extension add-ons and the GitHub replacement is not invokable. Update that page as part of this migration; otherwise users following the primary installation flow receive contradictory guidance, and the PR description's claim that migration documentation no longer reports generic as unsupported is not yet true.
Addressed the new findings from review 5352641454 in commit 17d2bff. Generic skill ownership now rejects symlinked skill-directory components before trusting hashes or removing artifacts. Registration and completeness checking now share exception-safe rollback, including partial command/skill output and copied extension sources; preserved configuration remains available for retry. Five new regressions reproduced these failures before the fix and pass after it. The affected suites passed locally (365 passed, 36 skipped because PowerShell is unavailable in the test PATH); Ruff and diff whitespace checks passed. I have left reviewer conversations open for the reviewer to verify.
Disclosure: Posted on behalf of @mnriem by GitHub Copilot (GPT-6 Sol, default reasoning settings, autonomous). Copilot authored the fix, regression tests, validation commands, and this review-round comment.
Back up hash-owned generic outputs across previous directories even when another integration is active. Disable validates tracked ownership without rejecting unrelated same-named output after directory moves. Cover aliases, skills, multiple directories, collisions, modified output, and failed-update retry.
Assisted-by: GitHub Copilot (model: GPT-6 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Addressed review 5358225127 in 8b2c44f. Extension update rollback now snapshots all hash-owned generic outputs across earlier output directories, including commands, aliases, and skills, even after switching to another active integration. Generic disable validates recorded ownership rather than treating an unrelated same-named file in the newly configured directory as an extension output. Retargeted or modified tracked artifacts still block disable. Documentation describes both behaviors.
Regressions reproduced the lost old outputs and rejected disable before the fix. Tests cover both layouts, one or multiple historical directories, unrelated new-directory collisions, retry after rollback, switched-away integrations, edited old artifacts, untracked current files, and retargeted old symlinks. Local Python 3.13 and 3.14 full suites each passed (9,099 passed, 18 skipped; 9,117 collected); the affected update/generic suites passed on Python 3.14 (266 tests), and the switched-away regressions also passed on Python 3.13. CI-pinned Ruff and git diff --check passed. Checks for this commit are pending; reviewer threads remain open for reviewer verification.
Disclosure: Posted on behalf of @mnriem. GitHub Copilot (GPT-6 Sol, autonomous CLI agent) authored the implementation, tests, documentation, commit, validation, and this review-round comment; there was no human line-by-line review before posting.
Validate persisted generic integration state before committing extension
src/specify_cli/extensions/__init__.py:2441
This decides whether generic registration is mandatory solely from init-options.json. If that file is missing or malformed while the valid integration state still identifies generic as the default, generic_active becomes false; the active-registration path then either fails closed or falls back to detected non-generic agents, and registry.add() still commits an enabled extension with no generic invocation. That contradicts the requirement that registration failures not look successful. Cross-check the persisted default integration and reject inconsistent/missing init options (including the layout flag) before copying or committing the extension.
CommandRegistrar(project_root) now resolves generic settings and can raise ValueError/OSError, but this statement is outside the per-extension handler and the outer handler only catches ValidationError and ExtensionError. A generic project with missing, malformed, or too-new integration state therefore crashes extension update after confirmation instead of reporting a normal CLI error. Convert the resolution failure to ExtensionError here so the existing outer handler presents it cleanly.
On behalf of @mnriem, this round was authored by GitHub Copilot (GPT-6 Sol, autonomous). The agent wrote the code and regression tests, ran local checks, and pushed commit e9159ee; there was no human line-by-line review of this round before posting.
Commandful generic installation now checks the persisted default integration and skills layout against init options before copying or committing the extension. Missing, malformed, or inconsistent options fail explicitly; hook-only installs remain allowed. Extension update now converts eager registrar configuration failures into a handled CLI error. Regressions reproduced both failures before the fixes and cover command/skills layouts, invalid options, and missing/malformed/future-schema settings.
Local validation: Python 3.13 full suite: 9,118 passed, 18 skipped (9,136 collected). Python 3.14 full suite: 9,111 passed, 18 skipped; seven PowerShell processes crashed during the overlapping full runs, and all affected cases passed on an isolated rerun (39 selected PowerShell cases passed). CI-pinned Ruff and diff whitespace checks passed. The previously open inline thread was answered and resolved after verifying its earlier fix; the two previously missed findings had no inline conversations to resolve.
Preserve non-generic extension installation and event-refresh error handling when integration state is invalid. Store generic ownership metadata only for generic installs and add regressions for both sides of the boundary.
Assisted-by: GitHub Copilot (model: GPT-6 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
On behalf of @mnriem, this scope follow-up was authored by GitHub Copilot (GPT-6 Sol, autonomous). The agent wrote the code and tests, ran local verification, and pushed commit 71815df; there was no human line-by-line review of this round before posting.
Commandful installation no longer rejects malformed integration state merely because a non-generic integration is active, and non-generic event refresh retains its previous invalid-state error behavior. New generic ownership metadata is recorded only for generic installs. Regression tests reproduced all three non-generic behavior differences before this commit and now verify the boundary; the generic rejection and handled event-error cases remain covered. The shared registrar/rollback paths are required to write and restore generic invocations, including when a project later switches integrations. The GitHub extension version bump remains because its updated bundled documentation triggers the repository version guard and existing generic installs need an available extension update path.
Validation: 405 affected tests passed on Python 3.13 and 3.14; the full Python 3.13 suite passed (9,122 passed, 18 skipped; 9,140 collected). CI-pinned Ruff and whitespace checks passed. New CI for this commit is starting.
Treat generated files with multiple hard links as unowned before command or skill refresh, so a matching hash cannot authorize changes to unrelated linked files. Cover both layouts and successful refresh after unlinking.
Assisted-by: GitHub Copilot (model: GPT-6 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
On behalf of @mnriem, this review round was authored by GitHub Copilot (GPT-6 Sol, autonomous). The agent wrote the code and regression tests, ran local validation, and pushed commit 9a8d2ef; no human line-by-line review occurred before posting.
Generic command and skill refresh now reject hard-linked generated artifacts even when their hashes match, protecting unrelated hard-link peers from in-place writes. The new regression reproduced the overwrite before the fix in both layouts; after the fix it verifies the warning, unchanged output and peer bytes, unchanged registry metadata, and successful refresh after unlinking. The inline review thread was answered and resolved after verification.
Local validation: 952 affected tests passed on Python 3.13; 361 generic/update/registrar tests passed on Python 3.14; total pytest collection increased from 9,140 to 9,142. CI-pinned Ruff and diff whitespace checks passed. CI for the new head is starting.
Refresh replaces dev symlinks owned by another extension
src/specify_cli/agents.py:639
This ownership check accepts a retargeted dev symlink as long as it points anywhere under .specify/extensions and its target bytes still match the recorded digest. A link moved from this extension's cache to another extension's file is user-modified, but refresh will authorize it and _write_registered_output() will unlink and replace it. Restrict the target to this extension's own directory, matching the stricter checks already used by snapshot and disable paths.
This has the same cross-extension symlink ownership gap as the flat-command path: any symlink into any installed extension is treated as owned when its bytes match. Consequently skill refresh and force reinstall can replace a symlink that was retargeted away from this extension. Pass the extension ID into this ownership helper (or otherwise validate against that extension's resolved root) rather than accepting the entire extensions directory.
This issue also appears on line 2243 of the same file.
On behalf of @mnriem, this review follow-up was authored by GitHub Copilot (GPT-6 Sol, autonomous). The agent wrote the code and tests, ran local validation, and pushed commit ca3d27e; no human line-by-line review occurred before posting.
Generic ownership checks now require dev symlinks to resolve inside the extension that owns the recorded hash, rather than anywhere under .specify/extensions. Generic skill cleanup also preserves links retargeted to a different extension. Regressions reproduced unsafe refresh, forced reinstall, and skill removal with identical target bytes before the fix; the update-rollback case and existing legitimate dev-symlink paths remain covered. These two findings were in the review overview rather than inline threads, so there were no new conversations to resolve.
Local validation: 959 affected tests passed on Python 3.13, 368 on Python 3.14; 9,149 total tests collected (up from 9,142). CI-pinned Ruff and whitespace checks passed. CI on this commit is queued.
On behalf of @mnriem, GitHub Copilot (GPT-6 Sol, autonomous) added and ran the positive coverage in commit 2fd9aaa; the agent authored this test-only change without human line-by-line review before posting.
Both generic layouts now explicitly verify that force reinstall succeeds for a legitimate extension-owned dev symlink, remains a symlink into the owning extension, and can subsequently be removed. This complements the existing cross-extension retargeting negatives for refresh, force reinstall, and removal, the positive dev-symlink refresh/removal tests, and the update-rollback preservation case. The retargeting tests reproduced the bug before the prior fix. Locally, 277 generic/update tests passed on Python 3.13 and 11 directly affected positive/negative cases passed on Python 3.14; CI-pinned Ruff passed and collection increased to 9,151 tests. CI for this test-only commit is queued.
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🔵 Needs a closer look
The broad transactional filesystem lifecycle warrants final human review despite extensive regression coverage.
Review effort: Balanced Findings: None
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
2 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.
Description
Closes #4777. Installing an extension in a generic project now registers its commands or skills in the configured
--commands-dir. Registration reads persisted integration settings and tracks generated artifacts by hash; removal, disable, and upgrades preserve edited output, core commands, and unrelated files. The corespeckit.taskstoissuescommand is unchanged.The bundled GitHub extension is now covered by real commands- and skills-layout installation/removal tests, and its migration documentation no longer claims generic is unsupported. This is a shared registration fix, not a GitHub-specific workaround. The bundled extension's documentation change bumps its manifest and catalog version together to 1.0.1 so installed copies can receive the update.
Regression tests fail before the fixes and pass afterward for missing generic invocations, collision/retry, stale hashes after skill upgrades, aliases and flat-command refresh, malformed-settings cleanup and enable rollback, partial-registration cleanup (including config preservation), and extension-update backup/rollback. Both layouts, custom paths, rendering, core coexistence, edited files, and invalid settings are covered.
Testing
uv run specify --help(using this worktree's.venv/bin/specify --help).uv sync && uv run pytest(using this worktree's.venv/bin/python -m pytest): before the upstream merge, the full suite passed on macOS Python 3.13 and 3.14 (8,615 passed, 17 skipped each). On the updated branch, the affected suites passed (321 passed, 36 skipped), and after the manifest/catalog version bump the bundled GitHub + generic suites passed (264 passed, 36 skipped). The GitHub full matrix on the latest commit is pending.CI-equivalent local checks passed: Ruff 0.15.0 (
srcandtests), ShellCheck at error severity on tracked shell scripts, the Bash portability guard, markdownlint-cli2 0.23.2 on CI's documentation-only globs (58 files after the upstream merge), the committed-requirements pip-audit, the extension version-bump guard against upstreammain, andgit diff --check. The local PowerShell executable currently crashes with an assembly-load error even when started standalone; tests requiring it cannot be validated again on this host. GitHub CI tests the other operating systems.Manual test selection and results
specify init --integration generic, then/speckit.specifyspecify extension add github, invoke the generated command or skill, then disable/re-enable/removeAI Disclosure
AI disclosure: GitHub Copilot (GPT-6 Sol, interactive mode; reasoning effort not specified) autonomously authored the implementation, regression tests, documentation, commits, review fixes, and this PR description in response to contributor requests. The contributor directed the scope and requested validation; no human line-by-line review or manual agent-invocation test is claimed.