Skip to content
Development
Skill

/gitlab-portfolio

Use when you need a single-pane cross-repo health view across all vault-registered GitLab and GitHub projects. Discovers repos from `_overview.md` frontmatter in `<vault>/01-projects/*/`, aggregates open issues, MRs, critical labels, and stale signals via parallel `glab`/`gh`

From plugin
session-orchestrator
5144 skills14 agents26 commands10 hooks
+1
Install
$ npx -y skills add Kanevry/session-orchestrator --skill gitlab-portfolio --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/gitlab-portfolio

Context preview

The summary Claude sees to decide when to auto-load this skill.

Use when you need a single-pane cross-repo health view across all vault-registered GitLab and GitHub projects. Discovers repos from `_overview.md` frontmatter in `<vault>/01-projects/*/`, aggregates open issues, MRs, critical labels, and stale signals via parallel `glab`/`gh`

SKILL.md

gitlab-portfolio.SKILL.md
name: gitlab-portfolio
description: >
  Use when you need a single-pane cross-repo health view across all vault-registered GitLab and GitHub
  projects. Discovers repos from `_overview.md` frontmatter in `<vault>/01-projects/*/`, aggregates open
  issues, MRs, critical labels, and stale signals via parallel `glab`/`gh` calls, then writes an
  idempotent `_PORTFOLIO.md` dashboard. Runs automatically at session-start Phase 2 when
  `gitlab-portfolio.enabled=true`. Triggers: "show portfolio status", "refresh the portfolio dashboard",
  "which repos have critical issues", "run /portfolio". <example>Context: session-start,
  gitlab-portfolio.enabled=true, vault has 5 registered repos. user: "/session deep" assistant:
  "Portfolio: 3 critical issues across 2 repos — run /portfolio for details. Dashboard written to
  vault/01-projects/_PORTFOLIO.md."</example>
model: sonnet

GitLab Portfolio Skill

> Aggregates cross-repo health signals from vault-registered projects into a single `_PORTFOLIO.md` dashboard — idempotent, opt-in, and `_generator`-marked.

Soul

gitlab-portfolio gives you a single-pane view of your entire project portfolio. It iterates all vault-registered repositories, dispatches parallel CLI calls to `glab` / `gh`, and writes a structured Markdown dashboard at `<vault-dir>/01-projects/_PORTFOLIO.md`. The result is a living status snapshot — updated on demand or at session-start — that never overwrites hand-edited files.

When to use

  • You want a cross-repo issue/MR summary across many projects without opening each GitLab/GitHub UI.
  • Session-start should surface a portfolio health banner (critical open issues, stale repos).
  • A nightly routine should refresh the dashboard automatically via `/portfolio`.
  • You need `--dry-run` preview before committing a refresh to the vault.

When NOT to use

  • Bidirectional sync (writing issues back to GitLab/GitHub) — use `gitlab-ops` for write operations.
  • Single-repo deep-dives — use `/discovery` or direct `glab` commands.
  • Projects not registered in the vault (`_overview.md` or `.vault.yaml`) — unregistered repos are silently skipped.
  • Replacing a hand-authored `_PORTFOLIO.md` — the `_generator` guard prevents overwriting human content.

Phase 1: Discovery

Implementation: `scripts/lib/gitlab-portfolio/aggregator.mjs` — `discoverRepos()`.

Iterates `<vault-dir>/01-projects/*/` subdirectories:

1. **Primary** — Read `_overview.md` YAML frontmatter. Look for `gitlab: <namespace/repo>` and/or `github: <owner/repo>`. Both keys may be present on a single repo. 2. **Fallback** — If `_overview.md` is absent or missing those keys, read `.vault.yaml` at the project root: `spec.links.gitlab` / `spec.links.github`. 3. **Skip** — Directories with neither source are silently skipped.

Output is an array of `RepoDescriptor` objects: `{ slug, gitlab, github }`.

Phase 2: Aggregation

Implementation: `scripts/lib/gitlab-portfolio/aggregator.mjs` — `aggregateAll(repos, config)`.

All repos are fetched in parallel via `Promise.allSettled`. Per-repo CLI dispatch:

# GitLab
glab issue list --repo <namespace/repo> --state opened --output json
glab mr list   --repo <namespace/repo> --state opened --output json

# GitHub
gh issue list --repo <owner/repo> --state open --json number,title,labels,createdAt,updatedAt,milestone
gh pr list    --repo <owner/repo> --state open --json number,title,labels,createdAt,updatedAt

Per-repo summary fields derived from JSON output:

| Field | Derivation | |---|---| | `openIssues` / `openMRs` | array lengths | | `critical` | issues where any label matches `critical-labels` (case-insensitive) | | `stale` | issues where `updatedAt` older than `stale-days` days | | `nextMilestone` | earliest non-null `milestone.title` across open issues | | `lastActivity` | max `updatedAt` across all issues + MRs | | `topIssues` | first 3 open issues sorted by `createdAt` ascending (oldest open first) |

On CLI failure per repo: behaviour is controlled by `mode` (see Error Handling).

Phase 3: Output

Implementation: `scripts/lib/gitlab-portfolio/markdown-writer.mjs` — `renderDashboard(summaries, config)`.

**Output path:** `<vault-dir>/01-projects/_PORTFOLIO.md`. Frontmatter keys: `_generator: session-orchestrator-gitlab-portfolio@1`, `created` (ISO8601, set once), `updated` (ISO8601, refreshed each write), `repos` (count). Atomic write: content built in memory, committed via a single `writeFileSync`.

Phase 4: Idempotency

`parseFrontmatter` and `emitAction` imported from `scripts/lib/vault-mirror/utils.mjs`. Rules:

1. No file → `created`. 2. File exists, `_generator` absent or differs → `skipped-handwritten` (never overwrite human content). 3. File exists, generator matches, `updated` ≤ fresh data → `updated`. 4. File exists, generator matches, `updated` > fresh data → `skipped-noop`.

Stdout action shape: `{"action":"updated","path":"01-projects/_PORTFOLIO.md","repos":16,"critical":3}`

Config

Opt-in via the `gitlab-portfolio:` block in Session Config (`CLAUDE.md` / `AGENTS.md`):

gitlab-portfolio:
  enabled: true
  mode: warn          # warn | strict | off
  stale-days: 30
  critical-labels: ["priority::critical", "priority::high"]

| Field | Default | Meaning | |---|---|---| | `enabled` | `false` | Master switch. | | `mode` | `warn` | `warn` / `strict` / `off` — failure handling; `off` ≡ disabled. | | `stale-days` | `30` | Issues older than N days are flagged stale. | | `critical-labels` | `["priority::critical","priority::high"]` | Label substrings that classify an issue as critical (case-insensitive). |

Security

**`--vault-dir` validation (SEC, GH #44).** The `--vault-dir` CLI argument and `vault-integration.vault-dir` Session Config value are validated against the user's home directory via `validatePathInsideProject` (`scripts/lib/path-utils.mjs`). Both phases apply:

  • **Lexical:** paths containing `..` traversal that resolve outside `os.homedir()` are rejected (`exit 2`)
Read more
Ships withsession-orchestrator

Give your agents a working rhythm. You type three commands: /session reads your repository, your open issues and the last session, proposes what to work on, and waits for your correction.

Get the whole plugin

Other skills on session-orchestrator.