daily-standup-prep — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited daily-standup-prep (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
/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| 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.
| 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.
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:
{{rosters_dir}}\<Team>.csv (case-insensitive). If present, use it.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.
Copy: mkdir -p "{{rosters_dir}}", then copy the file. Tell the user the new path is the canonical location going forward.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.
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 summaryResolve 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.
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.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)."
Issue these MCP/Bash calls in a single response when section toggles allow:
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.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.IncludeGitLabActivity):list_group_projects(group_id="{{gitlab.bessemer}}") to get all project IDs in the group (subgroups included).list_merge_requests(project_id, updated_after=<since_iso>, per_page=100).mr_discussions(project_id, merge_request_iid) capped at 100 notes.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.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").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.
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:
$isAssignedToTeamMember -or $hasRecentActivity filter.)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.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:
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):
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.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.day_n in x_axis:day_n ≤ today_n AND a sample exists for day_n: use the sample's scope_points.day_n ≤ today_n AND no sample exists (weekend/holiday/gap): carry forward the previous day's scope_points value (last-known wins).day_n > today_n (future): emit null.done_points. Day 0 defaults to 0 if no sample exists.day_n in x_axis:day_n < today_n: emit null.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.)done_points.Capacity across all len(x_axis) days.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.### Burn-up.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
type: line labels: ["0","1","2",...,"<sprint_length_days>"] series:
data: [<scope values for days 0..today_n; null for days today_n+1..end>]
data: [<done values for days 0..today_n; null for days today_n+1..end>]
data: [<null for days 0..today_n-1; today's scope repeated for days today_n..end>]
data: [<null for days 0..today_n-1; today's done repeated for days today_n..end>]
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._
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).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.
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).Read {{template_path}}. The template is the user's master Standup template — never modify it.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}}).{{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.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:
--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).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.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.[[@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.Read+Glob cannot replicate cheaply.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>fetch and log only — never checkout, pull, or merge.🗃Templates/Standup.md is read-only too.AskUserQuestion.<unset> value (GitLab base URL, etc.), use the PS-script default as the recommended option but let the user override before writing.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.pyrite → Pyrite (first char upper, rest lower) — matches the PS $formattedTeam line so vault paths and frontmatter stay consistent.Date columns and the filename use America/New_York. The git log %aI and ISO timestamps from Jira/GitLab/Confluence are converted before display.--no-gitlab) means: don't gather, don't fetch, and substitute an empty string for the placeholder — the template still resolves cleanly._<system> unavailable_ note in the corresponding section. Do not invent.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._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.if ($teamMembers.Count -eq 0) guard).First Last instead of a wikilink in {{talking_order}} and {{git_updates}}.{{git_updates}} section._No <Team> items found in current sprint._ for {{jira_state}}._No GitLab activity found in the specified time period._.daily-briefing._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-snapshot will prompt to create one. If the user declines, skip the entire pulse phase rather than half-rendering it.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). |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.