Conversation
mnriem
left a comment
There was a problem hiding this comment.
@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.
Implemented in b5a5e3f (file tracking) and a63c248 (finalize_setup fix):
|
mnriem
left a comment
There was a problem hiding this comment.
@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.
Implemented in e190116:
|
mnriem
left a comment
There was a problem hiding this comment.
@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
Implemented in c2227a7: Extensions: Yes β fully hooked up in Parity testing: 4 new tests verify that the pack-based flow produces the same results as the AGENT_CONFIG flow:
Triggering the new flow: Added
|
mnriem
left a comment
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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 withforce=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 (orforce=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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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 registryINTEGRATION_REGISTRYdict (empty β populated in later stages)_register()andget_integration()helperssrc/specify_cli/integrations/base.pyβ Base classesIntegrationOptionβ frozen dataclass declaring CLI options an integration acceptsIntegrationBaseABC β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 managementIntegrationManifestwithrecord_file(),record_existing(),check_modified(),save()/load()persistenceuninstall()β only removes files whose SHA-256 still matches; modified files are skipped and reportedtests/test_integrations.pyβ 34 testsWhat stays unchanged
Everything. This stage is purely additive β no existing files modified, all 929 existing tests pass.