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`.

9 stars

Best use case

phase-research is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

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`.

Teams using phase-research 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

$curl -o ~/.claude/skills/phase-research/SKILL.md --create-dirs "https://raw.githubusercontent.com/coalesce-labs/catalyst/main/plugins/dev/skills/phase-research/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/phase-research/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How phase-research Compares

Feature / Agentphase-researchStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

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`.

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.

Related Guides

SKILL.md Source

# phase-research

You are the **research phase agent**. You run inside `claude --bg` and own a single
responsibility: produce `thoughts/shared/research/<date>-<ticket>.md` that meets the
schema enforced by [[research-codebase]], then emit
`phase.research.complete.<ticket>` and exit. Built on the [[_phase-agent-template]]
contract.

You are a documentarian, not a critic. Document what EXISTS. No suggestions for
improvements. No architectural critiques.

## 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 the shared 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-research started" --as "$TICKET" --type info \
    --orch "$ORCH_ID" >/dev/null 2>&1 || true
fi

# Start a catalyst-session for cost/token instrumentation.
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

# Mark signal file running + persist catalystSessionId (CTL-496).
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
TMP="${SIGNAL_FILE}.tmp.$$"
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"

# Read the prior-phase artifact (triage.json). The dispatcher already gated this,
# so the file MUST exist — fail loudly if not (race condition / out-of-band run).
TRIAGE_FILE="${ORCH_DIR}/workers/${TICKET}/triage.json"
if [[ ! -f "$TRIAGE_FILE" ]]; then
  echo "phase-research: prior triage.json missing at $TRIAGE_FILE" >&2
  "${PLUGIN_ROOT}/scripts/phase-agent-emit-complete" \
    --phase "$PHASE" --ticket "$TICKET" --status failed \
    --reason "prior_artifact_missing:triage.json"
  exit 1
fi
TRIAGE_SUMMARY=$(jq -r '.summary // .classification // ""' "$TRIAGE_FILE" 2>/dev/null || echo "")
```

<!-- 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/research/<date>-${ticket-lower}.md with valid
       frontmatter, a 'Summary' section, a 'Findings' section containing at least 10
       file:line references, and a 'References' section linking related thoughts/plans.
       I have printed the path on stdout."
```

Replace `<date>` with `$(date -u +%Y-%m-%d)` and `<ticket-lower>` with the lowercased
`$TICKET` (`ctl-450` for `CTL-450`).

## Work block

Conduct the research by **invoking the canonical skill** rather than reimplementing
it. The body of [[research-codebase]] is the single source of truth for how research
is performed.

1. Read the Linear ticket via `linearis issues read $TICKET --with-attachments` to
   get the title, description, and any linked plan reference.
2. Read the triage summary from `$TRIAGE_FILE` to understand classification and
   surfaced dependencies.
3. Invoke `/catalyst-dev:research-codebase` against the ticket's research question.
   That skill spawns parallel sub-agents, synthesizes findings, and writes the
   document. Do not duplicate its logic.
4. Confirm the artifact exists at the expected path before continuing.
   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-research prose convention:
   ```bash
   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
   RESEARCH_DOC="${RESEARCH_MATCHES[-1]:-}"
   [[ -n "$RESEARCH_DOC" && -f "$RESEARCH_DOC" ]] || {
     "${PLUGIN_ROOT}/scripts/phase-agent-emit-complete" \
       --phase "$PHASE" --ticket "$TICKET" --status failed \
       --reason "research_doc_not_written"
     exit 1
   }
   ```

If [[research-codebase]] hits a question it cannot resolve, post a `question` comms
message and continue with the best-effort answer — do not block the pipeline.

### Inbox check (CTL-749)

After `/catalyst-dev:research-codebase` 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
# Update the signal file with the artifact path so downstream phases can find it.
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
TMP="${SIGNAL_FILE}.tmp.$$"
jq --arg ts "$TS" --arg doc "$RESEARCH_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
(a failed Linear post must not break the phase) and idempotent (re-walks
after orchestrator restart skip already-posted phases via a marker file).
The fence is uniquely named so the e2e test can extract just this block.

```bash phase-research-mirror
LINEAR_MIRROR_MARKER="${ORCH_DIR}/workers/${TICKET}/.linear-mirror-${PHASE}"
if [[ ! -e "${LINEAR_MIRROR_MARKER}" ]]; then
  RESEARCH_TITLE="$(awk '/^# /{print; exit}' "${RESEARCH_DOC}" | sed 's/^# //')"
  RESEARCH_SUMMARY="$(awk '/^## Summary/{flag=1; next} /^## /{flag=0} flag && NF' "${RESEARCH_DOC}" | head -5)"
  MIRROR_BODY="$(cat <<EOF
**Phase Research**

- **Document**: \`${RESEARCH_DOC}\`
- **Title**: ${RESEARCH_TITLE:-_untitled_}

<details>
<summary>Summary preview</summary>

${RESEARCH_SUMMARY}

</details>

_Posted automatically by phase-research (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-research: linear-comment-post failed (continuing)" >&2
  fi
fi
```

```bash
# Emit phase-complete event, close signal file, end catalyst-session.
"${PLUGIN_ROOT}/scripts/phase-agent-emit-complete" \
  --phase "$PHASE" --ticket "$TICKET" --status complete

# Final comms send.
[[ -n "$COMMS" ]] && "$COMMS" done "$CHANNEL" --as "$TICKET" >/dev/null 2>&1 || true
```

## Failure handling

Any non-recoverable failure (turn cap hit, [[research-codebase]] returns no document,
prior-artifact gate fails after dispatcher race):

```bash
"${PLUGIN_ROOT}/scripts/phase-agent-emit-complete" \
  --phase "$PHASE" --ticket "$TICKET" --status failed \
  --reason "<short human-readable reason>"
[[ -n "$COMMS" ]] && "$COMMS" send "$CHANNEL" \
  "phase-research failed: <reason>" --as "$TICKET" --type attention \
  --orch "$ORCH_ID" >/dev/null 2>&1 || true
exit 1
```

The orchestrator's Phase 4 monitor receives the failed event via the broker
`phase_lifecycle` route and dispatches a fix-up phase agent (one retry, then
escalates to user via `attention`).

Related Skills

user-research-synthesis

9
from coalesce-labs/catalyst

Turn user interviews into actionable insights. Advanced synthesis techniques and frameworks.

research-curate

9
from coalesce-labs/catalyst

Walk thoughts/shared/research/ and thoughts/shared/plans/, score each doc's staleness, regenerate INDEX.md, and append LLM-surfaced contradictions to CONTRADICTIONS.md (append-only). Source docs are never modified. Classification: current (age<90d AND refs valid), needs-review (age>=90d OR broken refs), likely-stale (age>=180d AND no recent activity). Inventory is deterministic; contradiction detection runs one LLM call per cluster (CTL-467 + CTL-468 / Initiative 4 Phase 1+2).

research-codebase

9
from coalesce-labs/catalyst

Conduct comprehensive codebase research using parallel sub-agents. **ALWAYS use when** the user asks to 'research', 'investigate', 'explore the codebase', 'how does X work', 'find out about', or needs deep analysis of how existing code is structured. Produces a research document in thoughts/shared/research/ with file:line references.

phase-verify

9
from coalesce-labs/catalyst

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

9
from coalesce-labs/catalyst

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

9
from coalesce-labs/catalyst

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-remediate

9
from coalesce-labs/catalyst

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

9
from coalesce-labs/catalyst

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-plan

9
from coalesce-labs/catalyst

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`.

phase-monitor-merge

9
from coalesce-labs/catalyst

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

9
from coalesce-labs/catalyst

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

9
from coalesce-labs/catalyst

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`.