focus

star 31

Session-start triage + routing. Reads the same `scripts/catchup-state.js` aggregator that `/catchup` uses, ranks 3–5 next-move candidates across five tiers (Resume / Blocked / Drift / Hot / Momentum), presents a numbered menu with a "Recommended" call that cites the rule, then routes the user pick to the right downstream skill (`/issue-triage`, `/ci-triage`, `/runtime-triage`, `/pr-review`, `/done`, `/log`, `/tech-debt-scan`) with a short session-context preamble so the reasoning that selected the candidate carries into the investigation. Read-only — never `Edit` / `Write`; routing is by prompt instruction, not by invoking other skills. Distinct from `/catchup` (state briefing, one suggestion) and `/ramp` (area-scoped deep-dive). Use when starting a fresh session and you want help picking *which* of several plausible next moves to take; for a state-only briefing prefer `/catchup`.

jellyrock By jellyrock schedule Updated 6/7/2026

name: focus description: Session-start triage + routing. Reads the same scripts/catchup-state.js aggregator that /catchup uses, ranks 3–5 next-move candidates across five tiers (Resume / Blocked / Drift / Hot / Momentum), presents a numbered menu with a "Recommended" call that cites the rule, then routes the user pick to the right path: in-thread domain triage (/issue-triage, /ci-triage, /runtime-triage, /pr-review) for investigations; the matching recipe skill (/new-setting, /new-migration, …) for "add an X" work (the recipe IS the plan); /done / /log for captures; an ad-hoc quick-fix → plan-file → /sonnet hand-off for procedural one-offs with no recipe skill; and /start-project / /resume-project <slug> for project-shaped work. Read-only apart from the one plan file the quick-fix route writes — routing is by printing the exact invocation, not by invoking other skills. Distinct from /catchup (state briefing, one suggestion) and /ramp (area-scoped deep-dive). Use when starting a fresh session and you want help picking which of several plausible next moves to take, and the right path to take it down; for a state-only briefing prefer /catchup. model: opus effort: high

/focus — triage the next move

Contract

Goal. Turn a cold session-start into the right next move — rank what's actionable from observed state, surface a short menu with a recommended call, then route the chosen pick down the path that fits its shape. JellyRock has a rich domain-skill set, so /focus is a tier-ranking router: it ranks 3–5 candidates across five tiers (Resume / Blocked / Drift / Hot / Momentum), presents a numbered menu with a "Recommended" line that cites the ranking rule, and on the user's pick routes to the right path. Most picks route in-thread to a domain skill — an investigation (CI / crash-log / issue / PR-comments) to the matching triage skill, an "add a setting / migration / api-version / translation" to the matching recipe skill (the committed recipe IS the better plan — don't re-derive it), a stale signal or cursor to /done / /log. Two routes are heavier: an ad-hoc procedural one-off with no dedicated recipe skill becomes a written, agreed-upon plan saved to disk and handed off to a fresh /sonnet session — planning is judgment-heavy and context-heavy while implementation is procedural, so splitting the two across sessions is cheaper (the implementation session runs at the Sonnet tier via /sonnet), more accurate (no context bleed between "what to do" and "how to do it"), and leaves a re-runnable artifact; and project-shaped work (spans multiple sessions, crosses a phase boundary, or carries a decision worth recording) routes to /start-project (new) or /resume-project <slug> (forward motion on a tracked project). This skill ships at the judgment-grade tier — ranking the menu, making the recommended call, and (on the quick-fix route) interrogating the plan fork-by-fork are all judgment, and the cost of guessing wrong is a plan or a route that sends the next session into the wrong shape.

Inputs. $ARGUMENTS: optional --area=<name> to scope triage to one subsystem (mirrors /ramp). Valid areas are the list scripts/catchup-state.js accepts (components, components/video, components/data, source, source/api, source/utils, tests, locale, scripts). Omit for global triage. If you already know the route — a specific project to resume, a new project to start, or a one-off you can plan directly — invoke the explicit command (/resume-project <slug>, /start-project, or plan mode) and skip /focus; its value is the triage half, the ranking call, and the clean route.

Outputs.

  • A numbered triage menu (3–5 candidates, each carrying its tier label in brackets and ending in the exact invocation to run if picked) plus a Recommended line that names the pick and cites the ranking rule that chose it. The user replies with a number, free-form context, skip, or none before anything executes.
  • On a triage / capture / recipe pick: a 1–5 line session-context preamble surfaced in the conversation, then the exact downstream invocation — /focus routes by printing the invocation, not by invoking the other skill itself.
  • On the quick-fix route only: a written plan file saved at .claude/plans/focus-YYYY-MM-DD-<slug>.md with the standard sections — Context (the why — which banner / followup / pick surfaced the work), Approach (single recommended approach, not a menu), Critical files (scope envelope as a table), Verification (numbered checklist with JellyRock's canonical npm wrappers), What this plan deliberately does NOT do (scope boundaries) — rendered as the user's approval preview via the plan-mode dialog (the user edits the markdown inline before approving), followed by a copy-paste /sonnet <plan-path> hand-off block naming the saved file.
  • On a scaffold / resume route: once the route is confirmed, the matching lifecycle invocation (/start-project / /resume-project <slug>) is named for the user to run — /focus writes no plan file in this case.
  • A Captures for /log tail listing any followups / decisions surfaced during triage or research that aren't yet journaled. Omit if nothing surfaced; do not pad.

Success criteria.

  • The menu ranks candidates by the fixed tier rubric (Resume > Blocked > Drift > Hot > Momentum; ties break on recency), caps each category so no one tier crowds the menu, and never lets a Tier-1/Tier-2 blocker fall off the end.
  • A Recommended line is always present — even on close calls — and cites the rule that picked the winner ("blockers > drift > hot > momentum"), so the reasoning is transparent and overridable.
  • Each pick is routed to exactly the path that fits its shape: investigation → in-thread domain triage; "add an X" → the recipe skill (not a re-derived plan); stale signal/cursor → /done / /log; ad-hoc procedural one-off with no recipe → quick-fix plan → /sonnet; project-shaped → /start-project / /resume-project <slug> (resume names the slug).
  • On the quick-fix route: every design choice with two or more viable answers is resolved via AskUserQuestion (never free-form prose), plan content is drafted INTERNALLY and first rendered in the plan-mode dialog (the sole preview surface), the Approach is a single recommended path, and the Verification section names JellyRock's canonical npm wrappers (npm run validate / test:scripts / test:tdd / lint), never bare runners.
  • The implementation tier is classified by /focus (default /sonnet; the judgment-grade tier only when a plan step needs in-flight judgment the plan can't pre-specify) and reflected in the hand-off block — the choice is made here, where the full plan context lives, not punted to the fresh session.
  • Routing happens by printing the exact downstream invocation; /focus writes nothing except the one plan file on the quick-fix route, and never auto-invokes a route before the user picks it.
  • The Recommended line and the routing preamble cite specific state — a named handoff, a failed CI run, a stale signal slug, a hot issue number — never "it feels like the next thing."

Failure modes to avoid.

  • Re-deriving a plan for work that already has a recipe skill. "Add a setting / migration / api-version / translation" routes to the committed recipe (/new-setting, …) — the recipe IS the plan. Writing a fresh quick-fix plan for it duplicates a maintained artifact and drifts from it. The quick-fix → plan → /sonnet route earns its keep ONLY on ad-hoc procedural work with no dedicated skill.
  • Rendering plan content to conversation before EnterPlanMode (quick-fix route). Any user-facing text before the EnterPlanMode call that contains plan markdown headers, a critical-files table, numbered verification steps, before/after code blocks, or any phrasing equivalent to "I'll show it inline first" / "does this look right before plan mode" is a structural failure — it double-gates approval and doubles token cost. Self-check before sending any text during plan-drafting: would it be load-bearing without the plan-mode dialog? If yes, you're re-rendering; delete it.
  • Free-form questions in place of AskUserQuestion (quick-fix route). Conversation prose like "want me to Y?" / "two confirmations before plan mode" / "which would you prefer" is the same anti-pattern with different wording. The trigger is mechanical: if you catch yourself drafting text containing ?, let me know, should we Y — STOP and convert it into an AskUserQuestion call, or decide it yourself if it's genuinely not a fork.
  • Guessing on forks. The No-fabrication rule applies in spades to plan content because the plan becomes the spec /sonnet executes against. A guessed api-version, a guessed BrightScript component path, a guessed scope boundary ships as a fact and corrupts the implementation. When the answer isn't load-bearing-obvious from the research, ask via AskUserQuestion.
  • Auto-implementing in the same session (quick-fix route). The planning / implementation context-split is load-bearing. Once the plan lands and the hand-off block is printed, STOP — don't start executing the plan in this session.
  • Punting the impl-model choice downstream. Deciding the implementation tier (/sonnet vs the judgment-grade tier) is /focus's call, made here where the full plan context lives — NOT deferred to the fresh session, which has less context and tends to default to the expensive tier by reflex. A fork-free "apply these edits, run these gates" plan is /sonnet; reserve the judgment-grade tier only for a step the plan cannot pre-specify (a perf rewrite shaped by a live run, an unknown-root-cause debug).
  • Bare-runner verification steps (quick-fix route). A Verification step that names bsc / vitest / markdownlint-cli2 directly instead of JellyRock's npm wrapper (npm run validate / test:scripts / test:tdd / lint) breaks on a clean machine — the wrappers encode the bsc project files, lint chain, and Roku-deploy conventions — and pushes the implementing agent into environmental yak-shaving. Spell out the wrapper.
  • Skipping the hand-off block (quick-fix route). The closing /sonnet <plan-path> copy-paste block is the load-bearing artifact for the cross-session pattern. Without it the user has to construct the implementation-session prompt themselves; always print it.
  • Writing journals or invoking downstream skills. Apart from the one quick-fix plan file, /focus is read-only: it doesn't bump frontmatter, doesn't create handoffs, doesn't auto-invoke /ci-triage / /start-project / /resume-project. It routes by printing the exact invocation for the user to run; the downstream skill owns the writes that follow.
  • Pre-executing a route before the pick. Always end the turn after the menu. Don't start an investigation, write a plan, or invoke a lifecycle command before the user replies — Step 5 is the user's call.

When NOT to use.

  • You're mid-task and want to know "what's the state of X?" → answer directly or use /catchup; don't run an opus triage cycle.
  • You're context-switching into a specific subsystem after a long time away → use /ramp <area> for the deep-dive briefing, then /focus --area=<name> if you also need to triage what's actionable there.
  • Same session, you already ran /focus and picked a route — don't re-run; follow the route you picked.
  • You already know the route. If you know you want to resume a specific project, run /resume-project <slug> directly; if the work is a new tracked project, run /start-project; if you have a one-off you can plan yourself, use plan mode. /focus's value is the triage half — it points at the right command, it doesn't replace the direct entry points.
  • The aggregator failed (_errors populated for git or every major section) → fix the underlying breakage first; /focus on broken state produces noise.

Implementation

The Contract above is the stable surface. The steps below are JellyRock's concrete triage + routing mechanics — the scripts/catchup-state.js reads, the tier rubric tuned to JellyRock's domain skills, and the per-pick routing. Edit these freely; the Contract is the audit target.

When you've been away for a day or a week and multiple things look actionable — a pending handoff, red CI, a stale signal row, a high-engagement bug, a procedural cleanup, a project to resume — /focus ranks them against a fixed rubric, surfaces a 3–5 item menu, makes a "Recommended" call you can override, then routes the chosen path. Distinct from siblings:

  • /catchup (sonnet, read-only briefing) — "What's the state of the world?" Single-shot, one "Suggested next" line. Use when you want context, not a deliberation.
  • /ramp <area> (sonnet, area-scoped briefing) — "Re-load me into this subsystem." Same aggregator with --area=; not a triage menu.
  • /focus (opus, triage + routing) — "Help me pick the right next move from several candidates, and the right path to take it down."

Step 1 — Pull state in one call

The aggregator at scripts/catchup-state.js returns one JSON document with every dynamic-state input the triage needs. One Bash call:

node scripts/catchup-state.js --pretty                # global
node scripts/catchup-state.js --pretty --area=<name>  # area-scoped

Top-level keys: meta, git, prs, issues, ci, handoffs, progress, signals, decisions, tech_debt, docs_stale, _errors. If _errors[<section>] is populated, surface it as a Tier 3 (Drift / schema-broken) candidate rather than letting the section silently disappear.

The aggregator surfaces handoffs + progress.md but not docs/projects/, so project-shaped resumes don't appear as a menu tier — they route via a free-form redirect in Step 5 (the user names the project, or none → "start/resume a project"). That's deliberate: wiring docs/projects/ into the aggregator is a separate enhancement, not part of routing.

Step 2 — Rank candidates into tiers

Build the candidate list from the JSON. Higher tier wins on tie; ties within a tier break on recency (most recent first). Categories:

Tier Category Inputs Cap "Why now" template
1. Resume Pending handoff handoffs.pending[] (already sorted recent-first) 2 "Investigation paused mid-flight — resume from .claude/handoffs/<name> (d ago)"
2. Blocked Failed CI on this branch ci.current_branch_runs[] where conclusion != 'success' 1 "CI run <name> ()"
2. Blocked PR review requested prs.review_requested[] 1 "# waiting on your review since "
3. Drift Stale signal signals.rows[] where stale=true 1 row with digest set (stable, open triage digest): "<slug>: open release-triage digest #<digest.number>"; else "<slug>: upstream moved "
3. Drift Stale progress.md progress.days_since > 7 && progress.commits_since > 0 1 "Cursor is d stale, commit(s) since"
3. Drift Schema-broken journal _errors.progress / _errors.signals (non-null) 1 "Parser error in : "
4. Hot High-engagement bug issues.high_engagement_bugs[] (top 1 by comments) 1 "# (<comments> comments, last touched <relative>)"</td> </tr> <tr> <td>4. Hot</td> <td>Recent bug report</td> <td><code>issues.recent_bug_reports[]</code> (top 1, last 7d)</td> <td>1</td> <td>"#<N> — <title> (filed <relative>)"</td> </tr> <tr> <td>5. Momentum</td> <td>Top tech-debt slug</td> <td><code>tech_debt.top_3[0]</code></td> <td>1</td> <td>"[<severity>] <code><slug></code> — <issue_oneline>"</td> </tr> </tbody></table> <p><strong>Selection rules:</strong></p> <ul> <li>At most 5 candidates total in the menu. Sort by tier ascending; take the first 5.</li> <li>Always include at least one Tier 1 or Tier 2 item if any exists — never let blockers fall off the end.</li> <li>Per-category caps prevent one hot category from crowding the menu.</li> <li>Empty everywhere? Skip directly to Step 5's "nothing-urgent" branch and stop.</li> </ul> <h3>Step 3 — Present the menu</h3> <p>Format short. Bullets carry the tier label in brackets for instant pattern recognition. Each line ends with the <strong>exact</strong> invocation the user should run if they pick it.</p> <pre><code class="language-markdown">**Triage — pick one:** 1. [Resume] `<handoff-name>` — Investigation paused mid-flight (<age>d ago). Suggested route: read `.claude/handoffs/<handoff-name>` and follow the sibling INVESTIGATION.md for that skill. 2. [Blocked] CI run `<name>` — <conclusion> (<relative>). Suggested route: `/ci-triage <run-id>` 3. [Hot] #<N> — <title> (<comments> comments, last touched <relative>). Suggested route: `/issue-triage <N>` 4. [Drift] `<slug>` signal — upstream moved <ack> → <latest>. Suggested route: `/done <slug>` after reviewing the changelog 5. [Momentum] [<severity>] `<slug>` — <issue_oneline>. Suggested route: read [tech-debt.md#<slug>](../../../docs/architecture/tech-debt.md#<slug>), then conversation **Recommended:** #<N> (<one-sentence reason that cites the rule — e.g., "blockers > drift > hot > momentum">). Which? Reply with the number, "skip" to dump full state via `/catchup`, "none" if nothing here lines up, or name a project / one-off you'd rather pick up. </code></pre> <p><strong>Recommended-line rules:</strong></p> <ul> <li><strong>Always include</strong>, even on close calls. The whole point is to make the triage call.</li> <li>When the top two candidates share a tier, acknowledge the closeness: "Recommended: #3 narrowly — #2 is also reasonable if you'd rather close the loop on the in-flight handoff first."</li> <li>Cite the <em>rule</em> that picked the winner ("blockers > drift > hot > momentum"; "Tier 1 in-flight resume beats Tier 4 hot bug"). Transparent reasoning is overridable reasoning.</li> </ul> <p>Then <strong>end the turn</strong>. Do not pre-execute any route — Step 5 is the user's call.</p> <h3>Step 4 — Wait for the pick</h3> <p>User replies with a number, <code>skip</code>, <code>none</code>, or free-form ("3, but also remind me about #5"; "none — I want to start the offline-mode project"). The next turn handles routing.</p> <p><strong>Don't</strong> call <code>AskUserQuestion</code> here — the menu format is richer than the 4-option cap and the user may want to add free-form context to their pick. Just stop after Step 3 and let the user reply.</p> <h3>Step 5 — Route with session context</h3> <p>When the user replies with a pick, branch on its shape:</p> <h4>Triage-shaped pick (Resume / Blocked CI / Blocked PR-review / Hot bug)</h4> <p>Surface a 3–5 line <strong>session-context preamble</strong> in the conversation, then name the exact downstream invocation. Example for "#2, the CI failure":</p> <pre><code class="language-markdown">**Session context for `/ci-triage <run-id>`:** - Other candidates considered: <one-line each, top 2 alternates> - Relevant journal state: <progress.md `## Currently running` summary if non-null; otherwise "(cursor unset)"> - In-flight handoffs in this area: <count + most-recent name, or "(none)"> Now invoke `/ci-triage <run-id>` to start the investigation. The triage skill will write its own handoff packet to `.claude/handoffs/`. </code></pre> <p>The downstream triage skill runs in the <strong>same conversation</strong>, so the preamble is naturally part of its context — no handoff-packet schema extension needed.</p> <h4>Recipe-shaped pick ("add a setting / migration / api-version / translation")</h4> <p>These don't appear as menu candidates (the aggregator doesn't surface them), but a free-form pick / redirect often is one. Route to the committed recipe skill — <strong>the recipe IS the plan; don't re-derive it into a quick-fix plan file:</strong></p> <pre><code class="language-markdown">That's a recipe-skill job — `/new-setting` (or `/new-migration` / `/new-api-version` / `/translation-add`) carries the numbered steps + the npm verify gates for exactly this. Run `/new-setting <name>`; it's a better plan than anything I'd re-derive here. </code></pre> <h4>Capture-shaped pick (Stale signal / Stale progress / Schema-broken journal)</h4> <p>Give the exact invocation with prefilled args. One line of context (why this matters now), then hand back. Routing depends on the row:</p> <ul> <li><strong><code>jellyfin-server-stable</code> with <code>row.digest</code> set</strong> — a candidate-bearing release left an open triage digest (clean releases auto-close theirs and never reach this tier). Route to <code>/server-upgrade</code>; that triage is what advances the anchor. Example:</li> </ul> <pre><code class="language-markdown">Release-triage digest #<row.digest.number> is open — the server-upgrade tracker found changes that intersect code we ship. Run `/server-upgrade` to investigate each candidate against real app usage (it edits the digest with verdicts and files sub-issues). Note: a mechanically-clean release would have auto-closed its digest, so this one genuinely needs eyes. </code></pre> <ul> <li><strong>Any other stale row</strong> (RC, roku-os) — the close-loop string compare fired. Example:</li> </ul> <pre><code class="language-markdown">Upstream moved <latest_acknowledged> → <latest_upstream>. Review the release notes first, then: - If no JellyRock change needed: `/done <slug>` (bumps `latest_acknowledged` and clears the stale flag). - If a JellyRock change is needed: `/log signal <slug>` to flip `status` to `action_pending` first. </code></pre> <h4>Project-shaped pick (resume an active project / start new tracked work)</h4> <p>Forward motion on an existing tracked project, or new work that meets the project test (spans multiple sessions, crosses a phase boundary, or carries a decision worth recording). These arrive via a free-form pick / <code>none</code> redirect (they're not a menu tier — see Step 1). Recommend-then-confirm, then <strong>name</strong> the lifecycle invocation (don't auto-invoke):</p> <ul> <li><strong>Resume</strong> — forward motion on an <code>active</code> project in <code>docs/projects/</code>. Confirm the slug, then: "That's forward motion on the <code><slug></code> project — run <code>/resume-project <slug></code>; it loads the PLAN + context and binds the session to <code>/end-session</code>."</li> <li><strong>Scaffold</strong> — new project-shaped work. "That's project-shaped (multi-session / phase boundary / decision worth recording) — run <code>/start-project <slug></code> to scaffold the PLAN and Charter."</li> </ul> <p>When the route is genuinely ambiguous (a one-off that <em>might</em> grow), default to the quick-fix route below and let the work prove it needs tracking — a stub project can be scaffolded later. Don't over-scaffold.</p> <h4>Ad-hoc procedural one-off (no recipe skill, not project-shaped) — quick-fix plan route</h4> <p>A momentum / tech-debt cleanup or a free-form procedural task with no dedicated recipe skill and not big enough to track as a project. This is the only route that writes. Hand it to a fresh <code>/sonnet</code> session via a plan file — see Step 6.</p> <h4>"skip"</h4> <p>Print the standard <code>/catchup</code> briefing format inline (or tell the user "invoke <code>/catchup</code> for the full state template — <code>/focus</code> is the triage cousin"). Don't auto-invoke <code>/catchup</code> — let the user pick.</p> <h4>"none"</h4> <p>Acknowledge: "Nothing here lines up. Last commit <code><sha></code>, tree <clean | dirty>. Pick something off <code>tech_debt.top_3</code> (<list>), name a project to resume, or call it a day." Exit.</p> <h4>Empty-everywhere from Step 2</h4> <p>Skip the menu entirely: "Nothing urgent. Last commit <code><sha></code>, tree clean, no pending handoffs, no failed CI, no stale signals, no hot bugs in the last 60 days. Pick something off <code>tech_debt.top_3</code> (<list>), start a project, or take the day off."</p> <h3>Step 6 — Quick-fix plan route (the one route that writes)</h3> <p>Reached only from Step 5's ad-hoc-procedural-one-off branch. The goal: produce a written, fork-free plan a fresh <code>/sonnet</code> session can execute without further judgment.</p> <ol> <li><strong>Research inline.</strong> A few targeted <code>Grep</code> + <code>Read</code> calls to ground the approach in JellyRock's real conventions for the touched area. Inline research is the right shape for the typical 1–3-area task; only reach for a sub-agent when the work spans more than three distinct areas each needing deep reads.</li> <li><strong>Resolve every fork via <code>AskUserQuestion</code>.</strong> Any design choice with two or more viable answers — never free-form prose. The plan must be fork-free by construction, because that's what makes the implementation procedural (and so <code>/sonnet</code>-eligible).</li> <li><strong>Draft the plan INTERNALLY</strong>, then surface it via <code>EnterPlanMode</code> → <code>ExitPlanMode</code>. The plan-mode dialog is the SOLE preview surface — never render plan markdown to the conversation first. Sections: <strong>Context</strong> (the why — which pick / banner / followup surfaced it), <strong>Approach</strong> (a single recommended path, not a menu), <strong>Critical files</strong> (scope envelope as a table — CREATE / Edit / Delete per file), <strong>Verification</strong> (numbered checklist using JellyRock's npm wrappers: <code>npm run validate</code> for the BrightScript typecheck, <code>npm run test:scripts</code> for the vitest suite, <code>npm run test:tdd</code> for Roku device tests when in scope, <code>npm run lint</code> for the lint gate — never bare runners), <strong>What this plan deliberately does NOT do</strong> (scope boundaries).</li> <li><strong>Save</strong> the approved plan to <code>.claude/plans/focus-YYYY-MM-DD-<slug>.md</code> (the path <code>/sonnet</code> reads; gitignored, like <code>.claude/handoffs/</code>).</li> <li><strong>Classify the implementation tier.</strong> Default <code>/sonnet</code> for a fork-free procedural plan; reserve the judgment-grade tier only when a plan step needs in-flight judgment the plan can't pre-specify (a perf rewrite shaped by a live run, an unknown-root-cause debug, a step flagged "approach TBD pending discovery"). The choice is made here, not punted downstream.</li> <li><strong>Print the hand-off block</strong> and STOP — do not implement in this session:</li> </ol> <pre><code class="language-text">Plan saved: .claude/plans/focus-YYYY-MM-DD-<slug>.md To implement in a fresh session: /sonnet .claude/plans/focus-YYYY-MM-DD-<slug>.md (Or, if the plan needs in-flight judgment: `Implement the plan at <path>` on the judgment-grade tier.) </code></pre> <h3>Read-only apart from the quick-fix plan file</h3> <p><code>/focus</code> doesn't write to journals, doesn't create handoffs, doesn't bump frontmatter, and doesn't auto-invoke downstream skills. Routing happens by <strong>printing the exact invocation the user should run</strong> (<code>/ci-triage</code>, <code>/new-setting</code>, <code>/done</code>, <code>/start-project</code>, <code>/resume-project</code>, …), not by invoking it. The single exception is Step 6's quick-fix route, which writes one plan file to <code>.claude/plans/</code> (gitignored). Downstream skills own every other write.</p> <p>If a sub-agent invokes <code>/focus</code> and the menu surfaces a capture-shaped finding, the sub-agent ends its report with a "Captures for /log" section so the parent can invoke <code>/log</code> — same pattern <code>/catchup</code> uses.</p> <h2>Sub-agent invocation</h2> <p>To invoke from a sub-agent: parent passes <code>Read .claude/skills/focus/SKILL.md and follow Steps 1–3 for $ARGUMENTS=<area-or-empty>; report the menu and Recommended line — do NOT execute Step 5/6 routing. If you discover any decisions or followups during this work, surface them at the end of your report as a "Captures for /log" section; I'll invoke /log for each. Do not write to journals directly.</code> in the Task prompt. Sub-agents surface candidates; the parent + user own the pick and the route.</p> </article> </div> <!-- Right: Metadata & Command Sidebar --> <div class="w-full lg:w-80 shrink-0 flex flex-col gap-6" data-astro-cid-7zzsworf> <!-- Install Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-mono" data-astro-cid-7zzsworf>Install via CLI</span> <div class="flex flex-col gap-2" data-astro-cid-7zzsworf> <div id="detail-install-cmd" class="font-mono text-[11px] p-3 rounded-lg bg-black/40 border border-border select-all break-all text-primary font-bold leading-relaxed" data-astro-cid-7zzsworf> npx skills add https://github.com/jellyrock/jellyrock --skill focus </div> <button id="detail-copy-btn" class="w-full py-2.5 rounded-lg bg-primary hover:bg-primary-hover text-on-primary font-sans font-bold text-sm shadow transition-all active:scale-95 flex items-center justify-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px]" data-astro-cid-7zzsworf>content_copy</span> <span data-astro-cid-7zzsworf>Copy Command</span> </button> </div> </div> <!-- Details & Stats Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-4 shadow-sm text-on-surface" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>Repository Details</span> <div class="flex flex-col gap-3.5" data-astro-cid-7zzsworf> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>star</span> Stars </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>31</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>call_split</span> Forks </span> <span class="font-mono font-bold text-on-surface" data-astro-cid-7zzsworf>2</span> </div> <div class="flex justify-between items-center text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>navigation</span> Branch </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant" data-astro-cid-7zzsworf>main</span> </div> <div class="flex justify-between items-start text-sm" data-astro-cid-7zzsworf> <span class="text-on-surface-variant/70 flex items-center gap-1.5 mt-0.5" data-astro-cid-7zzsworf> <span class="material-symbols-outlined text-[16px] text-on-surface-variant/60" data-astro-cid-7zzsworf>article</span> Path </span> <span class="font-mono bg-surface border border-border px-2 py-0.5 rounded text-[11px] text-on-surface-variant truncate max-w-[150px]" title="SKILL.md" data-astro-cid-7zzsworf>SKILL.md</span> </div> </div> </div> <!-- Occupations Tag Card --> <!-- Related Creators Card --> <div class="p-6 rounded-xl bg-surface-container border border-border/80 flex flex-col gap-3 shadow-sm" data-astro-cid-7zzsworf> <span class="text-xs font-bold uppercase tracking-widest text-on-surface-variant/60 font-sans" data-astro-cid-7zzsworf>More from Creator</span> <div class="flex items-center gap-2" data-astro-cid-7zzsworf> <img class="w-8 h-8 rounded-full border border-border" src="https://avatars.githubusercontent.com/u/256240496?v=4" alt="jellyrock" onerror="this.src='https://avatars.githubusercontent.com/u/9919?v=4'" data-astro-cid-7zzsworf> <div class="flex flex-col min-w-0" data-astro-cid-7zzsworf> <span class="font-bold text-sm truncate text-on-surface" data-astro-cid-7zzsworf>jellyrock</span> <a href="/?creator=jellyrock" class="text-xs text-primary hover:underline font-semibold transition-all" data-astro-cid-7zzsworf>Explore all skills →</a> </div> </div> </div> </div> </div> </div> </div> <script> const copyBtn = document.getElementById("detail-copy-btn"); const installCmd = document.getElementById("detail-install-cmd"); if (copyBtn && installCmd) { copyBtn.addEventListener("click", () => { const cmd = installCmd.textContent.trim(); navigator.clipboard.writeText(cmd).then(() => { const originalText = copyBtn.innerHTML; copyBtn.innerHTML = ` <span class="material-symbols-outlined text-[16px]">check</span> <span>Copied!</span> `; copyBtn.style.background = "#10b981"; copyBtn.style.borderColor = "#10b981"; setTimeout(() => { copyBtn.innerHTML = originalText; copyBtn.style.background = ""; copyBtn.style.borderColor = ""; }, 1500); }); }); } </script> </div> <!-- Footer --> <footer class="border-t border-border bg-surface-container-low text-on-surface-variant py-8 px-gutter mt-16 rounded-xl"> <div class="max-w-container-max mx-auto flex flex-col md:flex-row justify-between items-center gap-6"> <div class="flex items-center gap-2"> <div class="w-6 h-6 rounded bg-primary bg-opacity-20 flex items-center justify-center"> <span class="material-symbols-outlined text-primary text-sm">code_blocks</span> </div> <span class="font-bold text-on-surface text-sm">SkillMD</span> </div> <div class="flex flex-wrap justify-center gap-6 text-sm"> <a href="/about" class="hover:text-primary transition-colors">About Us</a> <a href="/contact" class="hover:text-primary transition-colors">Contact Us</a> <a href="/privacy" class="hover:text-primary transition-colors">Privacy Policy</a> <a href="/terms" class="hover:text-primary transition-colors">Terms of Service</a> <a href="/support" class="hover:text-primary transition-colors">Support</a> </div> <div class="text-xs text-on-surface-variant/80"> © 2026 SkillMD. All rights reserved. </div> </div> </footer> </main> <!-- Script for Theme Toggle, Mobile Menu, and Sidebar Filter Redirection --> <script> // Theme setup const savedTheme = localStorage.getItem("theme") || "dark"; function applyTheme(theme) { document.documentElement.classList.remove("dark", "green", "dracula", "nord"); if (theme === "dark") { document.documentElement.classList.add("dark"); } else if (theme === "green") { document.documentElement.classList.add("dark", "green"); } else if (theme === "dracula") { document.documentElement.classList.add("dark", "dracula"); } else if (theme === "nord") { document.documentElement.classList.add("dark", "nord"); } document.documentElement.setAttribute("data-theme", theme); const themeMoon = document.getElementById("theme-moon"); const themeSun = document.getElementById("theme-sun"); const themeLeaf = document.getElementById("theme-leaf"); const themeDracula = document.getElementById("theme-dracula"); const themeNord = document.getElementById("theme-nord"); if (themeMoon && themeSun && themeLeaf && themeDracula && themeNord) { themeMoon.style.display = theme === "dark" ? "inline" : "none"; themeSun.style.display = theme === "light" ? "inline" : "none"; themeLeaf.style.display = theme === "green" ? "inline" : "none"; themeDracula.style.display = theme === "dracula" ? "inline" : "none"; themeNord.style.display = theme === "nord" ? "inline" : "none"; } } applyTheme(savedTheme); const themeToggleBtn = document.getElementById("theme-toggle-btn"); if (themeToggleBtn) { themeToggleBtn.addEventListener("click", () => { const currentTheme = document.documentElement.getAttribute("data-theme") || "dark"; let newTheme = "dark"; if (currentTheme === "dark") { newTheme = "light"; } else if (currentTheme === "light") { newTheme = "green"; } else if (currentTheme === "green") { newTheme = "dracula"; } else if (currentTheme === "dracula") { newTheme = "nord"; } else { newTheme = "dark"; } applyTheme(newTheme); localStorage.setItem("theme", newTheme); }); } // Mobile menu toggle and sidebar logic const mobileMenuToggle = document.getElementById("mobile-menu-toggle"); const sidebarMenu = document.getElementById("sidebar-menu"); const sidebarOverlay = document.getElementById("sidebar-overlay"); function isMobile() { return window.innerWidth < 768; // 768px is the 'md' breakpoint in Tailwind } function openSidebar() { if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.remove("hidden"); } } function closeSidebar() { if (sidebarMenu && isMobile()) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } if (mobileMenuToggle && sidebarMenu) { mobileMenuToggle.addEventListener("click", (e) => { e.stopPropagation(); if (isMobile()) { const isClosed = sidebarMenu.classList.contains("-translate-x-full"); if (isClosed) { openSidebar(); } else { closeSidebar(); } } }); document.addEventListener("click", (e) => { if (isMobile()) { if (!sidebarMenu.contains(e.target) && !mobileMenuToggle.contains(e.target)) { closeSidebar(); } } }); if (sidebarOverlay) { sidebarOverlay.addEventListener("click", () => { if (isMobile()) { closeSidebar(); } }); } // Collapse sidebar when clicking a filter button, creator button, or nav item inside it sidebarMenu.addEventListener("click", (e) => { if (isMobile()) { const clickTarget = e.target.closest("button, a"); if (clickTarget) { closeSidebar(); } } }); // Sync sidebar state on window resize window.addEventListener("resize", () => { if (!isMobile()) { // Desktop: sidebar should be visible, no overlay if (sidebarMenu) { sidebarMenu.classList.remove("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } else { // Mobile: start collapsed if (sidebarMenu) { sidebarMenu.classList.add("-translate-x-full"); } if (sidebarOverlay) { sidebarOverlay.classList.add("hidden"); } } }); } // If not on homepage, redirect on sidebar filter click const isHomepage = window.location.pathname === "/"; document.querySelectorAll("#occupation-filters .filter-btn").forEach(btn => { btn.addEventListener("click", (e) => { const occ = e.currentTarget.getAttribute("data-occupation"); if (!isHomepage) { window.location.href = occ ? `/?occupation=${encodeURIComponent(occ)}` : "/"; } }); }); document.querySelectorAll("#creator-filters .creator-btn").forEach(btn => { btn.addEventListener("click", (e) => { const creator = e.currentTarget.getAttribute("data-creator"); if (!isHomepage) { window.location.href = `/?creator=${encodeURIComponent(creator)}`; } }); }); </script> </body> </html>