# Daily Standup Prep

> Generate a per-team standup markdown report by gathering activity over the last N days from Jira, GitLab, Confluence, and a local Git repo. Maps activity to team members from a roster CSV, renders a Mermaid kanban + randomized talking order, captures a `daily`-tagged sprint snapshot, and runs a clarity-council (infographics-expert burndown chart + statistics-expert forecast + scrum-master suggestions) for sprint pulse. Writes one file per team into the Obsidian vault. Use when the user says "daily standup prep", "standup report", "generate standup", "scrum prep", or invokes /daily-standup-prep.

- Skill: `risadams/daily-standup-prep` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add risadams/daily-standup-prep`
- Raw SKILL.md: https://api.skillmd.com/api/skills/risadams/daily-standup-prep/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: risadams (https://skillmd.com/u/risadams)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/risadams/daily-standup-prep

---


# Daily Standup Prep

Per-team standup report. Read-only across Jira, GitLab, Confluence, and Git. Writes one markdown file per team to the Obsidian vault.

This skill is a Claude Code port of `D:\powershell-scripting\src\bin\Get-Standup-Report.ps1` — same inputs, same template placeholders, same output path layout, same identity-matching rules. See [REFERENCE.md](REFERENCE.md) for the algorithm and per-section formatter contracts. See [EXAMPLES.md](EXAMPLES.md) for invocation parity with the PS script.

## Quick start

```text
/daily-standup-prep                                 → Pyrite, last 2 days, INC 25 / Sprint 1
/daily-standup-prep Onyx                            → single team
/daily-standup-prep Pyrite,Onyx --days 3 --inc 26   → multi-team, custom window
/daily-standup-prep --no-gitlab --no-confluence     → toggle sections off
```

## Parameters

| Param | Default | Maps to PS |
| :--- | :--- | :--- |
| `Teams` | `["Pyrite"]` | `-Teams` (string[]) |
| `DaysToLookBack` | `2` | `-DaysToLookBack` |
| `JQL` | auto-built | `-JQL` |
| `GitRepoPath` | `D:\paas\` | `-GitRepoPath` |
| `Inc` | `25` | `-Inc` |
| `Sprint` | `1` | `-Sprint` |
| `IncludeJiraIssues` / `IncludeGitCommits` / `IncludeGitLabActivity` / `IncludeConfluenceActivity` / `IncludeKanbanDiagram` / `IncludeStandupOrder` | all `true` | same |
| `IncludeSprintPulse` | `true` | new — runs Phase 4.5 (daily snapshot + council burndown/forecast/suggestions). Toggle off with `--no-sprint-pulse` |

JIRA base, Confluence base, GitLab base + group, vault root, and template path are all **resolved from memory** — never accept them as args. See *Config resolution* below.

## Config resolution (run once at the start of every invocation)

| Placeholder | Memory file | Default content / how to populate |
| :--- | :--- | :--- |
| `{{vault_root}}` | `reference_obsidian_vault.md` (`**Vault root:**` line) | `C:\Users\chris.adams\dev\gd-pkms\` — already populated |
| `{{jira_project}}` | `reference_jira_default_project.md` (`**Default Jira project key:**` line) | `SC2` — already populated |
| `{{confluence_space}}` | `reference_confluence_default_space.md` | `PP` — already populated |
| `{{gitlab_base_url}}` | `reference_gitlab_config.md` (`**GitLab base URL:**` line) | If `<unset>`, prompt and persist `https://gdgitlab01.gd-ms.us` (the PS-script default) |
| `{{gitlab.bessemer}}` | same memory, `Common project paths` table | If absent, prompt and add a row: `bessemer` → `bessemer` (group path) |
| `{{template_path}}` | derived | `{{vault_root}}\🗃Templates\Standup.md` (no separate memory) |
| `{{rosters_dir}}` | derived | `{{vault_root}}\Scrum Teams\_rosters\` |
| `{{output_root}}` | derived | `{{vault_root}}\Scrum Teams\` (per-team subfolder appended later) |

If a memory file is missing or its value is `<unset>`, **prompt once via `AskUserQuestion`** with the PS-script default as the recommended option, then write the answer back so the next run skips the prompt. Same pattern as `daily-briefing`.

## Roster bootstrap (first-run only)

The PS script reads `D:\powershell-scripting\src\bin\Teams\<Team>.csv`. We move ownership of those CSVs into the vault so the user maintains them in one place going forward.

For each team in the run:

1. Look for `{{rosters_dir}}\<Team>.csv` (case-insensitive). If present, use it.
2. If missing **and** `D:\powershell-scripting\src\bin\Teams\<Team>.csv` exists, prompt via `AskUserQuestion`:
   *"No roster found at `{{rosters_dir}}\<Team>.csv`. Copy from the powershell-scripting repo?"* — options: `Copy (recommended)`, `Use repo path this run only`, `Skip team`.
3. On `Copy`: `mkdir -p "{{rosters_dir}}"`, then copy the file. Tell the user the new path is the canonical location going forward.
4. If neither path exists, skip the team and warn — do not invent a roster.

CSV format is the same headerless layout the PS Team-Helpers module uses: `FullName,Email,Alias,Role,Flags`. Lines starting with `//` and blanks are ignored. `Alias` and `Flags` accept `;`-separated values. See [REFERENCE.md](REFERENCE.md#roster-csv-schema).

## Workflow (per team)

Loop the steps below for each team in `Teams`. Each iteration is independent — one file per team, matching the PS script's `$teamsToProcess | ForEach-Object` block.

```text
Per-team progress:
- [ ] Phase 1: Resolve config + load roster
- [ ] Phase 2: Compute window + auto-build JQL
- [ ] Phase 3: Gather data (Jira, Git, GitLab, Confluence) — parallel
- [ ] Phase 4: Match activity to team members + flag HasActivity
- [ ] Phase 4.5: Sprint pulse — daily snapshot + council (burndown / forecast / suggestions)
- [ ] Phase 5: Render section markdown + Mermaid kanban + talking order
- [ ] Phase 6: Substitute template placeholders + write to vault
- [ ] Phase 7: Console summary
```

### Phase 1 — Config + roster

Resolve every `{{placeholder}}` per the table above. Load the roster CSV into a list of `{FullName, FirstName, LastName, JiraName, Email, Alias[], DisplayFirstName, Role, Flags[]}` records — derive `JiraName = "{Last}, {First}"` and `DisplayFirstName = Alias[0] ?? FirstName`. Tag each member with `HasActivity = false` and empty `Issues / Commits / GitLabActivities / ConfluenceActivities` lists.

### Phase 2 — Window + JQL

- `activity_since` = midnight (Pittsburgh local) `DaysToLookBack` days ago.
- `since_iso` = `YYYY-MM-DDTHH:mm:ssZ` for GitLab/Confluence cutoffs.
- `since_date` = `YYYY-MM-DD` for `git log --since`.
- If `JQL` arg is empty: build `project = {{jira_project}} AND updated >= startOfDay(-{N}d)` where `N = DaysToLookBack`.

State the chosen window in one short sentence: *"Gathering Pyrite standup for window 2026-05-11 → 2026-05-13 (2-day lookback)."*

### Phase 3 — Gather data (parallel)

Issue these MCP/Bash calls in a single response when section toggles allow:

- **Jira issues** (`IncludeJiraIssues` or `IncludeKanbanDiagram`): `jira_search(jql=<jql>, fields="summary,status,assignee,issuetype,updated")` with pagination to a sane cap. For each result, follow up with `jira_get_changelog` and `jira_get_comments` only when the issue's `updated` is within the window — skip the fetch otherwise to save tokens.
- **Jira kanban** (`IncludeKanbanDiagram` only): second `jira_search` with `project = {{jira_project}} AND sprint in openSprints() AND labels in ({lowercase team names})`. Capture `key, summary, status, assignee.displayName, issuetype.name`.
- **GitLab MRs + discussions** (`IncludeGitLabActivity`):
  1. `list_group_projects(group_id="{{gitlab.bessemer}}")` to get all project IDs in the group (subgroups included).
  2. For each project: `list_merge_requests(project_id, updated_after=<since_iso>, per_page=100)`.
  3. For each MR: `mr_discussions(project_id, merge_request_iid)` capped at 100 notes.
- **GitLab pipelines** (`IncludeGitLabActivity`): for each project, `list_pipelines(project_id, updated_after=<since_iso>, per_page=100)`. For each, `get_pipeline(project_id, pipeline_id)` to read the triggering user.
- **Confluence pages** (`IncludeConfluenceActivity`): `confluence_search(cql="lastModified >= '{since_date}' AND space = {{confluence_space}}")`. For each, `confluence_get_page(page_id, expand="history.lastUpdated,version,space,body.view")`.
- **Git commits** (`IncludeGitCommits`): Bash sequence — confirm `{{git_repo}}\.git` exists; `git -C "{{git_repo}}" fetch --all`; `git -C "{{git_repo}}" log --all --since="{since_date}" --pretty=format:"%H|%an|%ae|%aI|%s"`. Parse pipe-delimited lines.

If any external system fails to respond, log a warning and continue with empty results for that section — never fabricate. If `IncludeKanbanDiagram` is on but Jira fails, render `_No kanban data — Jira unavailable._` rather than skipping the placeholder.

### Phase 4 — Match activity to team members

Use the cascading 8-strategy matcher described in [REFERENCE.md](REFERENCE.md#identity-matching-cascade). For every activity record, attempt to match by `(email, displayName, username)` — Jira gives email + displayName; GitLab gives email + name + username; Confluence gives email + displayName; Git gives email + author name only.

Filter rules:

- **Jira issue inclusion** — keep an issue iff its assignee matches a team member **OR** any comment/changelog entry within the window was authored by a team member. (Direct port of the PS `$isAssignedToTeamMember -or $hasRecentActivity` filter.)
- **Git/GitLab/Confluence inclusion** — keep an activity iff its author matches a team member.
- Set `member.HasActivity = true` for any matched member, and append the activity to the member's `Issues / Commits / GitLabActivities / ConfluenceActivities` lists for the talking-order/per-member sections.

### Phase 4.5 — Sprint pulse (daily snapshot + council)

Skip entirely if `IncludeSprintPulse` is off — substitute empty strings for the three pulse placeholders in Phase 6.

**Step 1 — Daily sprint snapshot.** Invoke the `sprint-snapshot` skill via `Skill` for the current team with `--phase "daily"`. This writes `daily.canvas` + `daily.md` to the team's current sprint folder (overwriting yesterday's `daily.*` is fine — the trend record lives in `_snapshots.jsonl`) and appends a row to that JSONL trend log. The JSONL is the burndown's data source. If `sprint-snapshot` fails (Jira down, sprint config missing, etc.), surface the failure in the console summary, render `_Sprint pulse unavailable — daily snapshot failed._` for `{{burndown_chart}}`, leave the other two pulse placeholders empty, and continue. Never fabricate trend data.

**Step 2 — Read trend data.** Read the team's `_snapshots.jsonl` from `{{output_root}}\<TeamTitleCase>\Scrum 📅\INC {Inc}\Sprint {Sprint}\_snapshots.jsonl`. Parse the rows for the current sprint only (filter by `inc` + `sprint`). Extract `(snapshot_at, remaining, totals.points, by_status.done.points)` per row. Also pull `capacity`, `velocity.last`, and `velocity.avg3` from the most recent row. If the JSONL has fewer than 2 rows for this sprint, the burndown will be a single point — note this in the rendered output, do not fake additional data points.

**Step 3 — Run clarity-council.** Invoke the `clarity-council` skill via `Skill` with three personas: `infographics-expert`, `statistics-expert`, `scrum-master`. Pass the parsed JSONL trend rows, sprint config (`StartDate`, `EndDate`, `Capacity`, `LastSprintVelocity`, `AvgVelocityLast3`), the day count (`day X of 21`), and the current matched-activity summary from Phase 4 as the council's shared context. Ask each persona for a tightly scoped artifact:

- **infographics-expert** → render an **Obsidian Charts plugin** burn-up chart following the **exact shape below**. The chart uses a `chart` code fence (Chart.js backend via the `obsidian-charts` community plugin, which must be installed in the vault). Burn-up (not burn-down) is the canonical shape because it visualizes scope changes explicitly — your sprints regularly take +20–40 pts of scope creep, which burn-down folds into a misleading "remaining" line.

  **Burn-up chart contract** (must be followed verbatim; no creative reinterpretation):

  1. **Determine actual data points.** From the JSONL trend rows, build a list `samples = [(day_n, scope_points, done_points), …]` where `day_n` is integer days since `StartDate` (Pittsburgh local, `day_0 = StartDate`), `scope_points = totals.points`, `done_points = by_status.done.points`. Sort by `day_n`. De-duplicate by `day_n` keeping the latest `snapshot_at` per row.
  2. **Build the x-axis.** Full sprint range — `x_axis = list(range(0, sprint_length_days + 1))` where `sprint_length_days = (EndDate - StartDate).days`. The x-axis always spans day 0 → day `sprint_length_days`, regardless of how far into the sprint we are.
  3. **Build the actual scope series.** For each `day_n` in `x_axis`:
     - If `day_n ≤ today_n` AND a sample exists for `day_n`: use the sample's `scope_points`.
     - If `day_n ≤ today_n` AND no sample exists (weekend/holiday/gap): carry forward the previous day's `scope_points` value (last-known wins).
     - If `day_n > today_n` (future): emit `null`.
  4. **Build the actual done series.** Same logic as scope, but using `done_points`. Day 0 defaults to `0` if no sample exists.
  5. **Build the pinned scope series** (visually distinct future segment). For each `day_n` in `x_axis`:
     - If `day_n < today_n`: emit `null`.
     - If `day_n ≥ today_n`: emit today's actual `scope_points`. (The series starts at today's point so the line connects visually from the actual series.)
  6. **Build the pinned done series.** Same logic as pinned scope, but using `done_points`.
  7. **Build the capacity reference series.** Constant `Capacity` across all `len(x_axis)` days.
  8. **Y-axis bounds.** `y_min = 0`, `y_max = ceil(max(all actual scope values + [Capacity]) * 1.1)` so the highest point isn't flush with the top edge.
  9. **Title.** None at the chart level (the markdown heading carries it). Set the surrounding section heading to `### Burn-up`.
  10. **Validate before emitting.** Confirm all five series have length `len(x_axis)`; confirm `null` is used (not `None` / `NaN` / empty string) for future-day gaps in the "actual" series and past-day gaps in the "pinned" series; confirm every non-null value is finite and inside `[y_min, y_max]`. If any check fails, do not emit the chart — emit a one-line markdown note explaining what failed, and surface it so the run can be debugged.

  **Reference template** (substitute the computed values; this is the only acceptable shape):

  ````markdown
  ### Burn-up

  ```chart
  type: line
  labels: ["0","1","2",...,"<sprint_length_days>"]
  series:
    - title: "Scope (actual)"
      data: [<scope values for days 0..today_n; null for days today_n+1..end>]
    - title: "Done (actual)"
      data: [<done values for days 0..today_n; null for days today_n+1..end>]
    - title: "Scope (pinned — no future data)"
      data: [<null for days 0..today_n-1; today's scope repeated for days today_n..end>]
    - title: "Done (pinned — no future data)"
      data: [<null for days 0..today_n-1; today's done repeated for days today_n..end>]
    - title: "Capacity (<Capacity> pts)"
      data: [<Capacity repeated len(x_axis) times>]
  tension: 0.2
  width: 100%
  labelColors: false
  fill: false
  beginAtZero: true
  bestFit: false
  yTitle: "Points"
  xTitle: "Sprint day (0 = <StartDate>, <sprint_length_days> = <EndDate> close)"
  yMin: 0
  yMax: <y_max>
  ```

  **Reading the chart**:

  - **Solid lines (days 0–<today_n>)** = actual snapshot data. <One-sentence summary of scope trajectory: trim, creep, what drove changes>. Done climbs ~<observed pts/day> pts/day average.
  - **Past gaps** (if any: weekend/holiday days with no snapshot) are carry-forward — value held from the previous day.
  - **Pinned lines (days <today_n>–<sprint_length_days>)** = today's values held flat, NOT a forecast. The visible gap between Scope (<today_scope>) and Done (<today_done>) at day <sprint_length_days> = ~<gap> pts that would land in next sprint at observed cadence.
  - **Capacity (<Capacity> pts)** = horizontal reference; scope is currently <+/-N> vs capacity.
  - **What it would take to close the sprint**: done line would need to climb +<gap> pts in <days_remaining> days (~<required pts/day> pts/day) — <compare to historical velocity>. <Sprint N+1 carry-over recommendation if gap > observed-cadence × days-remaining>.
  ````

  Chart.js (via the `obsidian-charts` plugin) renders each series in a distinct color, so the "actual" and "pinned" segments are visually different even though they share the chart. The `null` values create true gaps in the line, so past/future are visually separate. **Do not** add `chartjs:` style overrides unless the user explicitly asks — the default rendering is the canonical shape.

  **Plugin dependency.** The `chart` code fence requires the `obsidian-charts` community plugin enabled in the vault (folder: `{{vault_root}}/.obsidian/plugins/obsidian-charts/`). On first run, verify via `Glob` that the plugin folder exists. If missing, surface a one-line warning in the console summary and emit the chart anyway — Obsidian will render the raw YAML as a code block until the plugin is installed, which is recoverable. Do not fall back to Mermaid `xychart-beta` — the historical shape is inferior and the user has explicitly chosen Charts plugin going forward.

  **Single-sample case.** If only one trend row exists, day 0 acts as the implicit second point: Scope (actual) = `[<sample.scope>, ..., null after sample.day_n]`, Done (actual) = `[0, ..., <sample.done>, ..., null after sample.day_n]`. The pinned series start at sample.day_n. Caption appends: `_First snapshot at day <N> — trend will fill in over coming days._`
- **statistics-expert** → produce a single-paragraph forecast: project end-of-sprint completed points using simple linear extrapolation from the trend, compare against `Capacity` and `velocity.avg3`, and state a confidence band (e.g. "tracking 5pts under commit, ±3pts based on 3-sprint variance"). Hard cap: ≤4 sentences. No charts — words only. Must explicitly call out if the trend data is too sparse to forecast (≤2 rows).
- **scrum-master** → 1–3 actionable suggestions for the team based on the day's matched activity (Phase 4) + the snapshot bucket counts. Examples: "two tickets in In Review for >3 days — chase reviewers", "Ada has no activity tracked in 2 days — confirm not blocked", "WIP at 8 vs limit 5 — pull from Ready before starting new". Bullet list. No vague platitudes — each suggestion names a ticket, person, or measurable signal.

**Step 4 — Capture artifacts.** Store the three council outputs as the strings `{{burndown_chart}}` (the **Obsidian Charts `chart` code fence including its ` ```chart ` opening and closing ` ``` ` fences**, followed by the "Reading the chart" markdown block — see infographics-expert contract above), `{{forecast_note}}` (the statistics-expert paragraph, plain markdown), and `{{scrum_suggestions}}` (the bullet list, plain markdown). These feed Phase 6. The placeholder name `{{burndown_chart}}` is retained for template-backward-compatibility — its content is a burn-up chart per the v2 contract. The vault-side Standup template at `{{vault_root}}/🗃Templates/Standup.md` does not need to change; the substitution drops the new chart shape into the existing slot.

### Phase 5 — Render sections

Each placeholder in the template gets a string built from the matched data. Formatter contracts (exact markdown shape) live in [REFERENCE.md](REFERENCE.md#section-formatters):

- `{{talking_order}}` — randomized active members (`✅`) first, inactive (`🧽`) last. Each line `- [ ] [[@First Last]] - ✅` (or `🧽`). Multi-team runs append ` (TeamName)` after the wikilink. If `IncludeStandupOrder` is off, leave the placeholder empty.
- `{{jira_state}}` — Mermaid `flowchart` with five status columns (Blocked / Ready / In Progress / In Review / Done). Spec in [REFERENCE.md](REFERENCE.md#mermaid-kanban-spec). If `IncludeKanbanDiagram` is off, empty.
- `{{jira_issues}}` — per-issue blocks with `### KEY - Summary`, type/status/assignee line, then a bulleted activity log of `(date) WHO / WHAT / WHY` plus `text` code fences for details.
- `{{git_updates}}` — per-member blocks `### [[@First Last]]` then `- (date) `hash7` - message` lines, sorted desc by date.
- `{{gitlab_updates}}` — three subsections (`## Merge Request Activity`, `## Comment Activity`, `## Pipeline Activity`) only if non-empty; otherwise the placeholder line `_No GitLab activity found in the specified time period._`.
- `{{confluence_updates}}` — per-page blocks with title + space tag, type/last-modified/author line, link, and a content snippet (first 200 chars of stripped HTML in a `text` code fence).

### Phase 6 — Substitute and write

1. `Read` `{{template_path}}`. The template is the user's master Standup template — never modify it.
2. Replace placeholders **in this exact set** (matches the PS `ConvertTo-TemplateMarkdown` function plus the three Phase 4.5 additions): `{{date}}`, `{{team}}`, `{{sprint}}`, `{{increment}}`, `{{talking_order}}`, `{{jira_state}}`, `{{jira_issues}}`, `{{git_updates}}`, `{{confluence_updates}}`, `{{gitlab_updates}}`, `{{burndown_chart}}`, `{{forecast_note}}`, `{{scrum_suggestions}}`. Date format `YYYY-MM-DD` (Pittsburgh local). Team name written **TitleCase** (first char upper, rest lower) to match the PS `$formattedTeam` line. The three pulse placeholders are no-ops if the user's template doesn't reference them — the substitution still runs cleanly. Note this once when a template lacks them so the user knows to add them if they want the pulse rendered (e.g. under a `## Sprint Pulse` heading containing `{{burndown_chart}}`, then `### Forecast` `{{forecast_note}}`, then `### Suggestions` `{{scrum_suggestions}}`).
3. Compute output path: `{{vault_root}}\Scrum Teams\<TeamTitleCase>\Scrum 📅\INC {Inc}\Sprint {Sprint}\YYYY-MM-DD.md` — note the `📅` emoji is preserved literally; forward slashes work fine in bash on Windows.
4. `mkdir -p` the parent directory.
5. **Never overwrite silently.** If the file exists, `Read` it, then `AskUserQuestion`: `Overwrite` / `Append as ## Re-run HH:MM section` / `Skip save`.
6. `Write` the file. Print the saved path back as a clickable markdown link.

**Sibling-skill delegation in Phase 4.5 / Phase 6:**

- The daily snapshot is **always delegated to `sprint-snapshot` via `Skill`** with `--phase "daily"`. Never reimplement Jira sprint fetching inline — `sprint-snapshot` owns the JQL, identity matching, capacity math, and the JSONL trend schema. If its phase derivation logic doesn't recognize `daily`, pass it through as an explicit `--phase` override (the skill already supports arbitrary phase strings via that flag).
- The pulse council is **always delegated to `clarity-council` via `Skill`** with personas pinned to `infographics-expert + statistics-expert + scrum-master`. Do not author burndown SVG/Mermaid, forecast paragraphs, or suggestions inline — let each persona produce its own artifact under its own constraints.

- If extending the template with new Obsidian Flavored Markdown constructs (callouts, embeds, dataview, block IDs, frontmatter properties beyond `Increment/Sprint/Date`), invoke **`obsidian-markdown`** via `Skill` rather than authoring the syntax inline — it's the canonical reference and stays in sync with vault conventions.
- For **wikilink target verification** (the `[[@First Last]]` rule in talking_order / git_updates), prefer delegating the lookup to **`obsidian-vault`** if the run will need to verify many person notes — its search is more efficient than repeated `Glob` calls. For a single team run (≤15 lookups) the inline `Glob` is fine.
- If the user later wants a **dashboard `.base` file** that aggregates team standups (e.g. "all standups for Pyrite this sprint"), invoke **`obsidian-bases`** rather than hand-authoring the YAML.
- For **CLI-driven vault inspection** (e.g. checking note frontmatter, running a vault-wide property query before deciding whether to overwrite), invoke **`obsidian-cli`** — it has direct property/task access that `Read`+`Glob` cannot replicate cheaply.

### Phase 7 — Console summary

Print a brief block per team (matches the PS `=== REPORT GENERATION SUMMARY ===` block):

```
=== <Team> standup report ===
Team members loaded: N
Jira issues included: N (of M scanned)
Git commits matched: N
GitLab activities matched: N
Confluence activities matched: N
Sprint pulse: snapshot=<ok|failed|skipped> · trend rows=N · council=<ok|failed|skipped>
Saved: <markdown link>
```

## Rules

- **Read-only across Jira, GitLab, Confluence.** Never transition issues, post comments, approve MRs, edit pages, or modify any external state.
- **Read-only on the Git repo.** `fetch` and `log` only — never checkout, pull, or merge.
- **No vault writes outside `Scrum Teams/`.** The template at `🗃Templates/Standup.md` is read-only too.
- **Never overwrite an existing standup file silently.** Always confirm via `AskUserQuestion`.
- **Confirm before persisting to memory.** When populating an `<unset>` value (GitLab base URL, etc.), use the PS-script default as the recommended option but let the user override before writing.
- **Wikilinks for team members only when the vault person note exists.** Use `Glob("{{vault_root}}/🤼 Team/**/@*.md")` to verify before writing `[[@First Last]]`. If the vault has no canonical note for a roster member, render plain text — don't pollute the graph with broken links.
- **TitleCase team names in output.** `pyrite` → `Pyrite` (first char upper, rest lower) — matches the PS `$formattedTeam` line so vault paths and frontmatter stay consistent.
- **Pittsburgh local time.** All `Date` columns and the filename use America/New_York. The `git log` `%aI` and ISO timestamps from Jira/GitLab/Confluence are converted before display.
- **Section toggle semantics match the PS script.** Skipping a section (e.g., `--no-gitlab`) means: don't gather, don't fetch, and substitute an empty string for the placeholder — the template still resolves cleanly.
- **No fabricated data.** If an MCP fails, surface the failure in the console summary and put a `_<system> unavailable_` note in the corresponding section. Do not invent.
- **Sprint pulse is delegated, never inlined.** The `daily` snapshot must come from `sprint-snapshot` and the burndown/forecast/suggestions must come from `clarity-council`'s three pinned personas. Do not author burndown charts, forecasts, or scrum-master suggestions inline — that bypasses the persona constraints (infographics-expert's chartjunk rules, statistics-expert's confidence-band requirement, etc.) that make these artifacts trustworthy.
- **Burndown trend data is read-only and append-only.** Phase 4.5 reads `_snapshots.jsonl` to feed the chart but never edits past rows. Past `daily.canvas` / `daily.md` are allowed to be overwritten — the trend record lives in the JSONL.

## Edge cases

- **No team members loaded** — warn and skip the team. Don't render an empty report (matches PS `if ($teamMembers.Count -eq 0)` guard).
- **Roster CSV missing for one of N teams** — process the others; warn for the missing one.
- **Vault `🤼 Team` folder doesn't have a `@First Last.md` note for a roster member** — render plain `First Last` instead of a wikilink in `{{talking_order}}` and `{{git_updates}}`.
- **Git repo not present at `{{git_repo}}`** — skip git commit collection silently; render an empty `{{git_updates}}` section.
- **Jira sprint label missing** for the team (kanban query returns 0) — render `_No <Team> items found in current sprint._` for `{{jira_state}}`.
- **GitLab `bessemer` group returns 0 projects** — surface a warning; the user may have lost group membership. Render `_No GitLab activity found in the specified time period._`.
- **Re-run on the same day** — same overwrite/append/skip prompt as `daily-briefing`.
- **Multi-team run, partial failure** — generate reports for the teams that succeeded; surface per-team status in the final summary block.
- **Sprint pulse with no prior snapshots** — first daily run of a sprint will only have one trend row after Step 1 writes it. Render the burndown as a single point with a one-line caption (e.g. `_First daily snapshot of Sprint N — trend will fill in over coming days._`) and let the statistics-expert flag the data as too sparse to forecast.
- **Sprint config note missing** — `sprint-snapshot` will prompt to create one. If the user declines, skip the entire pulse phase rather than half-rendering it.

## Related skills

This skill writes into and reads from the user's Obsidian vault. When any vault-related extension or refinement is needed, **delegate to the relevant `obsidian-*` skill via `Skill`** rather than reimplementing — they are the canonical references for vault conventions and stay in sync with Obsidian feature changes.

| Skill | Use it for |
| :--- | :--- |
| `daily-briefing` | Personal (Outlook-driven) morning prep. Sibling skill — both can run on the same morning. Same `{{vault_root}}` resolution pattern. |
| `sprint-snapshot` | **Owns the daily snapshot in Phase 4.5.** Always invoke via `Skill` with `--phase "daily"`; never reimplement Jira sprint fetching, identity matching, or the JSONL trend schema inline. The `_snapshots.jsonl` it writes is the burndown's data source. |
| `clarity-council` | **Owns the pulse council in Phase 4.5.** Always invoke with the three pinned personas (`infographics-expert`, `statistics-expert`, `scrum-master`). Lets each persona enforce its own constraints on the burndown chart, forecast, and suggestions. |
| `issue-suggest-component` | Pattern reference for memory-driven default Jira project + bulk `AskUserQuestion` pacing if the multi-team loop ever grows beyond a handful of teams. |
| `issue-feature-breakdown` | Pattern reference for Jira+Confluence context-gathering depth. |
| `obsidian-markdown` | **Canonical reference for Obsidian Flavored Markdown.** Consult before extending the standup template with new constructs (callouts, embeds, dataview, block IDs, frontmatter properties). |
| `obsidian-vault` | Use for batch wikilink target verification, vault-wide note discovery, and managing the standup index notes. Prefer over repeated `Glob` calls when the run needs >15 lookups. |
| `obsidian-cli` | Use for property/task queries, frontmatter inspection, and any CLI-driven vault operation that `Read` + `Glob` cannot do cheaply. |
| `obsidian-bases` | Use when the user wants a dashboard `.base` file (e.g. "show me all standups for Pyrite this sprint", "which team members had no activity in the last 5 standups"). Don't hand-author `.base` YAML. |
| `obsidian-canvas` | Use if the user wants a visual board summarizing the standup (e.g. a sprint-overview canvas with linked standup notes as nodes). |


