Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/docs-tools/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "docs-tools",
"version": "0.0.61",
"version": "0.0.62",
"description": "Documentation review, writing, and workflow tools for Red Hat AsciiDoc and Markdown documentation.",
"author": {
"name": "Red Hat Documentation Team",
Expand Down
52 changes: 48 additions & 4 deletions plugins/docs-tools/skills/docs-orchestrator/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ Every step that produces markdown output also writes a `step-result.json` sideca

## Progress file

Claude writes the progress file directly using the Write tool. Create it after parsing arguments, before step 1. Update it after each step.
Claude writes the progress file directly using the Write tool. Create it after parsing arguments, before step 1. Update it after each step. Also write the active workflow marker at the same time (see [Active workflow marker](#active-workflow-marker)).

**Location**: `.claude/docs/<ticket>/workflow/<workflow-type>_<ticket>.json`

Expand Down Expand Up @@ -329,6 +329,47 @@ The `result` field stores selected sidecar data after each step completes. This

A top-level array listing steps in canonical order. This field exists so the Stop hook can determine step ordering without a hardcoded bash array. It **must** always be written by the orchestrator and kept in sync with the YAML step list.

## Active workflow marker

The active workflow marker tells the Stop hook which workflow (if any) is currently running in this session. Without the marker, the hook allows Claude to stop freely.

**Location**: `.claude/docs/.active-workflow`

### When to write the marker

Write the marker file using the Write tool at the same time as creating or updating the progress file to `"in_progress"` — after parsing arguments, before step 1. If resuming an existing workflow, overwrite any existing marker.

### Schema

```json
{
"ticket": "<TICKET>",
"workflow_type": "<workflow.name from YAML>",
"progress_file": ".claude/docs/<ticket-lower>/workflow/<workflow-type>_<ticket-lower>.json"
}
```

The `progress_file` path must be relative to the project root (matching the path the hook uses to locate the file).
Comment thread
coderabbitai[bot] marked this conversation as resolved.

### When to delete the marker

Delete `.claude/docs/.active-workflow` when:

1. The workflow completes — immediately after setting the progress file's `status` to `"completed"` in the [Completion](#completion) section
2. The workflow fails terminally — after setting `status` to `"failed"` (e.g., planning step produces 0 modules and user chooses to stop)

Do **not** delete the marker between steps. The marker must persist for the entire duration of the workflow so the Stop hook can block premature stops.

### Overwriting on resume or new workflow

If the user starts a new workflow (different ticket or different workflow type) or resumes an existing one, overwrite the marker with the new workflow's information. There is only ever one active workflow at a time. The previous marker is implicitly superseded.

### Edge cases

- **No marker exists**: The Stop hook allows Claude to stop. This is the correct default for sessions that don't involve a workflow.
- **Marker points to a missing progress file**: The Stop hook cleans up the stale marker and allows stop.
- **Marker exists but workflow status is `"completed"` or `"failed"`**: The Stop hook cleans up the marker and allows stop.

## Check for existing work

Before starting, check for a progress file at `.claude/docs/<ticket>/workflow/<workflow-type>_<ticket>.json`.
Expand All @@ -342,7 +383,9 @@ Before starting, check for a progress file at `.claude/docs/<ticket>/workflow/<w
5. Tell the user: "Found existing work for `<ticket>`. Resuming from `<step>`."
6. If the user provided additional flags on resume (e.g., `--create-jira`), update the progress file options accordingly

**If no progress file exists**, start from step 1 and create a new progress file.
**If no progress file exists**, start from step 1, create a new progress file, and write the active workflow marker.

In both cases (new or resume), write the [active workflow marker](#active-workflow-marker) with the current ticket and workflow type. This ensures the Stop hook tracks only this workflow.

## Running workflow steps

Expand Down Expand Up @@ -405,7 +448,7 @@ After each step completes, apply the rules below. When rules reference sidecar f

**planning**
- Log: `"Planning completed: N modules"`
- If `module_count` is 0, **warn**: `"Planning produced 0 modules — the plan may be empty. Review plan.md before continuing."` Ask the user whether to proceed or stop. If the user chooses to stop: mark the planning step as `failed` in the progress file, set the workflow status to `"failed"`, log `"Planning stopped by user after 0 modules — workflow cancelled."`, and halt without running subsequent steps
- If `module_count` is 0, **warn**: `"Planning produced 0 modules — the plan may be empty. Review plan.md before continuing."` Ask the user whether to proceed or stop. If the user chooses to stop: mark the planning step as `failed` in the progress file, set the workflow status to `"failed"`, delete the active workflow marker (`.claude/docs/.active-workflow`), log `"Planning stopped by user after 0 modules — workflow cancelled."`, and halt without running subsequent steps

**code-evidence**
- Log: `"Code evidence retrieved: N topics, N snippets"`
Expand Down Expand Up @@ -484,7 +527,8 @@ If the user declines, mark the `create-merge-request` step as `skipped` (with `s
After all steps complete (or are skipped):

1. Update the progress file: `status → "completed"`
2. Display a summary:
2. Delete the active workflow marker: remove `.claude/docs/.active-workflow`
3. Display a summary:
- List all output folders with paths
- Note any warnings (tech review didn't reach `HIGH`, planning had 0 modules, code-evidence had 0 snippets, etc.)
- Show MR/PR URL from `steps.create-merge-request.result.url` if present
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#!/bin/bash
# workflow-completion-check.sh
#
# Stop hook: blocks Claude from stopping while a workflow is still running.
# Checks each progress file for incomplete steps.
# Stop hook: blocks Claude from stopping while the ACTIVE workflow
# is still running. Only checks the workflow identified by the
# .active-workflow marker — stale workflows from other sessions
# are ignored.
#
# Exit codes:
# 0 = allow stop
Expand All @@ -19,64 +21,83 @@ if ! cd "${CLAUDE_PROJECT_DIR:-.}" 2>/dev/null; then
exit 2
fi

# Look for progress files
shopt -s nullglob
PROGRESS_FILES=(.claude/docs/*/workflow/*.json)
shopt -u nullglob
if [ ${#PROGRESS_FILES[@]} -eq 0 ]; then
MARKER=".claude/docs/.active-workflow"

# No marker → no active workflow → allow stop
if [ ! -f "$MARKER" ]; then
exit 0
fi

# Read the marker — fail closed on parse errors
PROGRESS_FILE=$(jq -r '.progress_file // empty' "$MARKER" 2>/dev/null)
JQ_RC_PF=$?
TICKET=$(jq -r '.ticket // empty' "$MARKER" 2>/dev/null)
JQ_RC_TK=$?

if [ "$JQ_RC_PF" -ne 0 ] || [ "$JQ_RC_TK" -ne 0 ]; then
echo "Failed to parse $MARKER; refusing to stop (fail closed)." >&2
exit 2
fi

# Marker parsed successfully but fields are empty → stale marker → clean up
if [ -z "$PROGRESS_FILE" ] || [ -z "$TICKET" ]; then
rm -f "$MARKER"
exit 0
fi

# Progress file doesn't exist → stale marker → clean up and allow stop
if [ ! -f "$PROGRESS_FILE" ]; then
rm -f "$MARKER"
exit 0
fi

# Check the workflow status — only block for in_progress workflows
WORKFLOW_STATUS=$(jq -r '.status' "$PROGRESS_FILE" 2>/dev/null)

if [ "$WORKFLOW_STATUS" != "in_progress" ]; then
rm -f "$MARKER"
exit 0
fi

for pfile in "${PROGRESS_FILES[@]}"; do
WORKFLOW_STATUS=$(jq -r '.status' "$pfile" 2>/dev/null)

# Skip workflows that aren't running
if [ "$WORKFLOW_STATUS" != "in_progress" ]; then
continue
fi

TICKET=$(jq -r '.ticket' "$pfile")
WORKFLOW_TYPE=$(jq -r '.workflow_type' "$pfile")

# Anti-loop guard: per-workflow counter prevents infinite blocking.
COUNTER_FILE="${pfile}.stop_count"
if [ -f "$COUNTER_FILE" ]; then
COUNT=$(cat "$COUNTER_FILE")
else
COUNT=0
fi
if [ "$COUNT" -ge 5 ]; then
rm -f "$COUNTER_FILE"
continue
fi

# Get step order from the progress file
mapfile -t STEP_ORDER < <(jq -r '.step_order[]' "$pfile" 2>/dev/null)

if [ ${#STEP_ORDER[@]} -eq 0 ]; then
# Fall back to alphabetical key order
mapfile -t STEP_ORDER < <(jq -r '.steps | keys[]' "$pfile" 2>/dev/null)
fi

# Find the first incomplete step
NEXT_STEP=""
for step in "${STEP_ORDER[@]}"; do
STEP_STATUS=$(jq -r --arg s "$step" '.steps[$s].status // "missing"' "$pfile")
case "$STEP_STATUS" in
completed|skipped|deferred) continue ;;
*) NEXT_STEP="$step"; break ;;
esac
done

if [ -n "$NEXT_STEP" ]; then
echo "$((COUNT + 1))" > "$COUNTER_FILE"
echo "Documentation workflow '$WORKFLOW_TYPE' for $TICKET is not complete. Next step: $NEXT_STEP. Continue the workflow." >&2
exit 2
fi

# All steps done — clean up counter
WORKFLOW_TYPE=$(jq -r '.workflow_type' "$PROGRESS_FILE" 2>/dev/null)

# Anti-loop guard: per-workflow counter prevents infinite blocking
COUNTER_FILE="${PROGRESS_FILE}.stop_count"
if [ -f "$COUNTER_FILE" ]; then
COUNT=$(cat "$COUNTER_FILE")
else
COUNT=0
fi
if [ "$COUNT" -ge 5 ]; then
rm -f "$COUNTER_FILE"
rm -f "$MARKER"
exit 0
fi

# Get step order from the progress file
mapfile -t STEP_ORDER < <(jq -r '.step_order[]' "$PROGRESS_FILE" 2>/dev/null)

if [ ${#STEP_ORDER[@]} -eq 0 ]; then
mapfile -t STEP_ORDER < <(jq -r '.steps | keys[]' "$PROGRESS_FILE" 2>/dev/null)
fi

# Find the first incomplete step
NEXT_STEP=""
for step in "${STEP_ORDER[@]}"; do
STEP_STATUS=$(jq -r --arg s "$step" '.steps[$s].status // "missing"' "$PROGRESS_FILE")
case "$STEP_STATUS" in
completed|skipped|deferred) continue ;;
*) NEXT_STEP="$step"; break ;;
esac
done

# No incomplete workflows found — allow stop
if [ -n "$NEXT_STEP" ]; then
echo "$((COUNT + 1))" > "$COUNTER_FILE"
echo "Documentation workflow '$WORKFLOW_TYPE' for $TICKET is not complete. Next step: $NEXT_STEP. Continue the workflow." >&2
exit 2
fi

# All steps done — clean up and allow stop
rm -f "$COUNTER_FILE"
rm -f "$MARKER"
exit 0
Loading