Skip to content

vt-c-domain-dashboard

Aggregate spec status across all projects in a PM domain (e.g. VisiMatch, VisiFair) and render a meeting-ready Markdown dashboard. Reads each registered project's .design-state.yaml + per-spec state.yaml, applies an owner resolution chain (assignee → git author → "—"), and writes -.md to Obsidian or docs/pm-dashboards/. Use before daily/weekly PM standups when you need a single view across multiple repos in your domain.

Plugin: core-standards
Category: Other
Command: /vt-c-domain-dashboard


/vt-c-domain-dashboard — PM Domain Dashboard

A PM manages a domain (e.g. VisiMatch) that spans multiple projects/repos. This skill aggregates spec status across all those repos into one Markdown view suitable for team standups.

When to Use

  • Before a daily/weekly PM round with a team that owns multiple repos in one product domain.
  • When you need a snapshot of "what is in flight, what is ready, what shipped this month" without flipping between project folders.
  • As the first beat in /vt-c-project-status-sync when domain-level grouping matters (this skill is the reader; that skill aggregates further upstream).

Not for: single-project reporting (use /vt-c-activate --status instead), proposal aggregation (use /vt-c-project-sync), or weekly planning (use /vt-c-weekly-planning).

Prerequisites

  • intake/projects.yaml exists in the toolkit and lists at least one project with either an explicit domain: field or a recognised path prefix (04-VisiMatch*, 04-VisiFair*, 04-VisiArea*, 03-VisiTrans*).
  • Each listed project has a checked-out working copy reachable via its path: entry.
  • Optional: OBSIDIAN_VAULT_PATH environment variable points at an Obsidian vault — the dashboard lands in <vault>/PM-Dashboards/. Falls back to TOOLKIT_ROOT/docs/pm-dashboards/.

Execution

The skill delegates the deterministic aggregation work (filter → walk → render) to a helper script. The skill's job is to interpret the user's request, invoke the script with the right arguments, and present the result.

Step 1: Resolve the domain argument

Read the argument passed to the skill (e.g. VisiMatch). If empty, ask the user which domain (offer the set of distinct domain: values from intake/projects.yaml).

Step 2: Invoke the aggregation script

Run the helper script scripts/aggregate.sh (sibling of this SKILL.md) with the required flags:

bash plugins/core-standards/skills/domain-dashboard/scripts/aggregate.sh \
  --projects-yaml "$TOOLKIT_ROOT/intake/projects.yaml" \
  --domain "$DomainName"

The script prints the path of the written dashboard to stdout. The output filename is always <DomainName>-<YYYY-MM-DD>.md — passing --date YYYY-MM-DD overrides the date (used for deterministic regeneration; tests rely on this).

Verbose mode (inline bug bodies)

When a PM is running the dashboard mid-meeting and wants the full bug body (repro steps, expected/actual behaviour) directly inline rather than opening each bugs/NNN-slug/report.md, pass --with-bug-bodies:

bash plugins/core-standards/skills/domain-dashboard/scripts/aggregate.sh \
  --projects-yaml "$TOOLKIT_ROOT/intake/projects.yaml" \
  --domain "$DomainName" \
  --with-bug-bodies

Each row in the Open Bugs section is followed by a collapsible <details> block with the bug's body (frontmatter stripped, H1 demoted). Cross-cutting bugs are flagged with [cross-cutting] in the summary line. Flag-off output is byte-identical to the Wave 1 baseline — opt-in only.

Step 3: Output location

By default the script writes to:

  1. $OBSIDIAN_VAULT_PATH/PM-Dashboards/<Domain>-<date>.md if OBSIDIAN_VAULT_PATH is set and the dir exists, else
  2. $TOOLKIT_ROOT/docs/pm-dashboards/<Domain>-<date>.md

Override with --output-dir <path> when the PM wants the file elsewhere (e.g. attached to a Granola meeting note).

Step 4: Report the result

Echo the output path to the user, summarise the section counts ("In Progress: 4, Specified: 6, Completed this month: 2"), and offer next actions (open in Obsidian, paste into PM-Weekly transcript template).

Interactive Mode

SPEC-122 / W1.9. Lets a PM walk through every unconfirmed spec in a domain during a meeting and lock in priorities. Skill writes ONLY to pm-priority-overrides/<Domain>.yamlspec.md and .design-state.yaml are never modified by this walk.

Invoke when the user asks to "confirm priorities" or "re-prioritize the {Domain} backlog". The walk is Claude-driven (orchestrates one AskUserQuestion per spec) and uses the --print-unconfirmed-specs script flag to enumerate the input list.

Step I-1: List unconfirmed specs

bash plugins/core-standards/skills/domain-dashboard/scripts/aggregate.sh \
  --print-unconfirmed-specs \
  --projects-yaml "$TOOLKIT_ROOT/intake/projects.yaml" \
  --domain "$DomainName"

Each emitted line is IPC-delimited (\037) with three fields: SPEC-NNN<US>title<US>current_priority. The flag exits 0 with no rows when there's nothing to confirm.

Step I-2: Empty-domain branch

If stdout is empty (zero non-completed specs in the domain), tell the user:

Keine zu priorisierenden Specs in {Domain}.

Then stop — no walk, no override file changes, no dashboard render.

Step I-3: Walk loop

For each emitted line, present the spec with AskUserQuestion and a fixed option set: P0/P1/P2/P3/skip.

AskUserQuestion:
  question:    "{SPEC-NNN}: {title} — currently {priority}. Confirm or change?"
  options:     [P0, P1, P2, P3, skip]

Answer routing:

  • P0 / P1 / P2 / P3 — record the answer by invoking the T10 helper: bash plugins/core-standards/skills/domain-dashboard/scripts/write_override.sh --domain "$DomainName" --spec-id "$SID" --priority "$Answer". The helper applies Decision-4 cleanup automatically (if answer equals the spec.md priority, the entry is removed and the helper emits a visible signal).
  • skip — do nothing. No file is touched. This is a no-op in both states (override present AND override absent): the override file ends byte-identical to its pre-walk state. Skip is the right answer when the PM hasn't formed an opinion yet — the spec stays unconfirmed for the next walk.

Step I-4: Wrap up

After the walk completes, optionally render the updated dashboard:

/vt-c-domain-dashboard {Domain}

The drift markers (P0 ↑ (date), P2 ↓ (date)) in the rendered priority column show which entries the PM just confirmed and how they differ from each spec's original priority:.

How the helper script works

  1. Parse projects.yaml — extract (id, path, domain) per project.
  2. Filter by domain — explicit domain: field wins; otherwise apply path-prefix heuristic (see Prerequisites).
  3. Walk each matching project — read .design-state.yaml specs_status: block, then read each spec's state.yaml to override status and pick up assignee + completed_at.
  4. Resolve owner — chain: per-spec assignee → most recent git log author on feature/spec-N-* branches → "—". Authors starting with Claude are filtered (co-authored commits often have Claude as first author).
  5. Group + sortin_progress first (active work), then specified (ready), then completed filtered to the current month (so the file stays meeting-short). Within each group: P0 → P1 → P2, then numeric SPEC ID.
  6. Render — three Markdown tables with section headers; empty sections are omitted to avoid filler.
  7. Write<output-dir>/<Domain>-<date>.md and echo the path.

Edge Cases

  • Project listed in projects.yaml but path doesn't exist → warning to stderr, project skipped, other projects still rendered.
  • .design-state.yaml missing in a listed project → warning, skip.
  • Malformed YAML in .design-state.yaml → warning, skip (does not crash).
  • Spec listed in .design-state.yaml but its per-spec state.yaml is missing → use the status from .design-state.yaml as fallback.
  • No projects match the domain → header-only dashboard (no rows, no section headers).
  • OBSIDIAN_VAULT_PATH points at a non-existent dir → fall back to docs/pm-dashboards/ silently.

Integration Points

  • intake/projects.yaml — source of truth for project paths + domain: field. Add a new project there to include it in domain dashboards.
  • /vt-c-project-sync — different concern (intake proposals back to toolkit). This skill does not call it.
  • /vt-c-project-status-sync — can consume this dashboard as one of its inputs when building Asana status updates per project.

Non-Goals (v1)

  • Hook / cron auto-refresh — manual invocation only in v1. Deferred to a follow-up spec.
  • Cross-domain dashboards — one domain per invocation.
  • Web UI / interactive filtering — Markdown file only; PMs paste into Obsidian or Granola.
  • Azure DevOps integration — out of scope; this skill operates on local repo state.

Examples

Run on demand before a Tuesday VisiMatch standup:

/vt-c-domain-dashboard VisiMatch

→ writes docs/pm-dashboards/VisiMatch-2026-05-18.md (or $OBSIDIAN_VAULT_PATH/PM-Dashboards/VisiMatch-2026-05-18.md if Obsidian is configured).

Regenerate yesterday's file for an audit trail:

bash plugins/core-standards/skills/domain-dashboard/scripts/aggregate.sh \
  --projects-yaml intake/projects.yaml \
  --domain VisiMatch \
  --date 2026-05-17

Drop the output into a meeting note location:

bash plugins/core-standards/skills/domain-dashboard/scripts/aggregate.sh \
  --projects-yaml intake/projects.yaml \
  --domain VisiFair \
  --output-dir ~/Documents/Granola/PM-VisiFair/

Success Criteria

  • One Markdown file written per invocation, filename <Domain>-<date>.md.
  • File contains only specs from projects whose resolved domain equals the argument.
  • Empty sections are omitted (PMs see only what's actually there).
  • Owner column never contains "Claude*" — the Claude-author filter is non-negotiable; commit attribution should reflect the human.