Initial commit
This commit is contained in:
932
commands/_shared-state-machine.md
Normal file
932
commands/_shared-state-machine.md
Normal file
@@ -0,0 +1,932 @@
|
||||
# CCPM Workflow State Machine
|
||||
|
||||
This file implements the CCPM workflow state machine for tracking task progression through workflow phases. It provides state management, validation, transitions, and persistence via Linear custom fields.
|
||||
|
||||
## Overview
|
||||
|
||||
The state machine tracks 8 workflow phases:
|
||||
- **IDEA** - Initial concept
|
||||
- **PLANNED** - Implementation plan created
|
||||
- **IMPLEMENTING** - Active development
|
||||
- **BLOCKED** - Cannot proceed due to blocker
|
||||
- **VERIFYING** - Quality checks in progress
|
||||
- **VERIFIED** - Verified and ready to complete
|
||||
- **COMPLETE** - Task finalized
|
||||
- **CANCELLED** - Task cancelled/abandoned
|
||||
|
||||
**Integration**: This file delegates all Linear operations to the `linear-operations` subagent for optimal performance.
|
||||
|
||||
---
|
||||
|
||||
## State Machine Definition
|
||||
|
||||
```javascript
|
||||
const STATE_MACHINE = {
|
||||
IDEA: {
|
||||
description: "Initial concept, not yet planned",
|
||||
linear_status_mapping: ["Backlog"],
|
||||
phase: "ideation",
|
||||
next_states: ["PLANNED", "CANCELLED"],
|
||||
confidence_to_transition: {
|
||||
PLANNED: 95, // High confidence via /ccpm:plan
|
||||
CANCELLED: 50 // Requires confirmation
|
||||
},
|
||||
allowed_commands: [
|
||||
"/ccpm:plan",
|
||||
"/ccpm:utils:*"
|
||||
]
|
||||
},
|
||||
|
||||
PLANNED: {
|
||||
description: "Requirements gathered, implementation plan created",
|
||||
linear_status_mapping: ["Planned", "Todo", "Ready"],
|
||||
phase: "planning",
|
||||
next_states: ["IMPLEMENTING", "IDEA", "CANCELLED"],
|
||||
confidence_to_transition: {
|
||||
IMPLEMENTING: 95, // High confidence via /ccpm:work
|
||||
IDEA: 70, // Re-planning
|
||||
CANCELLED: 50 // Requires confirmation
|
||||
},
|
||||
allowed_commands: [
|
||||
"/ccpm:work",
|
||||
"/ccpm:plan", // Update plan
|
||||
"/ccpm:utils:*"
|
||||
]
|
||||
},
|
||||
|
||||
IMPLEMENTING: {
|
||||
description: "Active development in progress",
|
||||
linear_status_mapping: ["In Progress", "In Development", "Doing"],
|
||||
phase: "implementation",
|
||||
next_states: ["VERIFYING", "PLANNED", "BLOCKED"],
|
||||
confidence_to_transition: {
|
||||
VERIFYING: 70, // Medium - depends on checklist
|
||||
PLANNED: 60, // Re-planning
|
||||
BLOCKED: 90 // High if blocker detected
|
||||
},
|
||||
allowed_commands: [
|
||||
"/ccpm:sync",
|
||||
"/ccpm:commit",
|
||||
"/ccpm:verify",
|
||||
"/ccpm:implementation:*",
|
||||
"/ccpm:utils:*"
|
||||
]
|
||||
},
|
||||
|
||||
BLOCKED: {
|
||||
description: "Cannot proceed due to blocker",
|
||||
linear_status_mapping: ["Blocked"],
|
||||
phase: "implementation",
|
||||
next_states: ["IMPLEMENTING", "CANCELLED"],
|
||||
confidence_to_transition: {
|
||||
IMPLEMENTING: 85, // High when unblocked
|
||||
CANCELLED: 50 // Requires confirmation
|
||||
},
|
||||
allowed_commands: [
|
||||
"/ccpm:verification:fix",
|
||||
"/ccpm:utils:status",
|
||||
"/ccpm:sync"
|
||||
]
|
||||
},
|
||||
|
||||
VERIFYING: {
|
||||
description: "Quality checks and verification in progress",
|
||||
linear_status_mapping: ["In Review", "Testing", "Verification"],
|
||||
phase: "verification",
|
||||
next_states: ["VERIFIED", "IMPLEMENTING"],
|
||||
confidence_to_transition: {
|
||||
VERIFIED: 85, // High if all checks pass
|
||||
IMPLEMENTING: 100 // Certain if checks fail
|
||||
},
|
||||
allowed_commands: [
|
||||
"/ccpm:verify",
|
||||
"/ccpm:verification:*",
|
||||
"/ccpm:utils:*"
|
||||
]
|
||||
},
|
||||
|
||||
VERIFIED: {
|
||||
description: "Verified and ready to complete",
|
||||
linear_status_mapping: ["Verified", "Ready for Review", "Approved"],
|
||||
phase: "completion",
|
||||
next_states: ["COMPLETE", "IMPLEMENTING"],
|
||||
confidence_to_transition: {
|
||||
COMPLETE: 95, // High confidence via /ccpm:done
|
||||
IMPLEMENTING: 70 // If changes needed
|
||||
},
|
||||
allowed_commands: [
|
||||
"/ccpm:done",
|
||||
"/ccpm:complete:finalize",
|
||||
"/ccpm:utils:*"
|
||||
]
|
||||
},
|
||||
|
||||
COMPLETE: {
|
||||
description: "Task finalized and closed",
|
||||
linear_status_mapping: ["Done", "Completed", "Closed"],
|
||||
phase: "complete",
|
||||
next_states: [], // Terminal state
|
||||
confidence_to_transition: {},
|
||||
allowed_commands: [
|
||||
"/ccpm:utils:status",
|
||||
"/ccpm:utils:report"
|
||||
]
|
||||
},
|
||||
|
||||
CANCELLED: {
|
||||
description: "Task cancelled or abandoned",
|
||||
linear_status_mapping: ["Cancelled", "Archived"],
|
||||
phase: "cancelled",
|
||||
next_states: [], // Terminal state
|
||||
confidence_to_transition: {},
|
||||
allowed_commands: [
|
||||
"/ccpm:utils:status"
|
||||
]
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Core Functions
|
||||
|
||||
### 1. loadWorkflowState
|
||||
|
||||
Load current workflow state from Linear custom fields.
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Load workflow state from Linear issue
|
||||
* @param {string} issueId - Linear issue ID or identifier
|
||||
* @returns {Promise<Object>} Workflow state object
|
||||
*/
|
||||
async function loadWorkflowState(issueId) {
|
||||
// Delegate to Linear subagent
|
||||
const result = await Task('linear-operations', `
|
||||
operation: get_issue
|
||||
params:
|
||||
issue_id: "${issueId}"
|
||||
include_custom_fields: true
|
||||
context:
|
||||
command: "state-machine:load"
|
||||
purpose: "Loading workflow state"
|
||||
`);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(`Failed to load workflow state: ${result.error?.message || 'Unknown error'}`);
|
||||
}
|
||||
|
||||
const issue = result.data;
|
||||
const customFields = issue.customFields || {};
|
||||
|
||||
// Extract CCPM state fields
|
||||
const phase = customFields.ccpmPhase || inferPhaseFromStatus(issue.state.name);
|
||||
const lastCommand = customFields.ccpmLastCommand || null;
|
||||
const lastUpdate = customFields.ccpmLastUpdate || issue.updatedAt;
|
||||
const autoTransitions = customFields.ccpmAutoTransitions !== false; // Default true
|
||||
const verificationGate = customFields.ccpmVerificationGate || 'STANDARD';
|
||||
const checklistRequired = customFields.ccpmChecklistRequired !== false; // Default true
|
||||
|
||||
return {
|
||||
// Current state
|
||||
phase,
|
||||
linearStatus: issue.state.name,
|
||||
lastCommand,
|
||||
lastUpdate,
|
||||
|
||||
// Configuration
|
||||
autoTransitions,
|
||||
verificationGate,
|
||||
checklistRequired,
|
||||
|
||||
// Metadata
|
||||
issueId: issue.id,
|
||||
issueIdentifier: issue.identifier,
|
||||
title: issue.title,
|
||||
teamId: issue.team.id,
|
||||
projectId: issue.project?.id || null,
|
||||
|
||||
// Calculated fields
|
||||
nextStates: STATE_MACHINE[phase]?.next_states || [],
|
||||
allowedCommands: STATE_MACHINE[phase]?.allowed_commands || []
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer CCPM phase from Linear status name (fallback)
|
||||
* @param {string} statusName - Linear status name
|
||||
* @returns {string} CCPM phase
|
||||
*/
|
||||
function inferPhaseFromStatus(statusName) {
|
||||
const lower = statusName.toLowerCase();
|
||||
|
||||
if (lower.includes('backlog')) return 'IDEA';
|
||||
if (lower.includes('plan') || lower.includes('todo') || lower.includes('ready')) return 'PLANNED';
|
||||
if (lower.includes('progress') || lower.includes('doing') || lower.includes('development')) return 'IMPLEMENTING';
|
||||
if (lower.includes('blocked')) return 'BLOCKED';
|
||||
if (lower.includes('review') || lower.includes('verif') || lower.includes('testing')) return 'VERIFYING';
|
||||
if (lower.includes('approved')) return 'VERIFIED';
|
||||
if (lower.includes('done') || lower.includes('complete') || lower.includes('closed')) return 'COMPLETE';
|
||||
if (lower.includes('cancel') || lower.includes('archived')) return 'CANCELLED';
|
||||
|
||||
// Default fallback
|
||||
return 'IDEA';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. saveWorkflowState
|
||||
|
||||
Persist workflow state to Linear custom fields.
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Save workflow state to Linear
|
||||
* @param {string} issueId - Linear issue ID or identifier
|
||||
* @param {Object} stateUpdates - State fields to update
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function saveWorkflowState(issueId, stateUpdates) {
|
||||
// Build custom fields update
|
||||
const customFields = {};
|
||||
|
||||
if (stateUpdates.phase !== undefined) {
|
||||
customFields.ccpmPhase = stateUpdates.phase;
|
||||
}
|
||||
|
||||
if (stateUpdates.lastCommand !== undefined) {
|
||||
customFields.ccpmLastCommand = stateUpdates.lastCommand;
|
||||
}
|
||||
|
||||
// Always update timestamp
|
||||
customFields.ccpmLastUpdate = new Date().toISOString();
|
||||
|
||||
if (stateUpdates.autoTransitions !== undefined) {
|
||||
customFields.ccpmAutoTransitions = stateUpdates.autoTransitions;
|
||||
}
|
||||
|
||||
if (stateUpdates.verificationGate !== undefined) {
|
||||
customFields.ccpmVerificationGate = stateUpdates.verificationGate;
|
||||
}
|
||||
|
||||
if (stateUpdates.checklistRequired !== undefined) {
|
||||
customFields.ccpmChecklistRequired = stateUpdates.checklistRequired;
|
||||
}
|
||||
|
||||
// Delegate to Linear subagent
|
||||
const result = await Task('linear-operations', `
|
||||
operation: update_issue_custom_fields
|
||||
params:
|
||||
issue_id: "${issueId}"
|
||||
custom_fields:
|
||||
${Object.entries(customFields).map(([key, value]) =>
|
||||
`${key}: ${typeof value === 'string' ? `"${value}"` : value}`
|
||||
).join('\n ')}
|
||||
context:
|
||||
command: "state-machine:save"
|
||||
purpose: "Persisting workflow state"
|
||||
`);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(`Failed to save workflow state: ${result.error?.message || 'Unknown error'}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. validateTransition
|
||||
|
||||
Validate if a state transition is allowed.
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Validate if transition is allowed
|
||||
* @param {string} fromPhase - Current phase
|
||||
* @param {string} toPhase - Target phase
|
||||
* @param {Object} options - Validation options
|
||||
* @param {string} options.issueId - Issue ID for pre-condition checks
|
||||
* @returns {Promise<Object>} Validation result
|
||||
*/
|
||||
async function validateTransition(fromPhase, toPhase, options = {}) {
|
||||
const stateMachine = STATE_MACHINE;
|
||||
const currentStateConfig = stateMachine[fromPhase];
|
||||
|
||||
if (!currentStateConfig) {
|
||||
return {
|
||||
valid: false,
|
||||
confidence: 0,
|
||||
error: `Unknown phase: ${fromPhase}`,
|
||||
suggestions: Object.keys(stateMachine)
|
||||
};
|
||||
}
|
||||
|
||||
const allowedNextStates = currentStateConfig.next_states || [];
|
||||
|
||||
if (!allowedNextStates.includes(toPhase)) {
|
||||
return {
|
||||
valid: false,
|
||||
confidence: 0,
|
||||
error: `Cannot transition from ${fromPhase} to ${toPhase}`,
|
||||
allowedStates: allowedNextStates,
|
||||
suggestions: allowedNextStates.map(s => `Try transitioning to: ${s}`)
|
||||
};
|
||||
}
|
||||
|
||||
// Validate pre-conditions if issueId provided
|
||||
if (options.issueId) {
|
||||
const preConditions = await validatePreConditions(options.issueId, toPhase);
|
||||
|
||||
if (!preConditions.passed) {
|
||||
return {
|
||||
valid: false,
|
||||
confidence: 0,
|
||||
error: `Pre-conditions not met for ${toPhase}`,
|
||||
failures: preConditions.failures,
|
||||
suggestions: preConditions.failures.map(f => `Fix: ${f}`)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Get confidence for this transition
|
||||
const confidence = currentStateConfig.confidence_to_transition[toPhase] || 90;
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
confidence
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate pre-conditions for target phase
|
||||
* @param {string} issueId - Issue ID
|
||||
* @param {string} toPhase - Target phase
|
||||
* @returns {Promise<Object>} Pre-condition validation result
|
||||
*/
|
||||
async function validatePreConditions(issueId, toPhase) {
|
||||
const failures = [];
|
||||
|
||||
// Load issue data
|
||||
const state = await loadWorkflowState(issueId);
|
||||
const issueResult = await Task('linear-operations', `
|
||||
operation: get_issue
|
||||
params:
|
||||
issue_id: "${issueId}"
|
||||
context:
|
||||
command: "state-machine:validate-preconditions"
|
||||
`);
|
||||
|
||||
if (!issueResult.success) {
|
||||
failures.push('Failed to fetch issue data');
|
||||
return { passed: false, failures };
|
||||
}
|
||||
|
||||
const issue = issueResult.data;
|
||||
|
||||
switch (toPhase) {
|
||||
case 'PLANNED':
|
||||
// Must have implementation checklist
|
||||
if (!issue.description.includes('## Implementation Checklist')) {
|
||||
failures.push('Missing implementation checklist');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'IMPLEMENTING':
|
||||
// Must be planned
|
||||
if (!issue.description.includes('## Implementation Checklist')) {
|
||||
failures.push('Not planned yet - run /ccpm:plan first');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'VERIFYING':
|
||||
// Check checklist completion
|
||||
const checklist = extractChecklist(issue.description);
|
||||
if (state.checklistRequired && checklist.completion < 100) {
|
||||
failures.push(`Checklist incomplete (${checklist.completion}%)`);
|
||||
}
|
||||
|
||||
// Check for uncommitted changes (local git check)
|
||||
const hasUncommitted = await hasUncommittedChanges();
|
||||
if (hasUncommitted) {
|
||||
failures.push('Uncommitted changes detected - commit first');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'VERIFIED':
|
||||
// Must have passing verification
|
||||
// Note: This is typically checked by verification command
|
||||
break;
|
||||
|
||||
case 'COMPLETE':
|
||||
// Must be verified
|
||||
if (state.phase !== 'VERIFIED') {
|
||||
failures.push('Not verified yet - run /ccpm:verify first');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
passed: failures.length === 0,
|
||||
failures
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract checklist from description
|
||||
* @param {string} description - Issue description
|
||||
* @returns {Object} Checklist data
|
||||
*/
|
||||
function extractChecklist(description) {
|
||||
const checklistItems = description.match(/- \[([ x])\] .+/g) || [];
|
||||
const completed = checklistItems.filter(i => i.includes('[x]')).length;
|
||||
const total = checklistItems.length;
|
||||
|
||||
return {
|
||||
items: checklistItems,
|
||||
completed,
|
||||
total,
|
||||
completion: total > 0 ? Math.round((completed / total) * 100) : 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for uncommitted changes (local git)
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async function hasUncommittedChanges() {
|
||||
try {
|
||||
const result = await Bash({
|
||||
command: 'git status --porcelain',
|
||||
description: 'Check for uncommitted changes'
|
||||
});
|
||||
return result.trim().length > 0;
|
||||
} catch (error) {
|
||||
return false; // Not a git repo or error
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. transitionState
|
||||
|
||||
Execute a state transition with validation and persistence.
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Execute state transition
|
||||
* @param {string} issueId - Issue ID
|
||||
* @param {string} toPhase - Target phase
|
||||
* @param {Object} options - Transition options
|
||||
* @param {string} options.reason - Reason for transition
|
||||
* @param {boolean} options.userConfirmed - User confirmed transition
|
||||
* @param {string} options.command - Command triggering transition
|
||||
* @returns {Promise<Object>} Transition result
|
||||
*/
|
||||
async function transitionState(issueId, toPhase, options = {}) {
|
||||
const {
|
||||
reason = 'State transition',
|
||||
userConfirmed = false,
|
||||
command = 'unknown'
|
||||
} = options;
|
||||
|
||||
// Load current state
|
||||
const currentState = await loadWorkflowState(issueId);
|
||||
|
||||
// Validate transition
|
||||
const validation = await validateTransition(
|
||||
currentState.phase,
|
||||
toPhase,
|
||||
{ issueId }
|
||||
);
|
||||
|
||||
if (!validation.valid) {
|
||||
return {
|
||||
success: false,
|
||||
error: validation.error,
|
||||
failures: validation.failures,
|
||||
suggestions: validation.suggestions
|
||||
};
|
||||
}
|
||||
|
||||
// Check if confirmation needed
|
||||
const requiresConfirmation = validation.confidence < 80;
|
||||
|
||||
if (requiresConfirmation && !userConfirmed) {
|
||||
return {
|
||||
success: false,
|
||||
requiresConfirmation: true,
|
||||
message: `Transition ${currentState.phase} → ${toPhase} requires confirmation`,
|
||||
confidence: validation.confidence
|
||||
};
|
||||
}
|
||||
|
||||
// Determine new Linear status
|
||||
const newLinearStatus = getLinearStatusForPhase(toPhase);
|
||||
|
||||
// Update Linear issue status
|
||||
const updateResult = await Task('linear-operations', `
|
||||
operation: update_issue
|
||||
params:
|
||||
issue_id: "${issueId}"
|
||||
state: "${newLinearStatus}"
|
||||
context:
|
||||
command: "state-machine:transition"
|
||||
from_phase: "${currentState.phase}"
|
||||
to_phase: "${toPhase}"
|
||||
`);
|
||||
|
||||
if (!updateResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Failed to update Linear status: ${updateResult.error?.message}`
|
||||
};
|
||||
}
|
||||
|
||||
// Update workflow state
|
||||
await saveWorkflowState(issueId, {
|
||||
phase: toPhase,
|
||||
lastCommand: command
|
||||
});
|
||||
|
||||
// Add transition comment
|
||||
await addTransitionComment(issueId, currentState.phase, toPhase, reason);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
fromPhase: currentState.phase,
|
||||
toPhase,
|
||||
newStatus: newLinearStatus,
|
||||
confidence: validation.confidence
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Linear status for phase
|
||||
* @param {string} phase - CCPM phase
|
||||
* @returns {string} Linear status name
|
||||
*/
|
||||
function getLinearStatusForPhase(phase) {
|
||||
const statusMap = {
|
||||
IDEA: 'Backlog',
|
||||
PLANNED: 'Planned',
|
||||
IMPLEMENTING: 'In Progress',
|
||||
BLOCKED: 'Blocked',
|
||||
VERIFYING: 'In Review',
|
||||
VERIFIED: 'Verified',
|
||||
COMPLETE: 'Done',
|
||||
CANCELLED: 'Cancelled'
|
||||
};
|
||||
|
||||
return statusMap[phase] || 'Backlog';
|
||||
}
|
||||
|
||||
/**
|
||||
* Add transition comment to Linear
|
||||
* @param {string} issueId - Issue ID
|
||||
* @param {string} fromPhase - Previous phase
|
||||
* @param {string} toPhase - New phase
|
||||
* @param {string} reason - Transition reason
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function addTransitionComment(issueId, fromPhase, toPhase, reason) {
|
||||
const emoji = getPhaseEmoji(toPhase);
|
||||
|
||||
const commentBody = `${emoji} **Workflow Phase: ${fromPhase} → ${toPhase}**
|
||||
|
||||
${reason}
|
||||
|
||||
---
|
||||
*Automated state transition*`;
|
||||
|
||||
await Task('linear-operations', `
|
||||
operation: create_comment
|
||||
params:
|
||||
issue_id: "${issueId}"
|
||||
body: |
|
||||
${commentBody}
|
||||
context:
|
||||
command: "state-machine:transition-comment"
|
||||
`);
|
||||
}
|
||||
|
||||
function getPhaseEmoji(phase) {
|
||||
const emojis = {
|
||||
IDEA: '💡',
|
||||
PLANNED: '📋',
|
||||
IMPLEMENTING: '🚀',
|
||||
BLOCKED: '🚫',
|
||||
VERIFYING: '🔍',
|
||||
VERIFIED: '✅',
|
||||
COMPLETE: '🎉',
|
||||
CANCELLED: '❌'
|
||||
};
|
||||
|
||||
return emojis[phase] || '📌';
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. suggestNextAction
|
||||
|
||||
Suggest the next command based on current workflow state.
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Suggest next action based on current state
|
||||
* @param {Object} state - Workflow state
|
||||
* @returns {Object} Action suggestion
|
||||
*/
|
||||
function suggestNextAction(state) {
|
||||
const phase = state.phase;
|
||||
|
||||
switch (phase) {
|
||||
case 'IDEA':
|
||||
return {
|
||||
command: '/ccpm:plan',
|
||||
description: 'Create implementation plan',
|
||||
confidence: 90,
|
||||
reasoning: 'Task needs planning before implementation'
|
||||
};
|
||||
|
||||
case 'PLANNED':
|
||||
return {
|
||||
command: '/ccpm:work',
|
||||
description: 'Start implementation',
|
||||
confidence: 90,
|
||||
reasoning: 'Plan is ready, begin development'
|
||||
};
|
||||
|
||||
case 'IMPLEMENTING':
|
||||
// Check progress
|
||||
if (state.checklistCompletion >= 100) {
|
||||
return {
|
||||
command: '/ccpm:verify',
|
||||
description: 'Run quality checks',
|
||||
confidence: 85,
|
||||
reasoning: 'Checklist complete, verify before completion'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
command: '/ccpm:sync',
|
||||
description: 'Save progress',
|
||||
confidence: 70,
|
||||
reasoning: 'Continue implementation and sync progress'
|
||||
};
|
||||
}
|
||||
|
||||
case 'BLOCKED':
|
||||
return {
|
||||
command: '/ccpm:verification:fix',
|
||||
description: 'Fix blocker',
|
||||
confidence: 80,
|
||||
reasoning: 'Address blocking issue to continue'
|
||||
};
|
||||
|
||||
case 'VERIFYING':
|
||||
return {
|
||||
command: '/ccpm:verify',
|
||||
description: 'Continue verification',
|
||||
confidence: 80,
|
||||
reasoning: 'Complete verification process'
|
||||
};
|
||||
|
||||
case 'VERIFIED':
|
||||
return {
|
||||
command: '/ccpm:done',
|
||||
description: 'Finalize and create PR',
|
||||
confidence: 95,
|
||||
reasoning: 'Verification passed, ready to complete'
|
||||
};
|
||||
|
||||
case 'COMPLETE':
|
||||
return null; // Terminal state
|
||||
|
||||
case 'CANCELLED':
|
||||
return null; // Terminal state
|
||||
|
||||
default:
|
||||
return {
|
||||
command: '/ccpm:utils:status',
|
||||
description: 'Check task status',
|
||||
confidence: 60,
|
||||
reasoning: 'Unknown state, check status'
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. isCommandAllowed
|
||||
|
||||
Check if command is allowed in current state.
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Check if command is allowed in current phase
|
||||
* @param {string} command - Command to check
|
||||
* @param {string} phase - Current phase
|
||||
* @returns {Object} Validation result
|
||||
*/
|
||||
function isCommandAllowed(command, phase) {
|
||||
const stateConfig = STATE_MACHINE[phase];
|
||||
|
||||
if (!stateConfig) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Unknown phase: ${phase}`
|
||||
};
|
||||
}
|
||||
|
||||
const allowedPatterns = stateConfig.allowed_commands || [];
|
||||
|
||||
// Check if command matches any pattern
|
||||
const isAllowed = allowedPatterns.some(pattern => {
|
||||
if (pattern.endsWith('*')) {
|
||||
// Wildcard pattern (e.g., "/ccpm:utils:*")
|
||||
const prefix = pattern.slice(0, -1);
|
||||
return command.startsWith(prefix);
|
||||
} else {
|
||||
// Exact match
|
||||
return command === pattern;
|
||||
}
|
||||
});
|
||||
|
||||
if (!isAllowed) {
|
||||
return {
|
||||
allowed: false,
|
||||
reason: `Command ${command} not typically used in ${phase} phase`,
|
||||
suggestedCommands: allowedPatterns
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
allowed: true
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration Example
|
||||
|
||||
```javascript
|
||||
// Example: Using state machine in /ccpm:work command
|
||||
|
||||
async function executeWork(issueId) {
|
||||
// Load current state
|
||||
const state = await loadWorkflowState(issueId);
|
||||
|
||||
console.log(`\n📊 Current Phase: ${state.phase}`);
|
||||
console.log(`📋 Status: ${state.linearStatus}\n`);
|
||||
|
||||
// Check if command is allowed
|
||||
const commandCheck = isCommandAllowed('/ccpm:work', state.phase);
|
||||
|
||||
if (!commandCheck.allowed) {
|
||||
console.log(`⚠️ ${commandCheck.reason}`);
|
||||
console.log(`\nSuggested commands for ${state.phase}:`);
|
||||
commandCheck.suggestedCommands.forEach(cmd => console.log(` • ${cmd}`));
|
||||
console.log('');
|
||||
|
||||
// Ask user if they want to continue anyway
|
||||
const answer = await askUserForClarification({
|
||||
question: "Continue anyway?",
|
||||
header: "Confirm",
|
||||
options: [
|
||||
{ label: "Yes, continue", description: "Proceed with command" },
|
||||
{ label: "No, cancel", description: "Cancel command" }
|
||||
]
|
||||
});
|
||||
|
||||
if (!answer.includes('Yes')) {
|
||||
console.log('❌ Cancelled');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine action based on current phase
|
||||
if (state.phase === 'PLANNED') {
|
||||
// START mode - transition to IMPLEMENTING
|
||||
console.log('🚀 Starting implementation...\n');
|
||||
|
||||
const transitionResult = await transitionState(issueId, 'IMPLEMENTING', {
|
||||
reason: 'Implementation started via /ccpm:work',
|
||||
command: '/ccpm:work'
|
||||
});
|
||||
|
||||
if (transitionResult.success) {
|
||||
console.log(`✅ Transitioned: ${transitionResult.fromPhase} → ${transitionResult.toPhase}`);
|
||||
// ... continue with implementation start
|
||||
} else {
|
||||
console.error(`❌ Transition failed: ${transitionResult.error}`);
|
||||
if (transitionResult.failures) {
|
||||
transitionResult.failures.forEach(f => console.log(` • ${f}`));
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if (state.phase === 'IMPLEMENTING') {
|
||||
// RESUME mode
|
||||
console.log('⏩ Resuming implementation...\n');
|
||||
// ... continue with resume logic
|
||||
} else {
|
||||
// Unexpected phase
|
||||
const suggestion = suggestNextAction(state);
|
||||
if (suggestion) {
|
||||
console.log(`💡 Suggested: ${suggestion.command}`);
|
||||
console.log(` ${suggestion.description}\n`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Display Helpers
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Display workflow state summary
|
||||
* @param {Object} state - Workflow state
|
||||
*/
|
||||
function displayStateSummary(state) {
|
||||
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
console.log('🎯 Workflow State');
|
||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
||||
|
||||
const emoji = getPhaseEmoji(state.phase);
|
||||
console.log(`${emoji} Phase: ${state.phase}`);
|
||||
console.log(`📋 Status: ${state.linearStatus}`);
|
||||
|
||||
if (state.lastCommand) {
|
||||
console.log(`⚙️ Last Command: ${state.lastCommand}`);
|
||||
}
|
||||
|
||||
const lastUpdateDate = new Date(state.lastUpdate);
|
||||
console.log(`🕐 Last Update: ${lastUpdateDate.toLocaleString()}`);
|
||||
|
||||
console.log('\n📍 Next Actions:');
|
||||
state.nextStates.forEach(nextState => {
|
||||
console.log(` • Transition to ${nextState}`);
|
||||
});
|
||||
|
||||
const suggestion = suggestNextAction(state);
|
||||
if (suggestion) {
|
||||
console.log(`\n💡 Suggested: ${suggestion.command}`);
|
||||
console.log(` ${suggestion.description}`);
|
||||
console.log(` Confidence: ${suggestion.confidence}%`);
|
||||
}
|
||||
|
||||
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always load state** before command execution
|
||||
2. **Validate transitions** before attempting state changes
|
||||
3. **Check pre-conditions** for critical transitions
|
||||
4. **Display state** for user awareness
|
||||
5. **Persist transitions** with comments for audit trail
|
||||
6. **Suggest next actions** to guide workflow
|
||||
7. **Handle terminal states** (COMPLETE, CANCELLED) gracefully
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
```javascript
|
||||
// Test state loading
|
||||
const state = await loadWorkflowState('PSN-29');
|
||||
console.log('Loaded state:', state);
|
||||
|
||||
// Test transition validation
|
||||
const validation = await validateTransition('IMPLEMENTING', 'VERIFYING', {
|
||||
issueId: 'PSN-29'
|
||||
});
|
||||
console.log('Validation:', validation);
|
||||
|
||||
// Test state transition
|
||||
const result = await transitionState('PSN-29', 'VERIFYING', {
|
||||
reason: 'Checklist complete',
|
||||
command: '/ccpm:verify'
|
||||
});
|
||||
console.log('Transition result:', result);
|
||||
|
||||
// Test next action suggestion
|
||||
const suggestion = suggestNextAction(state);
|
||||
console.log('Suggested action:', suggestion);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documents
|
||||
|
||||
- [Decision Framework](../docs/architecture/decision-framework.md)
|
||||
- [Workflow State Tracking Design](../docs/architecture/workflow-state-tracking.md)
|
||||
- [Decision Helpers](./_shared-decision-helpers.md)
|
||||
Reference in New Issue
Block a user