Skip to content

feat(integration): add none for shared scaffolding only - #5030

Open
mnriem wants to merge 5 commits into
github:mainfrom
mnriem:mnriem-integration-free-scaffolding
Open

mnriem wants to merge 5 commits into
github:mainfrom
mnriem:mnriem-integration-free-scaffolding

Conversation

@mnriem

@mnriem mnriem commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Description

Allow teams to share Spec Kit scaffolding without generating agent-specific commands, skills, settings, or native events. Following the contributor-requested pivot, none is now a real, registered built-in integration with an empty owned-file manifest, rather than an init-only sentinel:

specify init my-project --integration none
cd my-project
specify integration install copilot --force
specify integration use copilot
specify integration uninstall copilot

none supports ordinary install, use, switch, upgrade, and uninstall operations. Normal .specify/ project metadata remains: integration.json records the installed/default integration, init options record "none", and none.manifest.json contains no owned files. Shared scaffolding is tracked separately. Omitted --integration retains the existing interactive picker and non-interactive Copilot default; SPECKIT_INTEGRATION_DEFAULT=none is also supported.

Installing another integration preserves none as the default until explicit use/switch. Activation updates unmodified managed shared files for the agent's invocation syntax and registers enabled extension/preset contributions while preserving customized shared files. Existing multi-install safety rules remain unchanged: coexistence requires --force, and status reports that the pair is not declared multi-install safe. switch can replace the default instead.

The adapter only provides ordinary metadata and no-op setup. Its absent registrar configuration and disabled skills mode naturally produce no agent contribution files. No framework capability, registration, install, status, or collision-policy changes are needed. Remove the old init-only sentinel branches so init uses the registered adapter and normal lifecycle. Uninstalling the last integration clears active metadata and preserves shared scaffolding; none is not implicitly reinstalled. Existing legacy null-agent/cleared-agent projects remain supported by status.

The earlier review fixes retain contribution ownership through re-init/upgrade and synchronize observed component writes/removals without adopting pre-existing files or unrelated edits. Broader changes to fallback-activation registration and re-init registration timing were removed at the contributor's request.

Update the built-in registry/catalog, CLI help, core/integration reference, integration design documentation, and related tests. No issue is linked; this is a contributor-requested capability.

Testing

  • Tested locally with uv run specify --help
  • Ran existing tests with uv sync && uv run pytest
  • Tested with a sample project (if applicable)

Used this worktree's own virtual environment instead of bare uv run pytest, as recommended in CONTRIBUTING.md. The literal-command checkboxes above remain unchecked; the equivalent commands and their actual results follow.

  • .venv/bin/python -m pytest -p tests.specify_cli.presets._fixtures tests -q --tb=short — completed the entire suite without deselections: 12,263 passed, 71 skipped, 2 failed in 1,001.94 seconds. One failure was an existing GitHub-extension registry expectation that needed to exclude the newly registered, non-rendering none integration; that test-only expectation is now corrected. The other was tests/integrations/test_integration_claude.py::TestClaudeNoModelInvocationOption::test_upgrade_reapplies_option_to_preset_overrides, which expects an upgrade to fail but gets exit code 0. That identical failure was independently reproduced from a temporary archive of the pre-pivot committed HEAD, b986a1c38bc296a685a96884ae65cd8c338b7c84, and was left outside this change's scope.
  • .venv/bin/python -m pytest -p tests.specify_cli.presets._fixtures tests/integrations/test_integration_none.py tests/specify_cli/test_command_init.py tests/specify_cli/integrations/test_command_status.py tests/specify_cli/integrations/test_command_upgrade.py tests/test_agent_config_consistency.py tests/extensions/github/test_github_extension.py -q --tb=short — 378 passed, 1 skipped after the registry-expectation correction, covering every changed test module. The complete suite was not repeated after that test-only correction.
  • .venv/bin/python -m ruff check --select E4,E7,E9,F,S602,S604,S605 src/specify_cli/integrations/none/__init__.py tests/integrations/test_integration_none.py src/specify_cli/command_init.py — passed.
  • .venv/bin/specify --help — passed.
  • git diff --check — passed.

The tests cover no-op setup and empty ownership; init and optional component installation; normal install, use, switch, upgrade, and uninstall; explicit activation after secondary install; existing multi-install safety rejection; invalid options; and preservation of existing agent/user files. Existing cleared-state and legacy null-agent status cases remain covered separately from installed none.

Separately exercised the actual .venv/bin/specify executable in 13 successful subprocess invocations against disposable projects, covering optional components, forced coexistence, explicit activation, upgrade, uninstall, switch, preservation of existing outputs, and SPECKIT_INTEGRATION_DEFAULT=none. Temporary projects were removed afterward.

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, powered by GPT-6.1 Sol (model ID: gpt-6.1-sol), performed autonomous implementation within mnriem's requested and approved scope. Reasoning effort was not exposed in the session metadata. Assistance included code investigation, implementation, automated tests, subprocess-based dummy-project validation, documentation, and authoring the commit and this PR description. This does not claim human line-by-line review.

Add the init-only --integration none opt-out while preserving shared infrastructure and existing defaults. Support later integration installation and cover the complete install/uninstall lifecycle.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI max review requested due to automatic review settings October 19, 2026 20:45

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.

🟡 Changes recommended

Optional-component installation can still load adapters despite the integration-free contract.

3 open issues
What changed in this PR

Adds an init-only --integration none mode so teams can scaffold shared Spec Kit infrastructure before choosing an agent.

Changes:

  • Skips agent setup and records no active integration.
  • Refreshes unmodified shared files when installing the first integration.
  • Adds lifecycle tests and documents the new initialization flow.

AI review for the requester: GitHub Copilot; model/settings unavailable; automated source review and focused verification.

File Description
tests/​specify_cli/​test_command_init.py Tests shared-only scaffolding, validation, preservation, and lifecycle transitions.
src/​specify_cli/​integrations/​command_install.py Enables shared-file refresh on first integration installation.
src/​specify_cli/​integrations/​_lifecycle.py Bypasses adapter preparation for none.
src/​specify_cli/​command_init.py Implements integration-free initialization and next-step guidance.
docs/​reference/​integrations.md Introduces the shared-only initialization option.
docs/​reference/​core.md Documents usage, restrictions, and later integration installation.
design/​integration.md Defines the integration-free lifecycle contract.

Review effort: Max

Comment thread src/specify_cli/command_init.py
Comment on lines +439 to +440
if key == "none":
return handler(*args, **kwargs)
Comment thread src/specify_cli/integrations/command_install.py
Recognize integration-free status while checking shared manifests, prevent adapter loading during optional component installs, and register existing contributions when installing the first integration. Add before-and-after regressions and preserve secondary-install deferral.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI max review requested due to automatic review settings October 19, 2026 21:21
@mnriem

mnriem commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed all three findings from review 5577740091 in commit ef8faab.

  • Integration status now accepts initialized projects with no active agent, including cleared active keys after the last uninstall, while still checking the shared manifest. Integrated projects with missing selection metadata continue to fail; missing or modified shared files are reported.
  • Optional preset and extension installation checks active-agent state before entering command/skill registration. Event refresh skips adapter loading when there are no installed integrations. Regression coverage includes a loader sentinel and a corrupt leftover package registry.
  • Installing the first integration registers enabled extension and preset contributions after saving active metadata, then resynchronizes managed-file hashes. Secondary installs still defer contribution registration until selected.

Regression evidence: the initial review-focused selection produced 6 failures and 1 pass before the fixes, then 7 passes afterward. Additional last-uninstall status variants are included in the final targeted run.

Validation:

.venv/bin/python -m pytest tests/specify_cli/test_command_init.py tests/specify_cli/integrations/test_command_install.py tests/specify_cli/integrations/test_command_uninstall.py tests/specify_cli/integrations/test_command_status.py tests/specify_cli/integrations/test_installed_adapters.py tests/specify_cli/integrations/test_lifecycle.py tests/specify_cli/presets/test_manager.py tests/specify_cli/presets/test_manager_commands.py tests/specify_cli/presets/test_manager_skills.py tests/specify_cli/events/test_events.py tests/test_extensions.py tests/test_presets.py tests/integrations/test_cli.py -q --tb=short
Result: 1,749 passed, 3 warnings.

.venv/bin/python -m pytest tests/specify_cli/test_command_init.py tests/specify_cli/integrations/test_command_status.py tests/specify_cli/test_file_policy.py tests/test_agent_config_consistency.py -q --tb=short
Result: 294 passed on the final changes, including the additional last-uninstall status cases.

git diff --check
Result: passed.

Also ran the actual .venv/bin/specify executable in separate subprocesses against a disposable dummy project: init without an integration plus the Git extension and a team preset, status, install Copilot, status, uninstall Copilot, status, re-init without an integration, and status. All commands succeeded. Assertions verified real extension/preset artifacts, the preset's plan override, synchronized managed-file hashes, and preservation of shared scaffolding. The dummy project was removed.

AI disclosure: posted on behalf of mnriem by GitHub Copilot using GPT-6.1 Sol (gpt-6.1-sol), autonomous execution within the user-approved scope; reasoning effort was not exposed in session metadata. AI assistance covered investigation, implementation, tests, documentation, commit authorship, and this review response. No human line-by-line review is claimed.

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.

Comment thread src/specify_cli/integration_status.py
Comment thread src/specify_cli/integrations/command_install.py Outdated
Comment thread src/specify_cli/integrations/command_install.py Outdated
Keep integration-free recovery guidance explicit, preserve existing extension skills, and track newly generated contributions for hash-aware cleanup. Retain ownership through upgrade and avoid adapter loading in inactive registration helpers.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI max review requested due to automatic review settings October 19, 2026 22:10
@mnriem

mnriem commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed review 5578115557 in c0a68e9.

Integration-free shared-manifest recovery now consistently recommends specify init --here --force --integration none, with a warning to review customized files first. First-install extension registration no longer forces replacement of existing skills, including customized outputs retained by an earlier uninstall.

Newly generated contribution files are recorded with hashes using observed registration writes, without adopting pre-existing files or shared metadata. Uninstall removes unchanged extension and preset outputs while preserving edits. Upgrade captures regenerated contributions too, and extension layout reconciliation now clears stale skill tracking when manifest cleanup has already removed those outputs.

Also strengthened the earlier adapter-loading fix: direct inactive registration helpers skip adapter-loading contexts and return before constructing registrars. Existing integration-free status and optional-component regressions remain covered.

Automated validation:

.venv/bin/python -m pytest tests/specify_cli/test_command_init.py tests/specify_cli/integrations/test_command_status.py tests/specify_cli/integrations/test_helpers.py -q -k 'first_integration or inactive_registration_helpers or status_checks_shared_manifest_without_integration or registration_ownership' --tb=short
Result: 21 passed. The initial review-regression selection reproduced 11 failures before the fixes.

.venv/bin/python -m pytest tests/specify_cli/test_command_init.py tests/specify_cli/integrations/test_helpers.py tests/specify_cli/integrations/test_registration.py tests/specify_cli/integrations/test_command_install.py tests/specify_cli/integrations/test_command_uninstall.py tests/specify_cli/integrations/test_command_upgrade.py tests/specify_cli/integrations/test_command_status.py tests/specify_cli/integrations/test_lifecycle.py tests/specify_cli/presets/test_manager.py tests/specify_cli/presets/test_manager_commands.py tests/specify_cli/presets/test_manager_skills.py tests/specify_cli/events/test_events.py tests/test_extensions.py tests/test_presets.py tests/test_extension_registration.py tests/specify_cli/test_file_policy.py tests/test_agent_config_consistency.py -q --tb=short
Result: 1,313 passed, 15 warnings (legacy layouts and deliberately simulated composition/registration failures).

.venv/bin/python -m pytest tests/specify_cli/integrations/test_installed_adapters.py -q -k 'registration or skill_directory_uses_pinned or contributions_follow_active' --tb=short
Result: 33 passed.

git diff --check
Result: passed.

Separately exercised the standalone .venv/bin/specify executable in a disposable project: init without an integration plus Git extension and local team preset, first Copilot install, verification of every recorded SHA-256 hash, uninstall, reinstall, and uninstall again. Unchanged extension and preset-only outputs were removed, pre-existing and customized skills survived, shared scaffolding remained, and integration-free status reported ok. This was automated subprocess validation, not a claim of human testing.

AI disclosure: posted on behalf of @mnriem by GitHub Copilot, powered by GPT-6.1 Sol (gpt-6.1-sol), acting autonomously. Code changes, regression tests, validation, commit, and this review summary were AI-authored/executed; no line-by-line human review is claimed. The reasoning-effort setting is not exposed.

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.

🟡 Changes recommended

Contribution lifecycle handling can overwrite or delete files and lose ownership tracking.

5 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Preserve existing native skill files during first integration install

src/​specify_cli/​integrations/​command_install.py:221

First-install preservation does not cover native-skills integrations. After init --integration none --extension git, create a team-owned .agents/skills/speckit-git-feature/SKILL.md, then run integration install codex: this helper reaches register_commands_for_agent, whose writer overwrites the existing file. The force=False guard protects only the separate companion-skills pass. This can also overwrite edits preserved by an earlier uninstall, contrary to design/integration.md, “Ownership and lifecycle.” Pass a preserve-existing policy through native-skills registration for first installs and extend the Copilot preservation test to Codex.

AI disclosure: GitHub Copilot performed automated review for the requester; model/settings unavailable.

3 resolved since last review

Review effort: Max

Comment thread src/specify_cli/command_init.py
if not path.is_file() or path.is_symlink():
continue
try:
manifest.record_existing(relative, ownership="whole")
Comment thread src/specify_cli/integrations/command_upgrade.py
@mnriem mnriem added the triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review label Oct 20, 2026
Distinguish contribution artifacts from stale core files during re-init and upgrade. Synchronize observed component writes and removals without adopting pre-existing files or unrelated edits, including update rollback and inactive registrations.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI max review requested due to automatic review settings October 20, 2026 13:22
@mnriem

mnriem commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed review 5578546975 in b986a1c.

Integration manifests now identify component-produced files separately from core setup outputs. Forced re-init and upgrade retain those files and their original ownership hashes before stale-core cleanup, including when an integration is inactive or its extension is disabled or unreadable. Existing layout reconciliation still retires old component outputs only after replacement succeeds.

Component installation, removal, update, and reconciliation synchronize owning manifests from observed writes and removals, including whole skill-directory removal. Re-registration updates only affected hashes rather than re-baselining every tracked file. Pre-existing files are not adopted, unrelated edits keep their baselines, and update rollback preserves restored user edits. New historical outputs are attributed to the integration whose existing output root they belong to.

Regression coverage includes forced re-init followed by uninstall; extension and preset removal followed by status; real extension/preset update commands followed by upgrade without --force; extension update rollback; disabled, corrupt, and inactive upgrade preservation; and the existing Bob layout-failure case extended to integration-owned contributions. The initial focused run reproduced the lifecycle failures before implementation.

Validation:

.venv/bin/python -m pytest tests/specify_cli/test_command_init.py tests/specify_cli/integrations/test_contributions.py tests/specify_cli/integrations/test_command_upgrade.py -q -k 'forced_reinit_retains or component_removal_synchronizes or component_regeneration_rehashes or component_update_then_upgrade or upgrade_retains_owned or layout_change_preserves_extension_artifacts_when_reregistration_fails or contribution_provenance or retention or synchronization or new_core_output or failed_component or new_historical' --tb=short
Result: 24 passed, 2 expected legacy-layout warnings.

.venv/bin/python -m pytest -p tests.specify_cli.presets._fixtures tests/specify_cli/test_command_init.py tests/specify_cli/integrations/test_contributions.py tests/specify_cli/integrations/test_helpers.py tests/integrations/test_manifest.py tests/specify_cli/integrations/test_command_install.py tests/specify_cli/integrations/test_command_uninstall.py tests/specify_cli/integrations/test_command_upgrade.py tests/specify_cli/integrations/test_command_status.py tests/specify_cli/integrations/test_lifecycle.py tests/specify_cli/presets/test_manager.py tests/specify_cli/presets/test_manager_commands.py tests/specify_cli/presets/test_manager_skills.py tests/specify_cli/events/test_events.py tests/test_extensions.py tests/test_presets.py tests/test_extension_registration.py tests/specify_cli/test_file_policy.py tests/test_agent_config_consistency.py tests/specify_cli/extensions/test_command_update.py tests/specify_cli/extensions/test_command_update_transaction.py tests/specify_cli/presets/test_command_update.py tests/integration/test_preset_update_workflow.py -q --tb=short
Result: 1,479 passed, 47 warnings from legacy layouts and simulated composition/registration failures. The existing preset fixture module is loaded explicitly for the preset update command tests.

.venv/bin/python -m pytest tests/specify_cli/integrations/test_installed_adapters.py -q -k 'registration or skill_directory_uses_pinned or contributions_follow_active or failed_switch_restores_preset_composition_cache or overlapping_own_output_roots or extension_event_refresh_loads' --tb=short
Result: 52 passed.

git diff --check
Result: passed.

Also ran five standalone .venv/bin/specify subprocess scenarios in disposable projects: forced re-init/uninstall, preset update followed by no-force upgrade and component removals, disabled-extension upgrade, corrupt-extension upgrade, and inactive-integration upgrade. All ownership/content assertions passed, with zero missing or modified managed files. The deliberately forced Copilot/Claude multi-install retained its expected unsafe-multi-install diagnostic; the other four scenarios reported status ok. This was automated validation, not a claim of human testing.

AI disclosure: posted on behalf of @mnriem by GitHub Copilot, powered by GPT-6.1 Sol (gpt-6.1-sol), acting autonomously. Code, tests, validation, commit, and this summary were AI-authored/executed; no line-by-line human review is claimed. The reasoning-effort setting is not exposed.

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.

🟡 Changes recommended

User-file preservation, manifest synchronization, registration, and CI regressions remain unresolved.

10 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Save active agent options before registering contributions

src/​specify_cli/​command_init.py:801

Persist the new active-agent options before re-registering contributions. After integration-free init with an extension and preset, init --here --integration copilot --force reaches this block while init-options.json still has null agent keys. Preset registration therefore skips preset-only commands, and extension registration emits command-mode files instead of Copilot's default skills. The new ai/ai_skills values are not saved until line 924, so init succeeds with incomplete or incorrectly formatted contributions. Move this block after active metadata is saved and add coverage for forced re-init directly from the none state.

AI review for the requester: GitHub Copilot; model/settings unavailable; autonomous analysis.

Medium severity Preserve existing native integration skills during registration

src/​specify_cli/​integrations/​command_install.py:220

First-install registration still overwrites existing extension skills for native-skill integrations such as Claude and Codex. After init --integration none --extension git, create .claude/skills/speckit-git-feature/SKILL.md, then install Claude: registration reaches agents.CommandRegistrar.register_commands(), whose writer replaces the file unconditionally. The default force=False protects only the separate extension-skills pass used by Copilot, not this path. This also destroys edited skills preserved by a previous uninstall, contrary to design/integration.md, “Ownership and lifecycle.” Propagate a preserve-existing policy through native-skill registration and cover this round trip beyond Copilot.

AI review for the requester: GitHub Copilot; model/settings unavailable; autonomous analysis.

1 resolved since last review

Review effort: Max


return removed

@contribution_operation
Comment on lines +131 to +135
if removal:
removals.update(
entry for entry in owned
if entry == name or entry.startswith(name + "/")
)
Comment on lines +146 to +147
for key, owner in manifests.items():
original_files = owner.files
Comment on lines +162 to +163
if tracked and initial_hash == current_hash:
continue
"preset/extension overrides as modified files."
),
)
with _record_new_registration_files(new_manifest):
Comment on lines +680 to +687
if component == "extension":
command = project / ".specify/extensions/git/commands/speckit.git.feature.md"
manager = ExtensionManager(project)
register = lambda: manager.register_enabled_extensions_for_agent("copilot", force=True)
else:
command = project / ".specify/presets/team/command.md"
manager = PresetManager(project)
register = lambda: manager.register_enabled_presets_for_agent("copilot")
Replace the init-only none sentinel with a registered integration using the existing lifecycle. Keep setup artifact-free, wire discovery, and update obsolete sentinel expectations and documentation without changing framework policies.

Assisted-by: GitHub Copilot (model: GPT-6.1 Sol, autonomous)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI max review requested due to automatic review settings October 20, 2026 15:35
@mnriem mnriem changed the title feat(init): support scaffolding without an integration feat(integration): add none for shared scaffolding only Oct 20, 2026
@mnriem

mnriem commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Reworked in e65b350 at mnriem's direction: none is a registered no-op integration using the existing lifecycle, not an init-only sentinel. Removed the old init bypasses; no new artifact-free capability, registration guard, collision exception, or generic lifecycle fix is included. Ordinary multi-install safety remains unchanged, including --force for coexistence. Supporting edits update obsolete sentinel assumptions and documentation.

The full suite completed without deselections: 12,263 passed, 71 skipped, 2 failed. The new registry-expectation failure was corrected, then every changed test module passed: 378 passed, 1 skipped. The other failure, Claude's test_upgrade_reapplies_option_to_preset_overrides, was independently reproduced unchanged at the pre-pivot committed HEAD b986a1c and remains outside this scope. The complete suite was not repeated after the test-only correction. Actual CLI validation also passed 13 subprocess invocations against disposable projects. The PR description contains exact automated commands and results.

AI disclosure: Posted on behalf of mnriem by GitHub Copilot, powered by GPT-6.1 Sol (gpt-6.1-sol), operating autonomously within the contributor-approved scope; reasoning effort was not exposed. AI assistance included investigation, code, tests, documentation, commit authorship, and this response. This does not claim human line-by-line review.

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.

🔵 Needs a closer look

Ownership regressions can lose cleanup records and cause restored user edits to be deleted.

8 open issues
7 previously missed issues

In code that hasn't changed since last review

Medium severity Reinitialization drops ownership records without loading prior manifest

src/​specify_cli/​command_init.py:722

init --here permits re-initialization without --force after confirmation. This guard then skips loading the previous manifest, but setup still saves a replacement at line 762. For a project with owned extension/preset contributions, their files remain on disk while their ownership records disappear, so later integration uninstall leaves them behind. Load the previous manifest whenever the target directory already existed, independently of overwrite consent or re-registration timing. Add a regression for the confirmation path.

AI-generated review: GitHub Copilot; model/settings unavailable; autonomous review.

Medium severity Component removal leaves ownership for missing outputs

src/​specify_cli/​extensions/​__init__.py:3539

Removing a component does not clear ownership of its already-missing outputs. For example, delete an owned Git extension skill, then remove the extension. _find_extension_skill_dirs() skips the missing SKILL.md, so the observer receives no removal and leaves its manifest entry intact. retain_contributions() then carries that entry through every upgrade/re-init, and status keeps reporting a missing managed file although its component is no longer installed. Explicit component removal should retire its absent output records as well as files actually deleted. Add a removal regression with an already-missing contribution.

AI-generated review: GitHub Copilot; model/settings unavailable; autonomous review.

Medium severity Extract shared lifecycle changes into a prerequisite PR

src/​specify_cli/​integrations/​_contributions.py:39

The contribution observer, manifest provenance, and install/upgrade/manager changes introduce shared lifecycle behavior for existing integrations, not just wiring for none. Registration gating and integration-free status handling also change shared behavior. design/integration.md, “Integration delivery boundary” (441–451), and CONTRIBUTING.md:68–79 require these changes in a separately justified prerequisite PR, with coverage independent of the new integration. Please extract the shared changes and their tests, then reference that prerequisite here. The description's claim that no registration/install/status changes are needed should also match the delivered scope.

AI-generated review: GitHub Copilot; model/settings unavailable; autonomous review.

Medium severity Switch warns about cleanup after outgoing manifest removal

src/​specify_cli/​integrations/​_contributions.py:69

Switching to a not-yet-installed integration produces false recovery warnings. command_switch.py:152–197 tears down the outgoing integration, deleting its manifest, before invoking the newly decorated extension/preset cleanup methods. The selection file still lists the outgoing key, so both observers warn that contribution cleanup may need manual recovery although the switch succeeds. Skip an absent manifest here; status already diagnoses missing manifests. Keep warnings for unreadable or invalid manifests, and assert successful switch output contains no synchronization warning.

AI-generated review: GitHub Copilot; model/settings unavailable; autonomous review.

Medium severity Directory rollback loses original hashes for restored contributions

src/​specify_cli/​integrations/​_contributions.py:135

Recursive removal records descendant names but not their original hashes. Extension updates delete whole skill directories, then regeneration first observes each SKILL.md as absent. If installation fails and rollback restores a customized skill, finalization adopts the restored user bytes as the managed baseline. A standard-library reproduction confirmed that a later non-forced uninstall deletes that customization. Snapshot tracked descendants before directory removal so restored bytes retain their original baseline. Add a failed-update regression using an edited contribution, not only an unrelated core file.

AI-generated review: GitHub Copilot; model/settings unavailable; autonomous review.

Medium severity Event refresh overwrites contribution ownership changes

src/​specify_cli/​integrations/​_contributions.py:172

extension update refreshes native events before this observer exits (extensions/_command_update_transaction.py:912). Event refresh independently loads and saves the integration manifest, but owner is still the pre-update snapshot. When command output also changes, this save drops newly recorded dispatcher/settings ownership or restores retired event records. A standard-library check confirmed both new event records disappear while their files remain, breaking event cleanup and dispatcher reference counting. Finish contribution synchronization before event refresh, or merge the observed changes into the latest manifest without overwriting event ownership or modes. Cover event additions and removals during updates.

AI-generated review: GitHub Copilot; model/settings unavailable; autonomous review.

Medium severity Observe legacy command deletions during migration

src/​specify_cli/​integrations/​command_upgrade.py:274

Retaining contributions exposes an unobserved deletion in Qoder's legacy migration. _retire_legacy_flat_extension_commands() deletes replaced commands with plain Path.unlink() (extensions/__init__.py:3931), so synchronization never removes their retained manifest entries. The updated migration test now marks those legacy paths as contributions but checks only the filesystem. After upgrade, status reports the deliberately deleted commands as missing, and later upgrades retain them again. Use the observed unlink helper for these deletions and assert the migrated manifest/status is clean.

AI-generated review: GitHub Copilot; model/settings unavailable; autonomous review.

2 resolved since last review

Review effort: Max

This branch has not been deployed

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

Labels

triage-nice-to-have Verdict: evidence-backed fix or greenlit feature — land after review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants