Sync System Quick Reference¶
Quick lookup tables for understanding the current sync implementation.
File Locations¶
| Component | File | Purpose |
|---|---|---|
| Sync Manager | skillmeat/core/sync.py |
Drift detection, sync operations |
| Deployment Manager | skillmeat/core/deployment.py |
Deploy/undeploy artifacts |
| Deployment Tracker | skillmeat/storage/deployment.py |
Read/write deployment metadata |
| Context Sync Service | skillmeat/core/services/context_sync.py |
Bi-directional sync (contexts) |
| Sync Capture Service | skillmeat/core/sync_capture_service.py |
Auto-capture with debounce |
| Data Models | skillmeat/models.py |
DriftDetectionResult, SyncResult, etc. |
| Deployment Metadata | .claude/.skillmeat-deployed.toml |
Artifact baseline tracking |
CLI Commands¶
Sync Capture¶
| Command | Purpose | Example |
|---|---|---|
skillmeat watch |
Watch for filesystem changes in real time | skillmeat watch |
skillmeat watch -p <path> |
Watch specific project | skillmeat watch -p ~/my-project |
skillmeat watch --remote |
Forward changes to API server | skillmeat watch --remote --api-url http://localhost:9090 |
Configuration API¶
| Endpoint | Method | Purpose |
|---|---|---|
/api/v1/sync-capture-config |
GET | Fetch configuration for a scope |
/api/v1/sync-capture-config |
PUT | Update configuration |
/api/v1/sync-capture-config |
DELETE | Reset to defaults |
/api/v1/sync-capture/notify |
POST | Report file change (filesystem watcher) |
/api/v1/sync-capture/entries |
GET | Query debounce entries (state/history) |
Configuration Query Examples¶
# Get global config
curl http://localhost:8080/api/v1/sync-capture-config?scope=global
# Get project-specific config
curl "http://localhost:8080/api/v1/sync-capture-config?scope=project&scope_value=proj-123"
# Get tier config
curl "http://localhost:8080/api/v1/sync-capture-config?scope=tier&scope_value=tier-123"
Core Classes¶
SyncManager¶
| Method | Purpose | Input | Output |
|---|---|---|---|
check_drift() |
Detect changes | project_path | DriftDetectionResult[] |
sync_from_project() |
Pull sync (project→collection) | project_path, strategy | SyncResult |
sync_from_project_with_rollback() |
Pull with snapshot safety | project_path, strategy | SyncResult |
update_deployment_metadata() |
Record deployment | artifact_name, paths | - |
_compute_artifact_hash() |
SHA-256 hash | artifact_path | str (64 hex chars) |
_load_deployment_metadata() |
Read baseline | project_path | DeploymentMetadata |
_sync_artifact() |
Sync single artifact | drift, strategy | ArtifactSyncResult |
_sync_overwrite() |
Strategy: overwrite | source, dest | - |
_sync_merge() |
Strategy: merge | source, dest | ArtifactSyncResult |
_sync_fork() |
Strategy: fork | source, dest, name | Path |
DeploymentManager¶
| Method | Purpose | Input | Output |
|---|---|---|---|
deploy_artifacts() |
Deploy artifacts | names, collection, project | Deployment[] |
deploy_all() |
Deploy entire collection | collection, project | Deployment[] |
undeploy() |
Remove artifact | name, type, project | - |
list_deployments() |
List deployed artifacts | project | Deployment[] |
check_deployment_status() |
Sync status | project | Dict[key → status] |
DeploymentTracker (Static)¶
| Method | Purpose | Input | Output |
|---|---|---|---|
read_deployments() |
Load metadata | project_path | Deployment[] |
write_deployments() |
Save metadata | project_path, deployments | - |
record_deployment() |
Record artifact | project_path, artifact, hash | - |
get_deployment() |
Get single record | project_path, name, type | Deployment | None |
remove_deployment() |
Remove record | project_path, name, type | - |
detect_modifications() |
Check if modified | project_path, name, type | bool |
Data Models¶
Deployment (storage unit)¶
artifact_name: str # e.g., "my-skill"
artifact_type: str # "skill", "command", "agent"
from_collection: str # "default"
deployed_at: datetime
artifact_path: Path # Relative: "skills/my-skill"
content_hash: str # SHA-256 (baseline)
local_modifications: bool = False # ⚠️ Placeholder
parent_hash: Optional[str] = None # ⚠️ Placeholder
version_lineage: List[str] = [] # ⚠️ Placeholder
last_modified_check: Optional[datetime] = None # ⚠️ Placeholder
modification_detected_at: Optional[datetime] = None # ⚠️ Placeholder
DriftDetectionResult (analysis)¶
artifact_name: str
artifact_type: str
drift_type: Literal["modified", "outdated", "conflict", "added", "removed"]
collection_sha: Optional[str]
project_sha: Optional[str]
collection_version: Optional[str]
project_version: Optional[str]
last_deployed: Optional[str]
recommendation: str # "pull_from_project", "push_to_collection", etc.
ArtifactSyncResult (per-artifact result)¶
artifact_name: str
success: bool
has_conflict: bool = False
error: Optional[str] = None
conflict_files: List[str] = []
SyncResult (batch result)¶
status: str # "success", "partial", "cancelled", "no_changes", "dry_run"
artifacts_synced: List[str] = []
conflicts: List[ArtifactSyncResult] = []
message: str = ""
Drift Types and Transitions¶
State Matrix¶
Collection SHA Project SHA Deployed SHA Drift Type
State 1 A A A (none)
State 2 A B A modified
State 3 B A A outdated
State 4 B C A conflict
State 5 A ∅ ∅ added
State 6 ∅ A A removed
Transitions¶
State 1 (Synced)
↓ User edits project
State 2 (Modified)
↓ Pull sync (overwrite)
State 3 (Outdated) ← Now collection updated, baseline old
↓ Re-deploy OR update baseline
State 1 (Synced)
State 1 (Synced)
↓ Collection updated (upstream)
State 3 (Outdated)
↓ Deploy
State 1 (Synced)
State 1 (Synced)
↓ Both collection and project changed
State 4 (Conflict)
↓ Merge/Overwrite/Fork/Manual
State 1, 2, or 3 (depends on strategy)
Hash Computation Rules¶
What Gets Hashed¶
✅ Included: - All regular files - Subdirectories (recursive) - Binary files (raw bytes) - Files with any extension
❌ Excluded:
- .git/ (not in artifacts)
- __pycache__, .pyc
- Unreadable files (skipped with warning)
Hash Changes When¶
✅ File content modified (even 1 char) ✅ File added ✅ File deleted ✅ File renamed (path changes) ✅ File moved to subdirectory
❌ Timestamps change ❌ Permissions change ❌ Editor metadata changes ❌ Only line ending changes (content same)
Hash Formula¶
Sync Strategies¶
Overwrite¶
- Replace collection with project version
- Use when: Project has correct changes, collection should be ignored
- Risk: Loses collection updates
- Result: Collection = Project after
Merge¶
- 3-way merge (base, local, remote)
- Base: Current collection ⚠️ (should be deployed baseline)
- Local: Collection
- Remote: Project
- Use when: Want to combine changes from both
- Risk: Merge conflicts need manual resolution
- Result: Collection = merged; may have conflict markers
Fork¶
- Create new artifact with
-forksuffix - Use when: Want to keep both versions
- Risk: Creates duplicate artifact
- Result: Original + projectname-fork both exist
Prompt¶
- Ask user for each artifact
- Use when: Interactive sync, different strategies per artifact
- Risk: Time-consuming for many artifacts
- Result: User chooses per-artifact
Change Detection Examples¶
Example 1: Single File Edit¶
Before:
artifact/SKILL.md (10 lines)
deployed_sha = abc123
After:
artifact/SKILL.md (11 lines - added 1 line)
new_sha = def456
Hash changes because:
- File content is different
- File path same (relative path same)
- Result: Different hash ✓
Example 2: New File¶
Before:
artifact/SKILL.md
deployed_sha = abc123
After:
artifact/SKILL.md
artifact/new_file.txt
new_sha = def456
Hash changes because:
- New file added
- Hash includes all files
- Result: Different hash ✓
Example 3: File Rename¶
Before:
artifact/old_name.py
deployed_sha = abc123
After:
artifact/new_name.py (same content as old_name.py)
new_sha = def456
Hash changes because:
- File path changes (relative_path in hash)
- Even though content same, path different
- Result: Different hash ✓
Example 4: Whitespace Only¶
Before:
artifact/SKILL.md ("hello world")
deployed_sha = abc123
After:
artifact/SKILL.md ("hello world") (identical)
new_sha = abc123
Hash does NOT change because:
- Exact same content
- Exact same path
- Result: Same hash ✓
Metadata File Format¶
Location¶
.claude/.skillmeat-deployed.toml
Structure¶
[[deployed]]
artifact_name = "my-skill"
artifact_type = "skill"
from_collection = "default"
deployed_at = "2025-12-17T10:30:00.000000"
artifact_path = "skills/my-skill"
content_hash = "abc123def456789..."
local_modifications = false
# Optional fields (for v1.5)
parent_hash = "def456ghi789..."
version_lineage = [
"abc123def456789...",
"def456ghi789abc...",
"ghi789abc123def...",
]
last_modified_check = "2025-12-17T12:00:00.000000"
modification_detected_at = "2025-12-17T11:30:00.000000"
Backward Compatibility¶
- Old files use
collection_shainstead ofcontent_hash - Code reads
content_hashfirst, falls back tocollection_sha - New saves include both (for backward compat)
Deployment Recording Lifecycle¶
On Deploy¶
1. User: "skillmeat deploy my-skill /path/to/project"
↓
2. DeploymentManager.deploy_artifacts()
├─ Copy artifact from collection to project
├─ Compute content_hash
└─ Call DeploymentTracker.record_deployment()
↓
3. DeploymentTracker.record_deployment()
├─ Load .skillmeat-deployed.toml
├─ Check if existing deployment for artifact
├─ If yes: Replace with new record
├─ If no: Append new record
└─ Write updated list
↓
4. File saved:
.claude/.skillmeat-deployed.toml
[with new deployment record]
On Redeploy (Artifact Updated)¶
1. User: "skillmeat deploy my-skill /path/to/project"
(artifact updated in collection since last deploy)
↓
2. Copy new version to project
3. Compute new content_hash
4. Load .skillmeat-deployed.toml
5. Find existing deployment
6. Create new record with:
- content_hash = new hash
- parent_hash = old hash (NEW)
- version_lineage = [new_hash, old_hash, ...] (NEW)
- deployed_at = new timestamp
7. Replace old record
8. Save
On Undeploy¶
1. User: "skillmeat undeploy my-skill /path/to/project"
↓
2. Remove artifact from project
3. Load .skillmeat-deployed.toml
4. Find and remove deployment record
5. Save updated list
Drift Check Algorithm¶
def check_drift(project_path):
# 1. Load baseline
metadata = load_deployment_metadata(project_path)
deployed_sha = metadata.artifacts[0].sha # e.g., "abc123"
# 2. Get current states
collection_sha = compute_hash(collection_artifact) # e.g., "ghi789"
project_sha = compute_hash(project_artifact) # e.g., "def456"
# 3. Compare
collection_changed = (collection_sha != deployed_sha) # ghi ≠ abc → True
project_changed = (project_sha != deployed_sha) # def ≠ abc → True
# 4. Categorize
if collection_changed and project_changed:
drift_type = "conflict"
elif collection_changed:
drift_type = "outdated"
elif project_changed:
drift_type = "modified"
else:
drift_type = None # No drift
# 5. Return
return DriftDetectionResult(
drift_type=drift_type,
collection_sha=collection_sha,
project_sha=project_sha,
deployed_sha=deployed_sha,
recommendation=recommend(drift_type),
)
Pull Sync Flow¶
User: skillmeat sync pull /path/to/project
1. Check drift
→ Find artifacts with "modified" drift
2. Show preview
→ Display artifacts, SHAs, strategy
3. Confirm
→ Ask user "Proceed with sync?"
4. For each artifact:
a) Apply strategy
- overwrite: rm collection; cp project → collection
- merge: 3-way merge
- fork: cp project → collection-fork
b) Record result
c) Track event
5. Update lock files
→ If collection manager available
6. Create auto-snapshot
→ Link to collection state
7. Record analytics
→ Track sync events
8. Return SyncResult
status="success"
artifacts_synced=["my-skill", ...]
Modification Detection¶
detect_modifications(project_path, artifact_name, artifact_type)¶
# 1. Get deployment record
deployment = get_deployment(project_path, artifact_name, type)
# 2. Compute current hash
artifact_path = project_path / ".claude" / deployment.artifact_path
current_hash = compute_hash(artifact_path)
# 3. Compare
modified = (current_hash != deployment.content_hash)
# 4. Return bool
return modified # True if different, False if same
Result:
- True: Project file differs from deployed baseline
- False: Project file matches deployed baseline
Common Error Scenarios¶
Scenario 1: Deployment Metadata Not Found¶
Error: "No deployment metadata found"
Cause: .skillmeat-deployed.toml doesn't exist
Solution: Deploy artifacts first (create metadata)
Scenario 2: Collection Artifact Not Found¶
Error: "Artifact '{name}' not found in collection"
Cause: Artifact removed from collection, still deployed in project
Solution: Check if artifact name/type correct, or use "remove" sync
Scenario 3: Project Path Doesn't Exist¶
Error: "Project path does not exist: /path/to/project"
Cause: Wrong project path provided
Solution: Check path spelling, project must have .claude/ directory
Scenario 4: Three-Way Conflict Without Resolution¶
Conflict State:
deployed_sha = A
collection_sha = B
project_sha = C
Available Resolutions:
1. Merge (may produce conflict markers)
2. Overwrite (keep collection, lose project changes)
3. Overwrite (keep project, lose collection changes)
4. Fork (keep both as separate artifacts)
5. Manual (user edits and resolves manually)
Rate Limiting¶
Auto-capture is rate-limited to prevent noise:
| Limit | Default | Configurable | Purpose |
|---|---|---|---|
| Captures per hour per artifact | 10 | Yes | Prevent excessive versions |
| Quiet window | 30 seconds | Yes | Debounce rapid edits |
| Scan interval (background) | 5 minutes | Yes | Background detection frequency |
| Entries retention | 7 days | Yes | How long to keep capture records |
Exceeding the Rate Limit: When an artifact exceeds the hourly limit, additional changes are: - Tracked in the debounce entry (recorded as "skipped" or "rate_limited") - Displayed in the UI to show activity - Not captured as separate versions until the next hour
To adjust limits:
curl -X PUT http://localhost:8080/api/v1/sync-capture-config \
-H "Content-Type: application/json" \
-d '{"scope":"global","max_captures_per_hour":20}'
Performance Characteristics¶
| Operation | Time | Notes |
|---|---|---|
| Hash small artifact (<1MB) | <50ms | Single file, fast I/O |
| Hash large artifact (>100MB) | 1-5s | Multiple files, sequential read |
| Check drift | 100-500ms | Loads metadata, computes hashes, 3-way compare |
| Pull sync (10 artifacts) | 1-10s | Copy + merge + snapshot |
| Deploy (10 artifacts) | 1-10s | Copy + hash + record |
| Merge operation | 100-2000ms | Depends on file sizes and conflicts |
| Filesystem watcher startup | <500ms | Depends on number of paths watched |
| Change detection (watcher) | ~50ms | JSON serialization + network (remote mode) |
Test Matrix¶
Basic Scenarios¶
✓ Test 1: Deploy artifact
- Verify .skillmeat-deployed.toml created
- Verify artifact in project
- Verify content_hash recorded
✓ Test 2: Detect unmodified artifact
- Verify drift check shows no changes
- Verify project_sha == deployed_sha
✓ Test 3: Detect project modification
- Edit project artifact
- Verify drift type = "modified"
- Verify project_sha ≠ deployed_sha
✓ Test 4: Detect collection update
- Update collection artifact
- Verify drift type = "outdated"
- Verify collection_sha ≠ deployed_sha
✓ Test 5: Detect conflict
- Update both collection and project differently
- Verify drift type = "conflict"
- Verify all three SHAs different
Sync Scenarios¶
✓ Test 6: Pull sync (overwrite)
- Modified artifact
- Pull with overwrite
- Verify collection updated from project
- Verify next check shows "outdated" (need re-deploy)
✓ Test 7: Pull sync (merge)
- Conflict artifact
- Pull with merge
- Verify merged content in collection
- Verify conflict markers if needed
✓ Test 8: Pull sync (fork)
- Conflict artifact
- Pull with fork
- Verify artifact-fork created
- Verify original unchanged