# Session Management

## Session Start

Daily standup — fast, focused on what the user is doing right now.

### Step 1: Gather All Context (one call)

**Surface:** MCP — `session_start` (no arguments). Fallback: `flydocs run session.start-context` (script: `session.py start-context`). If the flydocs MCP tools are absent from the client's tool list, use the runner fallback — and tell the user the MCP server likely needs a one-time enable in the client's MCP settings (Cursor: Settings → Cursor Settings → MCP).

One call returns everything the dashboard needs — the active issue with its AC
progress, the last session's summary, pending work and blockers. There is no
follow-up read to make:

```json
{
  "identity": { "name": "...", "email": "..." },
  "workspace": { "name": "...", "tier": "cloud", "onboardComplete": true, "setupComplete": true },
  "activeBoard": { "name": "...", "type": "scrum|kanban", "sprintId": "...", "sprintName": "..." },
  "hasActiveProject": true,
  "activeSprintId": "...",
  "lastSession": { "timestamp": "...", "issues": [...], "pending": [...], "blockers": [...], "notes": "..." },
  "activeIssue": { "id": "FLY-123", "status": "IMPLEMENTING", "acTotal": 5, "acDone": 3 },
  "isMultiRepo": true,
  "repos": [...],
  "siblingSessionSummaries": [...]
}
```

**Do NOT read config.json, me.json, validation-cache.json, or last-summary.json manually.** The call handles all file reads, multi-repo aggregation, and graceful fallbacks. Fields are `null` when data is missing — not errors.

### Step 2: Onboard Gate

Check `workspace.onboardComplete` from the session_start result.

**If `false`:** Run onboard inline — present a condensed orientation
(welcome, setup summary, project context from start-context repos data,
commands reference), then set `onboardComplete: true` in config. Continue
directly to step 3. The user should never need to invoke a second command.

If the user declines, skip and continue.

### Step 3: Present the Dashboard

**Priority: last session first, then board, then issues, then recommendation.**

**Welcome + last session** — Use `identity.name` and `workspace.name`:

```
Welcome back, [Name]! Here's where things stand.
```

If `lastSession` is not null, lead with it:

```
Last session you worked on [lastSession.issues]. [lastSession.notes].
```

If `lastSession` is null: "This looks like your first session here."

**Active board/project** — Check `hasActiveProject`.

If `false`: guide the user interactively:

```
No active project selected. Would you like to:
1. **Pick from existing projects** — I'll list them
2. **Create a new project**
```

List: `flydocs run project.list` / Set:
`flydocs run workspace.set-active-project <ID>` (both long-tail — the runner,
no MCP tool. Scripts: `projects.py list-projects`,
`workspace.py set-active-project`.)

Do NOT tell the user to edit config. After setting, call `session_start` again and continue.

**Sprint check** — Only if `activeBoard.type` is `scrum`. Skip for kanban.

- Active sprint: show "X days remaining" in dashboard.
- No sprint: no mention.
- Kanban: never mention sprints.

### Step 4: Fetch Issues

**Surface:** runner — `flydocs run session.list-issues` (long-tail; script: `session.py list-issues`). `issue_list` reads issues, but only this operation resolves the board/sprint scope from config _and_ reports the empty-result fallback the dashboard depends on.

Run it with no arguments — it auto-applies the correct board/sprint filter from
config and falls back to unfiltered if the scoped query returns zero results:

```bash
flydocs run session.list-issues
```

Returns:

```json
{
  "issues": [...],
  "count": 5,
  "filter": "sprint|board|none",
  "fallbackUsed": false
}
```

**Do NOT reach for `issue_list` or `issue.list` here.** The `list-issues`
operation handles board/sprint scoping deterministically — no flags to remember.

If `fallbackUsed` is true, note it in the dashboard: the scoped view was
empty so issues are shown from the broader project.

### Step 5: Present Dashboard

**HARD RULE: Never show issues from other boards on the active dashboard.**

**If the board has issues:**

```
## [Product Name]

**Board:** [activeBoard.name] ([activeBoard.type])
**Sprint:** [activeBoard.sprintName] — [X days remaining]   (Scrum only)

### My Issues
**In Progress** ([N])
- ISSUE-ID — Title (priority, estimate)

**Blocked** ([N])
- ISSUE-ID — Title — [blocker]

**Ready** ([N])
- ISSUE-ID — Title (priority, estimate)
```

One line for other boards if relevant: `Also tracking: [N] issues on [Board Name]`

**If no issues assigned:**

```
No issues assigned to you on this board.

Would you like to:
1. **Switch boards**
2. **Stay here** and pick up unassigned work
```

Wait for choice. Do NOT show issues from other boards.

### Step 6: Recommend Where to Start

Active board issues only:

- Blocked issues needing unblocking
- In-progress issues to continue
- Issues due within 7 days
- Highest priority ready issue

---

## Session Wrap

When the user indicates they're done ("wrapping up", "done for today",
"end of day", "stopping here", "save progress"):

### Step 1: Knowledge Capture Check

Review the session for uncaptured knowledge:

- Architectural decisions made?
- Non-obvious workarounds discovered?
- Patterns established for future work?

If yes: "This session involved [discovery]. Capture it before wrapping?"
Use `/knowledge` if they agree.

### Step 2: Fill the Session-Wrap Template

The posted project update is a standup for the team — read by people who did
NOT do the work. Its structure is fixed by the template at:

```
templates/.claude/skills/flydocs-workflow/templates/session/session-wrap.md
```

Read that file and fill it in — as text you are holding, not a file you write.
Fill every required section — **Accomplished**, **Next up**, **Blockers & open
questions**, **Progress** — following the writing guidance in the template's
comment header. **Notes** is optional; delete the whole section if empty. Write
"None" for Blockers rather than leaving it empty.

The filled template is the `body` argument in the next step. It is one string —
headings, bullets and blank lines go in as-is. On the tool path it never touches
disk. Only the runner fallback needs it in a file, and that file belongs in
`.flydocs/scratch/`, not in a system scratch directory.

The wrap enforces the structure: on the posting path (`health` set) it validates
that the body contains all four required section headers and **refuses to post**
if any are missing, naming them and leaving session state intact so you can fix
the body and call again.

### Step 3: Wrap the Session

**Surface:** MCP — `session_wrap`. Fallback: `flydocs run session.wrap --health H --file .flydocs/scratch/wrap.md` — the runner is a command line, so on that path the body goes in a scratch file (see `SKILL.md`, "Long Inputs on the Runner"). Script: `session.py wrap --body-file FILE`.

Call `session_wrap` with the filled template as `body`, and give it a `title`
and a `summary`:

```yaml
issues: [FLY-XXX, FLY-YYY]
health: onTrack
title: Landed the Activity week view over live data
summary: |
  The week view now reads the session records instead of the fixture, so a
  wrap shows up on the timeline within the minute. The Slack renderer is the
  last consumer still on derived text.
body: |
  ## Accomplished
  ...
  ## Next up
  ...
  ## Blockers & open questions
  None
  ## Progress
  ...
```

**Write both for a teammate who was not here** — they are what the timeline,
the record modal and the Slack message open with, and nobody clicks through to
the body.

| Field     | Good                                                                              | Bad                                    |
| --------- | --------------------------------------------------------------------------------- | -------------------------------------- |
| `title`   | Landed the Activity week view over live data                                      | session wrap · Work on FLY-1498 · Done |
| `summary` | Two to three plain sentences: what changed, and what it means for the next person | A bullet list, or the body again       |

One line for the title (max 120 characters; line breaks are collapsed), 2-3
sentences for the summary (max 600). Omit either rather than sending an empty
one — a record without them falls back to a fragment derived from the body,
which is exactly what writing them avoids.

This single call:

1. Validates the body against the required sections (aborts if any missing)
2. Writes `last-summary.json` (the critical handoff for next start-session,
   including the exact `postedBody`)
3. **Posts the end-of-session project update itself** — golden rule 5 is
   satisfied by this call. Do not post a second one with `project_update`;
   that tool is for mid-session updates
4. Deletes the session-state files — `focus.md`, `status`, `status-ref`,
   `acceptance-criteria.md`, `mirror-mismatch-notice` (`SESSION_STATE_FILES`
   in `scripts/session.py`) — only after a successful post. During the session
   those files follow the focused issue, not every transition
   (`reference/status-workflow.md`, Session-State Bookkeeping)
5. Records session in the context graph (if available)

The wrap also produces a **session record** — the filled template plus the
structured facts around it (repo, window, health, issues, pending, blockers,
visibility). On the cloud tier the record is the write: the relay stores it,
validates the required sections server-side, and posts the project update as
one destination of it, so a provider without project updates still gets a
record. On every tier the record is appended to `stream.jsonl` in the session
directory — append-only, never cleaned up, and readable offline with
`flydocs stream` (`--limit`, `--since`, `--issue`, `--json`). A body the
validator refuses appends nothing, exactly as it posts nothing.

If the destination refuses the update after the record is stored, the wrap
posts the same narrative through the project-update route itself and reports
which way it went. Only when both fail is the session left unwrapped, with its
state intact for a retry.

Omitting `health` wraps without posting an update. That is an escape hatch, not
the normal path — the call says so in a warning when you take it.

**Do NOT write last-summary.json manually.** Do NOT delete focus files manually.
The wrap handles all of it deterministically.

### Step 4: Verify and Report

The call returns structured JSON with confirmation:

```json
{
  "success": true,
  "actions": [
    { "action": "write_summary", "success": true },
    {
      "action": "session_update",
      "success": true,
      "id": "...",
      "permalinkPath": "/<workspace>/activity/..."
    },
    { "action": "session_stream", "success": true, "relayState": "accepted" },
    { "action": "cleanup", "filesRemoved": ["focus.md", "status"] },
    { "action": "graph_record", "success": true }
  ],
  "record": { "id": "...", "permalinkPath": "/<workspace>/activity/..." },
  "summary": { "issueCount": 2, "pendingCount": 1, "blockerCount": 0 }
}
```

On the local tier there is no relay to store a record: the wrap reports a
`project_update` action written to the file store, `relayState` is `skipped`,
and the stream append is the record.

Report the result to the user using the same filled template content that was
posted — it is the canonical session summary. End with: "Session saved.
Project update posted."

### Step 5: Check for Uncommitted Changes

If git shows uncommitted work, offer to commit.

### Step 6: Audit (optional)

**Surface:** runner — `flydocs run issue.audit` (long-tail; no MCP tool. Script: `issues.py audit`).

```bash
flydocs run issue.audit --limit 20
```

Report findings briefly. Informational only.

## Mid-Session Project Update

**Surface:** MCP — `project_update` with `health` and `body`. Fallback: `flydocs run session.project-update --health H --file .flydocs/scratch/update.md` (script: `session.py project-update`).

For a health update posted while work continues — a milestone landing, a risk
the team needs now. End-of-session updates are `session_wrap`'s; posting one
here as well would double-post the same standup.

### Health Status

| Status   | When to Use                     |
| -------- | ------------------------------- |
| onTrack  | Progress made, no blockers      |
| atRisk   | Minor delays, attention needed  |
| offTrack | Major blockers, behind schedule |

---

## Stale Issue Awareness

Proactively surface during start-session if detected:

| State        | Threshold     | Action         |
| ------------ | ------------- | -------------- |
| Implementing | > 7 days idle | Warn           |
| Review       | > 3 days idle | Warn           |
| Testing/QA   | > 3 days idle | Warn           |
| Blocked      | Any           | Always surface |
