content-versioning
Use when implementing draft/publish workflows, version history, content rollback, or audit trails. Covers versioning strategies, snapshot storage, diff generation, and version comparison APIs for headless CMS.
What this skill does
# Content Versioning
Guidance for implementing version control, draft/publish workflows, and audit trails for CMS content.
## When to Use This Skill
- Implementing draft/publish workflows
- Adding version history to content types
- Building content rollback features
- Creating audit trails for compliance
- Comparing content versions
## Versioning Strategies
### Strategy 1: Separate Draft/Published Records
```csharp
public class ContentItem
{
public Guid Id { get; set; }
public string ContentType { get; set; } = string.Empty;
public ContentStatus Status { get; set; }
// Version tracking
public int Version { get; set; }
public Guid? PublishedVersionId { get; set; }
public Guid? DraftVersionId { get; set; }
// Timestamps
public DateTime CreatedUtc { get; set; }
public DateTime ModifiedUtc { get; set; }
public DateTime? PublishedUtc { get; set; }
}
public class ContentVersion
{
public Guid Id { get; set; }
public Guid ContentItemId { get; set; }
public int VersionNumber { get; set; }
// Snapshot of content at this version
public string DataJson { get; set; } = string.Empty;
// Metadata
public string CreatedBy { get; set; } = string.Empty;
public DateTime CreatedUtc { get; set; }
public string? ChangeNote { get; set; }
public bool IsPublished { get; set; }
}
public enum ContentStatus
{
Draft,
Published,
Unpublished,
Archived
}
```
### Strategy 2: History Table Pattern
```csharp
// Current content (always latest)
public class Article
{
public Guid Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Body { get; set; } = string.Empty;
public int CurrentVersion { get; set; }
public ContentStatus Status { get; set; }
}
// Automatic history tracking
public class ArticleHistory
{
public Guid Id { get; set; }
public Guid ArticleId { get; set; }
public int VersionNumber { get; set; }
// Copy of all fields at this version
public string Title { get; set; } = string.Empty;
public string Body { get; set; } = string.Empty;
// Audit info
public DateTime ValidFrom { get; set; }
public DateTime ValidTo { get; set; }
public string ModifiedBy { get; set; } = string.Empty;
public ChangeType ChangeType { get; set; }
}
public enum ChangeType
{
Created,
Updated,
Published,
Unpublished,
Deleted
}
```
### Strategy 3: Event Sourcing
```csharp
public abstract class ContentEvent
{
public Guid Id { get; set; }
public Guid ContentItemId { get; set; }
public DateTime OccurredUtc { get; set; }
public string UserId { get; set; } = string.Empty;
public int SequenceNumber { get; set; }
}
public class ContentCreatedEvent : ContentEvent
{
public string ContentType { get; set; } = string.Empty;
public string InitialDataJson { get; set; } = string.Empty;
}
public class ContentUpdatedEvent : ContentEvent
{
public Dictionary<string, FieldChange> Changes { get; set; } = new();
}
public class ContentPublishedEvent : ContentEvent
{
public int PublishedVersion { get; set; }
}
public class FieldChange
{
public object? OldValue { get; set; }
public object? NewValue { get; set; }
}
```
## Draft/Publish Workflow
### Basic Implementation
```csharp
public class ContentPublishingService
{
public async Task<ContentItem> CreateDraftAsync(
string contentType,
object data,
string userId)
{
var item = new ContentItem
{
Id = Guid.NewGuid(),
ContentType = contentType,
Status = ContentStatus.Draft,
Version = 1,
CreatedUtc = DateTime.UtcNow,
ModifiedUtc = DateTime.UtcNow
};
var version = new ContentVersion
{
Id = Guid.NewGuid(),
ContentItemId = item.Id,
VersionNumber = 1,
DataJson = JsonSerializer.Serialize(data),
CreatedBy = userId,
CreatedUtc = DateTime.UtcNow,
IsPublished = false
};
item.DraftVersionId = version.Id;
await _repository.AddAsync(item);
await _versionRepository.AddAsync(version);
return item;
}
public async Task PublishAsync(Guid contentItemId, string userId)
{
var item = await _repository.GetAsync(contentItemId);
if (item == null || item.DraftVersionId == null)
throw new InvalidOperationException("No draft to publish");
var draft = await _versionRepository.GetAsync(item.DraftVersionId.Value);
// Create published version from draft
var published = new ContentVersion
{
Id = Guid.NewGuid(),
ContentItemId = item.Id,
VersionNumber = item.Version + 1,
DataJson = draft!.DataJson,
CreatedBy = userId,
CreatedUtc = DateTime.UtcNow,
IsPublished = true
};
await _versionRepository.AddAsync(published);
// Update content item
item.Version = published.VersionNumber;
item.PublishedVersionId = published.Id;
item.Status = ContentStatus.Published;
item.PublishedUtc = DateTime.UtcNow;
item.ModifiedUtc = DateTime.UtcNow;
await _repository.UpdateAsync(item);
// Raise event
await _mediator.Publish(new ContentPublishedEvent(item.Id));
}
public async Task UnpublishAsync(Guid contentItemId, string userId)
{
var item = await _repository.GetAsync(contentItemId);
if (item == null)
throw new InvalidOperationException("Content not found");
item.Status = ContentStatus.Unpublished;
item.PublishedVersionId = null;
item.ModifiedUtc = DateTime.UtcNow;
await _repository.UpdateAsync(item);
await _mediator.Publish(new ContentUnpublishedEvent(item.Id));
}
}
```
### Simultaneous Draft and Published
```csharp
public class ContentQueryService
{
public async Task<ContentVersion?> GetPublishedAsync(Guid contentItemId)
{
var item = await _repository.GetAsync(contentItemId);
if (item?.PublishedVersionId == null)
return null;
return await _versionRepository.GetAsync(item.PublishedVersionId.Value);
}
public async Task<ContentVersion?> GetDraftAsync(Guid contentItemId)
{
var item = await _repository.GetAsync(contentItemId);
if (item?.DraftVersionId == null)
return null;
return await _versionRepository.GetAsync(item.DraftVersionId.Value);
}
public async Task<ContentVersion?> GetLatestAsync(
Guid contentItemId,
bool preferDraft = false)
{
var item = await _repository.GetAsync(contentItemId);
if (item == null) return null;
if (preferDraft && item.DraftVersionId != null)
return await _versionRepository.GetAsync(item.DraftVersionId.Value);
if (item.PublishedVersionId != null)
return await _versionRepository.GetAsync(item.PublishedVersionId.Value);
return null;
}
}
```
## Version History
### Retrieving History
```csharp
public async Task<List<ContentVersionSummary>> GetVersionHistoryAsync(
Guid contentItemId,
int page = 1,
int pageSize = 20)
{
return await _context.ContentVersions
.Where(v => v.ContentItemId == contentItemId)
.OrderByDescending(v => v.VersionNumber)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(v => new ContentVersionSummary
{
Id = v.Id,
VersionNumber = v.VersionNumber,
CreatedBy = v.CreatedBy,
CreatedUtc = v.CreatedUtc,
ChangeNote = v.ChangeNote,
IsPublished = v.IsPublished
})
.ToListAsync();
}
```
### Rollback
```csharp
public async Task RollbackToVersionAsync(
Guid contentItemId,
int targetVerRelated in Security
mac-ops
IncludedComprehensive macOS workstation operations — diagnose kernel panics, identify failing drives, audit launchd startup items, decode wake reasons, triage TCC permission denials, manage APFS snapshots, recover from no-boot. Use for: Mac is slow, slow bootup, won't boot, kernel panic, kernel_task hot, mds_stores CPU, photoanalysisd, cloudd, login loop, gray screen, sleep wake failure, drive failing, IO errors, APFS snapshots eating space, Time Machine local snapshots, Spotlight indexing, launchd, LaunchAgent, LaunchDaemon, login items, TCC permissions, Full Disk Access, Screen Recording denied, Gatekeeper, quarantine, com.apple.quarantine, app is damaged, helper tool, /Library/PrivilegedHelperTools, pmset, wake reasons, dark wake, sysdiagnose, panic.ips, DiagnosticReports, configuration profile, MDM profile, remote diagnostics over SSH.
a11y-audit
IncludedRun accessibility audits on web projects combining automated scanning (axe-core, Lighthouse) with WCAG 2.1 AA compliance mapping, manual check guidance, and structured reporting. Output is configurable: markdown report only, markdown plus machine-readable JSON, or markdown plus issue tracker integration. Use this skill whenever the user mentions "accessibility audit", "a11y audit", "WCAG audit", "accessibility check", "compliance scan", or asks to check a web project for accessibility issues. Also trigger when the user wants to verify WCAG conformance or map findings to a specific standard (CAN-ASC-6.2, EN 301 549, ADA/AODA).
erpclaw
IncludedAI-native ERP system with self-extending OS. Full accounting, invoicing, inventory, purchasing, tax, billing, HR, payroll, advanced accounting (ASC 606/842, intercompany, consolidation), and financial reporting. 413 actions across 14 domains, 43 expansion modules. Constitutional guardrails, adversarial audit, schema migration. Double-entry GL, immutable audit trail, US GAAP.
assess
IncludedAssesses and rates quality 0-10 across multiple dimensions (correctness, maintainability, security, performance, testability, simplicity) with pros/cons analysis. Compares against project conventions and prior decisions from memory. Produces structured evaluation reports with actionable improvement suggestions. Use when evaluating code, designs, architectures, or comparing alternative approaches.
spring-boot-security-jwt
IncludedProvides JWT authentication and authorization patterns for Spring Boot 3.5.x covering token generation with JJWT, Bearer/cookie authentication, database/OAuth2 integration, and RBAC/permission-based access control using Spring Security 6.x. Use when implementing authentication or authorization in Spring Boot applications.
code-hardcode-audit
IncludedDetect hardcoded values, magic numbers, and leaked secrets. TRIGGERS - hardcode audit, magic numbers, PLR2004, secret scanning.