fix: issue #288 (automated) - #291
Conversation
- Write a `pncli-origin.json` file inside each installed skill directory (alongside the existing `.pncli-installed.json` directory-level index) recording source, marketplace, plugin, installedFrom, branch, and installedAt. Per-skill files are self-contained so provenance survives directory copies. - Detect the marketplace repo branch at install time via `git rev-parse` and record it in both the index and per-skill origin file. - Add `pncli skills marketplace purge-plugin <plugin> [--marketplace <name>]` which removes all skills installed from a given plugin. Reads the directory-level index first and falls back to per-skill origin files for backward compatibility with pre-existing installs. - Add `pncli skills purge-user [--force]` which removes all skills from the user-level skills folder. Requires `--force` to prevent accidental mass-delete. Closes #288 Co-authored-by: Sunny Kolattukudy <kolatts@users.noreply.github.com>
|
Claude finished @github-actions[bot]'s task in 3m 7s —— View job Review Complete
PASS — no blocking issues. What's in this PR (issue #288): three related features added to the skills system:
What I verified:
One non-blocking observation: |
There was a problem hiding this comment.
PASS — no blocking issues found.
Summary
This PR adds three related features to the skills system:
- Per-skill
pncli-origin.json— a self-contained provenance file written inside each installed skill directory, supplementing the directory-level.pncli-installed.jsonindex. Enables backward-compatible provenance lookup for skills without a full index entry. marketplace purge-plugin— removes skills installed from a specific plugin or all plugins in a marketplace, using provenance from both the index and the per-skill origin file.skills purge-user— nukes the entire user-level skills folder for the target agent, gated behind--force.
What I checked
- Security / path traversal: Both new purge commands guard against traversal with
skillDir.startsWith(resolvedTarget + path.sep). ✓ - Token scrubbing:
scrubToken()is applied wherever git error output might reach the user. ✓ detectRepoBranch: UsesexecFileSync('git', [...], ...)(not shell) — no injection risk. Silently returnsundefinedon failure, which is the right non-fatal behaviour. ✓recordInstalledSkillswrite order: per-skill origin files are written first, directory-level index last. The per-skill write is wrapped in try/catch so a filesystem error there never aborts the index write. ✓upsertMarketplacetoken preservation:entry.token ?? all[idxByUrl].tokencorrectly retains a previously stored token when re-adding without--token. ✓- Test isolation: All tests stub
fs.*viavi.spyOnand restore mocks inafterEach. No live filesystem or network access. Matches the project's testing rule. ✓
Observations (non-blocking)
purge-user removes ALL user skills, not just marketplace-installed ones. This is a behavioural difference from purge-plugin, which filters by provenance. If a user has any hand-authored skills in their user skills folder, purge-user --force will silently delete them alongside marketplace-installed ones. The --force requirement and the pre-flight skill list in the error message mitigate this, but the description could be clearer.
loadJsonFile does not check existsSync — it calls readFileSync directly and catches on ENOENT. The tests that mock existsSync but not readFileSync for the meta path are still correct because the uncaught readFileSync throws, is caught by loadJsonFile, and returns null. Not a bug, but worth noting for future test authors.
Closes #288