Installs into .claude/skills of the current project.
Are you the author of Retro?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/alvaroofernaandez-retro)
---
name: retro
preamble-tier: 2
version: 2.0.0
description: Weekly engineering retrospective. (gstack)
allowed-tools:
- Bash
- Read
- Write
- Glob
- AskUserQuestion
triggers:
- weekly retro
- what did we ship
- engineering retrospective
gbrain:
schema: 1
context_queries:
- id: prior-retros
kind: filesystem
# #2552: /retro writes .context/retros/*.json (repo-local; see the save
# step below) — the old ~/.gstack/.../retros/*.md glob matched a
# directory and extension nothing ever writes, so this query was dead.
glob: ".context/retros/*.json"
sort: mtime_desc
limit: 5
render_as: "## Prior retros for this project"
- id: recent-timeline
kind: filesystem
glob: "~/.gstack/projects/{repo_slug}/timeline.jsonl"
tail: 30
render_as: "## Recent timeline events"
- id: recent-learnings
kind: filesystem
glob: "~/.gstack/projects/{repo_slug}/learnings.jsonl"
tail: 10
render_as: "## Recent learnings"
---
<!-- generated by scripts/gstack-patch.sh from gstack upstream. Do not edit by hand:
changes are lost on the next scripts/gstack-sync.sh run. -->
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
<!-- Regenerate: bun run gen:skill-docs -->
## When to invoke this skill
Analyzes commit history, work patterns,
and code quality metrics with persistent history and trend tracking.
Team-aware: breaks down per-person contributions with praise and growth areas.
Use when asked to "weekly retro", "what did we ship", or "engineering retrospective".
Proactively suggest at the end of a work week or sprint.
## Plan Mode Safe Operations
In plan mode, allowed because they inform the plan: `$B`, `$D`, `codex exec`/`codex review`, writes to `~/.gstack/`, writes to the plan file, and `open` for generated artifacts.
## Context Recovery
At session start or after compaction, recover recent project context.
```bash
eval "$(~/.gstack/bin/gstack-slug 2>/dev/null)"
_PROJ="${GSTACK_HOME:-$HOME/.gstack}/projects/${SLUG:-unknown}"
if [ -d "$_PROJ" ]; then
echo "--- RECENT ARTIFACTS ---"
find "$_PROJ/ceo-plans" "$_PROJ/checkpoints" -type f -name "*.md" 2>/dev/null | xargs -r ls -t 2>/dev/null | head -3
[ -f "$_PROJ/${BRANCH:-unknown}-reviews.jsonl" ] && echo "REVIEWS: $(wc -l < "$_PROJ/${BRANCH:-unknown}-reviews.jsonl" | tr -d ' ') entries"
[ -f "$_PROJ/timeline.jsonl" ] && tail -5 "$_PROJ/timeline.jsonl"
if [ -f "$_PROJ/timeline.jsonl" ]; then
_LAST=$(grep "\"branch\":\"${_BRANCH}\"" "$_PROJ/timeline.jsonl" 2>/dev/null | grep '"event":"completed"' | tail -1)
[ -n "$_LAST" ] && echo "LAST_SESSION: $_LAST"
_RECENT_SKILLS=$(grep "\"branch\":\"${_BRANCH}\"" "$_PROJ/timeline.jsonl" 2>/dev/null | grep '"event":"completed"' | tail -3 | grep -o '"skill":"[^"]*"' | sed 's/"skill":"//;s/"//' | tr '\n' ',')
[ -n "$_RECENT_SKILLS" ] && echo "RECENT_PATTERN: $_RECENT_SKILLS"
fi
_LATEST_CP=$(find "$_PROJ/checkpoints" -name "*.md" -type f 2>/dev/null | xargs -r ls -t 2>/dev/null | head -1)
[ -n "$_LATEST_CP" ] && echo "LATEST_CHECKPOINT: $_LATEST_CP"
if [ -f "$_PROJ/decisions.active.json" ]; then
echo "--- ACTIVE DECISIONS (recent, scope-relevant) ---"
~/.gstack/bin/gstack-decision-search --recent 5 2>/dev/null
echo "--- END DECISIONS ---"
fi
echo "--- END ARTIFACTS ---"
fi
```
If artifacts are listed, read the newest useful one. If `LAST_SESSION` or `LATEST_CHECKPOINT` appears, give a 2-sentence welcome back summary. If `RECENT_PATTERN` clearly implies a next skill, suggest it once.
**Cross-session decisions.** If `ACTIVE DECISIONS` are listed, treat them as prior settled calls with their rationale — do not silently re-litigate them; if you're about to reverse one, say so explicitly. Reach for `~/.gstack/bin/gstack-decision-search` whenever a question touches a past decision ("what did we decide / why / did we try"). When you or the user make a DURABLE decision (architecture, scope, tool/vendor choice, or a reversal) — NOT a turn-level or trivial choice — log it with `~/.gstack/bin/gstack-decision-log` (`--supersede <id>` for a reversal). Reliable and local; gbrain not required.
## Completeness Principle — Boil the Ocean
AI makes completeness cheap, so the complete thing is the goal. Recommend full coverage (tests, edge cases, error paths) — boil the ocean one lake at a time. The only thing out of scope is genuinely unrelated work (rewrites, multi-quarter migrations); flag that as separate scope, never as an excuse for a shortcut.
When options differ in coverage, include `Completeness: X/10` (10 = all edge cases, 7 = happy path, 3 = shortcut). When options differ in kind, write: `Note: options differ in kind, not coverage — no completeness score.` Do not fabricate scores.
## Confusion Protocol
For high-stakes ambiguity (architecture, data model, destructive scope, missing context), STOP. Name it in one sentence, present 2-3 options with tradeoffs, and ask. Do not use for routine coding or obvious changes.
## Claimed Limitations Need Evidence
A claimed limitation or requirement ("the API can't do this", "X requires a credential", "that's impossible on this platform") is a material claim. State one only with the verbatim error, the documented statement, or a live probe in hand — pattern-matching a failure to a familiar story is not evidence. When a cheap probe settles the question, run it BEFORE asking the user anything or declaring a step blocked.
## Context Health (soft directive)
During long-running skill sessions, periodically write a brief `[PROGRESS]` summary: done, next, surprises.
If you are looping on the same diagnostic, same file, or failed fix variants, STOP and reassess. Consider escalation or /context-save. Progress summaries must NEVER mutate git state.
## Completion Status Protocol
When completing a skill workflow, report status using one of:
- **DONE** — completed with evidence.
- **DONE_WITH_CONCERNS** — completed, but list concerns.
- **BLOCKED** — cannot proceed; state blocker and what was tried.
- **NEEDS_CONTEXT** — missing info; state exactly what is needed.
Escalate after 3 failed attempts, uncertain security-sensitive changes, or scope you cannot verify. Format: `STATUS`, `REASON`, `ATTEMPTED`, `RECOMMENDATION`.
## Step 0: Detect platform and base branch
First, detect the git hosting platform from the remote URL:
```bash
git remote get-url origin 2>/dev/null
```
- If the URL contains "github.com" → platform is **GitHub**
- If the URL contains "gitlab" → platform is **GitLab**
- Otherwise, check CLI availability:
- `gh auth status 2>/dev/null` succeeds → platform is **GitHub** (covers GitHub Enterprise)
- `glab auth status 2>/dev/null` succeeds → platform is **GitLab** (covers self-hosted)
- Neither → **unknown** (use git-native commands only)
Determine which branch this PR/MR targets, or the repo's default branch if no
PR/MR exists. Use the result as "the base branch" in all subsequent steps.
**If GitHub:**
1. `gh pr view --json baseRefName -q .baseRefName` — if succeeds, use it
2. `gh repo view --json defaultBranchRef -q .defaultBranchRef.name` — if succeeds, use it
**If GitLab:**
1. `glab mr view -F json 2>/dev/null` and extract the `target_branch` field — if succeeds, use it
2. `glab repo view -F json 2>/dev/null` and extract the `default_branch` field — if succeeds, use it
**Git-native fallback (if unknown platform, or CLI commands fail):**
1. `git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's|refs/remotes/origin/||'`
2. If that fails: `git rev-parse --verify origin/main 2>/dev/null` → use `main`
3. If that fails: `git rev-parse --verify origin/master 2>/dev/null` → use `master`
If all fail, fall back to `main`.
Print the detected base branch name. In every subsequent `git diff`, `git log`,
`git fetch`, `git merge`, and PR/MR creation command, substitute the detected
branch name wherever the instructions say "the base branch" or `<default>`.
---
# /retro — Weekly Engineering Retrospective
Generates a comprehensive engineering retrospective analyzing commit history, work patterns, and code quality metrics. Team-aware: identifies the user running the command, then analyzes every contributor with per-person praise and growth opportunities. Designed for a senior IC/CTO-level builder using Claude Code as a force multiplier.
## User-invocable
When the user types `/retro`, run this skill.
## Arguments
- `/retro` — default: last 7 days
- `/retro 24h` — last 24 hours
- `/retro 14d` — last 14 days
- `/retro 30d` — last 30 days
- `/retro compare` — compare current window vs prior same-length window
- `/retro compare 14d` — compare with explicit window
- `/retro global` — cross-project retro across all AI coding tools (7d default)
- `/retro global 14d` — cross-project retro with explicit window
## Section index — Read each section when its situation applies
This skill is a decision-tree skeleton. The steps below point to on-demand
sections. Read a section in full before doing its step; do not work from memory.
| When | Read this section |
|------|-------------------|
| writing the retrospective narrative (Step 14, after all metrics are computed and compared) | `sections/report-format.md` |
## Instructions
Parse the argument to determine the time window. Default to 7 days if no argument given. All times should be reported in the user's **local timezone** (use the system default — do NOT set `TZ`).
**Midnight-aligned windows:** For day (`d`) and week (`w`) units, compute an absolute start date at local midnight, not a relative string. For example, if today is 2026-03-18 and the window is 7 days: the start date is 2026-03-11. Use `--since "2026-03-11T00:00:00"` — the explicit `T00:00:00` suffix ensures git starts from midnight. Without it, git uses the current wall-clock time (e.g., `--since "2026-03-11"` at 11pm means 11pm, not midnight). For week units, multiply by 7 to get days (e.g., `2w` = 14 days back). For hour (`h`) units, use `--since "N hours ago"` since midnight alignment does not apply to sub-day windows. Compute "today" from the user-visible `## currentDate` tag in the session reminder — NEVER from `date` (the system clock can be hours off in containerized harnesses). If you cannot reliably compute "today", stop and ask the user via AskUserQuestion rather than proceeding.
**Argument validation:** If the argument doesn't match a number followed by `d`, `h`, or `w`, the word `compare` (optionally followed by a window), or the word `global` (optionally followed by a window), show this usage and stop:
```
Usage: /retro [window | compare | global]
/retro — last 7 days (default)
/retro 24h — last 24 hours
/retro 14d — last 14 days
/retro 30d — last 30 days
/retro compare — compare this period vs prior period
/retro compare 14d — compare with explicit window
/retro global — cross-project retro across all AI tools (7d default)
/retro global 14d — cross-project retro with explicit window
```
**If the first argument is `global`:** Skip the normal repo-scoped retro (Steps 1-14). Instead, follow the **Global Retrospective** flow at the end of this document. The optional second argument is the time window (default 7d). This mode does NOT require being inside a git repo.
## Prior Learnings
Search for relevant learnings from previous sessions:
```bash
_CROSS_PROJ=$(~/.gstack/bin/gstack-config get cross_project_learnings 2>/dev/null || echo "unset")
echo "CROSS_PROJECT: $_CROSS_PROJ"
if [ "$_CROSS_PROJ" = "true" ]; then
~/.gstack/bin/gstack-learnings-search --limit 10 --cross-project 2>/dev/null || true
else
~/.gstack/bin/gstack-learnings-search --limit 10 2>/dev/null || true
fi
```
If `CROSS_PROJECT` is `unset` (first time): Use AskUserQuestion:
> gstack can search learnings from your other projects on this machine to find
> patterns that might apply here. This stays local (no data leaves your machine).
> Recommended for solo developers. Skip if you work on multiple client codebases
> where cross-contamination would be a concern.
Options:
- A) Enable cross-project learnings (recommended)
- B) Keep learnings project-scoped only
If A: run `~/.gstack/bin/gstack-config set cross_project_learnings true`
If B: run `~/.gstack/bin/gstack-config set cross_project_learnings false`
Then re-run the search with the appropriate flag.
If learnings are found, incorporate them into your analysis. When a review finding
matches a past learning, display:
**"Prior learning applied: [key] (confidence N/10, from [date])"**
This makes the compounding visible. The user should see that gstack is getting
smarter on their codebase over time.
### Step 0.5: Freshness pre-flight (fetch)
Refresh `origin/<default>` so the retro doesn't misreport against a stale local ref. If the repo has no `origin` remote this fails harmlessly — the metrics script (Step 1) falls back to the local branch and its guard lines disclose it:
```bash
git fetch origin <default> --quiet 2>/dev/null \
|| echo "RETRO_FETCH: failed (offline or no remote) — proceeding against last-known refs"
```
Remember whether the fetch succeeded — the stale-base guard in Step 1 only BLOCKs when it did.
### Step 1: Gather Metrics (one command)
All raw data gathering and metric computation runs through `gstack-retro-metrics` — one command instead of a dozen git pipelines. Substitute the base branch detected in Step 0 and the midnight-aligned start computed above:
```bash
_RM="$HOME/.gstack/bin/gstack-retro-metrics"
[ -x "$_RM" ] || _RM=".gstack/bin/gstack-retro-metrics"
"$_RM" --base "<default>" --since "<since>" \
|| echo "RETRO_METRICS: unavailable — stale install (compute metrics manually from the steps below)"
```
Read the labeled `METRIC_NAME: value` lines — they feed every step below. **Degraded mode:** if `RETRO_METRICS_PROTO: 1` is missing from the output, the install is stale; compute each metric manually with git commands, using the metric definitions in Steps 2-11 as the spec.
**Identity:** `USER_NAME` is **"you"** — the person reading this retro. All other authors are teammates. Orient the narrative around this: "your" commits vs teammate contributions.
**Stale-base + bad-today-anchor guard.** The script echoes `GUARD_LATEST_COMMIT: <DATE>` (newest commit on the analyzed ref). If "today" drifts (model session-context error) or the local `origin/<default>` is materially behind the remote, the window returns zero or near-zero commits and the retro would fabricate a coherent-looking narrative from nothing. Evaluate in this order:
1. If `GUARD_REMOTE: none` or `GUARD_HEAD: detached` or the Step 0.5 fetch failed: proceed, but carry the disclosure into the narrative ("offline run, window not freshness-verified") rather than silently misreporting.
2. If the Step 0.5 fetch succeeded AND the `GUARD_LATEST_COMMIT` date is **older than (today − window-days)**: BLOCK with: "Retro window is stale. Latest commit on `origin/<default>` was `<DATE>`, but the window covers `<since>` to `<today>`. This usually means either (a) today's date is wrong in this session or (b) `origin/<default>` is materially behind the remote. Confirm today's date via the session reminder; if today is correct, run `git fetch origin <default>` manually and re-run /retro." Stop the skill until the user resolves.
3. Otherwise, write: "RETRO_GUARD: latest commit `<DATE>` within window — proceeding."
Also check `RETRO_REF`: if it is not `origin/<default>` (local-only repo, missing remote branch), disclose which ref the retro analyzed.
**Metric line reference** (what the script emits):
| Line | Meaning |
|------|---------|
| `COMMIT: hash\|author\|datetime\|+ins/-del\|subject` | One per commit, newest first (capped at 300) — the raw material for narrative anchoring |
| `COMMITS` / `MERGE_COMMITS` / `CONTRIBUTORS` | Window totals on the analyzed ref |
| `INSERTIONS` / `DELETIONS` / `NET_LOC` | Raw LOC |
| `LOGICAL_SLOC_ADDED` | Non-blank, non-comment added lines — the primary code-volume metric |
| `TEST_INSERTIONS` / `TEST_RATIO` | Test LOC (test/spec paths + .test./.spec. suffixes) and its share of insertions |
| `WEIGHTED_COMMITS` | Commits × files-touched, capped at 20 per commit |
| `ACTIVE_DAYS` | Distinct local dates with commits |
| `SESSIONS` / `DEEP_SESSIONS` / `MEDIUM_SESSIONS` / `MICRO_SESSIONS` | 45-minute-gap session detection: deep 50+ min, medium 20-50, micro <20 |
| `TOTAL_ACTIVE_MINUTES` / `AVG_SESSION_MINUTES` / `LOC_PER_SESSION_HOUR` | Session time aggregates (LOC/hour pre-rounded to nearest 50) |
| `COMMIT_TYPES` / `FIX_RATIO` | Conventional-commit prefix mix |
| `COMMIT_SIZE_BUCKETS` | small <100 / medium 100-500 / large 500-1500 / xl 1500+ LOC per commit |
| `HOURS` / `PEAK_HOUR` | Hourly commit histogram (local time), nonzero hours only |
| `FOCUS_SCORE` | % of file changes in the single busiest top-level directory |
| `BIGGEST_COMMIT` | Highest-LOC commit in the window (ship-of-the-week candidate) |
| `HOTSPOT: count file` | Top 10 most-changed files |
| `AUTHOR: name\|commits\|ins\|del\|test_ratio\|top_areas\|types\|peak_hour` | Per-contributor rollup, sorted by commits desc |
| `AUTHOR_BIGGEST: name\|hash\|loc\|subject` | Each contributor's biggest ship |
| `COAUTHOR: hash\|name` / `AI_ASSISTED_COMMITS` | Human co-author credit lines; count of commits with AI trailers |
| `WEEK: wN\|commits\|ins\|del\|test_ratio` | Weekly buckets, w0 = newest (for Step 10 trends) |
| `PR_REFS` / `PRS_REFERENCED` | PR/MR numbers from commit subjects (GitHub #NNN, GitLab !NNN) |
| `TEST_FILES_TOTAL` / `TEST_FILES_CHANGED` / `REGRESSION_TEST_COMMITS` / `REGRESSION_COMMIT` | Test health: repo-wide test file count, test files changed in window, `test(qa):` / `test(design):` / `test: coverage` commits |
| `VERSION_RANGE` | First → last VERSION file value in the window (when tracked) |
| `TEAM_STREAK` / `USER_STREAK` | Consecutive commit days with anchor date (Step 11) |
| `RETRO_CONTEXT` / `GREPTILE_HISTORY` / `TODOS_FILE` / `SKILL_USAGE_LOG` / `EUREKA_LOG` | Presence of optional inputs — Read the ones marked present |
**Optional inputs** (Read each file the script marks `present`):
- `RETRO_CONTEXT: present` → Read `~/.gstack/retro-context.md`. It is user-authored and may contain meeting notes, calendar events, decisions, and other context that doesn't appear in git history. Incorporate it into the retro narrative where relevant.
- `GREPTILE_HISTORY: present` → Read `~/.gstack/greptile-history.md`. Filter entries to the retro window by date. Count by type: `fix`, `fp`, `already-fixed`. Signal ratio = `(fix + already-fixed) / (fix + already-fixed + fp)`. Skip unparseable lines silently; if no entries fall in the window, skip the Greptile metric row.
- `TODOS_FILE: present` → Read `TODOS.md`. Compute: total open TODOs (exclude the `## Completed` section), P0/P1 count, P2 count, items completed this period (Completed entries dated within the window), items added this period (cross-reference `COMMIT:` lines that touched TODOS.md).
- `SKILL_USAGE_LOG: present` → Read `~/.gstack/analytics/skill-usage.jsonl`. Filter to the window by `ts`. Separate skill activations (no `event` field) from hook fires (`event: "hook_fire"`). Aggregate by skill name.
- `EUREKA_LOG: present` → Read `~/.gstack/analytics/eureka.jsonl`. Filter to the window by `ts`. For each eureka moment note the skill that flagged it, the branch, and a one-line summary of the insight.
### Step 2: Compute Metrics
Present these metrics in a summary table, straight from the metric lines:
| Metric | Value |
|--------|-------|
| **Features shipped** (from CHANGELOG + merged PR titles) | N |
| Commits to main | N |
| Weighted commits (`WEIGHTED_COMMITS`) | N |
| Contributors | N |
| PRs merged | N |
| **Logical SLOC added** (`LOGICAL_SLOC_ADDED` — primary code-volume metric) | N |
| Raw LOC: insertions | N |
| Raw LOC: deletions | N |
| Raw LOC: net | N |
| Test LOC (insertions) | N |
| Test LOC ratio | N% |
| Version range | vX.Y.Z.W → vX.Y.Z.W |
| Active days | N |
| Detected sessions | N |
| Avg raw LOC/session-hour | N |
| Greptile signal | N% (Y catches, Z FPs) |
| Test Health | N total tests · M added this period · K regression tests |
**Metric order rationale (V1):** features shipped leads — what users got. Commits
and weighted commits reflect intent-to-ship. Logical SLOC added reflects real
new functionality. Raw LOC is demoted to context because AI inflates it; ten
lines of a good fix is not less shipping than ten thousand lines of scaffold.
See docs/designs/PLAN_TUNING_V1.md §Workstream C.
Then show a **per-author leaderboard** immediately below, from the `AUTHOR:` lines:
```
Contributor Commits +/- Top area
You (garry) 32 +2400/-300 browse/
alice 12 +800/-150 app/services/
bob 3 +120/-40 tests/
```
Sort by commits descending. The current user (`USER_NAME`) always appears first, labeled "You (name)".
Conditional rows (skip each when its input is absent or empty in the window):
```
| Backlog Health | N open (X P0/P1, Y P2) · Z completed this period |
| Skill Usage | /ship(12) /qa(8) /review(5) · 3 safety hook fires |
| Eureka Moments | 2 this period |
```
If eureka moments exist, list them:
```
EUREKA /office-hours (branch: garrytan/auth-rethink): "Session tokens don't need server storage — browser crypto API makes client-side JWT validation viable"
EUREKA /plan-eng-review (branch: garrytan/cache-layer): "Redis isn't needed here — Bun's built-in LRU cache handles this workload"
```
### Step 3: Commit Time Distribution
Render the `HOURS` line as an hourly histogram in local time:
```
Hour Commits ████████████████
00: 4 ████
07: 5 █████
...
```
Identify and call out:
- Peak hours
- Dead zones
- Whether pattern is bimodal (morning/evening) or continuous
- Late-night coding clusters (after 10pm)
### Step 4: Work Session Detection
Sessions are pre-computed with a **45-minute gap** threshold between consecutive commits (`SESSIONS`, `DEEP_SESSIONS` 50+ min, `MEDIUM_SESSIONS` 20-50 min, `MICRO_SESSIONS` <20 min — typically single-commit fire-and-forget). Report:
- Session count and the deep/medium/micro split
- Total active coding time (`TOTAL_ACTIVE_MINUTES`) and average session length
- LOC per hour of active time (`LOC_PER_SESSION_HOUR`)
### Step 5: Commit Type Breakdown
Render `COMMIT_TYPES` (feat/fix/refactor/test/chore/docs) as a percentage bar:
```
feat: 20 (40%) ████████████████████
fix: 27 (54%) ███████████████████████████
refactor: 2 ( 4%) ██
```
Flag if `FIX_RATIO` exceeds 50% — this signals a "ship fast, fix fast" pattern that may indicate review gaps.
### Step 6: Hotspot Analysis
Show the `HOTSPOT` lines (top 10 most-changed files). Flag:
- Files changed 5+ times (churn hotspots)
- Test files vs production files in the hotspot list
- VERSION/CHANGELOG frequency (version discipline indicator)
### Step 7: PR Size Distribution
Report `COMMIT_SIZE_BUCKETS`:
- **Small** (<100 LOC)
- **Medium** (100-500 LOC)
- **Large** (500-1500 LOC)
- **XL** (1500+ LOC)
### Step 8: Focus Score + Ship of the Week
**Focus score:** `FOCUS_SCORE` is the percentage of file changes touching the single most-changed top-level directory (e.g., `app/services/`). Higher score = deeper focused work. Lower score = scattered context-switching. Report as: "Focus score: 62% (app/services/)"
**Ship of the week:** `BIGGEST_COMMIT` is the highest-LOC change in the window. Highlight it:
- PR number (match against `PR_REFS` / the subject) and title
- LOC changed
- Why it matters (infer from commit messages and files touched)
### Step 9: Team Member Analysis
For each contributor (including the current user), the `AUTHOR:` line carries commits, insertions, deletions, test ratio, top areas, commit type mix, and peak hour; `AUTHOR_BIGGEST:` carries their single highest-impact commit. Use the `COMMIT:` lines to anchor everything in actual work.
**For the current user ("You"):** This section gets the deepest treatment. Include all the detail from the solo retro — session analysis, time patterns, focus score. Frame it in first person: "Your peak hours...", "Your biggest ship..."
**For each teammate:** Write 2-3 sentences covering what they worked on and their pattern. Then:
- **Praise** (1-2 specific things): Anchor in actual commits. Not "great work" — say exactly what was good. Examples: "Shipped the entire auth middleware rewrite in 3 focused sessions with 45% test coverage", "Every PR under 200 LOC — disciplined decomposition."
- **Opportunity for growth** (1 specific thing): Frame as a leveling-up suggestion, not criticism. Anchor in actual data. Examples: "Test ratio was 12% this week — adding test coverage to the payment module before it gets more complex would pay off", "5 fix commits on the same file suggest the original PR could have used a review pass."
**If only one contributor (solo repo):** Skip the team breakdown and proceed as before — the retro is personal.
**Co-author credit:** `COAUTHOR:` lines carry human `Co-Authored-By:` trailers — credit those authors for the commit alongside the primary author. AI co-authors (e.g., `noreply@anthropic.com`) are counted in `AI_ASSISTED_COMMITS` instead — track "AI-assisted commits" as a separate metric, never as a team member.
## Capture Learnings
If you discovered a non-obvious pattern, pitfall, or architectural insight during
this session, log it for future sessions:
```bash
~/.gstack/bin/gstack-learnings-log '{"skill":"retro","type":"TYPE","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":N,"source":"SOURCE","files":["path/to/relevant/file"]}'
```
**Types:** `pattern` (reusable approach), `pitfall` (what NOT to do), `preference`
(user stated), `architecture` (structural decision), `tool` (library/framework insight),
`operational` (project environment/CLI/workflow knowledge).
**Sources:** `observed` (you found this in the code), `user-stated` (user told you),
`inferred` (AI deduction), `cross-model` (both Claude and Codex agree).
**Confidence:** 1-10. Be honest. An observed pattern you verified in the code is 8-9.
An inference you're not sure about is 4-5. A user preference they explicitly stated is 10.
**files:** Include the specific file paths this learning references. This enables
staleness detection: if those files are later deleted, the learning can be flagged.
**Only log genuine discoveries.** Don't log obvious things. Don't log things the user
already knows. A good test: would this insight save time in a future session? If yes, log it.
### Step 10: Week-over-Week Trends (if window >= 14d)
If the time window is 14 days or more, use the `WEEK:` lines (w0 = the week containing the newest commit) to show trends:
- Commits per week (total; per-author from the `COMMIT:` lines)
- LOC per week
- Test ratio per week
- Fix ratio per week
### Step 11: Streak Tracking
`TEAM_STREAK` and `USER_STREAK` count consecutive days with at least 1 commit (full history, no cutoff), anchored at the **newest commit date** — not at today, because the script never trusts the system clock. Interpret against today from the session reminder:
- If the anchor date is today or yesterday, the streak is live: "Team shipping streak: 47 consecutive days" / "Your shipping streak: 32 consecutive days"
- If the anchor is older, the streak is broken: report 0 days and note the last shipping day.
### Step 11.5: Shortcut Debt Ledger
Harvest deliberate `gstack-shortcut(...)` markers — the trail left when the user
accepted a Completeness ≤ 7 option (see the AskUserQuestion Format section). Zero
matches is the healthy case, not a failure:
```bash
grep -rn "gstack-shortcut(" . \
--exclude-dir=.git --exclude-dir=node_modules --exclude-dir=vendor \
--exclude-dir=.claude --exclude-dir=dist \
--exclude="SKILL.md" --exclude="*.md.tmpl" 2>/dev/null \
| grep -vE "gstack-shortcut\(dec-(<|\*)" || true
```
(The exclusions keep docs that merely document the convention — generated
SKILL.md, templates, skill installs — out of the ledger, and the trailing
filter drops placeholder forms like `dec-<id>` / `dec-*` that documentation
uses. Judgment call on what survives: discard any hit that quotes or tests
the convention itself — a sample marker in a checklist, resolver source, or
convention test — rather than marking a real cut corner in this repo's code.)
For each hit, one ledger row: `<file>:<line>, <what was simplified>. ceiling: <X>. upgrade: <Y>.`
- Markers carry a decision id (`dec-<id>`): join against `gstack-decision-search`
output — the ledger entry is the source of truth; never double-count a marker
against its resurfaced decision.
- Markers WITHOUT an id: tag `unlinked`.
- Markers naming no upgrade trigger: tag `no-trigger` — those are the ones that
silently rot.
End the section with: `N markers, M with no trigger.` If none: `No shortcut debt. Clean ledger.`
### Step 12: Load History & Compare
Before saving the new snapshot, check for prior retro history:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t .context/retros/*.json 2>/dev/null
```
**If prior retros exist:** Load the most recent one using the Read tool. Calculate deltas for key metrics and include a **Trends vs Last Retro** section:
```
Last Now Delta
Test ratio: 22% → 41% ↑19pp
Sessions: 10 → 14 ↑4
LOC/hour: 200 → 350 ↑75%
Fix ratio: 54% → 30% ↓24pp (improving)
Commits: 32 → 47 ↑47%
Deep sessions: 3 → 5 ↑2
```
**If no prior retros exist:** Skip the comparison section and append: "First retro recorded — run again next week to see trends."
### Step 13: Save Retro History
After computing all metrics (including streak) and loading any prior history for comparison, save a JSON snapshot:
```bash
mkdir -p .context/retros
```
Determine the next sequence number for today (substitute the actual date for `$(date +%Y-%m-%d)`):
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
# Count existing retros for today to get next sequence number
today=$(date +%Y-%m-%d)
existing=$(ls .context/retros/${today}-*.json 2>/dev/null | wc -l | tr -d ' ')
next=$((existing + 1))
# Save as .context/retros/${today}-${next}.json
```
Use the Write tool to save the JSON file with this schema:
```json
{
"date": "2026-03-08",
"window": "7d",
"metrics": {
"commits": 47,
"contributors": 3,
"prs_merged": 12,
"insertions": 3200,
"deletions": 800,
"net_loc": 2400,
"test_loc": 1300,
"test_ratio": 0.41,
"active_days": 6,
"sessions": 14,
"deep_sessions": 5,
"avg_session_minutes": 42,
"loc_per_session_hour": 350,
"feat_pct": 0.40,
"fix_pct": 0.30,
"peak_hour": 22,
"ai_assisted_commits": 32
},
"authors": {
"Garry Tan": { "commits": 32, "insertions": 2400, "deletions": 300, "test_ratio": 0.41, "top_area": "browse/" },
"Alice": { "commits": 12, "insertions": 800, "deletions": 150, "test_ratio": 0.35, "top_area": "app/services/" }
},
"version_range": ["1.16.0.0", "1.16.1.0"],
"streak_days": 47,
"tweetable": "Week of Mar 1: 47 commits (3 contributors), 3.2k LOC, 38% tests, 12 PRs, peak: 10pm",
"greptile": {
"fixes": 3,
"fps": 1,
"already_fixed": 2,
"signal_pct": 83
}
}
```
**Note:** Only include the `greptile` field if `~/.gstack/greptile-history.md` exists and has entries within the time window. Only include the `backlog` field if `TODOS.md` exists. Only include the `test_health` field if test files were found (`TEST_FILES_TOTAL` > 0). If any has no data, omit the field entirely.
Include test health data in the JSON when test files exist:
```json
"test_health": {
"total_test_files": 47,
"tests_added_this_period": 5,
"regression_test_commits": 3,
"test_files_changed": 8
}
```
Include backlog data in the JSON when TODOS.md exists:
```json
"backlog": {
"total_open": 28,
"p0_p1": 2,
"p2": 8,
"completed_this_period": 3,
"added_this_period": 1
}
```
### Step 14: Write the Narrative
> **STOP.** Before writing the retrospective narrative (Step 14, after all metrics are computed and compared), Read `~/.gstack/retro/sections/report-format.md` and execute it
> in full. Do not work from memory — that section is the source of truth for this step.
---
## Global Retrospective Mode
When the user runs `/retro global` (or `/retro global 14d`), follow this flow instead of the repo-scoped Steps 1-14. This mode works from any directory — it does NOT require being inside a git repo.
### Global Step 1: Compute time window
Same midnight-aligned logic as the regular retro. Default 7d. The second argument after `global` is the window (e.g., `14d`, `30d`, `24h`).
### Global Step 2: Run discovery
Locate and run the discovery script using this fallback chain:
```bash
DISCOVER_BIN=""
[ -x ~/.gstack/bin/gstack-global-discover ] && DISCOVER_BIN=~/.gstack/bin/gstack-global-discover
[ -z "$DISCOVER_BIN" ] && [ -x .gstack/bin/gstack-global-discover ] && DISCOVER_BIN=.gstack/bin/gstack-global-discover
[ -z "$DISCOVER_BIN" ] && which gstack-global-discover >/dev/null 2>&1 && DISCOVER_BIN=$(which gstack-global-discover)
[ -z "$DISCOVER_BIN" ] && [ -f bin/gstack-global-discover.ts ] && DISCOVER_BIN="bun run bin/gstack-global-discover.ts"
echo "DISCOVER_BIN: $DISCOVER_BIN"
```
If no binary is found, tell the user: "Discovery script not found. Run `bun run build` in the gstack directory to compile it." and stop.
Run the discovery:
```bash
$DISCOVER_BIN --since "<window>" --format json 2>/tmp/gstack-discover-stderr
```
Read the stderr output from `/tmp/gstack-discover-stderr` for diagnostic info. Parse the JSON output from stdout.
If `total_sessions` is 0, say: "No AI coding sessions found in the last <window>. Try a longer window: `/retro global 30d`" and stop.
### Global Step 3: Run git log on each discovered repo
For each repo in the discovery JSON's `repos` array, find the first valid path in `paths[]` (directory exists with `.git/`). If no valid path exists, skip the repo and note it.
**For local-only repos** (where `remote` starts with `local:`): skip `git fetch` and use the local default branch. Use `git log HEAD` instead of `git log origin/$DEFAULT`.
**For repos with remotes:**
```bash
git -C <path> fetch origin --quiet 2>/dev/null
```
Detect the default branch for each repo: first try `git symbolic-ref refs/remotes/origin/HEAD`, then check common branch names (`main`, `master`), then fall back to `git rev-parse --abbrev-ref HEAD`. Use the detected branch as `<default>` in the commands below.
```bash
# Commits with stats
git -C <path> log origin/$DEFAULT --since="<start_date>T00:00:00" --format="%H|%aN|%ai|%s" --shortstat
# Commit timestamps for session detection, streak, and context switching
git -C <path> log origin/$DEFAULT --since="<start_date>T00:00:00" --format="%at|%aN|%ai|%s" | sort -n
# Per-author commit counts
git -C <path> shortlog origin/$DEFAULT --since="<start_date>T00:00:00" -sn --no-merges
# PR/MR numbers from commit messages (GitHub #NNN, GitLab !NNN)
git -C <path> log origin/$DEFAULT --since="<start_date>T00:00:00" --format="%s" | grep -oE '[#!][0-9]+' | sort -t'#' -k1 | uniq
```
For repos that fail (deleted paths, network errors): skip and note "N repos could not be reached."
### Global Step 4: Compute global shipping streak
For each repo, get commit dates (capped at 365 days):
```bash
git -C <path> log origin/$DEFAULT --since="365 days ago" --format="%ad" --date=format:"%Y-%m-%d" | sort -u
```
Union all dates across all repos. Count backward from today — how many consecutive days have at least one commit to ANY repo? If the streak hits 365 days, display as "365+ days".
### Global Step 5: Compute context switching metric
From the commit timestamps gathered in Step 3, group by date. For each date, count how many distinct repos had commits that day. Report:
- Average repos/day
- Maximum repos/day
- Which days were focused (1 repo) vs. fragmented (3+ repos)
### Global Step 6: Per-tool productivity patterns
From the discovery JSON, analyze tool usage patterns:
- Which AI tool is used for which repos (exclusive vs. shared)
- Session count per tool
- Behavioral patterns (e.g., "Codex used exclusively for myapp, Claude Code for everything else")
### Global Step 7: Aggregate and generate narrative
Structure the output with the **shareable personal card first**, then the full
team/project breakdown below. The personal card is designed to be screenshot-friendly
— everything someone would want to share on X/Twitter in one clean block.
---
**Tweetable summary** (first line, before everything else):
```
Week of Mar 14: 5 projects, 138 commits, 250k LOC across 5 repos | 48 AI sessions | Streak: 52d 🔥
```
## 🚀 Your Week: [user name] — [date range]
This section is the **shareable personal card**. It contains ONLY the current user's
stats — no team data, no project breakdowns. Designed to screenshot and post.
Use the user identity from `git config user.name` to filter all per-repo git data.
Aggregate across all repos to compute personal totals.
Render as a single visually clean block. Left border only — no right border (LLMs
can't align right borders reliably). Pad repo names to the longest name so columns
align cleanly. Never truncate project names.
```
╔═══════════════════════════════════════════════════════════════
║ [USER NAME] — Week of [date]
╠═══════════════════════════════════════════════════════════════
║
║ [N] commits across [M] projects
║ +[X]k LOC added · [Y]k LOC deleted · [Z]k net
║ [N] AI coding sessions (CC: X, Codex: Y, Gemini: Z)
║ [N]-day shipping streak 🔥
║
║ PROJECTS
║ ─────────────────────────────────────────────────────────
║ [repo_name_full] [N] commits +[X]k LOC [solo/team]
║ [repo_name_full] [N] commits +[X]k LOC [solo/team]
║ [repo_name_full] [N] commits +[X]k LOC [solo/team]
║
║ SHIP OF THE WEEK
║ [PR title] — [LOC] lines across [N] files
║
║ TOP WORK
║ • [1-line description of biggest theme]
║ • [1-line description of second theme]
║ • [1-line description of third theme]
║
║ Powered by gstack
╚═══════════════════════════════════════════════════════════════
```
**Rules for the personal card:**
- Only show repos where the user has commits. Skip repos with 0 commits.
- Sort repos by user's commit count descending.
- **Never truncate repo names.** Use the full repo name (e.g., `analyze_transcripts`
not `analyze_trans`). Pad the name column to the longest repo name so all columns
align. If names are long, widen the box — the box width adapts to content.
- For LOC, use "k" formatting for thousands (e.g., "+64.0k" not "+64010").
- Role: "solo" if user is the only contributor, "team" if others contributed.
- Ship of the Week: the user's single highest-LOC PR across ALL repos.
- Top Work: 3 bullet points summarizing the user's major themes, inferred from
commit messages. Not individual commits — synthesize into themes.
E.g., "Built /retro global — cross-project retrospective with AI session discovery"
not "feat: gstack-global-discover" + "feat: /retro global template".
- The card must be self-contained. Someone seeing ONLY this block should understand
the user's week without any surrounding context.
- Do NOT include team members, project totals, or context switching data here.
**Personal streak:** Use the user's own commits across all repos (filtered by
`--author`) to compute a personal streak, separate from the team streak.
---
## Global Engineering Retro: [date range]
Everything below is the full analysis — team data, project breakdowns, patterns.
This is the "deep dive" that follows the shareable card.
### All Projects Overview
| Metric | Value |
|--------|-------|
| Projects active | N |
| Total commits (all repos, all contributors) | N |
| Total LOC | +N / -N |
| AI coding sessions | N (CC: X, Codex: Y, Gemini: Z) |
| Active days | N |
| Global shipping streak (any contributor, any repo) | N consecutive days |
| Context switches/day | N avg (max: M) |
### Per-Project Breakdown
For each repo (sorted by commits descending):
- Repo name (with % of total commits)
- Commits, LOC, PRs merged, top contributor
- Key work (inferred from commit messages)
- AI sessions by tool
**Your Contributions** (sub-section within each project):
For each project, add a "Your contributions" block showing the current user's
personal stats within that repo. Use the user identity from `git config user.name`
to filter. Include:
- Your commits / total commits (with %)
- Your LOC (+insertions / -deletions)
- Your key work (inferred from YOUR commit messages only)
- Your commit type mix (feat/fix/refactor/chore/docs breakdown)
- Your biggest ship in this repo (highest-LOC commit or PR)
If the user is the only contributor, say "Solo project — all commits are yours."
If the user has 0 commits in a repo (team project they didn't touch this period),
say "No commits this period — [N] AI sessions only." and skip the breakdown.
Format:
```
**Your contributions:** 47/244 commits (19%), +4.2k/-0.3k LOC
Key work: Writer Chat, email blocking, security hardening
Biggest ship: PR #605 — Writer Chat eats the admin bar (2,457 ins, 46 files)
Mix: feat(3) fix(2) chore(1)
```
### Cross-Project Patterns
- Time allocation across projects (% breakdown, use YOUR commits not total)
- Peak productivity hours aggregated across all repos
- Focused vs. fragmented days
- Context switching trends
### Tool Usage Analysis
Per-tool breakdown with behavioral patterns:
- Claude Code: N sessions across M repos — patterns observed
- Codex: N sessions across M repos — patterns observed
- Gemini: N sessions across M repos — patterns observed
### Ship of the Week (Global)
Highest-impact PR across ALL projects. Identify by LOC and commit messages.
### 3 Cross-Project Insights
What the global view reveals that no single-repo retro could show.
### 3 Habits for Next Week
Considering the full cross-project picture.
---
### Global Step 8: Load history & compare
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
ls -t ~/.gstack/retros/global-*.json 2>/dev/null | head -5
```
**Only compare against a prior retro with the same `window` value** (e.g., 7d vs 7d). If the most recent prior retro has a different window, skip comparison and note: "Prior global retro used a different window — skipping comparison."
If a matching prior retro exists, load it with the Read tool. Show a **Trends vs Last Global Retro** table with deltas for key metrics: total commits, LOC, sessions, streak, context switches/day.
If no prior global retros exist, append: "First global retro recorded — run again next week to see trends."
### Global Step 9: Save snapshot
```bash
mkdir -p ~/.gstack/retros
```
Determine the next sequence number for today:
```bash
setopt +o nomatch 2>/dev/null || true # zsh compat
today=$(date +%Y-%m-%d)
existing=$(ls ~/.gstack/retros/global-${today}-*.json 2>/dev/null | wc -l | tr -d ' ')
next=$((existing + 1))
```
Use the Write tool to save JSON to `~/.gstack/retros/global-${today}-${next}.json`:
```json
{
"type": "global",
"date": "2026-03-21",
"window": "7d",
"projects": [
{
"name": "gstack",
"remote": "<detected from git remote get-url origin, normalized to HTTPS>",
"commits": 47,
"insertions": 3200,
"deletions": 800,
"sessions": { "claude_code": 15, "codex": 3, "gemini": 0 }
}
],
"totals": {
"commits": 182,
"insertions": 15300,
"deletions": 4200,
"projects": 5,
"active_days": 6,
"sessions": { "claude_code": 48, "codex": 8, "gemini": 3 },
"global_streak_days": 52,
"avg_context_switches_per_day": 2.1
},
"tweetable": "Week of Mar 14: 5 projects, 182 commits, 15.3k LOC | CC: 48, Codex: 8, Gemini: 3 | Focus: gstack (58%) | Streak: 52d"
}
```
---
## Compare Mode
When the user runs `/retro compare` (or `/retro compare 14d`):
1. Run Steps 0.5-1 for the current window (default 7d) using the midnight-aligned start date (same logic as the main retro — e.g., if today is 2026-03-18 and window is 7d, `--since "2026-03-11T00:00:00"`)
2. Run `gstack-retro-metrics` a second time for the immediately prior same-length window, using both `--since` and `--until` with midnight-aligned dates to avoid overlap (e.g., for a 7d window starting 2026-03-11: `--since "2026-03-04T00:00:00" --until "2026-03-11T00:00:00"`)
3. Show a side-by-side comparison table with deltas and arrows
4. Write a brief narrative highlighting the biggest improvements and regressions
5. Save only the current-window snapshot to `.context/retros/` (same as a normal retro run); do **not** persist the prior-window metrics.
## Tone
- Encouraging but candid, no coddling
- Specific and concrete — always anchor in actual commits/code
- Skip generic praise ("great job!") — say exactly what was good and why
- Frame improvements as leveling up, not criticism
- **Praise should feel like something you'd actually say in a 1:1** — specific, earned, genuine
- **Growth suggestions should feel like investment advice** — "this is worth your time because..." not "you failed at..."
- Never compare teammates against each other negatively. Each person's section stands on its own.
- Keep total output around 3000-4500 words (slightly longer to accommodate team sections)
- Use markdown tables and code blocks for data, prose for narrative
- Output directly to the conversation — do NOT write to filesystem (except the `.context/retros/` JSON snapshot)
## Important Rules
- ALL narrative output goes directly to the user in the conversation. The ONLY file written is the `.context/retros/` JSON snapshot.
- The metrics script analyzes `origin/<default>` (not local main which may be stale); when `RETRO_REF` says otherwise, disclose it
- Display all timestamps in the user's local timezone (do not override `TZ`)
- If `COMMITS: 0`, say so and suggest a different window
- Round LOC/hour to nearest 50 (the script pre-rounds `LOC_PER_SESSION_HOUR`)
- Treat merge commits as PR boundaries
- Do not read CLAUDE.md or other docs — this skill is self-contained
- On first run (no prior retros), skip comparison sections gracefully
- **Global mode:** Does NOT require being inside a git repo. Saves snapshots to `~/.gstack/retros/` (not `.context/retros/`). Gracefully skip AI tools that aren't installed. Only compare against prior global retros with the same window value. If streak hits 365d cap, display as "365+ days".