Skip to content

Stage 1: Integration foundation β€” base classes, manifest system, and registry#1925

Merged
mnriem merged 8 commits intomainfrom
copilot/agent-catalog-self-bootstrapping
Mar 31, 2026
Merged

Stage 1: Integration foundation β€” base classes, manifest system, and registry#1925
mnriem merged 8 commits intomainfrom
copilot/agent-catalog-self-bootstrapping

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 20, 2026

Stage 1 of #1924 β€” ships the base classes and manifest system for the integration architecture. Purely additive β€” no behavior changes to existing code.

What's in this PR

src/specify_cli/integrations/__init__.py β€” Integration registry

  • INTEGRATION_REGISTRY dict (empty β€” populated in later stages)
  • _register() and get_integration() helpers

src/specify_cli/integrations/base.py β€” Base classes

  • IntegrationOption β€” frozen dataclass declaring CLI options an integration accepts
  • IntegrationBase ABC β€” key, config, registrar_config, context_file, setup(), teardown(), install(), uninstall(), templates_dir(), options()
  • MarkdownIntegration(IntegrationBase) β€” concrete base for ~20 standard markdown agents (subclass, set three attrs, done)

src/specify_cli/integrations/manifest.py β€” Hash-tracked file management

  • IntegrationManifest with record_file(), record_existing(), check_modified(), save()/load() persistence
  • uninstall() β€” only removes files whose SHA-256 still matches; modified files are skipped and reported
  • Empty parent directory cleanup on uninstall

tests/test_integrations.py β€” 34 tests

  • IntegrationOption (4), IntegrationBase/MarkdownIntegration (7), manifest record/hash (3), modified detection (3), uninstall (7), persistence (5), registry (3)
  • 98% code coverage on the new modules

What stays unchanged

Everything. This stage is purely additive β€” no existing files modified, all 929 existing tests pass.

Copilot AI linked an issue Mar 20, 2026 that may be closed by this pull request
17 tasks
Copilot AI changed the title [WIP] Update agent catalog for self-bootstrapping agent packs Agent catalog: self-bootstrapping agent packs with manifest schema, resolution stack, and CLI Mar 20, 2026
Copilot AI requested a review from mnriem March 20, 2026 21:06
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

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

@copilot The setup should store a list of installed files with their hash in the .specify directory so we can track what was installed by the agent. At teardown time that file needs to be used to determine if ANY of the installed files was updated and if so the remove should require a --force. Note you will NOT delete any directories but rather just the individual files.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 20, 2026

@copilot The setup should store a list of installed files with their hash in the .specify directory so we can track what was installed by the agent. At teardown time that file needs to be used to dete...

Implemented in b5a5e3f (file tracking) and a63c248 (finalize_setup fix):

  • setup() creates directories; finalize_setup() (base class method) scans the agent's commands_dir and writes .specify/agent-manifest-<agent_id>.json with SHA-256 hashes of all installed files
  • teardown(force=False) reads the manifest and removes only individual tracked files β€” directories are never deleted
  • If any tracked file was modified since installation, teardown raises AgentFileModifiedError unless --force is passed
  • specify agent switch now accepts --force to pass through to teardown
  • 11 new tests cover: record/check unmodified, detect modification, refuse without force, allow with force, directory preservation, deleted-file handling, manifest JSON structure

Copilot AI changed the title Agent catalog: self-bootstrapping agent packs with manifest schema, resolution stack, and CLI Agent catalog: self-bootstrapping agent packs with manifest schema, resolution stack, file-tracked teardown, and CLI Mar 20, 2026
Copilot AI requested a review from mnriem March 20, 2026 21:21
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

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

@copilot the agent setup should report the files it installed as the cli needs to record both what was installed by the agent for the core commands / templates as well as what installed by any extensions. It should the record that. And then BEFORE agent teardown is called determine if any file was changed and ask for confirmation. And then feed those lists into the teardown of the agent and/or the installed extensions.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 20, 2026

@copilot the agent setup should report the files it installed as the cli needs to record both what was installed by the agent for the core commands / templates as well as what installed by any extensi...

Implemented in e190116:

  • setup() now returns List[Path] of installed files
  • finalize_setup(agent_files=, extension_files=) records both agent and extension files in .specify/agent-manifest-<id>.json with separate agent_files / extension_files categories
  • _reregister_extension_commands() returns List[Path] of files it created, which get passed to finalize_setup() as extension_files
  • Before teardown: CLI calls check_modified_files(), lists modified files, prompts for confirmation β€” teardown is only called after the user confirms
  • get_tracked_files() returns (agent_files, extension_files) tuple so the CLI can retrieve both sets
  • teardown(files=...) accepts explicit file dict β€” the CLI feeds the tracked file lists directly into teardown
  • 5 new tests cover: categorised manifest structure, get_tracked_files split, explicit-file teardown, extension file modification detection

Copilot AI requested a review from mnriem March 20, 2026 21:38
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

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

@copilot Are extensions hooked up for setup and teardown? Also are you testing that it similar to our --offline support? We must make sure the install between old (current way of installing) and new (our agent setup/teardown) deliver the exact same files. How do we trigger the new flow? I would suggest --agent

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 20, 2026

@copilot Are extensions hooked up for setup and teardown? Also are you testing that it similar to our --offline support? We must make sure the install between old (current way of installing) and new (...

Implemented in c2227a7:

Extensions: Yes β€” fully hooked up in agent_switch. During setup, _reregister_extension_commands() returns List[Path] of files created by extension registration, which get passed to finalize_setup(extension_files=...) and recorded in the install manifest. During teardown, get_tracked_files() retrieves both agent and extension file lists, the CLI checks for modifications and prompts, then feeds both lists into teardown(files=...).

Parity testing: 4 new tests verify that the pack-based flow produces the same results as the AGENT_CONFIG flow:

  • Every AGENT_CONFIG agent resolves through the pack system
  • commands_dir derived from AGENT_CONFIG matches the pack manifest
  • finalize_setup records files created by the init pipeline
  • Pack command_registration matches CommandRegistrar.AGENT_CONFIGS (same dir, format, args, extension)

Triggering the new flow: Added --agent flag to specify init:

specify init my-project --agent claude          # pack-based flow
specify init --here --agent gemini --ai-skills  # with skills

--agent and --ai are mutually exclusive. --agent resolves through the pack system, validates the pack, runs the same init pipeline (scaffold/download β†’ skills β†’ presets β†’ extensions), then calls finalize_setup() to record all installed files. This enables specify agent switch to tear down cleanly later. init-options.json gains "agent_pack": true when --agent is used.

Copilot AI requested a review from mnriem March 20, 2026 21:56
Copy link
Copy Markdown
Collaborator

@mnriem mnriem left a comment

Choose a reason for hiding this comment

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

You must include parity tests that use the init command for both cases and compare that the ACTUAL generated files and directory structure are identical between the --ai and the --agent flows All agents MUST be tested!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/specify_cli/integrations/manifest.py:179

  • uninstall() deletes the manifest file unconditionally, even when some files are skipped due to modification. This makes it impossible to re-run uninstall in a later CLI invocation with force=True (or after reverting changes) because the manifest state is gone. A safer pattern is to only remove the manifest when all tracked files were removed (or force=True), or to rewrite the manifest to contain only the remaining/skipped entries.
        # Remove the manifest file itself
        manifest = root / ".specify" / "integrations" / f"{self.key}.manifest.json"
        if manifest.exists():
            manifest.unlink()
            parent = manifest.parent
            while parent != root:
                try:
                    parent.rmdir()
                except OSError:
                    break
                parent = parent.parent

        return removed, skipped

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

- Store manifest file keys using as_posix() after resolving relative
  to project root, ensuring cross-platform portable manifests
- Type the manifest parameter as IntegrationManifest (via TYPE_CHECKING
  import) instead of Any in IntegrationBase methods
@mnriem mnriem requested a review from Copilot March 31, 2026 14:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


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

- uninstall() now uses non-resolved path for deletion so symlinks
  themselves are removed, not their targets; resolve only for
  containment validation
- setup() keeps unresolved dst_file for copy; resolves separately
  for project-root validation
- load() catches json.JSONDecodeError and re-raises as ValueError
  with the manifest path for clearer diagnostics
- Added test for invalid JSON manifest loading
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


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

- uninstall() now uses os.path.normpath for lexical containment check
  instead of resolve(), so in-project symlinks pointing outside are
  still properly removed
- setup() asserts manifest.project_root matches the passed project_root
  to prevent path mismatches between file operations and manifest
  recording
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


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

- check_modified() treats non-regular-files (dirs, symlinks) as modified
  instead of crashing with IsADirectoryError
- uninstall() skips directories (adds to skipped list), only unlinks
  files and symlinks
- load() validates stored integration key matches the requested key
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.


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

- Broken symlinks now removable (lexists check via is_symlink fallback)
- Symlinks never hashed (avoids following to external targets)
- Symlinks only removed with force=True, otherwise skipped
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.


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

- uninstall() wraps path.unlink() in try/except OSError to avoid
  partial cleanup on race conditions or permission errors
- setup() raises ValueError on missing config or folder instead of
  silently returning empty
- Added 3 tests: symlink in check_modified, symlink skip/force in
  uninstall (47 total)
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.


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

- check_modified() no longer calls _validate_rel_path (which resolves
  symlinks); uses lexical checks (is_absolute, '..' in parts) instead
- is_symlink() checked before is_file() so symlinks to files are still
  treated as modified
- Fixed templates_dir() docstring to match actual behavior
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.


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

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

From Agents to Integrations

3 participants