phase-plan
Phase agent for the plan step of the 9-phase orchestrator pipeline (CTL-450). Wraps /catalyst-dev:create-plan and produces thoughts/shared/plans/<date>-<ticket>.md, then emits phase.plan.complete.<ticket>. Reads the prior research document from thoughts/shared/research/ as its prior-phase artifact. Spawned via plugins/dev/scripts/phase-agent-dispatch, which invokes it via slash command — hence `user-invocable: true`.
Best use case
phase-plan is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Phase agent for the plan step of the 9-phase orchestrator pipeline (CTL-450). Wraps /catalyst-dev:create-plan and produces thoughts/shared/plans/<date>-<ticket>.md, then emits phase.plan.complete.<ticket>. Reads the prior research document from thoughts/shared/research/ as its prior-phase artifact. Spawned via plugins/dev/scripts/phase-agent-dispatch, which invokes it via slash command — hence `user-invocable: true`.
Teams using phase-plan should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/phase-plan/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How phase-plan Compares
| Feature / Agent | phase-plan | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Phase agent for the plan step of the 9-phase orchestrator pipeline (CTL-450). Wraps /catalyst-dev:create-plan and produces thoughts/shared/plans/<date>-<ticket>.md, then emits phase.plan.complete.<ticket>. Reads the prior research document from thoughts/shared/research/ as its prior-phase artifact. Spawned via plugins/dev/scripts/phase-agent-dispatch, which invokes it via slash command — hence `user-invocable: true`.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# phase-plan
You are the **plan phase agent**. You run inside `claude --bg` and own a single
responsibility: produce `thoughts/shared/plans/<date>-<ticket>.md` that meets the
schema enforced by [[create-plan]], then emit `phase.plan.complete.<ticket>` and
exit. Built on the [[_phase-agent-template]] contract.
Plans are TDD-structured: Tests First (Red) → Implementation (Green) → Refactor →
Success Criteria for every phase, with success criteria split into Automated and
Manual Verification.
## Prelude
```bash
set -uo pipefail
: "${CATALYST_ORCHESTRATOR_DIR:?required (set by phase-agent-dispatch)}"
: "${CATALYST_ORCHESTRATOR_ID:?required}"
: "${CATALYST_PHASE:?required}"
: "${CATALYST_TICKET:?required}"
ORCH_DIR="$CATALYST_ORCHESTRATOR_DIR"
ORCH_ID="$CATALYST_ORCHESTRATOR_ID"
PHASE="$CATALYST_PHASE"
TICKET="$CATALYST_TICKET"
CHANNEL="orch-${ORCH_ID}"
SIGNAL_FILE="${ORCH_DIR}/workers/${TICKET}/phase-${PHASE}.json"
[[ -f "$SIGNAL_FILE" ]] || { echo "phase-${PHASE}: signal file missing" >&2; exit 1; }
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/../../.." && pwd)}"
# Join comms channel (best-effort).
COMMS="${PLUGIN_ROOT}/scripts/catalyst-comms"
[[ -x "$COMMS" ]] || COMMS="$(command -v catalyst-comms 2>/dev/null || true)"
if [[ -n "$COMMS" ]]; then
"$COMMS" join "$CHANNEL" --as "$TICKET" \
--capabilities "phase-${PHASE}: ${TICKET}" \
--orch "$ORCH_ID" --parent orchestrator --ttl 3600 >/dev/null 2>&1 || true
"$COMMS" send "$CHANNEL" "phase-plan started" --as "$TICKET" --type info \
--orch "$ORCH_ID" >/dev/null 2>&1 || true
fi
# Start session.
SESSION_SCRIPT="${PLUGIN_ROOT}/scripts/catalyst-session.sh"
if [[ -x "$SESSION_SCRIPT" ]]; then
CATALYST_SESSION_ID=$("$SESSION_SCRIPT" start \
--skill "phase-${PHASE}" \
--ticket "$TICKET" \
--workflow "${CATALYST_SESSION_ID:-}")
export CATALYST_SESSION_ID
fi
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
TMP="${SIGNAL_FILE}.tmp.$$"
# CTL-496: persist catalystSessionId so orchestrate-roll-usage --phase can
# attribute cost to the right session_metrics row.
jq --arg ts "$TS" --arg sid "${CATALYST_SESSION_ID:-}" '
.status = "running"
| .updatedAt = $ts
| if $sid != "" then .catalystSessionId = $sid else . end
' "$SIGNAL_FILE" > "$TMP" \
&& mv "$TMP" "$SIGNAL_FILE"
# Prior-phase artifact: the research document. Dispatcher gates this via glob;
# we re-read it here so we can fail loudly on race. Two-step match (CTL-494)
# mirrors the dispatcher: lowercase-tail form first, wider *${TICKET}*.md
# fallback with nocaseglob to also accept the canonical create-plan
# filename convention (uppercase ticket + descriptive suffix).
shopt -s nullglob
RESEARCH_MATCHES=( thoughts/shared/research/*-${TICKET,,}.md )
if [[ ${#RESEARCH_MATCHES[@]} -eq 0 ]]; then
RESEARCH_MATCHES=( thoughts/shared/research/*${TICKET}*.md )
if [[ ${#RESEARCH_MATCHES[@]} -eq 0 ]]; then
shopt -s nocaseglob
RESEARCH_MATCHES=( thoughts/shared/research/*${TICKET}*.md )
shopt -u nocaseglob
fi
fi
shopt -u nullglob
if [[ ${#RESEARCH_MATCHES[@]} -eq 0 ]]; then
echo "phase-plan: research document missing for $TICKET" >&2
"${PLUGIN_ROOT}/scripts/phase-agent-emit-complete" \
--phase "$PHASE" --ticket "$TICKET" --status failed \
--reason "prior_artifact_missing:research_doc"
exit 1
fi
RESEARCH_DOC="${RESEARCH_MATCHES[-1]}"
```
<!-- Linear status is written by the coordinator (CTL-558): the execution-core
scheduler / orchestrate-phase-advance applies the mapped state on every
committed phase transition. The phase agent no longer transitions Linear. -->
## /goal
```
/goal "I have written thoughts/shared/plans/<date>-${ticket-lower}.md containing the
full plan with: Overview, Phase 1..N sections each with Tests First (Red),
Implementation (Green), Refactor, and Success Criteria (Automated + Manual).
I have printed the path on stdout."
```
## Work block
Generate the plan by **invoking the canonical skill** rather than reimplementing it.
The body of [[create-plan]] is the single source of truth.
Phase agents run inside `claude --bg` — there is no interactive user. Pass the
research document as the input and operate non-interactively:
1. Read `$RESEARCH_DOC` to understand the problem.
2. Invoke `/catalyst-dev:create-plan` against the research document. When that skill
asks for clarifications, answer from the research document; if the research
document is silent on a point, default to the most conservative reasonable choice
and record the assumption in the plan's "Open questions" section.
3. Confirm the artifact exists. Two-step match (CTL-494) — try lowercase-tail
first, then the wider `*${TICKET}*.md` pattern with `nocaseglob` fallback
so canonical create-plan filenames (uppercase ticket + descriptive
suffix) are accepted alongside the phase-plan prose convention:
```bash
shopt -s nullglob
PLAN_MATCHES=( thoughts/shared/plans/*-${TICKET,,}.md )
if [[ ${#PLAN_MATCHES[@]} -eq 0 ]]; then
PLAN_MATCHES=( thoughts/shared/plans/*${TICKET}*.md )
if [[ ${#PLAN_MATCHES[@]} -eq 0 ]]; then
shopt -s nocaseglob
PLAN_MATCHES=( thoughts/shared/plans/*${TICKET}*.md )
shopt -u nocaseglob
fi
fi
shopt -u nullglob
[[ ${#PLAN_MATCHES[@]} -gt 0 ]] || {
"${PLUGIN_ROOT}/scripts/phase-agent-emit-complete" \
--phase "$PHASE" --ticket "$TICKET" --status failed \
--reason "plan_doc_not_written"
exit 1
}
PLAN_DOC="${PLAN_MATCHES[-1]}"
```
If [[create-plan]] runs into a question it cannot resolve from the research
document, post a `question` comms message to the orchestrator with `--re <msg_id>`
correlation; do not block waiting for a reply — record the assumption and proceed.
### Inbox check (CTL-749)
After `/catalyst-dev:create-plan` Task returns, check for mid-flight context updates from the human:
1. If `${ORCH_DIR}/workers/${TICKET}/inbox.jsonl` exists and is non-empty, read it fully.
2. Parse each JSONL line — entries have `kind: "comment"` or `kind: "description_changed"`.
3. For each entry, decide:
- **Absorb and continue**: the update is additive context (clarification, extra constraints,
"also handle X") — fold it into your working context and continue. Post a brief reply comment
acknowledging the update (one sentence).
- **Pause and replan**: the update fundamentally changes scope or invalidates the current
approach — emit `failed` with `reason: "mid_flight_replan_needed"` via
`${PLUGIN_ROOT}/scripts/phase-agent-emit-complete` and post the reason to Linear as a
comment before exiting.
4. After reading, archive processed entries:
```bash
[[ -f "${ORCH_DIR}/workers/${TICKET}/inbox.jsonl" ]] && \
mv "${ORCH_DIR}/workers/${TICKET}/inbox.jsonl" \
"${ORCH_DIR}/workers/${TICKET}/inbox.processed-$(date +%s).jsonl" || true
```
5. If no inbox file or it is empty, continue normally.
## End block
```bash
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
TMP="${SIGNAL_FILE}.tmp.$$"
jq --arg ts "$TS" --arg doc "$PLAN_DOC" \
'.updatedAt = $ts | .artifact = $doc' \
"$SIGNAL_FILE" > "$TMP" && mv "$TMP" "$SIGNAL_FILE"
```
Mirror the phase output to Linear as a single comment (CTL-632). Fail-open
and idempotent via the per-phase marker file. Uniquely-named fence so the
e2e test can extract just this block.
```bash phase-plan-mirror
LINEAR_MIRROR_MARKER="${ORCH_DIR}/workers/${TICKET}/.linear-mirror-${PHASE}"
if [[ ! -e "${LINEAR_MIRROR_MARKER}" ]]; then
PLAN_TITLE="$(awk '/^# /{print; exit}' "${PLAN_DOC}" | sed 's/^# //')"
PLAN_PHASES_COUNT="$(grep -c '^## Phase ' "${PLAN_DOC}" || true)"
: "${PLAN_PHASES_COUNT:=0}"
MIRROR_BODY="$(cat <<EOF
**Phase Plan**
- **Document**: \`${PLAN_DOC}\`
- **Title**: ${PLAN_TITLE:-_untitled_}
- **Phases**: ${PLAN_PHASES_COUNT}
- **Research backlink**: \`${RESEARCH_DOC}\`
_Posted automatically by phase-plan (CTL-632)._
EOF
)"
MIRROR_FOOTER=""
if [[ -n "${PLUGIN_ROOT:-}" && -x "${PLUGIN_ROOT}/scripts/lib/phase-mirror-footer.sh" ]]; then
MIRROR_FOOTER="$("${PLUGIN_ROOT}/scripts/lib/phase-mirror-footer.sh" --orch-dir "${ORCH_DIR}" --ticket "${TICKET}" --phase "${PHASE}" 2>/dev/null || true)"
fi
[[ -n "${MIRROR_FOOTER}" ]] && MIRROR_BODY="${MIRROR_BODY}
${MIRROR_FOOTER}"
COMMENT_POST="${CATALYST_COMMENT_POST_HELPER:-${PLUGIN_ROOT}/scripts/lib/linear-comment-post.sh}"
if [[ ! -x "$COMMENT_POST" ]]; then COMMENT_POST="$(command -v linear-comment-post.sh 2>/dev/null || true)"; fi
if [[ -n "$COMMENT_POST" && -x "$COMMENT_POST" ]] && "$COMMENT_POST" "${TICKET}" "${MIRROR_BODY}" >/dev/null 2>&1; then
: > "${LINEAR_MIRROR_MARKER}"
else
echo "phase-plan: linear-comment-post failed (continuing)" >&2
fi
fi
```
```bash
"${PLUGIN_ROOT}/scripts/phase-agent-emit-complete" \
--phase "$PHASE" --ticket "$TICKET" --status complete
[[ -n "$COMMS" ]] && "$COMMS" done "$CHANNEL" --as "$TICKET" >/dev/null 2>&1 || true
```
## Failure handling
```bash
"${PLUGIN_ROOT}/scripts/phase-agent-emit-complete" \
--phase "$PHASE" --ticket "$TICKET" --status failed \
--reason "<short reason>"
[[ -n "$COMMS" ]] && "$COMMS" send "$CHANNEL" \
"phase-plan failed: <reason>" --as "$TICKET" --type attention \
--orch "$ORCH_ID" >/dev/null 2>&1 || true
exit 1
```Related Skills
weekly-plan
Set next week's priorities
daily-plan
Generate PM daily plan with context
validate-plan
Validate that implementation plans were correctly executed. **ALWAYS use when** the user says 'validate the plan', 'check if the plan was implemented correctly', 'verify the implementation', or after completing /implement-plan to confirm all phases were properly executed and success criteria met.
phase-verify
Phase agent for the verify step of the 9-phase orchestrator pipeline (CTL-450). NEW skill — has no canonical wrapper. Runs read-only adversarial verification against the implement-phase diff: tsc, tests, lint, security scan, reward-hacking scan, code review, test coverage, silent-failure hunt. Writes ${ORCH_DIR}/workers/<TICKET>/verify.json then emits phase.verify.complete.<ticket>. Reads phase-implement.json as its prior-phase artifact. NEVER writes application code — only test files allowed. Spawned via phase-agent-dispatch via slash command — hence `user-invocable: true`.
phase-triage
Phase agent that triages a Linear ticket — expands acronyms, classifies (feature/bug/docs/refactor/chore), identifies dependencies, estimates scope, writes triage.json, and posts a triage analysis comment to Linear. Triage completion is signaled by that comment plus the local triage.json — there is no `triaged` label. Emits phase.triage.complete.<TICKET> on success and phase.triage.failed.<TICKET> on error. Dispatched by the phase-agent orchestrator (CTL-452) via slash command — `user-invocable: true` so the dispatcher's `claude --bg "/catalyst-dev:phase-triage ..."` resolves.
phase-review
Phase agent for the review step of the 9-phase orchestrator pipeline (CTL-450). Wraps the /review skill (gstack) — explicitly skips /ultrareview per user decision. Reads verify.json from the prior phase, runs /review against the diff, writes ${ORCH_DIR}/workers/<TICKET>/review.json, and creates a remediation commit for any HIGH-severity finding that has a deterministic fix. Emits phase.review.complete.<ticket>. Spawned via phase-agent-dispatch via slash command — hence `user-invocable: true`.
phase-research
Phase agent for the research step of the 9-phase orchestrator pipeline (CTL-450). Wraps /catalyst-dev:research-codebase and produces thoughts/shared/research/<date>-<ticket>.md, then emits phase.research.complete.<ticket>. Reads triage.json from the worker dir as its prior-phase artifact. Spawned via plugins/dev/scripts/phase-agent-dispatch, which invokes it via slash command — hence `user-invocable: true`.
phase-remediate
Phase-agent that fixes a failing verify verdict so the pipeline self-heals instead of stalling to needs-human (CTL-653). Reads `${ORCH_DIR}/workers/<ticket>/verify.json`, fixes the `findings[]` (every severity:"high" plus the regression_risk drivers) directly via Edit/Write, commits the remediation, and emits `phase.remediate.complete.<ticket>`. The scheduler's router then re-dispatches `verify` to re-check (the verify⇄remediate cycle, cap 3). Dispatched as a `claude --bg` job by `phase-agent-dispatch`, which invokes it via slash command — hence `user-invocable: true`.
phase-pr
Phase-agent wrapper that opens the pull request after implementation completes (CTL-449 Initiative 1 Phase 3). Delegates to `/catalyst-dev:create-pr` (which already auto-runs `describe-pr` and transitions Linear to `inReview`), then writes the PR number + URL into the phase signal file so the downstream `phase-monitor-merge` agent can read it without re-querying GitHub. Dispatched as a `claude --bg` job by `phase-agent-dispatch`, which invokes it via slash command — hence `user-invocable: true`.
phase-monitor-merge
Phase-agent that watches the open PR through to merge (CTL-449 Initiative 1 Phase 3). Lifts the active listen loop from the legacy `oneshot` Phase 5 body: event-driven wait on `catalyst-events wait-for`, inline resolution of CI fix-ups, bot review threads, and BEHIND rebases, then `gh pr merge --squash --delete-branch` when the PR reaches CLEAN, then transitions Linear to `done`. Dispatched as a `claude --bg` job by `phase-agent-dispatch`, which invokes it via slash command — hence `user-invocable: true`.
phase-monitor-deploy
Phase agent that watches the post-merge deployment for a ticket. Reads the merge SHA from phase-monitor-merge.json (the signal file phase-monitor-merge writes after `gh pr merge` confirms via REST), subscribes via `catalyst-events wait-for` to deploy events on that SHA, then delegates a live verification check to the /canary skill (gstack). Emits phase.monitor-deploy.complete.<TICKET> on canary success, phase.monitor-deploy.failed.<TICKET> on deploy or canary failure, and phase.monitor-deploy.skipped.<TICKET> when no deploy event arrives before the timeout. Dispatched by the phase-agent orchestrator (CTL-452) via slash command — `user-invocable: true` so the dispatcher's `claude --bg "/catalyst-dev:phase-monitor-deploy ..."` resolves.
phase-implement
Phase-agent wrapper that drives TDD implementation from an approved plan (CTL-449 Initiative 1 Phase 3). Reads `thoughts/shared/plans/*-<ticket>.md`, delegates the red→green→refactor cycle to `/catalyst-dev:implement-plan`, commits each plan phase as it lands, and transitions the Linear ticket to `inProgress`. Dispatched as a `claude --bg` job by `phase-agent-dispatch`, which invokes it via slash command — hence `user-invocable: true`.