feat(sync): use on-disk content hashing to skip unchanged sync work#1
feat(sync): use on-disk content hashing to skip unchanged sync work#1willmruzek merged 20 commits intomainfrom
Conversation
BREAKING CHANGE: sync-manifest.json is now version 3 and requires a contentHash on each output. Older manifests fail validation and are treated as empty (next sync re-writes and re-seeds hashes). - Compare would-be-written content to the prior manifest hash to mark targets unchanged and omit them from the report without re-writing. - Show Applied changes: None and drop empty agent sections when nothing applied for an agent. - Add integration test for repeat no-op sync and update prune fixtures.
There was a problem hiding this comment.
Pull request overview
This PR upgrades the sync manifest format to v3 by adding a contentHash per output, and uses those hashes to skip rewriting targets whose would-be-written content is unchanged—also simplifying the sync report output for no-op runs.
Changes:
- Bump
sync-manifest.jsonschema to version 3 and requirecontentHashon every output entry. - Compute per-target SHA-256 content hashes during sync, detect
unchangedtargets, and skip writing + omit them from the “Applied changes” report. - Add an integration test for repeat no-op sync runs and update prune-related fixtures to satisfy the new manifest schema.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/lib/sync.ts |
Adds manifest v3 schema + content hashing to detect unchanged targets, skip writes, and adjust report rendering. |
tests/commands/sync.test.ts |
Adds coverage for repeat no-op sync reporting and updates seeded manifests to include contentHash + version 3. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…shes Drop `contentHash` from manifest rows. Compare would-be-written SHA-256 snapshots to on-disk markdown or copied skill trees, skip writes when they match, and treat unreadable or schema-invalid manifests as empty so the next run re-reports installs. Omit empty per-agent sections and print "Applied changes: None" when nothing applied.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
tests/commands/sync.test.ts:195
- The test manifest schema (
mockSyncManifestSchema) only asserts agent/kind/name/outputPath. If sync-manifest.json outputs now require acontentHash(per PR description), this schema and the expected manifest row builders should be updated to validate thatcontentHashexists and is stable across runs (especially in the new no-op sync test).
/** On-disk sync manifest: current schema version, outputs with agent / kind / name / outputPath. */
const mockSyncManifestSchema = z.object({
version: z.literal(SYNC_MANIFEST_VERSION),
outputs: z.array(
z.object({
agent: z.enum(['copilot', 'cursor']),
kind: z.enum(['command', 'rule', 'skill']),
name: z.string().min(1),
outputPath: z.string().min(1),
}),
),
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
tests/commands/sync.test.ts:193
- mockSyncManifestSchema still models outputs as {agent, kind, name, outputPath} only. If the manifest is intended to be v3 and require contentHash per the PR description, this test schema should require contentHash as well so fixtures/tests fail when it is missing.
/** On-disk sync manifest: current schema version, outputs with agent / kind / name / outputPath. */
const mockSyncManifestSchema = z.object({
version: z.literal(SYNC_MANIFEST_VERSION),
outputs: z.array(
z.object({
agent: z.enum(['copilot', 'cursor']),
kind: z.enum(['command', 'rule', 'skill']),
name: z.string().min(1),
outputPath: z.string().min(1),
tests/commands/sync.test.ts:1660
- This manifest fixture omits the per-output contentHash described as required in the PR. Once the manifest schema is updated to v3, this JSON should include contentHash for every output row (and use the bumped manifest version).
mockFileSystem,
path.join(DEFAULT_CONFIG_ROOT, 'sync-manifest.json'),
JSON.stringify({
version: SYNC_MANIFEST_VERSION,
outputs: [
{
agent: 'copilot',
kind: 'command',
name: 'gone-cmd',
…nonical After strict validation fails, parse outputs entries best-effort so removed config items still delete old Copilot/Cursor copies. Log warnings for unreadable, damaged, or unusable manifests. Add tests for recovery, invalid JSON, and invalid rows.
…into fix/dont-show-updated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Use `import type` for SyncAgent, a local mock manifest version, and `satisfies Record<SyncAgent, true>` instead of importing SYNC_AGENTS or SYNC_MANIFEST_VERSION.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Summary
unchanged: no write, omitted from the applied report.Applied changes: Noneand drops empty per-agent sections.outputsrows so pruning still runs after upgrades or slightly non-canonical files;runtime.logWarnwhen the file can’t be read/parsed, recovery is used, or rows can’t be validated.SKILL.md, stray skill files, registry layout / partial agent failures, and pruning with strict-invalid but recoverable manifests (plus JSON / invalid-row cases).Details