Adds reproducible version pinning at three layers:
CLI surface
- `allagents skills add` gains a `--pin <ref>` flag. The same effect is
achievable inline via `--from owner/repo@<ref>`. The two are mutually
exclusive and produce an actionable error when combined.
Parsing
- `parseGitHubUrl` / `isGitHubUrl` recognise the `owner/repo@<ref>` shorthand
(and the combined `owner/repo@<ref>/subpath` form). Distinguished from the
existing `plugin@marketplace` shorthand by the presence of a `/` before `@`.
- `isPluginSpec` correspondingly rejects `owner/repo@ref` so GitHub-pinned
sources are not mis-routed through the marketplace resolver.
- New `stripGitRef()` helper produces the canonical un-pinned form used as
the sync-state lookup key.
Workspace config
- `PluginEntrySchema` adds an optional `pin: string`. `buildPluginSyncPlans`
splices `@<pin>` into GitHub-shaped sources so the existing fetch path
honours the pin during `allagents update`. New `getPluginPin()` accessor.
Sync state
- `SyncStateSchema` adds an optional `sources` map: `pluginSpec → {
pluginSpec, resolvedRef, resolvedSha, pinnedRef? }`. Field names are a
strict subset of the gh-skill lockfile to keep a future migration mechanical.
- `fetchPlugin` returns the resolved ref and the cached HEAD SHA so callers
can stamp provenance without a second git op.
- `allagents update` populates `sources` from validated plugins.
- `allagents skills add --from ...` writes the same entry up-front via a new
`upsertSyncStateSource` helper so the entry exists even before the first
full sync.
Misc
- `getPluginName` strips the `@<ref>` suffix from cache-dir basenames so
pin-suffixed cache paths don't confuse `setPluginSkillsMode`'s lookup.
- Added unit coverage for the new parsing branches.
Verified end-to-end against `obra/superpowers@v3.1.0`: inline pin, --pin
flag, mutex error, sources block in sync-state, and workspace.yaml
round-trip via `allagents update` all pass.
Closes #372
Summary
Reproducible version pinning end-to-end:
allagents skills add --from owner/repo@<ref>and--pin <ref>are accepted (mutually exclusive — supplying both errors).parseGitHubUrl/isGitHubUrlunderstand theowner/repo@<ref>shorthand.isPluginSpecrejects it so marketplace resolution stays correct.PluginEntrySchemagains an optionalpin: string;allagents updatesplices it into GitHub-shaped sources so existing fetch paths honour the pin.sourcesblock keys perowner/repoto{ pluginSpec, resolvedRef, resolvedSha, pinnedRef? }. Field names are a strict subset of the gh-skill lockfile shape.Test plan
bun run buildbun test(1195 pass / 0 fail) — includes new unit coverage for the@refparsing branches.obra/superpowers@v3.1.0for the example repo, per the issue's note):allagents skills add brainstorming --from obra/superpowers@v3.1.0→ installs at v3.1.0.allagents skills add brainstorming --from obra/superpowers --pin v3.1.0→ equivalent.allagents skills add brainstorming --from obra/superpowers@v3.1.0 --pin v3.1.0→ exits non-zero withCannot combine inline @version in --from with --pin.jq '.sources["obra/superpowers"].pinnedRef'returns"v3.1.0";resolvedShais a 40-char commit SHA.pin: v3.1.0round-trips throughallagents updateand writes the samesourcesblock.Closes #372