brief · git:20260902.349157d · 2026-09-02 · sha256 71eccc1fbcde8f99

brief git:20260902.349157dA

Immutable. This exact content is served forever at /api/v1/blob/71eccc1fbcde8f99.

---
name: brief
description: Returns a 5-line executive summary of recent work. Checks active session first, falls back to latest report. Activates on messages like "brief", "what happened", "morning update", "overnight summary", "progress", "what are you working on", "how's it going".
---
# Session Brief

Provide a concise executive summary of recent session activity. Designed for morning check-ins, phone/channel consumption, and quick status updates.

## Always-On Delivery Rule

If `config.always_on` is `true`, deliver all operator-facing output per `CLAUDE-APPEND.md § Operator Notification`. The terminal is unmonitored in always-on mode. For the push-fallback branch, condense the brief to a single line (per § Operator Notification push format): include whichever of open proposal count and active heartbeat alerts are present and non-zero; omit zero or unavailable fields. Example: `Brief: 16 proposals open, 1 alert — open CC to view`. In interactive mode, output to terminal. This applies to all flags below.

## Dispatch

Before composing any brief, determine the dispatch mode:

1. Read `session_state` from `.claude-code-hermit/state/runtime.json` (live state — always in main).
2. Resolve the active flag (`--morning`, `--evening`, "brief today"/"daily summary", or no flag).

Live files (SHELL.md, `alert-state.json`, `proposals/`) are read fresh on this turn; a value held in context from before a compaction is stale.

**Dispatch decision:**
- `--morning` → dispatch (mode: `morning`)
- `--evening` → dispatch (mode: `evening`)
- "brief today" / "daily summary" / "what happened today" → dispatch (mode: `daily`)
- No flag + `session_state` is `in_progress` → **no dispatch** — summarize the live SHELL.md in main (Plan step 1a)
- No flag + `session_state` is `idle` → **no dispatch** — read the live SHELL.md in main and emit the idle block (Plan step 1b)
- No flag + no active session (no SHELL.md / no `session_state`) → dispatch (mode: `default-no-session`, Plan step 1c)

For dispatching modes: invoke `claude-code-hermit:skill-eval-runner` pointed at `${CLAUDE_PLUGIN_ROOT}/skills/brief/reference.md`. Pass in the dispatch prompt: `mode` (one of the values above), `today` (current ISO date), and for `morning` only: `context_recovery` (set to `true` if auto-memory seems sparse — new instance, fresh machine — `false` otherwise).

**Boundary rule:** `sessions/SHELL.md` is the live session document — it stays in main, never goes to the runner. Archived `sessions/S-*-REPORT.md` bodies, `proposals/*.md` frontmatter, `OPERATOR.md`, and `NEXT-TASK.md` go to the runner.

**Failure policy:** if the runner returns null or malformed JSON, fail-open — compose the brief from whatever live data main holds (SHELL.md) and skip the runner-derived lines. Note nothing fatal to the operator.

**Eval runner return schema** — the runner returns a JSON object conforming to this block. The schema is byte-identical in `reference.md` (producer) and here (consumer); a contract test asserts this.

<!-- brief-eval-schema:start -->
```json
{
  "report_summary": { "date": "<ISO>", "tags": ["<tag>"], "working_on": "<one-line>",
                       "status": "<completed|partial|blocked>", "next_start_point": "<text>" }|null,
  "sessions_today": [ { "session": "S-NNN", "summary": "<one-line>" } ],
  "findings": ["<text>"],
  "tomorrow": ["<text>"],
  "pending_proposals": ["<PROP-NNN: title>"],
  "operator_priorities": ["<text>"],
  "queued_work": ["<text>"]
}
```
<!-- brief-eval-schema:end -->

## Flags

### --morning (routine mode)

**Delivery:** Write the full composed brief text (before any push-fallback single-line condensing) to `.claude-code-hermit/state/last-brief.json` as `{"kind":"morning","text":"<brief text>","generated_at":"<now, ISO>"}`, so the dashboard's "latest brief" section can pick it up. Then refresh the dashboard per `${CLAUDE_PLUGIN_ROOT}/docs/artifacts.md`; if it returns a URL, append a final line `📎 <url>`. Then deliver the brief to the operator (see Always-On Delivery Rule above).

Emphasize forward-looking content. Compose from runner JSON (see Dispatch above) and live main-session data:
- **Pending proposals:** use `runner.pending_proposals`
- **Operator priorities:** use `runner.operator_priorities`
- **Queued work:** use `runner.queued_work`
- **Context recovery:** if `runner.report_summary` is non-null, use it for session context
- If `config.always_on` is `true`: frame as "what happened overnight (activity since evening routine)"
- If `config.always_on` is `false`: frame as "here's where things stand"
- If `config.always_on` is `true`: run `bash "${CLAUDE_PLUGIN_ROOT}/scripts/check-upgrade.sh" "${CLAUDE_PLUGIN_ROOT}"` from the project root. If it emits an `---Upgrade Available---` section, append a final line to the brief: `⚠ Plugin update available: <the version line>` (pass the directive verbatim). If it emits `---Stale Plugin Runtime---` instead, append `⚠ Stale plugin install: <the notice>` — never label it an update, and never turn it into an evolve instruction (evolve cannot fix a stale install). Output nothing if the script is silent. (Interactive operators already see this notice at session-start step 2; the gate avoids double-notification.)

After composing the morning brief, age the micro-proposal queue in one pass: run `bun ${CLAUDE_PLUGIN_ROOT}/scripts/proposal.ts micro .claude-code-hermit brief-cycle`. It reads `state/micro-proposals.json`, runs the whole lifecycle atomically (re-nudges `follow_up_count` 1 entries, expires `follow_up_count` ≥2 entries and records each expiry, prunes any entry whose `status` isn't `"pending"`), and prints one JSON line `{"new":[…],"renudged":[…],"expired":[…],"dropped":[…]}` — never hand-edit the file or read it separately. Render straight from that verdict:
- Each `new` entry (first display): append as a final line. Without `options`: `MP-YYYYMMDD-N (tier N): [question]` — Reply `"MP-YYYYMMDD-N yes"` or `"MP-YYYYMMDD-N no"`. (Bare `yes`/`no` accepted when only one pending.) With `options`: render them numbered under the question and reply hint `Reply "MP-YYYYMMDD-N <number or label>"` (bare accepted when only one pending).
- Each `renudged` entry: append with softer framing: "Still waiting on MP-YYYYMMDD-N: [question] — ignore again to drop it" (if it carries `options`, re-render them numbered beneath it so the choices aren't lost on the re-nudge).
- `expired` entries were dropped this cycle — do not surface them, and do not resurrect unless fresh evidence accumulates from scratch.
- `dropped` entries were resolved elsewhere: never surface them, never count them.
- If `new` and `renudged` are both empty: brief ends without a decision prompt.

### --evening (routine mode)

**Delivery:** Write the full composed brief text (before any push-fallback single-line condensing) to `.claude-code-hermit/state/last-brief.json` as `{"kind":"evening","text":"<brief text>","generated_at":"<now, ISO>"}`, so the dashboard's "latest brief" section can pick it up. Then refresh the dashboard per `${CLAUDE_PLUGIN_ROOT}/docs/artifacts.md`; if it returns a URL, append a final line `📎 <url>`. Then deliver the brief to the operator (see Always-On Delivery Rule above).

Emphasize backward-looking content. Compose from runner JSON (see Dispatch above) and live main-session data:
- **Sessions today:** use `runner.sessions_today`; also note any progress in the current SHELL.md progress log (read SHELL.md in main).
- **Key findings:** use `runner.findings`
- **Tomorrow:** use `runner.tomorrow`
- After generating summary: if `runtime.json session_state` is `in_progress` or SHELL.md has progress entries since last report, note it in the brief (e.g., "Session still open — run /session-close to archive.") and let the operator close explicitly. Exception: if `config.always_on` is `true` AND `config.routines` contains an enabled entry with `id` `daily-auto-close` (the midnight routine, which invokes `/claude-code-hermit:session-close --scheduled`), suppress the note — the auto-close routine archives it at midnight. Idle transitions are owned by the `session` skill and `scripts/session-archive.ts`; brief does not trigger them.

### No flag (default)

Current behavior — general purpose summary as described below.

## Plan

1. Use `session_state` already read in the Dispatch step:
   - **1a. `in_progress` (no dispatch):** read `.claude-code-hermit/sessions/SHELL.md`. Summarize the active task using its Progress Log for Done/Next lines; produce the standard 5-line output. Then read `.claude-code-hermit/state/alert-state.json`; count the entries in its `alerts` object whose `suppressed` is not `true` (suppressed ones are digest-only — not "active"). If that count is above zero, append one line: `⚠ N alert(s) active — run /claude-code-hermit:hermit-health`.
   - **1b. `idle` (no dispatch):** read SHELL.md. Format as:
     ```
     [Brief] YYYY-MM-DD | idle | N tasks completed
     Session: since [start date]
     Last: [latest Session Summary entry, with the trailing `($X.XX)` spend figure stripped] — [status]
     Status: Idle — ready for what's next (run /claude-code-hermit:session-start to begin)
     ```
     Then check for auto-detected proposals (step after Output Format) and return.
   - **1c. No active session (dispatch):** runner JSON is already available (mode: `default-no-session`). Use `runner.report_summary` for the brief. If `report_summary` is null (runner failed), fall back to reading the most recent archived report directly in main.
2. If no session and no runner result: respond "No session history yet. Run `/claude-code-hermit:session` to start."

## Output Format

One line per field below, plus an optional line for pending proposals (see Rules below):

```
[Brief] YYYY-MM-DD | [tags if present]
Working on: one-line description
Status: completed/partial/blocked
Done: step1, step2, step3
Next: description of next action (or "Session complete" if all done)
```

## Rules

- One line per field; a reader on a phone should get the whole brief without scrolling. Extra lines only for the alert count and the proposal count
- When delivered over a channel, replace every slash-command pointer in the template with the plain reply the operator can send (e.g. 'reply "start" to begin', 'ask me for a health check'); command names stay in terminal output
- Use the session's date, not today's date
- Include tags in the header only if they exist
- For the "Done" line: list the completed steps from the SHELL.md Progress Log, comma-separated. If too many, show first 3 and "+ N more"
- For the "Next" line: show the step in flight, or the next one the plan implies if none is. If blocked, show "Blocked: reason — run /debug to diagnose, or /claude-code-hermit:session for a fresh session" (keeps the actionable pointers on the existing line, no extra line)
- If summarizing a completed report: "Next" becomes the report's "Next Start Point" content
- After composing the 5-line output: scan `.claude-code-hermit/proposals/` for files with `source: auto-detected` and `status: proposed` (read `status:` and `source:` from the **leading `---` YAML frontmatter block only** — do not count files where those phrases appear in the proposal body text; skip files with no frontmatter block). If any exist, append a 6th line: `Proposals: N auto-detected proposal(s) pending review`

## Daily Summary Format

When invoked with "brief today", "daily summary", or "what happened today":

Compose from runner JSON (mode: `daily`). Use `runner.sessions_today`, `runner.findings`, and `runner.tomorrow` for the day narrative. Format as a day-level summary covering: work done and proposals created/resolved.