sprint-fanout
Draft per-repo sprint plans in parallel through the resolved review engine for a given PRD or goal. Each plan lands at the target repo's docs/sprints/sprint-XX/sprint-plan.md path. Use when the CEO wa
Files1
View on GitHubSKILL.md.claude/skills/sprint-fanout/SKILL.md
GitHubSprint Fanout: $ARGUMENTS
Drafts a sprint-plan.md per active repo by feeding the PRD/goal + per-repo CLAUDE.md to the configured engine in parallel. Engine resolved by scripts/agentic/resolve-review-engine.sh --role author (AUTHOR_ENGINE env → .author-engine file → default subagent) — the AUTHORING engine, separate from the review engine. For CLI engines (gemini/kimi/claude-p) the body drafts inline; for subagent/handoff the body stages per-repo prompts and the CTO drafts them via the Agent tool.
Setup + dispatch
set -uo pipefail
# CTO-HOME ANCHORING. These skills read the fleet registry, which lives in the CTO home
# — but `git rev-parse` returns whatever repo the SHELL happens to sit in. A lingering cd
# into a project repo made /handoff report "no target repos found" and made vp-review
# resolve personas against the wrong tree. $CLAUDE_PROJECT_DIR is the session's project
# root regardless of cwd drift, so it is the correct anchor; git root is the fallback.
CTO_HOME_REQUIRED=1
# >>> CTO-HOME ANCHOR (canonical — sync with: bash scripts/cto/lint-skills.sh --apply) >>>
# Fleet state lives in the CTO home, and neither obvious anchor is reliable alone:
# `git rev-parse` returns whatever repo the SHELL sits in, so one lingering cd into a dev
# repo silently retargets the skill; and $CLAUDE_PROJECT_DIR was observed EMPTY in skill
# shells (2026-08-13) — /handoff then resolved the registry against a dev repo and refused
# with an error that blamed the registry rather than the anchoring. So: try every anchor,
# VALIDATE each candidate before accepting it, and when none holds either fail naming the
# real cause or fall back explicitly and say so.
# Skills that read the fleet registry set CTO_HOME_REQUIRED=1 before this block.
#
# NO POSITIONAL PARAMETERS IN THIS BLOCK — no dollar-1, no dollar-2, not even awk's dollar-1.
# (Spelled out rather than written literally, because the rewrite described next hits comments
# too: a literal example here would itself be replaced with argument text.)
#
# This block is embedded in SKILL.md, and the skill runtime rewrites every dollar-followed-by-
# digits token in the FILE with the invocation's arguments before the shell ever sees it. The
# rewrite is document-wide: no awareness of code fences, no escape (a backslash before it does
# not protect it — it renders empty), and it applies inside comments and string literals alike.
# A shell function's own positional parameter is therefore replaced by argument text, and when
# the skill is invoked with no arguments it is replaced by nothing at all.
#
# The first version of this block used positional parameters and was consequently broken from
# the day it shipped: _cto_is_home tested a garbage path on every candidate, so the anchor
# NEVER matched, and every skill carrying it silently fell back to the cwd repo — precisely the
# confident wrong answer it was written to prevent. Found 2026-09-01, six weeks after it
# shipped, because the fallback usually landed on the right repo by luck.
#
# Candidates are passed in named variables instead. Brace-wrapped positionals happen to survive
# the rewrite; do not use them either — the next person to write the bare form reintroduces the
# bug, and it fails silently. Guarded by lint CHECK E.
_CTO_REJECTED=""
_CTO_CAND=""
_cto_is_home() { # in: _CTO_CAND · appends to _CTO_REJECTED
[ -n "$_CTO_CAND" ] && [ -f "$_CTO_CAND/.cto/projects.yaml" ] || return 1
# The PUBLIC template ships a placeholder registry (/Users/yourname/repos/my-project).
# Accepting it resolves cleanly and then reports a fleet of repos that do not exist —
# a confident wrong answer, which is worse than the error it replaced. Observed live:
# four dev repos still point .cto-path at the template, so this is the real path.
if grep -q '/Users/yourname/' "$_CTO_CAND/.cto/projects.yaml" 2>/dev/null; then
_CTO_REJECTED="$_CTO_REJECTED $_CTO_CAND (placeholder registry — the unconfigured template)"
return 1
fi
return 0
}
# Sets _CTO_FOUND rather than printing: a $(…) capture runs in a SUBSHELL, so the rejected-
# candidate diagnostics collected by _cto_is_home would be discarded exactly when they are
# needed — on the failure path.
_CTO_FOUND=""
_cto_walk() { # in: _CTO_START · out: _CTO_FOUND
_d="$_CTO_START"
while [ -n "$_d" ] && [ "$_d" != "/" ]; do
_CTO_CAND="$_d"; _cto_is_home && { _CTO_FOUND="$_d"; return 0; }
if [ -f "$_d/.cto-path" ]; then
_p="$(tr -d '[:space:]' < "$_d/.cto-path")"
_CTO_CAND="$_p"; _cto_is_home && { _CTO_FOUND="$_p"; return 0; }
fi
# `--` because a mis-rendered candidate can begin with a dash, and `dirname --repos=x`
# exits with "illegal option" instead of walking. That message was the only outward sign
# of the rewrite bug above for two weeks, and it was read as cosmetic noise.
_d="$(dirname -- "$_d")"
done
return 1
}
ROOT=""
for _c in "${CTO_HOME:-}" "${CTO_REPO:-}" "${CLAUDE_PROJECT_DIR:-}"; do
[ -n "$_c" ] || continue
_CTO_CAND="$_c"; _cto_is_home && { ROOT="$_c"; break; }
done
if [ -z "$ROOT" ]; then _CTO_START="$PWD"; if _cto_walk; then ROOT="$_CTO_FOUND"; fi; fi
if [ -z "$ROOT" ] && [ -f "$HOME/.cto/home" ]; then
_p="$(tr -d '[:space:]' < "$HOME/.cto/home")"
_CTO_CAND="$_p"; _cto_is_home && ROOT="$_p"
fi
if [ -z "$ROOT" ]; then
if [ "${CTO_HOME_REQUIRED:-0}" = "1" ]; then
echo "ERROR: no CTO home found — no directory containing .cto/projects.yaml." >&2
echo " \$CTO_HOME='${CTO_HOME:-}' \$CTO_REPO='${CTO_REPO:-}' \$CLAUDE_PROJECT_DIR='${CLAUDE_PROJECT_DIR:-}'" >&2
echo " walked up from: $PWD" >&2
# Report the permanent anchor's CONTENT, not just that the remedy exists. When the anchor
# is set correctly and the skill still fails, the error must not keep recommending it —
# that loop cost a full diagnosis cycle on 2026-09-01.
if [ -f "$HOME/.cto/home" ]; then
echo " ~/.cto/home is SET and was tested: '$(tr -d '[:space:]' < "$HOME/.cto/home")'" >&2
echo " It did not validate, so the remedy below is already applied and is NOT the fix." >&2
echo " Check that path holds .cto/projects.yaml, then suspect the skill rendering itself." >&2
else
echo " This is an ANCHORING failure, not a missing registry. Run the skill from the CTO" >&2
echo " home, or fix it permanently: echo /path/to/<project>-cto > ~/.cto/home" >&2
fi
[ -n "$_CTO_REJECTED" ] && { echo " rejected candidates:" >&2; printf '%s\n' "$_CTO_REJECTED" >&2; }
exit 1
fi
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
echo "NOTE: no CTO home found; using the current repo ($ROOT)." >&2
fi
CTO_REGISTRY="$ROOT/.cto/projects.yaml"
cd "$ROOT" || { echo "ERROR: cannot enter $ROOT" >&2; exit 1; }
# <<< CTO-HOME ANCHOR <<<
if [ ! -f "$CTO_REGISTRY" ]; then
echo "ERROR: fleet registry not found at $CTO_REGISTRY"
echo " This skill must run from the CTO home (the repo holding .cto/projects.yaml)."
echo " If you are in a project repo, the session project dir is wrong — reopen in the CTO home."
exit 1
fi
# zsh doesn't word-split unquoted parameter expansion by default. Skills run
# in whatever shell the harness uses (zsh on macOS). This makes `for tok in $ARGS`
# behave consistently in both bash and zsh.
[ -n "${ZSH_VERSION:-}" ] && setopt sh_word_split
# Parse arguments
ARGS="$ARGUMENTS"
PRD_PATH=""
REPOS_FILTER=""
SPRINT=""
DRY_RUN=0
EXPECT=""
for tok in $ARGS; do
# FLAG RULE (2026-10-19): value flags take `--x v` OR `--x=v`; an unknown flag or a stray
# extra word REFUSES. `--repos a,b` used to print "Unknown flag", drop the list, and fan out
# to every registered repo — a parse error must stop the skill, never widen it.
if [ -n "$EXPECT" ]; then
case "$EXPECT" in repos) REPOS_FILTER="$tok" ;; sprint) SPRINT="$tok" ;; esac
EXPECT=""; continue
fi
case "$tok" in
--repos=*) REPOS_FILTER="${tok#--repos=}" ;;
--repos) EXPECT=repos ;;
--sprint=*) SPRINT="${tok#--sprint=}" ;;
--sprint) EXPECT=sprint ;;
--dry-run) DRY_RUN=1 ;;
--*) echo "ERROR: unknown flag '$tok' — refusing rather than guessing. Usage: see argument-hint." >&2; exit 2 ;;
*) if [ -z "$PRD_PATH" ]; then PRD_PATH="$tok"; else echo "ERROR: unexpected extra argument '$tok' (path already '$PRD_PATH') — refusing." >&2; exit 2; fi ;;
esac
done
[ -n "$EXPECT" ] && { echo "ERROR: --$EXPECT needs a value — refusing." >&2; exit 2; }
if [ -z "$PRD_PATH" ] || [ ! -f "$PRD_PATH" ]; then
echo "ERROR: provide a path to a PRD or goal file as the first argument"
echo "Usage: /sprint-fanout <path> --sprint <slug-or-NN> [--repos r1,r2] [--dry-run]"
exit 1
fi
# Resolve sprint number — default to next available across repos, but for v1
# require explicit --sprint if not provided
# The sprint name is REQUIRED for a real run: the old default "XX-TBD" wrote plans into a
# sprint-XX-TBD dir in every repo. A slug or a number both work; a leading "sprint-" is
# stripped so `--sprint sprint-foo` does not become sprint-sprint-foo.
SPRINT="${SPRINT#sprint-}"
if [ -z "$SPRINT" ]; then
if [ "$DRY_RUN" -eq 1 ]; then SPRINT="XX-TBD"
else echo "ERROR: --sprint <slug-or-NN> is required (it names docs/sprints/sprint-<it>/ in every repo). Refusing." >&2; exit 2; fi
fi
# Output dir for staging + summary
OUT=/tmp/cto/sprint-fanout-$(date +%Y%m%d-%H%M%S)
mkdir -p "$OUT"
echo "Staging dir: $OUT"
echo "PRD/goal: $PRD_PATH"
echo "Sprint: $SPRINT"
echo "Dry-run: $DRY_RUN"
echo ""
# Read active repos
python3 - <<PYEOF > "$OUT/repos.tsv"
import re
with open('$CTO_REGISTRY') as f:
text = f.read()
blocks = re.split(r'(?=- name:)', text)
filter_set = set("$REPOS_FILTER".split(',')) if "$REPOS_FILTER" else None
for b in blocks:
m = re.search(r'- name:\s*(\S+)', b)
p = re.search(r'path:\s*(\S+)', b)
a = re.search(r'active:\s*(\S+)', b)
if not m or not p:
continue
name = m.group(1)
path = p.group(1)
is_active = (a is None) or a.group(1).lower() not in ('false','no','0')
if not is_active:
continue
if filter_set and name not in filter_set:
continue
print(f"{name}\t{path}")
PYEOF
NUM_REPOS=$(wc -l < "$OUT/repos.tsv" | tr -d ' ')
echo "Active target repos: $NUM_REPOS"
cat "$OUT/repos.tsv" | sed 's/^/ /'
echo ""
if [ "$NUM_REPOS" -eq 0 ]; then
echo "ERROR: no active repos matched"
exit 1
fi
# Resolve the engine (shared resolver) — configurable per CEO 2026-06-19, not hardcoded gemini.
RESOLVER="$ROOT/scripts/agentic/resolve-review-engine.sh"
# AUTHORING engine, not the review engine (2026-10-19): drafting a plan needs the repo in front of
# it, which only a subagent has. AUTHOR_ENGINE env > <cto-home>/.author-engine > subagent.
ENGINE=$( [ -x "$RESOLVER" ] && REPO_ROOT="$ROOT" "$RESOLVER" --role author || echo subagent ) || true
case "$ENGINE" in claude) ENGINE=claude-p;; dual) ENGINE=gemini;; none|"") ENGINE=subagent;; esac
[ "$ENGINE" = "claude-p-blocked" ] && { echo "ENGINE=claude-p refused (metered; set REVIEW_ALLOW_METERED=1)"; exit 3; }
[ "$ENGINE" = "claude-p" ] && [ "${REVIEW_ALLOW_METERED:-0}" != "1" ] && { echo "ERROR: claude-p is metered; set REVIEW_ALLOW_METERED=1."; exit 1; }
echo "ENGINE=$ENGINE"
echo ""
echo "Staging per-repo prompts$( [ "$ENGINE" = gemini ] || [ "$ENGINE" = claude-p ] && echo ' + firing in parallel' )..."
date
PRD_CONTENT=$(cat "$PRD_PATH")
while IFS=$'\t' read -r REPO_NAME REPO_PATH; do
REPO_CLAUDE_MD=""
[ -f "$REPO_PATH/CLAUDE.md" ] && REPO_CLAUDE_MD=$(head -200 "$REPO_PATH/CLAUDE.md")
PROMPT_FILE="$OUT/${REPO_NAME}-prompt.md"
cat > "$PROMPT_FILE" <<EOF
You are the Dev Team for the repo: $REPO_NAME at $REPO_PATH.
TASK: Draft a sprint plan for sprint $SPRINT scoped to your repo's portion of the following PRD/goal.
PRD / GOAL:
---
$PRD_CONTENT
---
YOUR REPO'S CLAUDE.md (first 200 lines for context):
---
$REPO_CLAUDE_MD
---
OUTPUT REQUIREMENTS:
- Produce a sprint plan in markdown
- Title: "# Sprint $SPRINT — <one-line summary>"
- Sections: Scope, Tasks (numbered list with file paths + effort estimates), Test Plan, Acceptance Criteria, Dependencies, Risks
- Keep tasks specific to THIS repo only. Do not propose work in other repos.
- If the PRD doesn't apply to this repo, output: "NO_SCOPE_FOR_THIS_REPO" and explain in one line why.
- Do NOT write code in the plan. Plan only.
EOF
OUT_PLAN="$OUT/${REPO_NAME}-plan.md"
if [ "$ENGINE" = "gemini" ] || [ "$ENGINE" = "kimi" ] || [ "$ENGINE" = "agy" ] || [ "$ENGINE" = "codex" ] || [ "$ENGINE" = "claude-p" ]; then
# Stdin-only invocation — every CLI engine reads the prompt on stdin.
if [ "$ENGINE" = "gemini" ]; then RUNCMD="cat '$PROMPT_FILE' | gemini"
elif [ "$ENGINE" = "kimi" ]; then RUNCMD="cat '$PROMPT_FILE' | '$ROOT/scripts/agentic/openrouter-chat.sh'"
elif [ "$ENGINE" = "agy" ]; then RUNCMD="cat '$PROMPT_FILE' | '$ROOT/scripts/agentic/agy-exec.sh'"
elif [ "$ENGINE" = "codex" ]; then RUNCMD="cat '$PROMPT_FILE' | '$ROOT/scripts/agentic/codex-exec.sh'" # UNTESTED (2026-07-02)
else RUNCMD="cat '$PROMPT_FILE' | claude -p --max-turns 1"; fi
# TIMEOUT MUST STAY UNDER THE HARNESS SHELL CAP. A skill body's shell is reaped at
# roughly two minutes; an engine call with a longer internal timeout dies WITH its
# parent, leaving a 0-byte plan in the staging dir and no status file. Keep this
# below the cap so every call resolves to a real SUCCESS or ERROR while the body
# still lives. (Queued redesign: detach via nohup + status file and poll from "Your
# task", the way the subagent path already avoids the cap entirely.)
FANOUT_TIMEOUT="${FANOUT_TIMEOUT:-90}"
TIMEOUT_BIN=""
command -v gtimeout >/dev/null 2>&1 && TIMEOUT_BIN="gtimeout"
[ -z "$TIMEOUT_BIN" ] && command -v timeout >/dev/null 2>&1 && TIMEOUT_BIN="timeout"
echo " -> firing $ENGINE for $REPO_NAME (timeout ${FANOUT_TIMEOUT}s)..."
( if [ -n "$TIMEOUT_BIN" ]; then "$TIMEOUT_BIN" "$FANOUT_TIMEOUT" sh -c "$RUNCMD" > "$OUT_PLAN" 2> "$OUT/${REPO_NAME}.log"
else sh -c "$RUNCMD" > "$OUT_PLAN" 2> "$OUT/${REPO_NAME}.log"; fi
RC=$?
# A zero exit with no output is NOT success. An engine that dies, refuses, or is
# reaped can exit 0 having written nothing; marking that SUCCESS is what lets an
# empty file land in a sprint dir and be read as a drafted plan.
SZ=$(wc -c < "$OUT_PLAN" 2>/dev/null || echo 0)
if [ "$RC" -ne 0 ]; then echo "ERROR($RC)" > "$OUT/${REPO_NAME}.status"
elif [ "${SZ:-0}" -lt 200 ]; then echo "FAILED-EMPTY(${SZ}b)" > "$OUT/${REPO_NAME}.status"
else echo "SUCCESS" > "$OUT/${REPO_NAME}.status"; fi ) &
else
# subagent / handoff: a skill body can't draft via an Agent/window — the CTO does (Your task).
echo " -> staged prompt for $REPO_NAME (engine=$ENGINE: CTO drafts via the Agent tool)"
echo "PENDING" > "$OUT/${REPO_NAME}.status"
fi
done < "$OUT/repos.tsv"
wait
if [ "$ENGINE" = "gemini" ] || [ "$ENGINE" = "kimi" ] || [ "$ENGINE" = "agy" ] || [ "$ENGINE" = "codex" ] || [ "$ENGINE" = "claude-p" ]; then
echo "All $ENGINE calls done at $(date)"
else
# Subagent model (model policy 2026-09-25): every Agent call passes `model:` explicitly. An
# Agent call that omits it inherits the PARENT's model — a CTO on the stretch model then runs
# every review/draft on it too. The alias comes from the lane pin (.cto/devteam-model;
# `default` maps to the policy's lane family, .cto/model-budget.yaml).
AGENT_MODEL="$(bash "$ROOT/scripts/cto/resolve-devteam-model.sh" --alias 2>/dev/null)"; AGENT_MODEL="${AGENT_MODEL:-opus}"
echo "AGENT_MODEL=$AGENT_MODEL # pass model: \"$AGENT_MODEL\" on EVERY Agent call below"
echo "DISPATCH=$ENGINE — prompts staged; the CTO drafts each plan via the Agent tool (see Your task)."
fi
echo ""
# Report per-repo result
echo "=== RESULTS ==="
printf "%-32s %-12s %s\n" "REPO" "STATUS" "PLAN PATH"
echo "------------------------------------------------------------------------"
while IFS=$'\t' read -r REPO_NAME REPO_PATH; do
STATUS=$(cat "$OUT/${REPO_NAME}.status" 2>/dev/null || echo "UNKNOWN")
PLAN="$OUT/${REPO_NAME}-plan.md"
SIZE=$(wc -c < "$PLAN" 2>/dev/null || echo "0")
# The NO_SCOPE_FOR_THIS_REPO sentinel only marks whole-plan-skip if it appears
# in the FIRST 5 lines (i.e. the gemini response opens with it). Plans that
# mention it inline as a per-PRD callout ("PRD-011 NO_SCOPE_FOR_THIS_REPO —
# belongs to other repo") are valid plans, not skips.
if head -5 "$PLAN" 2>/dev/null | grep -q "NO_SCOPE_FOR_THIS_REPO"; then NO_SCOPE=1; else NO_SCOPE=0; fi
if [ "$NO_SCOPE" -gt 0 ]; then
STATUS="SKIPPED"
NOTE="(no scope for this repo)"
else
NOTE="$SIZE bytes"
fi
printf "%-32s %-12s %s %s\n" "$REPO_NAME" "$STATUS" "$PLAN" "$NOTE"
done < "$OUT/repos.tsv"
IS_CLI=0
case "$ENGINE" in gemini|kimi|agy|codex|claude-p) IS_CLI=1 ;; esac
if [ "$IS_CLI" -eq 1 ]; then
N_ALL=$(grep -c . "$OUT/repos.tsv"); N_OK=$(cat "$OUT"/*.status 2>/dev/null | grep -cx SUCCESS)
if [ "${N_OK:-0}" -eq 0 ]; then
echo ""
echo "FAILED: no engine call produced a plan (0 of $N_ALL; engine=$ENGINE). Nothing was written."
echo " Per-repo logs: $OUT/<repo>.log. Fix the engine or re-run with AUTHOR_ENGINE=subagent."
exit 3
fi
[ "$N_OK" -lt "$N_ALL" ] && echo "PARTIAL: $N_OK of $N_ALL repos produced a plan; only those are written."
fi
if [ "$IS_CLI" -eq 0 ]; then
echo ""
echo "NOT WRITTEN YET: engine=$ENGINE — the plans are drafted by the CTO session (see Your task),"
echo " which writes each one to its dest. Nothing has been written to any repo by this body."
elif [ "$DRY_RUN" -eq 1 ]; then
echo ""
echo "DRY-RUN: plans staged at $OUT but NOT written to per-repo sprint dirs."
echo "Review the plans, then re-run without --dry-run to land them."
else
echo ""
echo "Writing plans to per-repo sprint dirs..."
while IFS=$'\t' read -r REPO_NAME REPO_PATH; do
STATUS=$(cat "$OUT/${REPO_NAME}.status" 2>/dev/null || echo "UNKNOWN")
[ "$STATUS" != "SUCCESS" ] && continue
PLAN="$OUT/${REPO_NAME}-plan.md"
# The NO_SCOPE_FOR_THIS_REPO sentinel only marks whole-plan-skip if it appears
# in the FIRST 5 lines (i.e. the gemini response opens with it). Plans that
# mention it inline as a per-PRD callout ("PRD-011 NO_SCOPE_FOR_THIS_REPO —
# belongs to other repo") are valid plans, not skips.
if head -5 "$PLAN" 2>/dev/null | grep -q "NO_SCOPE_FOR_THIS_REPO"; then NO_SCOPE=1; else NO_SCOPE=0; fi
[ "$NO_SCOPE" -gt 0 ] && continue
DEST_DIR="$REPO_PATH/docs/sprints/sprint-$SPRINT"
mkdir -p "$DEST_DIR"
cp "$PLAN" "$DEST_DIR/sprint-plan.md"
echo " wrote $DEST_DIR/sprint-plan.md"
done < "$OUT/repos.tsv"
fi
Your task as CTO
First check the ENGINE= / DISPATCH= lines in the script output.
If DISPATCH=subagent (default) or handoff: the plans were NOT drafted yet — you draft them now. For each repo=… prompt=… plan=… dest=… repo, launch one Task/Agent call in parallel (single message), passing model: "<AGENT_MODEL>" from the output above on every call (omitting it inherits your model), instructing it to read the staged prompt file ($OUT/<repo>-prompt.md), draft the sprint plan exactly per the prompt's OUTPUT REQUIREMENTS (markdown only, this-repo-only, NO_SCOPE_FOR_THIS_REPO if it doesn't apply), and write it to both $OUT/<repo>-plan.md and the dest …/docs/sprints/sprint-<NN>/sprint-plan.md (unless --dry-run, then staging only). When all return, proceed to synthesize. (Engine is bright-line clean: in-session Agent = subscription pool.)
If the CLI engine ran (gemini/kimi/agy/codex/claude-p): (note: codex is ⚠️ untested — sanity-check its output before trusting it the way you would gemini/kimi) plans are already drafted + landed above.
Then, regardless of engine, synthesize:
- Headline: "Drafted N plans, M skipped (no scope), K failed"
- For each non-skipped plan: read it briefly via Read tool, note the top 2-3 tasks and the effort estimate
- Cross-repo synthesis: flag any tasks across repos that seem to duplicate work or that imply an interface contract that wasn't in the PRD
- Recommend next step to CEO:
- If plans look good:
/vp-review docs/sprints/sprint-NN/sprint-plan.mdper repo, then/sprint-accept - If plans need revision: identify which repos and what to fix, suggest re-running with refined prompt
- If plans look good:
Present a tight summary to the CEO. Brief. Sign as: — CTO