feat(cli): lockfileVersion 3 and qualified install paths (#130) - #131
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
Add per-package pathEncodingVersion in the lock, doctor checks for legacy flat paths and cross-package collisions, and update install fixtures for hierarchical deployment ZIP extraction. Co-authored-by: Cursor <cursoragent@cursor.com>
Pre-ready validation evidence
Depends on agents-repo/registry#231 Closes #130 |
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
🟡 Changes recommended
doctor’s legacy encoding check currently only detects legacy skill paths (missing legacy Claude flat paths), and the new doctor checks re-download artifacts independently, which can significantly increase runtime for larger installs.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR updates the CLI install/lock workflow to support lockfileVersion 3 with a per-package pathEncodingVersion that tracks qualified install-leaf deployment paths, and extends doctor to detect legacy path encoding and cross-package install path collisions.
Changes:
- Bump lock schema to lockfileVersion 3, adding optional per-package
pathEncodingVersion(persist + parse/serialize + specs/tests). - Accept qualified deployment ZIP layouts (no install-time rewrite) and persist
pathEncodingVersionfrom manifest or inferred ZIP entry layout. - Add
doctorchecks forlegacy_path_encodingandagent_path_collision, plus migration/docs updates and broader test fixture updates (including a collision integration test).
File summaries
| File | Description |
|---|---|
| tests/modules/install/packageRemover.test.ts | Updates expected extracted paths/content to qualified install-leaf layout. |
| tests/modules/install/packageExtractor.test.ts | Updates ZIP entry mapping/extraction assertions for qualified paths and new filenames. |
| tests/modules/install/installService.test.ts | Updates install flow assertions for qualified skill output paths/content. |
| tests/modules/install/installPersistenceRollback.test.ts | Updates rollback assertions to new qualified extract paths. |
| tests/modules/install/ciInstallService.test.ts | Updates CI install assertions for qualified skill/agent paths and content. |
| tests/modules/install/bulkInstallService.test.ts | Adds collision test and updates expectations for lockfileVersion 3 + qualified paths. |
| tests/modules/config/lockFileService.test.ts | Adjusts lock read/write expectations to support v2+v3 and v3 pathEncodingVersion. |
| tests/modules/cli/removeCommand.test.ts | Updates CLI subprocess remove/unlink tests to qualified path layout. |
| tests/modules/cli/installCommand.test.ts | Updates CLI subprocess install tests (including global scope) to qualified path layout. |
| tests/fixtures/installFixtures.ts | Updates fixtures to emit qualified ZIPs and manifests including pathEncoding. |
| src/modules/registry/domain/manifest.ts | Extends manifest artifact type with optional pathEncoding. |
| src/modules/install/infrastructure/zipSecurityScanner.ts | Extends ZIP validation to allow qualified skill/Claude entry layouts (while keeping legacy support). |
| src/modules/install/domain/pathEncoding.ts | Introduces path encoding constants + inference helpers for ZIP entry layouts. |
| src/modules/install/application/installPersistence.ts | Persists per-package pathEncodingVersion into the lock entry. |
| src/modules/install/application/bulkInstallService.ts | Captures mapped ZIP entry list for persistence inference. |
| src/modules/config/infrastructure/agentsLockRepository.ts | Serializes lock entries according to the document’s lockfileVersion. |
| src/modules/config/domain/packageLockEntry.ts | Adds pathEncodingVersion to lock entries and v3-aware parse/serialize rules. |
| src/modules/config/domain/configConstants.ts | Sets default lockfile version to 3 and supports reading 2+3. |
| src/modules/config/application/lockFileService.ts | Reads lockfileVersion 2 or 3; serializes entries based on lockfileVersion. |
| src/modules/config/application/doctorService.ts | Adds two new doctor checks and wires them into the doctor run. |
| src/modules/config/application/doctorPathChecks.ts | Implements legacy encoding detection and cross-package collision detection by enumerating ZIP-mapped paths. |
| specs/lock-schema.md | Documents lockfileVersion 3 and pathEncodingVersion rules. |
| specs/command-contracts.md | Updates doctor contract to include the two new checks and v3 lock presence. |
| docs/path-encoding-migration.md | Adds migration guidance for mixed legacy/new artifacts and how doctor helps. |
| docs/commands/doctor.md | Documents the new doctor checks. |
Review details
- Files reviewed: 25/25 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Consolidate legacy_path_encoding, agent_path_collision, and install_paths checks behind a single loadLockSlotArtifacts pass. Extend legacy encoding detection to Claude flat agent paths and extract shared install path regex patterns to reduce duplication. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
🟡 Changes recommended
One change introduces an import-order issue likely to break linting, and there’s an identified completeness gap around single-install lock encoding inference that should be addressed or explicitly justified.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
tests/modules/install/bulkInstallService.test.ts:213
- There’s coverage for greenfield
lockfileVersion: 3creation, but no test asserting migration behavior when an existinglockfileVersion: 2lock is present (e.g., running bulk install upgrades the document to v3 while preserving entries and addingpathEncodingVersionwhen applicable). Adding an explicit migration test would reduce the risk of regressions during the v2→v3 rollout.
src/modules/install/application/installPersistence.ts:23
InstallPersistence.save()only passesartifact.pathEncodingintoresolveLockPathEncodingVersion, so single-package installs cannot fall back to ZIP-shape inference when the manifest omitspathEncoding. Bulk installs anddoctoralready compute ZIP entry names, so consider extendingInstallPersistenceInput(and its callers) to optionally supplyzipEntryNamesfor consistent lock annotation.
export interface InstallPersistenceInput {
readonly resolved: ResolvedAgentsConfig
readonly packageId: string
readonly version: string
readonly target: InstallTargetId
readonly artifact: ManifestArtifact
readonly resolvedRef: string
readonly adHocInstall: boolean
}
- Files reviewed: 26/26 changed files
- Comments generated: 1
- Review effort level: Lite
Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
🔵 Needs a closer look
There are a few correctness/contract gaps (notably around manifest pathEncoding handling) and a measurable doctor performance regression due to redundant ZIP re-processing that should be addressed before approval.
Review details
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
src/modules/install/domain/pathEncoding.ts:16
computeInstallLeaftakes a parameter namedpackageId, but it’s actually the package name/slug (the middle segment innamespace-package-source). UsingpackageIdhere is ambiguous and makes it easy to accidentally pass a fully-qualified id likeacme/alpha, which would produce an invalid leaf (includes/) and break path patterns.
src/modules/install/domain/pathEncoding.ts:26resolvePathEncodingVersionFromManifestsilently treats any non-1pathEncodingvalue as “unset”. If the registry accidentally ships an unsupported value, the CLI will fall back to legacy behavior rather than failing fast, which can mask a bad publish and potentially reintroduce path-collision risk.
src/modules/config/application/doctorService.ts:195verifyInstallPathsFromArtifactsre-opens and re-scans each ZIP viaplanArtifactExtractFromZip, even thoughloadLockSlotArtifactsalready scanned the ZIP and producedmappedPaths. This duplicates CPU work (ZIP scan + entry iteration + hashing) and will makedoctornoticeably slower on large installs.
- Files reviewed: 26/26 changed files
- Comments generated: 0 new
- Review effort level: Lite
Rename computeInstallLeaf package segment param, fail fast on unsupported manifest pathEncoding values, and reuse mappedPaths in install_paths check. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Re: Copilot review (5175299023): Fixed in 0a82147:
|
There was a problem hiding this comment.
🔵 Needs a closer look
It changes lockfile schema, install path layout, and doctor behavior across multiple subsystems, warranting careful human validation of migration and edge cases.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/modules/config/domain/packageLockEntry.ts:157
- The error message for an unsupported
pathEncodingVersiondoes not include the actual value found or the expected value, which makes lockfile debugging harder.
- Files reviewed: 26/26 changed files
- Comments generated: 1
- Review effort level: Lite
The comment incorrectly described the return value as the middle segment; the function returns the full qualified install-leaf string. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
🔵 Needs a closer look
doctor currently retains full artifact ZIP buffers in memory and can overcount missing install paths due to duplicates, which risks unnecessary memory pressure and inaccurate diagnostics.
Review details
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
src/modules/config/application/doctorPathChecks.ts:38
LockSlotArtifactstores the full downloaded ZIP buffer (zipBytes) but none of the downstream checks use it (they only usemappedPaths). Keeping these buffers for the whole doctor run can significantly increase peak memory usage for large installs.
This issue also appears on line 91 of the same file.
src/modules/config/application/doctorService.ts:198
missingPathscan include duplicate absolute paths (e.g. if two artifacts map to the same relative path), inflating the reported count and repeating paths in the preview. Dedupe before pushing to keep the error message accurate.
src/modules/config/application/doctorPathChecks.ts:95
- After removing
zipBytesfromLockSlotArtifact, the pushed artifact object should also drop thezipBytesfield to avoid retaining large buffers unnecessarily.
target,
version: lockEntry.version,
zipBytes,
mappedPaths,
})
- Files reviewed: 26/26 changed files
- Comments generated: 0 new
- Review effort level: Lite
LockSlotArtifact only needs mappedPaths after download; dedupe missing install paths so duplicate relative mappings do not inflate diagnostics. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Re: Copilot review (5175379983): Fixed in 34dceb6:
|
|
There was a problem hiding this comment.
🔵 Needs a closer look
It changes lock schema semantics and install/doctor behavior across multiple modules, so a final human review should validate end-to-end compatibility and migration scenarios.
Review details
- Files reviewed: 26/26 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
🎉 This PR is included in version 1.22.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |



Summary
Related Issues
Closes #130
Depends on agents-repo/registry#230
Test plan
Made with Cursor