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 for the algorithm and per-section formatter contracts. See EXAMPLES.md for invocation parity with the PS script.
Quick start
/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:
- Look for
{{rosters_dir}}\<Team>.csv (case-insensitive). If present, use it.
- 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.
- On
Copy: mkdir -p "{{rosters_dir}}", then copy the file. Tell the user the new path is the canonical location going forward.
- 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.
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.
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):
list_group_projects(group_id="{{gitlab.bessemer}}") to get all project IDs in the group (subgroups included).
- For each project:
list_merge_requests(project_id, updated_after=<since_iso>, per_page=100).
- 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. 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):
- 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.
- 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.
- 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.
- Build the actual done series. Same logic as scope, but using
done_points. Day 0 defaults to 0 if no sample exists.
- 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.)
- Build the pinned done series. Same logic as pinned scope, but using
done_points.
- Build the capacity reference series. Constant
Capacity across all len(x_axis) days.
- 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.
- Title. None at the chart level (the markdown heading carries it). Set the surrounding section heading to
### Burn-up.
- 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):
### 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:
{{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. 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
Read {{template_path}}. The template is the user's master Standup template — never modify it.
- 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}}).
- 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.
mkdir -p the parent directory.
- Never overwrite silently. If the file exists,
Read it, then AskUserQuestion: Overwrite / Append as ## Re-run HH:MM section / Skip save.
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). |
1---2name: daily-standup-prep3description: 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.4---56# Daily Standup Prep78Per-team standup report. Read-only across Jira, GitLab, Confluence, and Git. Writes one markdown file per team to the Obsidian vault.910This 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.1112## Quick start1314```text15/daily-standup-prep → Pyrite, last 2 days, INC 25 / Sprint 116/daily-standup-prep Onyx → single team17/daily-standup-prep Pyrite,Onyx --days 3 --inc 26 → multi-team, custom window18/daily-standup-prep --no-gitlab --no-confluence → toggle sections off19```2021## Parameters2223| Param | Default | Maps to PS |24| :--- | :--- | :--- |25| `Teams` | `["Pyrite"]` | `-Teams` (string[]) |26| `DaysToLookBack` | `2` | `-DaysToLookBack` |27| `JQL` | auto-built | `-JQL` |28| `GitRepoPath` | `D:\paas\` | `-GitRepoPath` |29| `Inc` | `25` | `-Inc` |30| `Sprint` | `1` | `-Sprint` |31| `IncludeJiraIssues` / `IncludeGitCommits` / `IncludeGitLabActivity` / `IncludeConfluenceActivity` / `IncludeKanbanDiagram` / `IncludeStandupOrder` | all `true` | same |32| `IncludeSprintPulse` | `true` | new — runs Phase 4.5 (daily snapshot + council burndown/forecast/suggestions). Toggle off with `--no-sprint-pulse` |3334JIRA 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.3536## Config resolution (run once at the start of every invocation)3738| Placeholder | Memory file | Default content / how to populate |39| :--- | :--- | :--- |40| `{{vault_root}}` | `reference_obsidian_vault.md` (`**Vault root:**` line) | `C:\Users\chris.adams\dev\gd-pkms\` — already populated |41| `{{jira_project}}` | `reference_jira_default_project.md` (`**Default Jira project key:**` line) | `SC2` — already populated |42| `{{confluence_space}}` | `reference_confluence_default_space.md` | `PP` — already populated |43| `{{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) |44| `{{gitlab.bessemer}}` | same memory, `Common project paths` table | If absent, prompt and add a row: `bessemer` → `bessemer` (group path) |45| `{{template_path}}` | derived | `{{vault_root}}\🗃Templates\Standup.md` (no separate memory) |46| `{{rosters_dir}}` | derived | `{{vault_root}}\Scrum Teams\_rosters\` |47| `{{output_root}}` | derived | `{{vault_root}}\Scrum Teams\` (per-team subfolder appended later) |4849If 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`.5051## Roster bootstrap (first-run only)5253The 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.5455For each team in the run:56571. Look for `{{rosters_dir}}\<Team>.csv` (case-insensitive). If present, use it.582. If missing **and** `D:\powershell-scripting\src\bin\Teams\<Team>.csv` exists, prompt via `AskUserQuestion`:59 *"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`.603. On `Copy`: `mkdir -p "{{rosters_dir}}"`, then copy the file. Tell the user the new path is the canonical location going forward.614. If neither path exists, skip the team and warn — do not invent a roster.6263CSV 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).6465## Workflow (per team)6667Loop 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.6869```text70Per-team progress:71- [ ] Phase 1: Resolve config + load roster72- [ ] Phase 2: Compute window + auto-build JQL73- [ ] Phase 3: Gather data (Jira, Git, GitLab, Confluence) — parallel74- [ ] Phase 4: Match activity to team members + flag HasActivity75- [ ] Phase 4.5: Sprint pulse — daily snapshot + council (burndown / forecast / suggestions)76- [ ] Phase 5: Render section markdown + Mermaid kanban + talking order77- [ ] Phase 6: Substitute template placeholders + write to vault78- [ ] Phase 7: Console summary79```8081### Phase 1 — Config + roster8283Resolve 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.8485### Phase 2 — Window + JQL8687- `activity_since` = midnight (Pittsburgh local) `DaysToLookBack` days ago.88- `since_iso` = `YYYY-MM-DDTHH:mm:ssZ` for GitLab/Confluence cutoffs.89- `since_date` = `YYYY-MM-DD` for `git log --since`.90- If `JQL` arg is empty: build `project = {{jira_project}} AND updated >= startOfDay(-{N}d)` where `N = DaysToLookBack`.9192State the chosen window in one short sentence: *"Gathering Pyrite standup for window 2026-05-11 → 2026-05-13 (2-day lookback)."*9394### Phase 3 — Gather data (parallel)9596Issue these MCP/Bash calls in a single response when section toggles allow:9798- **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.99- **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`.100- **GitLab MRs + discussions** (`IncludeGitLabActivity`):101 1. `list_group_projects(group_id="{{gitlab.bessemer}}")` to get all project IDs in the group (subgroups included).102 2. For each project: `list_merge_requests(project_id, updated_after=<since_iso>, per_page=100)`.103 3. For each MR: `mr_discussions(project_id, merge_request_iid)` capped at 100 notes.104- **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.105- **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")`.106- **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.107108If 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.109110### Phase 4 — Match activity to team members111112Use 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.113114Filter rules:115116- **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.)117- **Git/GitLab/Confluence inclusion** — keep an activity iff its author matches a team member.118- 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.119120### Phase 4.5 — Sprint pulse (daily snapshot + council)121122Skip entirely if `IncludeSprintPulse` is off — substitute empty strings for the three pulse placeholders in Phase 6.123124**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.125126**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.127128**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:129130- **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.131132 **Burn-up chart contract** (must be followed verbatim; no creative reinterpretation):133134 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.135 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.136 3. **Build the actual scope series.** For each `day_n` in `x_axis`:137 - If `day_n ≤ today_n` AND a sample exists for `day_n`: use the sample's `scope_points`.138 - If `day_n ≤ today_n` AND no sample exists (weekend/holiday/gap): carry forward the previous day's `scope_points` value (last-known wins).139 - If `day_n > today_n` (future): emit `null`.140 4. **Build the actual done series.** Same logic as scope, but using `done_points`. Day 0 defaults to `0` if no sample exists.141 5. **Build the pinned scope series** (visually distinct future segment). For each `day_n` in `x_axis`:142 - If `day_n < today_n`: emit `null`.143 - 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.)144 6. **Build the pinned done series.** Same logic as pinned scope, but using `done_points`.145 7. **Build the capacity reference series.** Constant `Capacity` across all `len(x_axis)` days.146 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.147 9. **Title.** None at the chart level (the markdown heading carries it). Set the surrounding section heading to `### Burn-up`.148 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.149150 **Reference template** (substitute the computed values; this is the only acceptable shape):151152 ````markdown153 ### Burn-up154155 ```chart156 type: line157 labels: ["0","1","2",...,"<sprint_length_days>"]158 series:159 - title: "Scope (actual)"160 data: [<scope values for days 0..today_n; null for days today_n+1..end>]161 - title: "Done (actual)"162 data: [<done values for days 0..today_n; null for days today_n+1..end>]163 - title: "Scope (pinned — no future data)"164 data: [<null for days 0..today_n-1; today's scope repeated for days today_n..end>]165 - title: "Done (pinned — no future data)"166 data: [<null for days 0..today_n-1; today's done repeated for days today_n..end>]167 - title: "Capacity (<Capacity> pts)"168 data: [<Capacity repeated len(x_axis) times>]169 tension: 0.2170 width: 100%171 labelColors: false172 fill: false173 beginAtZero: true174 bestFit: false175 yTitle: "Points"176 xTitle: "Sprint day (0 = <StartDate>, <sprint_length_days> = <EndDate> close)"177 yMin: 0178 yMax: <y_max>179 ```180181 **Reading the chart**:182183 - **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.184 - **Past gaps** (if any: weekend/holiday days with no snapshot) are carry-forward — value held from the previous day.185 - **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.186 - **Capacity (<Capacity> pts)** = horizontal reference; scope is currently <+/-N> vs capacity.187 - **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>.188 ````189190 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.191192 **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.193194 **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._`195- **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).196- **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.197198**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.199200### Phase 5 — Render sections201202Each 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):203204- `{{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.205- `{{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.206- `{{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.207- `{{git_updates}}` — per-member blocks `### [[@First Last]]` then `- (date) `hash7` - message` lines, sorted desc by date.208- `{{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._`.209- `{{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).210211### Phase 6 — Substitute and write2122131. `Read` `{{template_path}}`. The template is the user's master Standup template — never modify it.2142. 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}}`).2153. 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.2164. `mkdir -p` the parent directory.2175. **Never overwrite silently.** If the file exists, `Read` it, then `AskUserQuestion`: `Overwrite` / `Append as ## Re-run HH:MM section` / `Skip save`.2186. `Write` the file. Print the saved path back as a clickable markdown link.219220**Sibling-skill delegation in Phase 4.5 / Phase 6:**221222- 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).223- 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.224225- 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.226- 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.227- 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.228- 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.229230### Phase 7 — Console summary231232Print a brief block per team (matches the PS `=== REPORT GENERATION SUMMARY ===` block):233234```235=== <Team> standup report ===236Team members loaded: N237Jira issues included: N (of M scanned)238Git commits matched: N239GitLab activities matched: N240Confluence activities matched: N241Sprint pulse: snapshot=<ok|failed|skipped> · trend rows=N · council=<ok|failed|skipped>242Saved: <markdown link>243```244245## Rules246247- **Read-only across Jira, GitLab, Confluence.** Never transition issues, post comments, approve MRs, edit pages, or modify any external state.248- **Read-only on the Git repo.** `fetch` and `log` only — never checkout, pull, or merge.249- **No vault writes outside `Scrum Teams/`.** The template at `🗃Templates/Standup.md` is read-only too.250- **Never overwrite an existing standup file silently.** Always confirm via `AskUserQuestion`.251- **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.252- **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.253- **TitleCase team names in output.** `pyrite` → `Pyrite` (first char upper, rest lower) — matches the PS `$formattedTeam` line so vault paths and frontmatter stay consistent.254- **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.255- **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.256- **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.257- **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.258- **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.259260## Edge cases261262- **No team members loaded** — warn and skip the team. Don't render an empty report (matches PS `if ($teamMembers.Count -eq 0)` guard).263- **Roster CSV missing for one of N teams** — process the others; warn for the missing one.264- **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}}`.265- **Git repo not present at `{{git_repo}}`** — skip git commit collection silently; render an empty `{{git_updates}}` section.266- **Jira sprint label missing** for the team (kanban query returns 0) — render `_No <Team> items found in current sprint._` for `{{jira_state}}`.267- **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._`.268- **Re-run on the same day** — same overwrite/append/skip prompt as `daily-briefing`.269- **Multi-team run, partial failure** — generate reports for the teams that succeeded; surface per-team status in the final summary block.270- **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.271- **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.272273## Related skills274275This 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.276277| Skill | Use it for |278| :--- | :--- |279| `daily-briefing` | Personal (Outlook-driven) morning prep. Sibling skill — both can run on the same morning. Same `{{vault_root}}` resolution pattern. |280| `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. |281| `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. |282| `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. |283| `issue-feature-breakdown` | Pattern reference for Jira+Confluence context-gathering depth. |284| `obsidian-markdown` | **Canonical reference for Obsidian Flavored Markdown.** Consult before extending the standup template with new constructs (callouts, embeds, dataview, block IDs, frontmatter properties). |285| `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. |286| `obsidian-cli` | Use for property/task queries, frontmatter inspection, and any CLI-driven vault operation that `Read` + `Glob` cannot do cheaply. |287| `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. |288| `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). |289