diff --git a/standup/SKILL.md b/standup/SKILL.md new file mode 100644 index 000000000..4085177a8 --- /dev/null +++ b/standup/SKILL.md @@ -0,0 +1,536 @@ +--- +name: standup +preamble-tier: 1 +version: 1.0.0 +description: | + Daily async standup generator. Analyzes recent commits, PR activity, and + TODOS.md to generate a concise standup update: what was done, what's next, + and any blockers. Use when asked for "standup", "daily update", "what did I + do today", or "write my standup". + Proactively invoke when the user asks what they worked on recently, wants to + share a daily update, or needs to summarize yesterday's progress. (gstack) +allowed-tools: + - Bash + - Read + - Glob + - AskUserQuestion +--- + + + +## Preamble (run first) + +```bash +_UPD=$(~/.claude/skills/gstack/bin/gstack-update-check 2>/dev/null || .claude/skills/gstack/bin/gstack-update-check 2>/dev/null || true) +[ -n "$_UPD" ] && echo "$_UPD" || true +mkdir -p ~/.gstack/sessions +touch ~/.gstack/sessions/"$PPID" +_SESSIONS=$(find ~/.gstack/sessions -mmin -120 -type f 2>/dev/null | wc -l | tr -d ' ') +find ~/.gstack/sessions -mmin +120 -type f -exec rm {} + 2>/dev/null || true +_PROACTIVE=$(~/.claude/skills/gstack/bin/gstack-config get proactive 2>/dev/null || echo "true") +_PROACTIVE_PROMPTED=$([ -f ~/.gstack/.proactive-prompted ] && echo "yes" || echo "no") +_BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown") +echo "BRANCH: $_BRANCH" +_SKILL_PREFIX=$(~/.claude/skills/gstack/bin/gstack-config get skill_prefix 2>/dev/null || echo "false") +echo "PROACTIVE: $_PROACTIVE" +echo "PROACTIVE_PROMPTED: $_PROACTIVE_PROMPTED" +echo "SKILL_PREFIX: $_SKILL_PREFIX" +source <(~/.claude/skills/gstack/bin/gstack-repo-mode 2>/dev/null) || true +REPO_MODE=${REPO_MODE:-unknown} +echo "REPO_MODE: $REPO_MODE" +_LAKE_SEEN=$([ -f ~/.gstack/.completeness-intro-seen ] && echo "yes" || echo "no") +echo "LAKE_INTRO: $_LAKE_SEEN" +_TEL=$(~/.claude/skills/gstack/bin/gstack-config get telemetry 2>/dev/null || true) +_TEL_PROMPTED=$([ -f ~/.gstack/.telemetry-prompted ] && echo "yes" || echo "no") +_TEL_START=$(date +%s) +_SESSION_ID="$$-$(date +%s)" +echo "TELEMETRY: ${_TEL:-off}" +echo "TEL_PROMPTED: $_TEL_PROMPTED" +mkdir -p ~/.gstack/analytics +if [ "$_TEL" != "off" ]; then +echo '{"skill":"standup","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi +# zsh-compatible: use find instead of glob to avoid NOMATCH error +for _PF in $(find ~/.gstack/analytics -maxdepth 1 -name '.pending-*' 2>/dev/null); do + if [ -f "$_PF" ]; then + if [ "$_TEL" != "off" ] && [ -x "~/.claude/skills/gstack/bin/gstack-telemetry-log" ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true + fi + rm -f "$_PF" 2>/dev/null || true + fi + break +done +# Learnings count +eval "$(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null)" 2>/dev/null || true +_LEARN_FILE="${GSTACK_HOME:-$HOME/.gstack}/projects/${SLUG:-unknown}/learnings.jsonl" +if [ -f "$_LEARN_FILE" ]; then + _LEARN_COUNT=$(wc -l < "$_LEARN_FILE" 2>/dev/null | tr -d ' ') + echo "LEARNINGS: $_LEARN_COUNT entries loaded" + if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then + ~/.claude/skills/gstack/bin/gstack-learnings-search --limit 3 2>/dev/null || true + fi +else + echo "LEARNINGS: 0" +fi +# Session timeline: record skill start (local-only, never sent anywhere) +~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"standup","event":"started","branch":"'"$_BRANCH"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null & +# Check if CLAUDE.md has routing rules +_HAS_ROUTING="no" +if [ -f CLAUDE.md ] && grep -q "## Skill routing" CLAUDE.md 2>/dev/null; then + _HAS_ROUTING="yes" +fi +_ROUTING_DECLINED=$(~/.claude/skills/gstack/bin/gstack-config get routing_declined 2>/dev/null || echo "false") +echo "HAS_ROUTING: $_HAS_ROUTING" +echo "ROUTING_DECLINED: $_ROUTING_DECLINED" +``` + +If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills AND do not +auto-invoke skills based on conversation context. Only run skills the user explicitly +types (e.g., /qa, /ship). If you would have auto-invoked a skill, instead briefly say: +"I think /skillname might help here — want me to run it?" and wait for confirmation. +The user opted out of proactive behavior. + +If `SKILL_PREFIX` is `"true"`, the user has namespaced skill names. When suggesting +or invoking other gstack skills, use the `/gstack-` prefix (e.g., `/gstack-qa` instead +of `/qa`, `/gstack-ship` instead of `/ship`). Disk paths are unaffected — always use +`~/.claude/skills/gstack/[skill-name]/SKILL.md` for reading skill files. + +If output shows `UPGRADE_AVAILABLE `: read `~/.claude/skills/gstack/gstack-upgrade/SKILL.md` and follow the "Inline upgrade flow" (auto-upgrade if configured, otherwise AskUserQuestion with 4 options, write snooze state if declined). If `JUST_UPGRADED `: tell user "Running gstack v{to} (just updated!)" and continue. + +If `LAKE_INTRO` is `no`: Before continuing, introduce the Completeness Principle. +Tell the user: "gstack follows the **Boil the Lake** principle — always do the complete +thing when AI makes the marginal cost near-zero. Read more: https://garryslist.org/posts/boil-the-ocean" +Then offer to open the essay in their default browser: + +```bash +open https://garryslist.org/posts/boil-the-ocean +touch ~/.gstack/.completeness-intro-seen +``` + +Only run `open` if the user says yes. Always run `touch` to mark as seen. This only happens once. + +If `TEL_PROMPTED` is `no` AND `LAKE_INTRO` is `yes`: After the lake intro is handled, +ask the user about telemetry. Use AskUserQuestion: + +> Help gstack get better! Community mode shares usage data (which skills you use, how long +> they take, crash info) with a stable device ID so we can track trends and fix bugs faster. +> No code, file paths, or repo names are ever sent. +> Change anytime with `gstack-config set telemetry off`. + +Options: +- A) Help gstack get better! (recommended) +- B) No thanks + +If A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry community` + +If B: ask a follow-up AskUserQuestion: + +> How about anonymous mode? We just learn that *someone* used gstack — no unique ID, +> no way to connect sessions. Just a counter that helps us know if anyone's out there. + +Options: +- A) Sure, anonymous is fine +- B) No thanks, fully off + +If B→A: run `~/.claude/skills/gstack/bin/gstack-config set telemetry anonymous` +If B→B: run `~/.claude/skills/gstack/bin/gstack-config set telemetry off` + +Always run: +```bash +touch ~/.gstack/.telemetry-prompted +``` + +This only happens once. If `TEL_PROMPTED` is `yes`, skip this entirely. + +If `PROACTIVE_PROMPTED` is `no` AND `TEL_PROMPTED` is `yes`: After telemetry is handled, +ask the user about proactive behavior. Use AskUserQuestion: + +> gstack can proactively figure out when you might need a skill while you work — +> like suggesting /qa when you say "does this work?" or /investigate when you hit +> a bug. We recommend keeping this on — it speeds up every part of your workflow. + +Options: +- A) Keep it on (recommended) +- B) Turn it off — I'll type /commands myself + +If A: run `~/.claude/skills/gstack/bin/gstack-config set proactive true` +If B: run `~/.claude/skills/gstack/bin/gstack-config set proactive false` + +Always run: +```bash +touch ~/.gstack/.proactive-prompted +``` + +This only happens once. If `PROACTIVE_PROMPTED` is `yes`, skip this entirely. + +If `HAS_ROUTING` is `no` AND `ROUTING_DECLINED` is `false` AND `PROACTIVE_PROMPTED` is `yes`: +Check if a CLAUDE.md file exists in the project root. If it does not exist, create it. + +Use AskUserQuestion: + +> gstack works best when your project's CLAUDE.md includes skill routing rules. +> This tells Claude to use specialized workflows (like /ship, /investigate, /qa) +> instead of answering directly. It's a one-time addition, about 15 lines. + +Options: +- A) Add routing rules to CLAUDE.md (recommended) +- B) No thanks, I'll invoke skills manually + +If A: Append this section to the end of CLAUDE.md: + +```markdown + +## Skill routing + +When the user's request matches an available skill, ALWAYS invoke it using the Skill +tool as your FIRST action. Do NOT answer directly, do NOT use other tools first. +The skill has specialized workflows that produce better results than ad-hoc answers. + +Key routing rules: +- Product ideas, "is this worth building", brainstorming → invoke office-hours +- Bugs, errors, "why is this broken", 500 errors → invoke investigate +- Ship, deploy, push, create PR → invoke ship +- QA, test the site, find bugs → invoke qa +- Code review, check my diff → invoke review +- Update docs after shipping → invoke document-release +- Weekly retro → invoke retro +- Design system, brand → invoke design-consultation +- Visual audit, design polish → invoke design-review +- Architecture review → invoke plan-eng-review +- Save progress, checkpoint, resume → invoke checkpoint +- Code quality, health check → invoke health +``` + +Then commit the change: `git add CLAUDE.md && git commit -m "chore: add gstack skill routing rules to CLAUDE.md"` + +If B: run `~/.claude/skills/gstack/bin/gstack-config set routing_declined true` +Say "No problem. You can add routing rules later by running `gstack-config set routing_declined false` and re-running any skill." + +This only happens once per project. If `HAS_ROUTING` is `yes` or `ROUTING_DECLINED` is `true`, skip this entirely. + +## Voice + +**Tone:** direct, concrete, sharp, never corporate, never academic. Sound like a builder, not a consultant. Name the file, the function, the command. No filler, no throat-clearing. + +**Writing rules:** No em dashes (use commas, periods, "..."). No AI vocabulary (delve, crucial, robust, comprehensive, nuanced, etc.). Short paragraphs. End with what to do. + +The user always has context you don't. Cross-model agreement is a recommendation, not a decision — the user decides. + +## Completion Status Protocol + +When completing a skill workflow, report status using one of: +- **DONE** — All steps completed successfully. Evidence provided for each claim. +- **DONE_WITH_CONCERNS** — Completed, but with issues the user should know about. List each concern. +- **BLOCKED** — Cannot proceed. State what is blocking and what was tried. +- **NEEDS_CONTEXT** — Missing information required to continue. State exactly what you need. + +### Escalation + +It is always OK to stop and say "this is too hard for me" or "I'm not confident in this result." + +Bad work is worse than no work. You will not be penalized for escalating. +- If you have attempted a task 3 times without success, STOP and escalate. +- If you are uncertain about a security-sensitive change, STOP and escalate. +- If the scope of work exceeds what you can verify, STOP and escalate. + +Escalation format: +``` +STATUS: BLOCKED | NEEDS_CONTEXT +REASON: [1-2 sentences] +ATTEMPTED: [what you tried] +RECOMMENDATION: [what the user should do next] +``` + +## Operational Self-Improvement + +Before completing, reflect on this session: +- Did any commands fail unexpectedly? +- Did you take a wrong approach and have to backtrack? +- Did you discover a project-specific quirk (build order, env vars, timing, auth)? +- Did something take longer than expected because of a missing flag or config? + +If yes, log an operational learning for future sessions: + +```bash +~/.claude/skills/gstack/bin/gstack-learnings-log '{"skill":"SKILL_NAME","type":"operational","key":"SHORT_KEY","insight":"DESCRIPTION","confidence":N,"source":"observed"}' +``` + +Replace SKILL_NAME with the current skill name. Only log genuine operational discoveries. +Don't log obvious things or one-time transient errors (network blips, rate limits). +A good test: would knowing this save 5+ minutes in a future session? If yes, log it. + +## Telemetry (run last) + +After the skill workflow completes (success, error, or abort), log the telemetry event. +Determine the skill name from the `name:` field in this file's YAML frontmatter. +Determine the outcome from the workflow result (success if completed normally, error +if it failed, abort if the user interrupted). + +**PLAN MODE EXCEPTION — ALWAYS RUN:** This command writes telemetry to +`~/.gstack/analytics/` (user config directory, not project files). The skill +preamble already writes to the same directory — this is the same pattern. +Skipping this command loses session duration and outcome data. + +Run this bash: + +```bash +_TEL_END=$(date +%s) +_TEL_DUR=$(( _TEL_END - _TEL_START )) +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true +# Session timeline: record skill completion (local-only, never sent anywhere) +~/.claude/skills/gstack/bin/gstack-timeline-log '{"skill":"SKILL_NAME","event":"completed","branch":"'$(git branch --show-current 2>/dev/null || echo unknown)'","outcome":"OUTCOME","duration_s":"'"$_TEL_DUR"'","session":"'"$_SESSION_ID"'"}' 2>/dev/null || true +# Local analytics (gated on telemetry setting) +if [ "$_TEL" != "off" ]; then +echo '{"skill":"SKILL_NAME","duration_s":"'"$_TEL_DUR"'","outcome":"OUTCOME","browse":"USED_BROWSE","session":"'"$_SESSION_ID"'","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true +fi +# Remote telemetry (opt-in, requires binary) +if [ "$_TEL" != "off" ] && [ -x ~/.claude/skills/gstack/bin/gstack-telemetry-log ]; then + ~/.claude/skills/gstack/bin/gstack-telemetry-log \ + --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ + --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & +fi +``` + +Replace `SKILL_NAME` with the actual skill name from frontmatter, `OUTCOME` with +success/error/abort, and `USED_BROWSE` with true/false based on whether `$B` was used. +If you cannot determine the outcome, use "unknown". The local JSONL always logs. The +remote binary only runs if telemetry is not off and the binary exists. + +## Plan Mode Safe Operations + +When in plan mode, these operations are always allowed because they produce +artifacts that inform the plan, not code changes: + +- `$B` commands (browse: screenshots, page inspection, navigation, snapshots) +- `$D` commands (design: generate mockups, variants, comparison boards, iterate) +- `codex exec` / `codex review` (outside voice, plan review, adversarial challenge) +- Writing to `~/.gstack/` (config, analytics, review logs, design artifacts, learnings) +- Writing to the plan file (already allowed by plan mode) +- `open` commands for viewing generated artifacts (comparison boards, HTML previews) + +These are read-only in spirit — they inspect the live site, generate visual artifacts, +or get independent opinions. They do NOT modify project source files. + +## Plan Status Footer + +When you are in plan mode and about to call ExitPlanMode: + +1. Check if the plan file already has a `## GSTACK REVIEW REPORT` section. +2. If it DOES — skip (a review skill already wrote a richer report). +3. If it does NOT — run this command: + +\`\`\`bash +~/.claude/skills/gstack/bin/gstack-review-read +\`\`\` + +Then write a `## GSTACK REVIEW REPORT` section to the end of the plan file: + +- If the output contains review entries (JSONL lines before `---CONFIG---`): format the + standard report table with runs/status/findings per skill, same format as the review + skills use. +- If the output is `NO_REVIEWS` or empty: write this placeholder table: + +\`\`\`markdown +## GSTACK REVIEW REPORT + +| Review | Trigger | Why | Runs | Status | Findings | +|--------|---------|-----|------|--------|----------| +| CEO Review | \`/plan-ceo-review\` | Scope & strategy | 0 | — | — | +| Codex Review | \`/codex review\` | Independent 2nd opinion | 0 | — | — | +| Eng Review | \`/plan-eng-review\` | Architecture & tests (required) | 0 | — | — | +| Design Review | \`/plan-design-review\` | UI/UX gaps | 0 | — | — | + +**VERDICT:** NO REVIEWS YET — run \`/autoplan\` for full review pipeline, or individual reviews above. +\`\`\` + +**PLAN MODE EXCEPTION — ALWAYS RUN:** This writes to the plan file, which is the one +file you are allowed to edit in plan mode. The plan file review report is part of the +plan's living status. + +## 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 ``. + +--- + +# /standup — Daily Async Standup Generator + +Generates a concise standup update from your actual git activity. No guessing — your commits tell the real story. + +## Arguments + +- `/standup` — default: work done since yesterday morning (local midnight) +- `/standup today` — commits from today only +- `/standup 24h` — last 24 hours of activity +- `/standup 48h` — last 48 hours (covers a long weekend) +- `/standup 3d` — last 3 days (use after time off) +- `/standup ` — activity on a specific branch vs base + +## Step 1: Parse arguments and set time window + +Parse the argument: +- No argument or `today` → since yesterday midnight (covers yesterday + today so far) +- `24h` → last 24 hours +- `48h` → last 48 hours +- `3d` → last 3 days +- A branch name → commits on that branch vs base branch + +Compute an absolute start timestamp at local midnight for day-based windows. For +hour-based windows, use `--since="N hours ago"`. This ensures consistent coverage +regardless of what time you run the standup. + +--- + +## Step 2: Collect activity signals + +Run these in order and collect the output. + +**Commits in the time window:** + +```bash +git log --oneline --since="" --format="%h %s (%ai)" 2>/dev/null +``` + +**Pull requests opened or merged in the window:** + +```bash +gh pr list --state all --limit 20 --json number,title,state,createdAt,mergedAt,url 2>/dev/null | \ + python3 -c " +import json,sys +from datetime import datetime +items = json.load(sys.stdin) +cutoff = '' +for pr in items: + created = pr.get('createdAt','') + merged = pr.get('mergedAt','') or '' + if created >= cutoff or merged >= cutoff: + state = 'MERGED' if merged >= cutoff else pr['state'].upper() + print(f' {state} #{pr[\"number\"]}: {pr[\"title\"]} {pr[\"url\"]}') +" 2>/dev/null || echo "(gh not available or no PRs)" +``` + +**In-progress TODOS (if TODOS.md exists):** + +```bash +grep -E "^\s*-\s+\[ \]|\bWIP\b|🚧|in.progress" TODOS.md 2>/dev/null | head -10 || echo "(no TODOS.md)" +``` + +**Current branch and uncommitted work:** + +```bash +git branch --show-current && git status --short 2>/dev/null | head -10 +``` + +--- + +## Step 3: Build the standup + +Analyze the signals from Step 2 and write a standup in this format: + +``` +STANDUP — +════════════════════════════════════════ + +YESTERDAY / DONE + • + • + +TODAY / NEXT + • + • + • + +BLOCKERS + • + • None (if nothing is blocking) + +LINKS + • PR #N: — <url> (if any open PRs) +``` + +**Grouping rules:** +- Cluster commits by topic (e.g., 3 commits touching auth/ → "Refactored auth middleware to use JWT") +- Describe the *outcome*, not the diff ("Added retry logic" not "Changed lines 42-58 in client.ts") +- If there are zero commits in the window, say so plainly: "No commits in the last X hours." +- If on a long-lived feature branch, note the PR number/title as the primary unit of work + +**If the user asked for a specific output format** (Slack, email, Jira comment), adapt the format: +- Slack: shorter bullets, no box-drawing chars, add emoji if appropriate +- Email: add subject line `Standup – <Name> – <Date>` +- Jira: plain text, no markdown + +--- + +## Step 4: Check for blockers + +Scan for signals that something is stuck: + +```bash +# Tests failing? +git stash list 2>/dev/null | head -5 +# Any open review requests? +gh pr list --state open --json number,title,reviewDecision,url 2>/dev/null | \ + python3 -c "import json,sys; prs=json.load(sys.stdin); [print(f' AWAITING_REVIEW #{p[\"number\"]}: {p[\"title\"]}') for p in prs if p.get('reviewDecision') in ('REVIEW_REQUIRED', None)]" 2>/dev/null || true +``` + +If PRs have been open > 48h without review, add to BLOCKERS: "PR #N waiting on review for Xh" + +--- + +## Step 5: Output and copy + +Print the formatted standup. Then ask: + +Use AskUserQuestion: +- **Context:** Standup generated for <date window>. +- **RECOMMENDATION:** Choose A — standup is ready to paste. +- A) Done — I'll copy this to my standup channel +- B) Adjust format — tell me Slack/email/Jira and I'll reformat +- C) Add context — I want to mention something specific + +On B: reformat as requested and re-present. +On C: take the user's input, fold it into the standup, re-present. + +--- + +## Important Rules + +- **Git activity is ground truth.** Don't invent work. If git shows nothing, say nothing happened. +- **No fluff.** No "I had a productive day", no corporate filler. Just what changed and what's next. +- **Cluster, don't list.** 8 commits for one feature = 1 bullet about the feature. +- **One minute to read.** If the standup takes more than 60 seconds to read, it's too long. Trim. +- **Blockers are optional but honest.** If something is stuck, say it. That's the whole point of standups. +- **Local timezone for all timestamps.** Do NOT set TZ. Use the system default. diff --git a/standup/SKILL.md.tmpl b/standup/SKILL.md.tmpl new file mode 100644 index 000000000..f08fbedb5 --- /dev/null +++ b/standup/SKILL.md.tmpl @@ -0,0 +1,170 @@ +--- +name: standup +preamble-tier: 1 +version: 1.0.0 +description: | + Daily async standup generator. Analyzes recent commits, PR activity, and + TODOS.md to generate a concise standup update: what was done, what's next, + and any blockers. Use when asked for "standup", "daily update", "what did I + do today", or "write my standup". + Proactively invoke when the user asks what they worked on recently, wants to + share a daily update, or needs to summarize yesterday's progress. (gstack) +allowed-tools: + - Bash + - Read + - Glob + - AskUserQuestion +--- + +{{PREAMBLE}} + +{{BASE_BRANCH_DETECT}} + +# /standup — Daily Async Standup Generator + +Generates a concise standup update from your actual git activity. No guessing — your commits tell the real story. + +## Arguments + +- `/standup` — default: work done since yesterday morning (local midnight) +- `/standup today` — commits from today only +- `/standup 24h` — last 24 hours of activity +- `/standup 48h` — last 48 hours (covers a long weekend) +- `/standup 3d` — last 3 days (use after time off) +- `/standup <branch>` — activity on a specific branch vs base + +## Step 1: Parse arguments and set time window + +Parse the argument: +- No argument or `today` → since yesterday midnight (covers yesterday + today so far) +- `24h` → last 24 hours +- `48h` → last 48 hours +- `3d` → last 3 days +- A branch name → commits on that branch vs base branch + +Compute an absolute start timestamp at local midnight for day-based windows. For +hour-based windows, use `--since="N hours ago"`. This ensures consistent coverage +regardless of what time you run the standup. + +--- + +## Step 2: Collect activity signals + +Run these in order and collect the output. + +**Commits in the time window:** + +```bash +git log --oneline --since="<absolute-start-timestamp>" --format="%h %s (%ai)" 2>/dev/null +``` + +**Pull requests opened or merged in the window:** + +```bash +gh pr list --state all --limit 20 --json number,title,state,createdAt,mergedAt,url 2>/dev/null | \ + python3 -c " +import json,sys +from datetime import datetime +items = json.load(sys.stdin) +cutoff = '<ISO-start>' +for pr in items: + created = pr.get('createdAt','') + merged = pr.get('mergedAt','') or '' + if created >= cutoff or merged >= cutoff: + state = 'MERGED' if merged >= cutoff else pr['state'].upper() + print(f' {state} #{pr[\"number\"]}: {pr[\"title\"]} {pr[\"url\"]}') +" 2>/dev/null || echo "(gh not available or no PRs)" +``` + +**In-progress TODOS (if TODOS.md exists):** + +```bash +grep -E "^\s*-\s+\[ \]|\bWIP\b|🚧|in.progress" TODOS.md 2>/dev/null | head -10 || echo "(no TODOS.md)" +``` + +**Current branch and uncommitted work:** + +```bash +git branch --show-current && git status --short 2>/dev/null | head -10 +``` + +--- + +## Step 3: Build the standup + +Analyze the signals from Step 2 and write a standup in this format: + +``` +STANDUP — <day, date> +════════════════════════════════════════ + +YESTERDAY / DONE + • <concise summary of each commit cluster — group related commits> + • <merged PR title if any> + +TODAY / NEXT + • <current branch work in progress> + • <top TODOS.md items that are next up> + • <any planned follow-up from recent PR review feedback> + +BLOCKERS + • <any merge conflicts, failing tests, or review backlog> + • None (if nothing is blocking) + +LINKS + • PR #N: <title> — <url> (if any open PRs) +``` + +**Grouping rules:** +- Cluster commits by topic (e.g., 3 commits touching auth/ → "Refactored auth middleware to use JWT") +- Describe the *outcome*, not the diff ("Added retry logic" not "Changed lines 42-58 in client.ts") +- If there are zero commits in the window, say so plainly: "No commits in the last X hours." +- If on a long-lived feature branch, note the PR number/title as the primary unit of work + +**If the user asked for a specific output format** (Slack, email, Jira comment), adapt the format: +- Slack: shorter bullets, no box-drawing chars, add emoji if appropriate +- Email: add subject line `Standup – <Name> – <Date>` +- Jira: plain text, no markdown + +--- + +## Step 4: Check for blockers + +Scan for signals that something is stuck: + +```bash +# Tests failing? +git stash list 2>/dev/null | head -5 +# Any open review requests? +gh pr list --state open --json number,title,reviewDecision,url 2>/dev/null | \ + python3 -c "import json,sys; prs=json.load(sys.stdin); [print(f' AWAITING_REVIEW #{p[\"number\"]}: {p[\"title\"]}') for p in prs if p.get('reviewDecision') in ('REVIEW_REQUIRED', None)]" 2>/dev/null || true +``` + +If PRs have been open > 48h without review, add to BLOCKERS: "PR #N waiting on review for Xh" + +--- + +## Step 5: Output and copy + +Print the formatted standup. Then ask: + +Use AskUserQuestion: +- **Context:** Standup generated for <date window>. +- **RECOMMENDATION:** Choose A — standup is ready to paste. +- A) Done — I'll copy this to my standup channel +- B) Adjust format — tell me Slack/email/Jira and I'll reformat +- C) Add context — I want to mention something specific + +On B: reformat as requested and re-present. +On C: take the user's input, fold it into the standup, re-present. + +--- + +## Important Rules + +- **Git activity is ground truth.** Don't invent work. If git shows nothing, say nothing happened. +- **No fluff.** No "I had a productive day", no corporate filler. Just what changed and what's next. +- **Cluster, don't list.** 8 commits for one feature = 1 bullet about the feature. +- **One minute to read.** If the standup takes more than 60 seconds to read, it's too long. Trim. +- **Blockers are optional but honest.** If something is stuck, say it. That's the whole point of standups. +- **Local timezone for all timestamps.** Do NOT set TZ. Use the system default.