Helper methods for PM agents to read, write, and update workflow-state.json files consistently during workflow execution. Use when managing workflow state persistence, tracking wave progress, handling HITL stops, or resuming workflows.
This skill inherits all available tools. When active, it can use any tool Claude has access to.
Provides consistent operations for workflow-state.json management during /ms workflow execution. Ensures PM agents have standardized methods for reading, writing, and updating workflow state files.
{projectFolder}/workflow-state.json
Example: docs/projects/20251219-positive-reinforcement-hooks/workflow-state.json
Read and parse workflow state from project folder.
Input: Absolute path to project folder Output: State object or null if not found Error Handling: Returns null with warning if file missing or corrupt
Pseudocode:
function readWorkflowState(projectFolder):
statePath = join(projectFolder, "workflow-state.json")
if not fileExists(statePath):
log.warn("No workflow state found at {statePath}")
return null
try:
content = readFile(statePath)
state = JSON.parse(content)
return state
catch (JSONError):
log.error("Invalid JSON in workflow state: {statePath}")
return null
Write complete state object to file with validation.
Input:
Output: Success boolean Validation: Schema validation before write
Pseudocode:
function writeWorkflowState(projectFolder, state):
statePath = join(projectFolder, "workflow-state.json")
// Validate required fields
if not validateSchema(state):
log.error("Invalid state schema")
return false
// Update timestamp
state.lastUpdate = new Date().toISOString()
try:
writeFile(statePath, JSON.stringify(state, null, 2))
log.info("Workflow state updated: {statePath}")
return true
catch (WriteError):
log.error("Failed to write workflow state: {error}")
return false
Merge partial updates into existing state.
Input:
Output: Updated state object or null on failure Behavior: Read existing → merge updates → write
Pseudocode:
function updateWorkflowState(projectFolder, updates):
state = readWorkflowState(projectFolder)
if state is null:
log.error("Cannot update: no existing workflow state")
return null
// Deep merge updates into state
mergedState = deepMerge(state, updates)
if writeWorkflowState(projectFolder, mergedState):
return mergedState
else:
return null
Returns current wave number from state.
Input: State object Output: Current wave number (0 if not in execution)
Pseudocode:
function getCurrentWave(state):
return state.currentWave || 0
Returns array of story IDs scheduled for wave.
Input:
Output: Array of story IDs from pending array
Pseudocode:
function getNextStories(state, wave = null):
// This is a simplified version - actual implementation
// would read wave assignments from execution plan
if wave is null:
wave = state.currentWave + 1
// Return subset of pending stories for this wave
// (wave assignments should be in execution plan)
return state.stories.pending.slice(0, 3) // Example: 3 stories per wave
Moves story from inProgress to completed array, updates epic progress.
Input:
Output: Updated state object
Pseudocode:
function markStoryComplete(state, storyId):
// Remove from inProgress
state.stories.inProgress = state.stories.inProgress.filter(id => id !== storyId)
// Add to completed
if not state.stories.completed.includes(storyId):
state.stories.completed.push(storyId)
// Update epic progress
// (Assumes epic mapping exists in PRD or state)
epicId = findEpicForStory(storyId)
if epicId:
epic = state.epics.find(e => e.id === epicId)
if epic:
epic.storiesCompleted += 1
if epic.storiesCompleted === epic.storiesTotal:
epic.status = "complete"
state.lastUpdate = new Date().toISOString()
return state
Sets HITL fields for workflow pause.
Input:
Output: Updated state object
Pseudocode:
function setHITL(state, question, resumeAction):
state.status = "hitl_waiting"
state.hitlQuestion = question
state.resumeAction = resumeAction
state.lastUpdate = new Date().toISOString()
return state
Validates state object against schema rules.
Input: State object Output: Boolean (true if valid)
Validation Rules:
// After wave execution completes
const state = readWorkflowState("/absolute/path/to/project");
if (state) {
// Mark wave 2 stories complete
["US-003", "US-004", "US-005"].forEach((storyId) => {
markStoryComplete(state, storyId);
});
// Set HITL for user approval
setHITL(state, "Wave 2 complete. Proceed with wave 3?", "spawn-wave-3");
// Write updated state
writeWorkflowState("/absolute/path/to/project", state);
}
// PM checks if workflow exists and is active
const state = readWorkflowState("/absolute/path/to/project");
if (state === null) {
console.log("No active workflow found");
} else if (state.status === "complete") {
console.log("Workflow already complete");
} else if (state.status === "hitl_waiting") {
console.log(`Waiting for user response: ${state.hitlQuestion}`);
console.log(`Resume action: ${state.resumeAction}`);
} else {
console.log(`Active workflow at phase ${state.phase}: ${state.phaseName}`);
}
// PM starts wave 3
const updates = {
currentWave: 3,
status: "executing",
stories: {
...state.stories,
inProgress: ["US-006", "US-007", "US-008"],
},
};
const updatedState = updateWorkflowState("/absolute/path/to/project", updates);
if (updatedState) {
console.log(
`Wave ${updatedState.currentWave} started with ${updatedState.stories.inProgress.length} stories`,
);
}
| Error Condition | Response |
|---|---|
| Missing file | Return null, log warning |
| Invalid JSON | Return null, log error with parse details |
| Schema validation failed | Return false from write, log error details |
| Write permission denied | Return false, log error |
| Corrupted state | Return null, suggest manual inspection |
| Duplicate story IDs | Log warning, deduplicate before processing |
Referenced by:
/skill pm-build-workflow - Uses all methods for workflow management/skill pm-wave-coordinator - Uses markStoryComplete, setHITL/skill pm-resume-workflow - Uses readWorkflowState, getCurrentWaveSchema reference: docs/workflow-state-spec.md