Receber por email
← Voltar para o catálogo

phase-triage

Phase agent that triages a Linear ticket — expands acronyms, classifies (feature/bug/docs/refactor/chore), identifies genuine blockers (a semantic second-pass over the backlog — NOT a prose scrape; CTL-838), 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. Emit

18estrelas
Atualizado há 3 meses

Ver no GitHub ↗

Como adicionar

/plugin marketplace add coalesce-labs/catalyst

O comando exato pode variar conforme o repositório. Confira o README no GitHub.

Para o autor da skill

Cole no README do seu repo

Mostra que sua skill está catalogada na Skillteca, gera backlink e tráfego rastreável.

[![Skillteca](https://www.skillteca.com.br/api/badge/phase-triage/svg)](https://www.skillteca.com.br/skills/phase-triage)

Skills relacionadas

Ver todas de Pesquisa e Web →

Alerta por categoria

Receba novas skills de Pesquisa e Web toda segunda

1 email curto, só com as skills novas de Pesquisa e Web. 4 minutos de leitura, sem spam, cancela com 1 clique.

Você confirma o email no primeiro envio. Sem spam. Cancela com 1 clique.

Ler descrição completa

phase-triage

Greenfield phase agent shipped in CTL-451 (Initiative 1 Phase 5). Reads a Linear ticket, produces a structured triage analysis, and lands the analysis as a Linear comment + label so subsequent phase agents (research, plan, …) can rely on the classification.

The skill is dispatched in two modes:

  • Production: An Opus phase agent reads triage.json placeholder fields produced by the bash

body below, then refines them with model-quality analysis (acronym expansion, judgment-of-scope, dep inference) before the comment is posted.

  • CI / test runner: The bash body alone is self-sufficient — it derives all five fields

deterministically from the ticket JSON so e2e tests run without a model call.

Both modes produce the same triage.json shape and emit the same canonical phase event.

Use-case conformance (Opus mode)

When refining the analysis, also assess whether the ticket meets the /catalyst-dev:gherkin-ticket standard: an outcome-first title (not a mechanism/file/symbol name) and a body that opens with a plain-English use case followed by tiered Gherkin acceptance criteria. If it does not conform, add one line to the triage analysis comment flagging it — e.g. _"⚠️ Ticket does not lead with a use-case (per gherkin-ticket); consider rewriting the title/opening for scannability."_ This surfaces non-conformant tickets for the operator without auto-rewriting them — triage is a documentarian, so do not edit the ticket's title or description here; flag only. (The deterministic bash body and triage.json schema are unchanged.)

/goal

/goal "I have written ${WORKER_DIR}/triage.json populated with all five fields —
       classification (feature|bug|docs|refactor|chore), estimated_scope,
       acronyms_expanded, dependencies, and a non-empty summary — refined with
       real analysis (not just the deterministic placeholders), AND posted the
       triage analysis comment to the Linear ticket, AND printed the triage.json
       path on stdout."

CTL-656: the /goal evaluator keeps the agent working until the triage is genuinely complete — every field populated and the Linear comment posted — instead of emitting the first-pass deterministic placeholder and exiting. A real blocker (unreadable ticket, a Linear 4xx on the comment) surfaces as needs-input rather than a silently-thin triage.json. (Production/Opus mode only; the CI bash body is self-sufficient and does not invoke the evaluator.)

Triage completion signal

Triage completion is recorded by two artifacts, not a Linear label: the analysis comment this skill posts to the ticket, and the local triage.json the coordinator reads (hasTriageArtifact). There is no triaged workspace label — the daemon never writes one.

Inputs

Environment:

  • TICKET — Linear identifier (e.g. CTL-451). Required; falls back to CATALYST_TICKET (the

env the dispatcher actually sets — CTL-1441).

  • WORKER_DIR — output directory for triage.json. Defaults to ${ORCH_DIR}/workers/${TICKET},

else the canonical ~/catalyst/execution-core/workers/${TICKET} — NEVER $(pwd) (a triage.json outside the worker dir is invisible to the monitor and re-triages forever; CTL-1441/CTL-1403).

  • ORCH_DIR — falls back to CATALYST_ORCHESTRATOR_DIR. CATALYST_ORCHESTRATOR_ID,

CATALYST_SESSION_ID — used for trace/span id derivation in the emitted event; optional.

Body

set -uo pipefail

# Resolve repo root from the skill location so the helper path works whether
# run from a worktree, the plugin cache, or a checked-out clone.
__PT_SCRIPT_PATH="${BASH_SOURCE[0]:-${0}}"
__PT_SKILL_DIR="$(cd "$(dirname "$__PT_SCRIPT_PATH")" && pwd 2>/dev/null || pwd)"
__PT_REPO_ROOT="${PHASE_AGENT_REPO_ROOT:-$(cd "$__PT_SKILL_DIR/../../../.." 2>/dev/null && pwd || pwd)}"
# CTL-1410 Phase A: terminal events go through the production wrapper
# (phase-agent-emit-complete) instead of the event-only phase-emit-complete.sh
# lib helper, so the phase signal file's `status` field is flipped to done/failed
# canonically in-band. Triage previously delegated that flip to the bg-only
# reclaim path, which silently no-ops under executor=sdk (bg_job_id null →
# classifyWorker "unknown" → reclaim "noop") — the strand this phase closes. The
# wrapper also owns the broker emit, the session-DB close, and completedAt.
__PT_WRAPPER="${PHASE_EMIT_WRAPPER:-${__PT_REPO_ROOT}/plugins/dev/scripts/phase-agent-emit-complete}"

if [[ ! -x "$__PT_WRAPPER" ]]; then
  echo "phase-triage: cannot find phase-agent-emit-complete wrapper at $__PT_WRAPPER" >&2
  exit 1
fi

# CTL-1397: direct-SQLite Linear reads (replica-first, loud linearis fallback).
__PT_READ_LIB="${PHASE_LINEAR_READ_HELPER:-${__PT_REPO_ROOT}/plugins/dev/scripts/lib/linear-read-replica.sh}"
if [[ ! -r "$__PT_READ_LIB" ]]; then
  echo "phase-triage: cannot find linear-read-replica.sh at $__PT_READ_LIB" >&2
  exit 1
fi
# shellcheck disable=SC1090
. "$__PT_READ_LIB"

# CTL-1441: the CATALYST_-prefixed env WINS — that is the channel
# phase-agent-dispatch actually populates (DISPATCH_ENV / worker settings), and
# it must beat any stale ambient bare TICKET/ORCH_DIR inherited from a shell or
# a prior invocation (Codex P2 on #2588). The bare names exist only via
# slash-command substitution — exactly the fragile channel that produced the
# CTL-1403 re-triage loop: a substitution miss let WORKER_DIR fall back to
# $(pwd), triage.json landed outside the worker dir, and the monitor (blind to
# it) re-dispatched forever. Bare names remain the operator-sweep fallback.
TICKET="${CATALYST_TICKET:-${TICKET:-}}"
: "${TICKET:?phase-triage: TICKET env var required}"
# CTL-1441: NEVER fall back to $(pwd) for the worker dir — a triage.json outside
# it is invisible to hasTriageArtifact and re-triages forever. Resolve ORCH_DIR
# itself to the canonical location (honoring a custom CATALYST_DIR install) and
# EXPORT it, so the phase-agent-emit-complete wrapper's signal flip — gated on
# CATALYST_ORCHESTRATOR_DIR — lands in the SAME tree as triage.json (Codex R2:
# a worker-dir-only fallback left phase-triage.json never flipping to done,
# blocking triage→research advancement).
ORCH_DIR="${CATALYST_ORCHESTRATOR_DIR:-${ORCH_DIR:-}}"
if [[ -z "$ORCH_DIR" ]]; then
  ORCH_DIR="${CATALYST_DIR:-${HOME}/catalyst}/execution-core"
  echo "phase-triage: orchestrator dir unset — defaulting to ${ORCH_DIR} (CTL-1441)" >&2
fi
export CATALYST_ORCHESTRATOR_DIR="${CATALYST_ORCHESTRATOR_DIR:-$ORCH_DIR}"

WORKER_DIR="${WORKER_DIR:-${ORCH_DIR}/workers/${TICKET}}"
mkdir -p "$WORKER_DIR"

# 1. Read ticket via direct SQL against the replica (CTL-1397 — never bare
#    linearis; the helper falls back loudly to linearis when the replica is
#    stale/absent). stderr is left visible so the loud fallback surfaces in the
#    phase log. Test stubs point CATALYST_REPLICA_DB at a nonexistent path to
#    force the deterministic linearis-stub fallback.
TICKET_JSON_FILE="$(mktemp)"
trap 'rm -f "$TICKET_JSON_FILE"' EXIT

if ! linear_read_ticket "$TICKET" > "$TICKET_JSON_FILE"; then
  "$__PT_WRAPPER" --phase triage --ticket "$TICKET" --status failed \
    --reason "linear read failed"
  exit 1
fi

if ! jq -e . "$TICKET_JSON_FILE" >/dev/null 2>&1; then
  "$__PT_WRAPPER" --phase triage --ticket "$TICKET" --status failed \
    --reason "linear read returned non-JSON output"
  exit 1
fi

TITLE="$(jq -r '.title // ""' "$TICKET_JSON_FILE")"
DESCRIPTION="$(jq -r '.description // ""' "$TICKET_JSON_FILE")"
COMBINED="${TITLE}
${DESCRIPTION}"

# 2. Derive the five triage fields deterministically (the bash fallback path).

# 2a. Classification — first-match over a small regex table. Inlined (no helper
#    function) so the body carries no bare positional parameter. At dispatch the
#    skill is rendered as a slash command (`/catalyst-dev:phase-triage <TICKET>
#    --orch-dir <PATH>`) and Claude Code substitutes bare positional tokens
#    everywhere — including inside this fenced ba
Compartilhar X LinkedIn