feat(sync-state): per-skill content hashes for drift detection#382
Conversation
Code Review:
|
cae071d to
7c296cd
Compare
Extends sync-state with a `sources` map keyed by `owner/repo`, each carrying the resolved git ref + commit SHA and a per-skill `contentHash` derived deterministically from the on-disk skill folder contents. Lays the groundwork for `allagents skill update --dry-run` (issue #375) to diff installed vs. upstream without re-fetching. - src/models/sync-state.ts: add SyncStateSkillSchema + SyncStateSourceSchema with optional `skills` map. Field names are a strict subset of gh-skill's `.skill-lock.json` (`contentHash` ↔ `skillFolderHash`) so a future migration is mechanical. - src/core/sync-state.ts: add `computeSkillFolderHash` — sha256 over a sorted list of per-file sha256 digests, no mtimes, so the hash is reproducible across machines and re-clones. Also `upsertSyncStateSource` / `upsertSyncStateSkill` for atomic field updates that preserve the rest of the sync-state file. - src/core/plugin.ts: `fetchPlugin` returns `resolvedRef` and `resolvedSha` (via a private `resolveHeadSha` helper using `git rev-parse HEAD`). - src/cli/commands/plugin-skills.ts: after a successful `skills add --from` install (single or `--all`), record the source provenance and per-skill content hash via the new helpers. - src/core/workspace-modify.ts: pre-existing bug — `addPlugin` was calling `resolvePluginSpecWithAutoRegister` without `workspacePath`, hiding project-scope marketplaces from the resolver. Plumbed through so a newly-registered project marketplace is discoverable on the same call. Verified end-to-end against `obra/superpowers`: - contentHash present and `sha256:`-prefixed after install. - Re-install (after `skills remove`) produces the same hash. - A local edit to the installed SKILL.md changes its on-disk sha vs. the recorded sync-state hash (drift detectable). - Old sync-state.json without `sources` loads without error. Closes #374
7c296cd to
bb93391
Compare
#389) Reverts the per-skill content-hash machinery added in #382. For git-based plugins, identity is already given for free by `url + ref` — `git rev-parse HEAD` after each fetch returns the commit SHA, which is what `resolvedSha` already records on the source entry. Computing a per-file sha256 tree over every installed skill folder on every install adds CPU work and code complexity without giving us anything `resolvedSha` doesn't. - src/models/sync-state.ts: remove `SyncStateSkillSchema` and the `skills` map on `SyncStateSourceSchema`. Each lock entry is now just `{ pluginSpec, resolvedRef, resolvedSha, pinnedRef? }`. The top-level `lastSync` already records the timestamp. - src/core/sync-state.ts: drop `computeSkillFolderHash`, `upsertSyncStateSkill`, and the unused `crypto.createHash` / `readdir` / `relative` imports. The private `persistMergedSyncState` helper folds back into `upsertSyncStateSource`. - src/cli/commands/plugin-skills.ts: rename `recordContentProvenance` → `recordSourceProvenance` and drop the now-unused `skills` param + skill folder walk. The two callers (`--from <skill>` install and `--all`) no longer need to thread `skills` through. Verified end-to-end: `skill add brainstorming --from obra/superpowers --pin v3.1.0` writes a `sources["obra/superpowers"]` entry with `pluginSpec`, `resolvedRef`, `resolvedSha`, `pinnedRef` only — no `skills` map. `bun test`: 1209 pass / 0 fail. Closes #388
Summary
Adds per-skill
contentHashto sync-state under a newsourcesmap keyed byowner/repo. Each entry records the resolved ref + commit SHA and a hash for every installed skill. This is the lockfile-shaped foundationallagents skill update --dry-run(#375) needs to detect drift without re-fetching.sha256over a sorted list of per-filesha256(content)digests. No mtimes; reproducible across machines and re-clones..skill-lock.json(contentHash↔skillFolderHash) so a future migration to that file format is mechanical.sourcesloads without error.Also fixes a pre-existing project-scope marketplace resolution bug in
addPlugin(resolvePluginSpecWithAutoRegisterwas being called withoutworkspacePath, hiding freshly-registered project marketplaces). Without that fix the issue'sskills add brainstorming --from obra/superpowersgate fails.Test plan
bun run buildbun test— 1191 pass / 0 failobra/superpowersper the issue's "Substitute the example repo if needed" note):.sources["obra/superpowers"].resolvedSha≥ 7 chars and.skills.brainstorming.contentHashissha256:-prefixed..claude/skills/brainstorming/SKILL.mdchanges the on-disk sha vs. the recorded hash (drift detectable).sync-state.jsonwithoutsourcesstill loads (workspace statusexits 0).Closes #374