Files
gh-duongdev-ccpm/commands/spec:sync.md
2025-11-29 18:24:24 +08:00

666 lines
19 KiB
Markdown

---
description: Sync spec document with implementation reality
allowed-tools: [LinearMCP, Read, Glob, Grep, AskUserQuestion]
argument-hint: <doc-id-or-issue-id>
---
# Sync Spec: $1
## 🚨 CRITICAL: Safety Rules
**READ FIRST**: ``$CCPM_COMMANDS_DIR/SAFETY_RULES.md``
**NEVER** submit, post, or update anything to Jira, Confluence, BitBucket, or Slack without explicit user confirmation, even in bypass permission mode.
---
## Argument
- **$1** - Document ID or Issue ID (will find linked spec doc)
## Workflow
### Step 1: Fetch Spec and Related Issues
If `$1` is an issue ID:
```javascript
// 1. Get issue
const issue = await getLinearIssue($1)
// 2. Extract spec doc link from description
const docLinkPattern = /\[(?:Epic Spec|Feature Design): .+?\]\((.+?)\)/
const match = issue.description.match(docLinkPattern)
if (match) {
const docUrl = match[1]
const docId = extractDocId(docUrl)
}
// 3. Get all sub-issues (tasks)
const tasks = await getLinearSubIssues(issue.id)
```
If `$1` is a doc ID:
```javascript
// 1. Get document
const doc = await getLinearDocument($1)
// 2. Find linked issue from document content or metadata
// Look for "Linear Epic:" or "Linear Feature:" links
const issueLinkPattern = /\[WORK-\d+\]\((.+?)\)/
const match = doc.content.match(issueLinkPattern)
if (match) {
const issueUrl = match[1]
const issueId = extractIssueId(issueUrl)
const issue = await getLinearIssue(issueId)
const tasks = await getLinearSubIssues(issue.id)
}
```
### Step 2: Analyze Spec vs Reality
**Compare spec sections with actual implementation:**
#### 2.1: Requirements Drift
```javascript
function checkRequirementsDrift(specDoc, tasks) {
const specRequirements = extractRequirements(specDoc)
const implementedFeatures = extractImplementedFeatures(tasks)
const drift = {
missing: [], // In spec but not implemented
extra: [], // Implemented but not in spec
changed: [] // Different from spec
}
// Compare
for (const req of specRequirements) {
const implemented = implementedFeatures.find(f => matches(f, req))
if (!implemented) {
drift.missing.push(req)
} else if (!exactMatch(implemented, req)) {
drift.changed.push({ spec: req, actual: implemented })
}
}
for (const feature of implementedFeatures) {
if (!specRequirements.find(r => matches(feature, r))) {
drift.extra.push(feature)
}
}
return drift
}
```
#### 2.2: Implementation Tasks Drift
```javascript
function checkTasksDrift(specDoc, linearTasks) {
const specTasks = extractTasksFromSpec(specDoc)
// From "## Implementation Plan" or "## Task Breakdown"
const drift = {
inSpecNotLinear: [], // Tasks in spec but no Linear issue
inLinearNotSpec: [], // Linear tasks not documented in spec
statusMismatch: [] // Different completion status
}
// Compare task lists
for (const specTask of specTasks) {
const linearTask = linearTasks.find(lt => matches(lt, specTask))
if (!linearTask) {
drift.inSpecNotLinear.push(specTask)
} else {
// Check if status matches
const specCompleted = specTask.checked
const linearCompleted = linearTask.status === 'Done'
if (specCompleted !== linearCompleted) {
drift.statusMismatch.push({
task: specTask,
specStatus: specCompleted ? 'Done' : 'Pending',
linearStatus: linearTask.status
})
}
}
}
for (const linearTask of linearTasks) {
if (!specTasks.find(st => matches(linearTask, st))) {
drift.inLinearNotSpec.push(linearTask)
}
}
return drift
}
```
#### 2.3: API Design Drift
```javascript
function checkApiDrift(specDoc, codebase) {
const specApis = extractApiDesign(specDoc)
const implementedApis = searchCodebaseForApis(codebase)
const drift = {
endpointsMissing: [],
endpointsExtra: [],
signatureChanged: []
}
// Compare endpoints
for (const specApi of specApis) {
const impl = implementedApis.find(api => api.path === specApi.path)
if (!impl) {
drift.endpointsMissing.push(specApi)
} else if (!signaturesMatch(impl, specApi)) {
drift.signatureChanged.push({
spec: specApi,
actual: impl,
differences: compareSignatures(specApi, impl)
})
}
}
return drift
}
```
#### 2.4: Data Model Drift
```javascript
function checkDataModelDrift(specDoc, codebase) {
const specModels = extractDataModel(specDoc)
const implementedModels = searchCodebaseForModels(codebase)
const drift = {
tablesMissing: [],
tablesExtra: [],
fieldsMissing: [],
fieldsChanged: []
}
// Compare schemas
for (const specModel of specModels) {
const impl = implementedModels.find(m => m.name === specModel.name)
if (!impl) {
drift.tablesMissing.push(specModel)
} else {
// Compare fields
for (const specField of specModel.fields) {
const implField = impl.fields.find(f => f.name === specField.name)
if (!implField) {
drift.fieldsMissing.push({
table: specModel.name,
field: specField
})
} else if (specField.type !== implField.type) {
drift.fieldsChanged.push({
table: specModel.name,
field: specField.name,
specType: specField.type,
actualType: implField.type
})
}
}
}
}
return drift
}
```
### Step 3: Search Codebase for Implementation
Use **Glob** and **Grep** to find implemented code:
```javascript
async function searchCodebaseForApis(projectPath) {
// Search for API route files
const apiFiles = await glob(`${projectPath}/**/api/**/*.{ts,js}`)
const apis = []
for (const file of apiFiles) {
const content = await read(file)
// Extract API endpoints
// Look for: app.post('/api/endpoint', ...) or export async function POST(...)
const endpointPattern = /(?:app\.(get|post|put|delete|patch)|export async function (GET|POST|PUT|DELETE|PATCH))\s*\(\s*['"`](.+?)['"`]/g
let match
while ((match = endpointPattern.exec(content)) !== null) {
const method = match[1] || match[2]
const path = match[3]
apis.push({
method: method.toUpperCase(),
path: path,
file: file,
// Could extract request/response types if TypeScript
})
}
}
return apis
}
async function searchCodebaseForModels(projectPath) {
// Search for database schema files
const schemaPattern = `${projectPath}/**/schema/**/*.{ts,js,sql}`
const schemaFiles = await glob(schemaPattern)
const models = []
for (const file of schemaFiles) {
const content = await read(file)
// Extract table definitions
// Drizzle: pgTable('table_name', { ... })
// Prisma: model TableName { ... }
// SQL: CREATE TABLE table_name ( ... )
// Parse and extract models
// This is simplified - actual implementation would parse properly
models.push(...parseSchema(content))
}
return models
}
```
### Step 4: Generate Sync Report
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔄 Spec Sync Report: [Document Title]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📄 Spec Doc: [DOC-456](link)
🎯 Issue: [WORK-123 - Feature Title](link)
📅 Last Synced: [Never / Date]
📊 Drift Score: [XX]% ([Grade])
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Overall Drift Analysis
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Drift Score**: 85% ← Lower is better (0% = perfect sync)
Legend: ✅ In Sync | ⚠️ Minor Drift | ❌ Major Drift
✅ Requirements: 2 missing, 1 extra (10% drift)
⚠️ Implementation Tasks: 3 status mismatches (25% drift)
❌ API Design: 2 endpoints differ (40% drift)
✅ Data Model: All tables match (0% drift)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 Requirements Drift
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**In Spec, Not Implemented:**
1. FR3: Password reset via email
- Spec says: "Users can reset password via email link"
- Reality: No implementation found
- Action: Implement or remove from spec
2. NFR2: Response time < 200ms
- Spec says: "95th percentile response time under 200ms"
- Reality: Not measured/verified
- Action: Add performance monitoring
**Implemented, Not in Spec:**
1. Social Login (Google OAuth)
- Found: POST /api/auth/google endpoint
- Not documented in spec
- Action: Add to spec or mark as scope creep
**Changed from Spec:**
1. Login Rate Limiting
- Spec: "10 attempts per hour"
- Actual: 5 attempts per 15 minutes (stricter)
- Action: Update spec to reflect current implementation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Implementation Tasks Drift
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Status Mismatches:**
1. Task 2: API endpoints
- Spec Checklist: ✅ Marked complete
- Linear Status: In Progress
- Action: Update spec checklist or complete Linear task
2. Task 4: Testing
- Spec Checklist: ⏳ Not checked
- Linear Status: Done
- Action: Check off in spec
3. Task 5: Documentation
- Spec Checklist: ⏳ Not checked
- Linear Status: Done
- Action: Check off in spec
**In Spec, No Linear Task:**
1. Task 6: Performance optimization
- Missing Linear task
- Action: Create Linear task or remove from spec
**In Linear, Not in Spec:**
1. WORK-125: Fix login bug (subtask)
- Unplanned task added during implementation
- Action: Add to spec as "Bug Fixes" section
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔌 API Design Drift
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Signature Changed:**
1. POST /api/auth/login
- Spec Request:
```typescript
{ email: string, password: string }
```
- Actual Request:
```typescript
{ email: string, password: string, rememberMe?: boolean }
```
- Change: Added optional `rememberMe` field
- Action: Update spec with new signature
2. POST /api/auth/refresh
- Spec Response:
```typescript
{ token: string }
```
- Actual Response:
```typescript
{ accessToken: string, refreshToken: string, expiresIn: number }
```
- Change: More detailed response
- Action: Update spec to match implementation
**Endpoints Missing:**
- None ✅
**Extra Endpoints (Not in Spec):**
1. GET /api/auth/session
- Found in code but not documented
- Action: Add to spec
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🗄️ Data Model Drift
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ All tables and fields match spec! No drift detected.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 Recommended Actions
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Critical (Fix Immediately):**
1. Update API signatures in spec (2 endpoints changed)
2. Implement missing requirements or remove from spec (2 items)
**Important (Fix Soon):**
1. Sync task statuses between spec checklist and Linear (3 mismatches)
2. Document unplanned features added during implementation (1 endpoint)
**Nice to Have:**
1. Add performance monitoring for NFR validation
2. Create missing Linear tasks for spec items
**Estimated Time to Sync**: 2-3 hours
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
### Step 5: Ask User How to Resolve Drift
Use **AskUserQuestion**:
```javascript
{
questions: [{
question: "Drift detected! How would you like to resolve it?",
header: "Sync Action",
multiSelect: false,
options: [
{
label: "Update Spec to Match Reality",
description: "Modify spec doc to reflect current implementation (recommended)"
},
{
label: "Update Implementation to Match Spec",
description: "Modify code to match original spec design"
},
{
label: "Hybrid Approach",
description: "Update spec for some items, code for others (I'll choose)"
},
{
label: "Review Only",
description: "Just show the report, I'll fix manually"
}
]
}]
}
```
### Step 6: Apply Sync Changes
#### If "Update Spec to Match Reality":
```javascript
// Update spec document with actual implementation
// 1. Update Requirements section
updateSpecSection(doc, 'requirements', {
add: drift.requirements.extra,
remove: drift.requirements.missing,
modify: drift.requirements.changed
})
// 2. Update API Design section
updateSpecSection(doc, 'api-design', {
updateSignatures: drift.api.signatureChanged,
addEndpoints: drift.api.endpointsExtra
})
// 3. Update Task Checklist
updateSpecChecklist(doc, {
check: drift.tasks.statusMismatch.filter(t => t.linearStatus === 'Done'),
uncheck: drift.tasks.statusMismatch.filter(t => t.linearStatus !== 'Done'),
add: drift.tasks.inLinearNotSpec
})
// 4. Add "Change Log" entry
appendToChangeLog(doc, {
date: new Date().toISOString().split('T')[0],
change: 'Synced spec with implementation reality',
details: `Updated ${changesCount} sections to match current implementation`,
author: currentUser
})
// 5. Update "Last Synced" timestamp in spec
updateMetadata(doc, {
lastSynced: new Date().toISOString()
})
```
#### If "Update Implementation to Match Spec":
```javascript
// Show what needs to be implemented to match spec
const implementationPlan = {
missingRequirements: drift.requirements.missing,
missingEndpoints: drift.api.endpointsMissing,
changedSignatures: drift.api.signatureChanged,
missingTasks: drift.tasks.inSpecNotLinear
}
// Show plan and ask if user wants to create Linear tasks for fixes
const tasks = generateFixTasks(implementationPlan)
// Offer to create tasks via /ccpm:spec:break-down or manually
```
#### If "Hybrid Approach":
```javascript
// Show each drift item and ask user decision
for (const item of allDriftItems) {
const choice = await askUserQuestion({
question: `Drift: ${item.description}. How to resolve?`,
options: [
"Update Spec",
"Update Code",
"Keep As Is"
]
})
if (choice === "Update Spec") {
updateSpec(item)
} else if (choice === "Update Code") {
addToImplementationBacklog(item)
}
// else: skip
}
```
### Step 7: Display Sync Results
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Spec Synced Successfully!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📄 Document: [DOC-456](link)
🔄 Sync Method: [Update Spec to Match Reality]
📊 Changes Applied: 12
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 Changes Summary
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Requirements Section:**
- ✅ Added: 1 new requirement (Social Login)
- ✅ Removed: 2 unimplemented requirements
- ✅ Updated: 1 changed requirement (Rate Limiting)
**API Design Section:**
- ✅ Updated: 2 endpoint signatures
- ✅ Added: 1 undocumented endpoint (GET /api/auth/session)
**Task Checklist:**
- ✅ Checked: 2 completed tasks
- ✅ Added: 1 new task (Bug Fixes)
**Metadata:**
- ✅ Last Synced: ${new Date().toISOString().split('T')[0]}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
💡 New Drift Score: 5% (was 85%)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
### Step 8: Interactive Next Actions
```javascript
{
questions: [{
question: "Sync complete! What would you like to do next?",
header: "Next Step",
multiSelect: false,
options: [
{
label: "Review Updated Spec",
description: "Open spec document in Linear to review changes"
},
{
label: "Review Spec",
description: "Run /ccpm:spec:review to validate updated spec"
},
{
label: "View Project Status",
description: "Check overall project status (/ccpm:utils:status)"
},
{
label: "Done",
description: "Finish for now"
}
]
}]
}
```
## Drift Score Calculation
```javascript
function calculateDriftScore(drift) {
let totalItems = 0
let driftItems = 0
// Requirements
totalItems += drift.requirements.total
driftItems += drift.requirements.missing.length +
drift.requirements.extra.length +
drift.requirements.changed.length
// Tasks
totalItems += drift.tasks.total
driftItems += drift.tasks.statusMismatch.length +
drift.tasks.inSpecNotLinear.length +
drift.tasks.inLinearNotSpec.length
// API
totalItems += drift.api.total
driftItems += drift.api.endpointsMissing.length +
drift.api.endpointsExtra.length +
drift.api.signatureChanged.length
// Data Model
totalItems += drift.dataModel.total
driftItems += drift.dataModel.tablesMissing.length +
drift.dataModel.tablesExtra.length +
drift.dataModel.fieldsMissing.length +
drift.dataModel.fieldsChanged.length
// Calculate percentage
if (totalItems === 0) return 0
const driftPercentage = Math.round((driftItems / totalItems) * 100)
return driftPercentage
}
function getDriftGrade(score) {
if (score <= 10) return { grade: 'A', label: 'Excellent Sync' }
if (score <= 25) return { grade: 'B', label: 'Minor Drift' }
if (score <= 50) return { grade: 'C', label: 'Moderate Drift' }
if (score <= 75) return { grade: 'D', label: 'Major Drift' }
return { grade: 'F', label: 'Significant Drift' }
}
```
## Notes
- Non-destructive: Always creates backups before updating
- Bidirectional: Can sync spec → code or code → spec
- Smart Detection: Uses codebase analysis to find actual implementation
- Preserves Intent: Asks user before resolving ambiguous drift
- Change Log: Tracks all sync operations in spec document
- Drift Score: Quantifies how much spec diverged from reality