بنقرة واحدة
briefing-followup
Interactive walk-through of today's morning briefing. Loads the briefing markdown at
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Interactive walk-through of today's morning briefing. Loads the briefing markdown at
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Migrate a single-harness repo to the dual-harness layout so both Claude Code and Codex load the same instructions and skills — AGENTS.md as the portable canonical doc, a thin CLAUDE.md `@AGENTS.md` bridge, and a `.agents/skills` dir with a `.claude/skills` symlink onto it. Use when asked to migrate to dual-harness, make this repo work in both Claude and Codex, or for agent metadata cleanup.
Diagnose and fix Catalyst setup issues. Validates tools, database, config, OTel, direnv, and thoughts. Automatically fixes what it can — creates directories, initializes the database, sets WAL mode, runs migrations. Use for new installs, upgrades, or when something isn't working.
Fast browser automation CLI for AI agents. **ALWAYS use instead of Playwright MCP tools** for web testing, screenshots, form filling, and UI verification. Use when user says "open in browser", "check the site", "take a screenshot", "fill the form", "test the UI", or any browser interaction. Also use as a fallback when a task requires visual browser interaction that CLIs and APIs cannot handle (e.g., OAuth flows, complex dashboards, visual verification).
Goal-driven senior-engineer pipeline-unstick sweep (CTL-1176 rung 3). Given the stuck/failed/needs-human set (or ONE ticket handed by the recovery router), its GOAL is to get the pipeline MOVING again — not to fix one ticket's review findings (that is phase-remediate). It runs AFTER the eyes (diagnostician evidence) and the hands (deterministic unstuck-sweep seams) have already tried, and it CONSUMES their output from a recovery-pass.json brief rather than re-diagnosing or redoing their narrow work. It acts like a senior engineer with full tool access — it resolves merge conflicts, rebases, force-pushes, merges green PRs, and re-dispatches stalled phases AUTONOMOUSLY — and escalates to the operator ONLY for a genuine value judgment / something that degrades other functionality / a real cost-benefit trade-off / a serious architecture change / an ADR conflict. On escalation it AUTHORS the operator inbox row + the push notification (executive-voiced). Dispatched as a `claude --bg` job by phase-agent-dispatch via
Systematically pull, categorize, and address all PR review comments — code change requests, questions, and suggestions. This skill fetches comments via gh api, groups them by file, implements fixes, handles disagreements diplomatically, and pushes a single commit. You should not try to handle PR review feedback manually — this skill ensures nothing gets missed. **ALWAYS consult this skill when** the user says 'address comments', 'fix review feedback', 'handle PR comments', 'respond to reviewers', 'address review', 'review feedback', or mentions that a PR has unresolved comments or review threads. Also used by /oneshot Phase 5 to process reviewer feedback before merging.
Reference template every phase-agent skill copies (CTL-448). The leading underscore on the directory name prevents the skill loader from picking this up — it is NOT a runnable skill, only a structural template the nine real phase agents (phase-triage, phase-research, phase-plan, phase-implement, phase-verify, phase-review, phase-pr, phase-monitor-merge, phase-monitor-deploy) clone and specialize. The real phase skills MUST set `user-invocable: true` so `phase-agent-dispatch`'s `claude --bg "/catalyst-dev:phase-X ..."` slash command resolves (CTL-490).
| name | briefing-followup |
| description | Interactive walk-through of today's morning briefing. Loads the briefing markdown at |
Invoke as /catalyst-dev:briefing-followup after /catalyst-dev:morning-briefing has
produced today's briefing. The skill reads that briefing's decisions: block and walks
the user through each open decision in turn. Phase 2 wires the real action handlers
listed below; ADR-drift-specific actions ship in Phase 3 (CTL-464) and resolution
write-back to the briefing markdown ships in Phase 4 (CTL-465). See
[[2026-05-16-catalyst-phase-agent-architecture]] §Initiative 3 Phase 2.
| Flag | Meaning |
|---|---|
--date YYYY-MM-DD | Target briefing date. Default: today (UTC). |
--file PATH | Override path resolution entirely (test/dev usage). |
--status STATUS | Decision-status filter: open (default) or all. |
SCRIPT_DIR="${CLAUDE_PLUGIN_ROOT:-plugins/dev}/scripts/briefing-followup"
SESSION_SCRIPT="${CLAUDE_PLUGIN_ROOT:-plugins/dev}/scripts/catalyst-session.sh"
CATALYST_SESSION_ID=$("$SESSION_SCRIPT" start --skill "briefing-followup" \
--ticket "" --workflow "${CATALYST_SESSION_ID:-}")
export CATALYST_SESSION_ID
# Resolve briefing path. Pass --date / --file straight through from the user.
BRIEFING_PATH=$(bash "$SCRIPT_DIR/parse-briefing.sh" path "$@")
DATE=$(basename "$BRIEFING_PATH" .md)
echo "Briefing date: $DATE"
echo "Briefing path: $BRIEFING_PATH"
# Load + validate frontmatter (exits 1 with a helpful suggestion if missing,
# exits 2 if frontmatter is malformed or absent).
if ! FRONTMATTER_JSON=$(bash "$SCRIPT_DIR/parse-briefing.sh" load "$@"); then
"$SESSION_SCRIPT" end "$CATALYST_SESSION_ID" --status failed \
--reason "briefing not found or malformed"
exit 1
fi
If the briefing doesn't exist, parse-briefing.sh prints the resolved path and a
suggestion to run /catalyst-dev:morning-briefing before failing. Surface that message
verbatim to the user.
Render the open decisions as a numbered list with summary + type:
echo
echo "─── Agenda for $DATE ───"
bash "$SCRIPT_DIR/parse-briefing.sh" agenda "$@"
echo "───────────────────────"
echo
DECISION_COUNT=$(bash "$SCRIPT_DIR/parse-briefing.sh" decisions "$@" | jq 'length')
if [[ "$DECISION_COUNT" -eq 0 ]]; then
echo "No open decisions in this briefing. Nothing to follow up on."
"$SESSION_SCRIPT" end "$CATALYST_SESSION_ID" --status done \
--reason "no open decisions"
exit 0
fi
echo "$DECISION_COUNT open decision(s) to walk through."
Resolve the log path before the loop. Inside an orchestrator dispatch
($CATALYST_ORCHESTRATOR_DIR is set), prefer the orchestrator's run directory so the
record lands next to other worker artifacts; otherwise fall back to /tmp for local
runs. Phase 4 (CTL-465) of the parent plan replaces the placeholder log with a real
resolutions: write-back to the briefing markdown; for now the recorder writes both
a TSV log (Phase 1 contract) and a structured JSON file (Phase 2 contract that Phase 4
will consume).
if [[ -n "${CATALYST_ORCHESTRATOR_DIR:-}" ]]; then
LOG_DIR="$CATALYST_ORCHESTRATOR_DIR"
else
LOG_DIR="/tmp"
fi
LOG_FILE="$LOG_DIR/briefing-followup-$DATE.log"
mkdir -p "$LOG_DIR"
: > "$LOG_FILE" # truncate any prior run from today
log_response() {
local id="$1" action="$2" note="${3:-}"
printf '%s\t%s\t%s\t%s\n' \
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$id" "$action" "$note" \
>> "$LOG_FILE"
}
# Structured resolution recorder — appends to a JSON array consumed by Phase 4.
# Call after a successful action with the action name and the action's JSON output.
record_resolution() {
local id="$1" action="$2" result_json="${3:-{\}}"
bash "$SCRIPT_DIR/record-resolution.sh" \
--log-dir "$LOG_DIR" --date "$DATE" \
--id "$id" --action "$action" --result "$result_json"
}
For each open decision, present the details and the action set filtered by decision
type. The action handlers live in sibling scripts and each emit a one-line JSON result
on stdout; the skill captures that JSON and feeds it to record_resolution.
DECISIONS_JSON=$(bash "$SCRIPT_DIR/parse-briefing.sh" decisions "$@")
# Iterate over the JSON array. `jq -c .[]` emits one decision per line.
INDEX=0
TOTAL="$DECISION_COUNT"
echo "$DECISIONS_JSON" | jq -c '.[]' | while IFS= read -r dec; do
INDEX=$((INDEX + 1))
ID=$(echo "$dec" | jq -r '.id')
TYPE=$(echo "$dec" | jq -r '.type')
SUMMARY=$(echo "$dec" | jq -r '.summary')
PR_URL=$(echo "$dec" | jq -r '.pr_url // empty')
TICKET=$(echo "$dec" | jq -r '.ticket // empty')
ADR=$(echo "$dec" | jq -r '.adr // empty')
PENDING=$(echo "$dec" | jq -r '.pending // empty')
echo
echo "═══ Decision $INDEX of $TOTAL ═══"
echo "id: $ID"
echo "type: $TYPE"
echo "summary: $SUMMARY"
[[ -n "$PR_URL" ]] && echo "pr: $PR_URL"
[[ -n "$TICKET" ]] && echo "ticket: $TICKET"
[[ -n "$ADR" ]] && echo "adr: $ADR"
[[ -n "$PENDING" ]] && echo "pending: $PENDING"
echo
# A compound-engineering ADR proposal is discriminated by a non-empty
# `pending:` field (morning-briefing emits these as type=judgment_call with a
# `pending:` path, because the frontmatter schema's type enum has no
# `compound_adr` value). Check it before the type switch.
if [[ -n "$PENDING" ]]; then
echo "Actions: [a]pply ADR proposal · [e]dit then apply · [D]efer · [r]eject · [s]kip · [q]uit"
else
case "$TYPE" in
blocked_pr)
echo "Actions: [a]pprove · [r]eject · [d]efer · [o]rchestrate · [s]kip · [q]uit"
;;
adr_drift)
echo "Actions: [u]pdate ADR · [t]icket (code drift) · [D]efer · [s]kip · [q]uit"
;;
*)
echo "Actions: [a]pprove · [r]eject · [d]efer · [c]alendar · [t]icket · [o]rchestrate · [e]mail · [s]kip · [q]uit"
;;
esac
fi
done
When this skill runs in an interactive Claude Code session, present each decision as above and use the model to interpret the user's natural-language response. Map intents to action handlers as follows:
| User intent | Handler | Captures resolution? |
|---|---|---|
| approve / accept / yes / ship it | log_response "$ID" approve "$NOTE" | TSV log only |
| reject / no / dismiss | log_response "$ID" reject "$NOTE" | TSV log only |
| defer / later / skip for today | log_response "$ID" defer "$NOTE" | TSV log only |
| schedule meeting / book time / put on calendar | action-schedule.sh → record_resolution "$ID" schedule_calendar "$JSON" | TSV + JSON |
| file a ticket / open Linear issue | action-ticket.sh → record_resolution "$ID" file_ticket "$JSON" | TSV + JSON |
| dispatch orchestrator / kick off the work / run oneshot | action-orchestrate.sh --bg → record_resolution "$ID" dispatch_orchestrator "$JSON" | TSV + JSON |
| draft email / send a note to X / message Y | action-email.sh → record_resolution "$ID" draft_email "$JSON" | TSV + JSON |
| edit / update the ADR (adr_drift only) | action-adr.sh --mode update --adr-file "$ADR" → record_resolution "$ID" adr_update "$JSON" | TSV + JSON |
| file code-drift ticket / fix the code (adr_drift only) | action-adr.sh --mode ticket --adr-file "$ADR" --team CTL --summary "$SUMMARY" --drift-status "$DRIFT_STATUS" → record_resolution "$ID" adr_ticket "$JSON" | TSV + JSON |
| defer / note as intentional (adr_drift only) | action-adr.sh --mode defer --adr-file "$ADR" --reason "$REASON" → record_resolution "$ID" adr_defer "$JSON" | TSV + JSON |
apply / approve / accept the ADR proposal (decisions with a pending: path) | action-compound.sh --mode apply --pending "$PENDING" --ticket "$TICKET" → record_resolution "$ID" compound_apply "$JSON" | TSV + JSON |
edit / tweak then apply the proposal (pending:) | action-compound.sh --mode edit --pending "$PENDING" --ticket "$TICKET" → record_resolution "$ID" compound_edit "$JSON" | TSV + JSON |
defer / not yet (pending:) | action-compound.sh --mode defer --pending "$PENDING" --ticket "$TICKET" --reason "$REASON" → record_resolution "$ID" compound_defer "$JSON" | TSV + JSON |
reject / decline the proposal (pending:) | action-compound.sh --mode reject --pending "$PENDING" --ticket "$TICKET" --reason "$REASON" → record_resolution "$ID" compound_reject "$JSON" | TSV + JSON |
| skip | move on without logging | |
| quit / stop / done | break out of the loop |
When the user picks a real action handler, call the corresponding script and pass any
context the user supplied. Each handler emits one JSON line on stdout — capture it,
display the relevant field to the user, then call record_resolution so Phase 4 can
write it back to the briefing markdown.
# Example — schedule_calendar from a judgment_call decision:
RESULT=$(bash "$SCRIPT_DIR/action-schedule.sh" \
--title "$EVENT_TITLE" \
--start "$START_ISO8601" \
--end "$END_ISO8601" \
--description "$EVENT_DESCRIPTION")
STATUS=$(echo "$RESULT" | jq -r '.status')
case "$STATUS" in
scheduled) echo "Scheduled — $(echo "$RESULT" | jq -r '.html_link')" ;;
skipped) echo "Skipped: $(echo "$RESULT" | jq -r '.reason')" ;;
*) echo "Failed: $(echo "$RESULT" | jq -r '.reason // "unknown"')" ;;
esac
record_resolution "$ID" schedule_calendar "$RESULT"
log_response "$ID" schedule_calendar "$STATUS"
The same pattern applies to action-ticket.sh, action-orchestrate.sh,
action-email.sh, action-adr.sh, and action-compound.sh — only the script name and
the action label change. All handlers soft-skip cleanly when their underlying tool or
input is missing (e.g. action-compound.sh returns
{"status": "skipped", "reason": "pending proposal not found: ..."} when the proposal
has already been resolved on another machine); the returned skip JSON is captured the
same way as a success result so the resolution log faithfully records what happened.
For a compound-engineering ADR proposal (any decision carrying a pending: path —
morning-briefing emits these as type: judgment_call with a pending: field, since the
frontmatter schema's type enum has no compound_adr value), --pending is that path,
surfaced by morning-briefing from thoughts/shared/compound/pending/*.md:
# Example — apply an approved ADR proposal:
RESULT=$(bash "$SCRIPT_DIR/action-compound.sh" \
--mode apply --pending "$PENDING" --ticket "$TICKET")
STATUS=$(echo "$RESULT" | jq -r '.status')
case "$STATUS" in
applied) echo "Applied — $(echo "$RESULT" | jq -r '.target') $(echo "$RESULT" | jq -r '.adr_id') @ $(echo "$RESULT" | jq -r '.commit_sha')" ;;
deferred) echo "Deferred — proposal left pending" ;;
rejected) echo "Rejected — $(echo "$RESULT" | jq -r '.reason')" ;;
skipped) echo "Skipped: $(echo "$RESULT" | jq -r '.reason')" ;;
*) echo "Failed: $(echo "$RESULT" | jq -r '.reason // "unknown"')" ;;
esac
record_resolution "$ID" compound_apply "$RESULT"
log_response "$ID" compound_apply "$STATUS"
action-compound.sh --mode apply (and edit, which tweaks then applies) is the ONLY
writer of docs/adrs.md in the system — the ticket-compound curator only ever
proposes ADR changes; a human approves them here.
Per-decision the user may attach a one-line note (e.g., why they rejected, what the
calendar event is for). Pass it as the third argument to log_response and, when
relevant, as --description / --body to the action handler.
Before ending the session, persist the recorded resolutions into the briefing
markdown's frontmatter resolutions: block and append a "## Decisions Made
Today" section to the body. The script commits to the routine-scoped branch
(when running inside the morning-briefing routine's writable clone) and emits
a briefing.followup.complete.<date> event so the next morning's briefing
routine can surface yesterday's decisions as carryovers.
WRITEBACK_RESULT=$(bash "$SCRIPT_DIR/writeback.sh" \
--briefing "$BRIEFING_PATH" \
--resolutions "$LOG_DIR/briefing-followup-$DATE-resolutions.json" \
--date "$DATE" 2>&1)
WRITEBACK_STATUS=$(echo "$WRITEBACK_RESULT" | jq -r '.status // "failed"')
case "$WRITEBACK_STATUS" in
updated)
COMMIT_SHA=$(echo "$WRITEBACK_RESULT" | jq -r '.commit_sha // "none"')
echo "Wrote resolutions back to $BRIEFING_PATH (commit: $COMMIT_SHA)"
;;
skipped)
REASON=$(echo "$WRITEBACK_RESULT" | jq -r '.reason // "no resolutions"')
echo "Skipped write-back: $REASON"
;;
*)
echo "Write-back failed: $WRITEBACK_RESULT" >&2
;;
esac
Flags that callers may pass to writeback.sh:
| Flag | Meaning |
|---|---|
--no-commit | Update the markdown in place but do not run git commit. |
--no-push | Commit but do not push. Default in cloud routine mode is push. |
--no-event | Skip emitting briefing.followup.complete.<date>. |
--events-dir DIR | Override the event log dir (defaults to $CATALYST_DIR/events). |
The script is idempotent: re-running with the same resolutions file produces
the same markdown (the previous "## Decisions Made Today" block is stripped
before the new one is appended, and the resolutions: array is replaced
rather than amended).
echo
echo "Logged $(wc -l < "$LOG_FILE" | tr -d ' ') response(s) to $LOG_FILE"
"$SESSION_SCRIPT" end "$CATALYST_SESSION_ID" --status done \
--reason "briefing-followup completed for $DATE"
thoughts/briefings/YYYY-MM-DD.md produced by [[morning-briefing]],
validated against plugins/dev/templates/briefing-frontmatter.schema.json.$CATALYST_ORCHESTRATOR_DIR/briefing-followup-<date>.log (or /tmp/... outside
orchestrator mode), one TSV line per resolved decision:
<utc-timestamp>\t<id>\t<action>\t<note>.$LOG_DIR/briefing-followup-<date>-resolutions.json, one entry per resolution that
invoked an action handler. Each entry is
{decision_id, action, timestamp, result} where result is the JSON the handler
returned. Phase 4 (CTL-465) reads this file to write the resolutions: block back
to the briefing markdown frontmatter.Per [[2026-05-16-catalyst-phase-agent-architecture]] §Initiative 3 Phase 2, the
following sibling scripts implement the supported actions. Each emits one JSON line on
stdout and exits 0 on success or soft-skip ({"status": "skipped", "reason": "..."});
non-zero exit indicates a hard failure (handler reached but the underlying API
returned no usable result).
| Action | Script | Output JSON (success) | Soft-skip trigger |
|---|---|---|---|
| Schedule a calendar event | action-schedule.sh | {event_id, html_link, status: "scheduled"} | GOOGLE_OAUTH_ACCESS_TOKEN unset |
| File a Linear ticket | action-ticket.sh | {identifier, url, status: "filed"} | linearis not on PATH |
| Dispatch orchestrator | action-orchestrate.sh | {orchestrator_id, status: "dispatched"} | claude (or $CATALYST_DISPATCH_CLAUDE_BIN) not on PATH |
| Draft an email | action-email.sh | {draft_id, status: "drafted"} | GMAIL_OAUTH_ACCESS_TOKEN unset |
| Update ADR (adr_drift) | action-adr.sh --mode update | {adr_file, adr_id, commit_sha, status: "updated"} | $EDITOR unset, no save, or ADR not in a git repo |
| File code-drift ticket (adr_drift) | action-adr.sh --mode ticket | {identifier, url, adr_id, status: "filed"} | linearis not on PATH |
| Defer ADR drift (adr_drift) | action-adr.sh --mode defer | {adr_file, adr_id, commit_sha, status: "deferred"} | ADR not in a git repo |
Apply ADR proposal (pending:) | action-compound.sh --mode apply | {adrs_file, adr_id, target, commit_sha, status: "applied"} | proposal missing, or not in a git repo |
Edit + apply proposal (pending:) | action-compound.sh --mode edit | {adrs_file, adr_id, target, commit_sha, status: "applied"} | $EDITOR unset, proposal missing, or not in a git repo |
Defer ADR proposal (pending:) | action-compound.sh --mode defer | {pending, ticket, status: "deferred"} | proposal missing |
Reject ADR proposal (pending:) | action-compound.sh --mode reject | {pending, ticket, reason, status: "rejected"} | proposal missing |
See cma/mcp/google-calendar.md and cma/mcp/gmail.md for the OAuth setup required
to bypass the calendar / email soft-skip paths. Linear and orchestrator handlers
require no extra setup beyond having linearis / claude on PATH (the standard local
dev environment provides both).
Each handler accepts --help to print its flag set. The skill captures the JSON,
surfaces the relevant field to the user, then calls record_resolution "$ID" <action> "$JSON" so the result lands in the resolutions JSON file for Phase 4 write-back.
action-adr.sh
with --mode update|ticket|defer for the three options per the parent plan.resolutions: block via
writeback.sh. Also appends a "## Decisions Made Today" section to the body,
commits to the routine-scoped branch (routines/briefings in the cloud
routine), and emits briefing.followup.complete.<date> so the next morning's
briefing routine reads yesterday's resolutions as carryovers.pending: path surface the ticket-compound curator's queued ADR proposals
(thoughts/shared/compound/pending/*.md; emitted as type: judgment_call
since the schema enum has no compound_adr). action-compound.sh resolves
them — apply (the only writer of docs/adrs.md), edit (tweak then apply),
defer, reject — keeping ADR edits human-gated and off the critical path.