Skip to content

feat(cli): syspi remove drops a usage, re-locks and prunes what nothing else needs from lib/ - #10

Open
devin-ai-integration[bot] wants to merge 6 commits into
developfrom
feat/remove
Open

devin-ai-integration[bot] wants to merge 6 commits into
developfrom
feat/remove

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

syspi remove [flags] <resource>... — the first of the commands 0.1.0 lacks (list #13 and verify #14 are independent; update/outdated will stack on this branch because moving a lock entry needs the same pruning of the copy it leaves behind).

  • The usage is matched in .project.json by canonical identity, exactly as add matches it (origin.Parse(u.Resource).Identity()), so any accepted spelling removes it. An unknown usage is an error that leaves .project.json untouched; when the resource is in the lock only because another package uses it, the message says so (no usage of X; it is locked because Y uses it).
  • The remaining usages are re-resolved and re-locked through the existing install.Run (so -offline works from the lock and lib/, and lib/ is still only touched after everything resolved and verified). A removed usage that another locked package still uses stays locked and installed; the log names the requester.
  • New Options.Prune in install.Run: after the new lock is written, each lib/ path the previous lock had placed (libPath(entry): <publisher>-<name>_<version> or .kpar) that the new lock does not claim is removed — but only when its contents still match that old entry (installedTreeMatches / .kpar SHA-256). A directory the lock never placed under lib/, or an installed copy whose contents were edited, is kept and said so (kept lib/base_2.1.0: its contents differ from the lock entry that installed it). Pruning runs after the lock is written, so a copy that cannot be removed is a logged leftover the next pruning run removes, never an inconsistency between lock and lib/.
  • libPath and installedMatches live in the new internal/install/libpath.go; feat(cli): syspi list prints the locked packages and whether each installed copy is present #13 and feat(cli): syspi verify checks every installed copy under lib/ against the lock #14 add the identical file, so the branches merge in any order.
  • .meta.json of the consuming project is refreshed as add does.
$ syspi remove -offline git+file:///srv/widgets.git#packages/gears
gears 1.1.0 is no longer used; lib/acme-gears_1.1.0 removed
removed usage pkg:generic/widgets?vcs_url=git%2Bfile:%2F%2F%2Fsrv%2Fwidgets.git%23packages%2Fgears
removed lib/acme-gears_1.1.0
2 package(s) locked: 0 installed, 1 already present, 1 not installed (standard libraries and local directories); .project.lock.json updated

Tests: internal/install/remove_test.go (transitive package stays, user directory and edited copy kept, failing remove changes nothing, archive + standard library in one call) and tests/e2e/remove_test.go (git flow with -offline, refusal, .meta.json refresh, and the git/hg/svn fixtures). Docs: README "Commands", docs/packages.md "Install layout", changes/unreleased/remove.added.md.

Proved on a real project: syspi add pkg:git/github.com/MBSE4U/sysmod-sysmlv2@5.1.5, syspi add pkg:git/github.com/Open-MBEE/OpenSysML-Extensions-Library@1.0.0, a model importing SYSMOD::* and OOSEM::* passes OpenSysML's sysml -validate; after syspi remove pkg:git/github.com/MBSE4U/sysmod-sysmlv2 the tree lib/mbse4u-sysmod_5.1.5 is gone and -validate fails with unresolved reference: SYSMOD.

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/888897f71c264c89a8abc11e9a432241
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/888897f71c264c89a8abc11e9a432241?variant=devin
Requested by: @HuiJun


🟢 Devin fixed all 4 findings · Follow Devin's work

Devin Review

…ng else needs from lib/

The usage is matched by its canonical identity as add matches it. After the remaining usages re-resolve and re-lock, only installed copies the previous lock placed under lib/ that the new lock no longer claims are removed, and only when their contents still match that lock entry; anything else under lib/ is kept.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

devin-ai-integration Bot and others added 2 commits November 12, 2026 06:16
…ot a failed remove

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration Bot and others added 3 commits November 12, 2026 06:29
A crafted version with path separators or .. components in a lock entry could name a path prune would remove outside lib/; libPath now yields nothing for such an entry.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
The tree digest skips .git, .hg and .svn, so a copy a user had started a repository in still matched the lock; digest.MetadataDirs reports them and prune keeps the copy.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…lds repository metadata, or logs to a nil writer

A remaining local usage pointing inside a removed package's tree kept that tree; a tree holding .git, .hg or .svn is kept rather than deleted with the unpushed history; Remove defaults Options.Log as Run does instead of panicking after the lock changed.

Co-Authored-By: jason.han <hanhuijun@gmail.com>

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Devin Review found 4 findings. Devin automatically fixed all 4 findings.

Fixed by Devin (4)
  • Pruning deletes local repository history — prune keeps a copy that holds .git, .hg or .svn (digest.MetadataDirs) and says so; tested in TestRemoveKeepsWhatPruneMustNotTouch.
  • Relative local dependencies deleted during pruning — prune keeps a copy that a local entry of the new lock is or lies inside (localWithin); tested with a local usage of lib//packages/gears.
  • Optional logger causes removal panic — Remove defaults a nil Options.Log to io.Discard before anything is written; tested with a nil logger and a still-used resource.
  • Crafted locks delete files outside lib — libPath now returns nothing for an entry whose install name is not a plain segment (separators, . or ..) or whose joined path is not a direct child of lib/; TestLibPathStaysUnderLib covers traversal through the version.

View findings in Devin Review · Follow Devin's work

Devin Review

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.

1 participant