ワンクリックで
announce
Post a customer-voice product update to
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Post a customer-voice product update to
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | announce |
| description | Post a customer-voice product update to |
You've just shipped something via /open-pr. This skill turns it into a short product-voice update in #pf-product-news. Customer value, not engineering detail. Bullets read like "Your first brief now asks how we did" — never like "Added an fte_feedback table with NPS column."
Operates with no arguments. Assume the most recent merged PR on the user's history is the one to announce; confirm before posting.
SLACK_BOT_TOKEN must be set in the environment. The token starts with xoxb-. If missing, stop and tell the user to set it — see the Setup gotcha below before suggesting a fix.#pf-product-news. Channel ID is C0B78CER88H — hardcoded into the API calls below. The bot must be a member of that channel.chat:write, files:write. We do not need channels:read because the channel ID is baked in.argv. Always read it from the environment inside the curl call (-H "Authorization: Bearer $SLACK_BOT_TOKEN") so it never appears in shell history. Per the project secret-handling rule.Claude Code's Bash tool spawns /bin/bash, not zsh — so an export SLACK_BOT_TOKEN=… in ~/.zshrc alone is invisible to this skill. If ${#SLACK_BOT_TOKEN} is 0 and the user says they "set it in zshrc", tell them to either:
~/.bashrc, or~/.profile and source that from both rc files.Then fully restart Claude Code — env vars are inherited at process startup; source ~/.bashrc inside a single Bash tool call doesn't propagate to subsequent calls (each spawns a fresh subshell).
Verify without leaking the value: echo "${#SLACK_BOT_TOKEN}" should print ~56.
git log --merges --oneline -5 origin/main
The most recent merge commit's title typically contains the PR number (Merge pull request #117 ... or a squash-merge subject ending in (#117)). Extract the number. If ambiguous or none found, ask the user for the PR number.
Then pull the canonical inputs:
gh pr view <n> --json title,body,url,mergedAt,number,headRefName — title/body/url.headRefName (e.g. feat/pf-197-... → PF-197).mcp__linear-server__get_issue with that ID — title, description, project, milestone.If the PR is not merged yet, stop and ask the user to confirm they want to announce an unmerged change. Default behavior is post-merge only.
Look in /tmp/ for files named pf-<n>-*.png or <linear-id>-*.png matching this task. Include all of them.
If there are no screenshots, that's fine. Skip image upload, post text-only.
Voice rules — these are not optional:
pnpm tsx scripts/x.ts), no env-var names, no library names, no commit hashes, no branch names. If the change is purely internal and nothing about it can be described without referencing code, that's a signal the post shouldn't exist — re-confirm with the user whether to announce at all.@here so active channel members get a notification. Use the literal mrkdwn token <!here> (Slack renders it as @here and triggers the ping). Place it on the headline line, before the emoji: <!here> 🚀 *<headline>*. Plain-text text fallback should mirror the same prefix so the notification preview reads naturally.Show the draft to the user with AskUserQuestion, offering: Post as-is / Edit / Cancel. If Edit, ask for the revised text; loop until approved.
Example draft for PF-197 (use this as a tonal reference, not a template):
✨ *Your first brief now asks how we did*
• The "Next Brief" countdown on the briefs list is gone — it was promising a cadence we don't have yet, and even when daily sends ship we won't promise a brief every day.
• A new NPS-style feedback panel sits at the bottom of your first brief: score 0–10 + a quick "what landed, what missed" box. Your honest "meh" is more useful than a polite 7.
• Replaced with a small "private beta" banner that sets honest expectations.
_Why this matters: the moment right after the first brief is where the mind-blown reaction either lands or doesn't — we want to capture it, not redirect to a stale countdown._
PR: <url>
Linear: <url>
Screenshots, when present, are embedded inline in the main message via image blocks referencing the uploaded files — not as a thread reply. Use curl directly — no SDK. Write the final chat.postMessage payload to /tmp/announce-payload.json (--data @file keeps the body out of argv and shell history).
Slack's modern external-upload flow (files.upload is deprecated). For each file:
GET https://slack.com/api/files.getUploadURLExternal?filename=<name>&length=<bytes> → returns upload_url + file_id.POST the raw bytes to upload_url:
curl -sS -X POST "$upload_url" --data-binary @"$file"
CRITICAL: do NOT use curl -F "filename=@$file" (multipart form). Slack's external upload URL accepts the multipart POST without complaint but stores a 0-byte file, and files.completeUploadExternal happily returns ok: true with a broken mimetype: "" size mismatch. Use --data-binary only.POST https://slack.com/api/files.completeUploadExternal with body:
{ "files": [ {"id":"<file_id>","title":"<filename>"}, ... ] }
Omit channel_id and thread_ts. The file is uploaded but not yet shared anywhere — it'll be attached to the channel as part of the chat.postMessage call below via slack_file references.Verify the upload didn't silently fail. ok: true is not sufficient. For each returned file, check size > 0 and size matches the local file's byte count. If any file shows size: 0 or mimetype: "", the raw-bytes POST didn't land — re-run step 2 with --data-binary and re-complete. Don't post the message until the size check passes.
Use Block Kit. Append one image block per uploaded file, each referencing the file by id:
{
"channel": "C0B78CER88H",
"unfurl_links": false,
"unfurl_media": false,
"text": "<plain-text fallback for notifications>",
"blocks": [
{ "type": "section", "text": { "type": "mrkdwn", "text": "<!here> 🚀 *<headline>*" } },
{ "type": "section", "text": { "type": "mrkdwn", "text": "• <bullet 1>\n\n• <bullet 2>\n\n• <bullet 3>" } },
{ "type": "section", "text": { "type": "mrkdwn", "text": "_<why this matters line>_" } },
{ "type": "image", "slack_file": { "id": "<file_id>" }, "alt_text": "<short description>" },
{ "type": "context", "elements": [
{ "type": "mrkdwn", "text": "<https://github.com/.../pull/<n>|PR #<n>> · <https://linear.app/.../PF-<n>|PF-<n>>" }
]}
]
}
curl -sS https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
-H "Content-Type: application/json; charset=utf-8" \
--data @/tmp/announce-payload.json
Parse the response; if ok: false, surface the error field and stop. Common failures: not_in_channel (add the bot), invalid_auth (token expired/scope missing), channel_not_found (wrong channel ID), invalid_blocks (file_id wrong or file not in shareable state — re-check step 3's response). Capture ts from the response for the permalink lookup.
After a successful post:
chat.getPermalink with channel=C0B78CER88H and message_ts=<ts>, print the permalink.The channel ID C0B78CER88H is hard-coded above. If the channel is renamed, the ID stays the same — nothing to do. If the channel is recreated, ask the user for the new ID and update the two references in this file (the Channel ID is … line and the chat.postMessage body).
If the bot isn't in the channel (not_in_channel on chat.postMessage), tell the user to /invite @<bot-name> in #pf-product-news — don't try to auto-join.
SLACK_BOT_TOKEN into a heredoc, an echo, or a gh command (it'd land in shell history). Always read from $SLACK_BOT_TOKEN inside the curl invocation only./open-pr. The skill is manual on purpose so the user can read the draft before it goes out.image blocks with slack_file references — the channel reads as one clean update per ship.curl -F to push bytes to Slack's external-upload URL. It silently uploads 0 bytes and files.completeUploadExternal returns ok: true anyway. Always --data-binary @<file>, and always size-check the response.ok: true on files.completeUploadExternal without verifying size > 0 per file. The API is generous about accepting broken uploads.conversations.list). It needs channels:read we didn't grant; the ID is hardcoded for a reason.Pick up a Linear task and execute it on a fresh branch through local validation. Invoked via `/checkout` (no args → pick the highest-priority unblocked issue in the ProductFlash team) or `/checkout <LINEAR-ID>` (e.g. `/checkout PF-12` → work that specific issue). Loads the Linear issue into context, branches from main, executes, validates locally. Stops before PR — use `/open-pr` next to ship. Pairs with CLAUDE.md (Karpathy rules + stack + hard rules — don't restate them here).
Ship the current branch — open a PR, wait for CI, squash-merge into main, and move the Linear issue to Done. Invoked via `/open-pr` (no args; always operates on the current branch). Picks up where `/checkout` left off. Assumes local validation is already done; pairs with CLAUDE.md (Karpathy + hard rules apply).
Create a new Linear task in the ProductFlash team. Invoked via `/task <one-line description>` or `/task` (then ask). Captures title, description, labels, blockers/dependencies, and optionally fast-tracks it (state=Todo + priority=Urgent) so `/checkout` picks it up next. Optimized for low-friction capture — one round of clarifying questions max.
Perform a read-only architecture and code-quality audit of a TypeScript web/SaaS codebase. Covers SOLID (as applicable), DRY/KISS/YAGNI/POLA, file-structure compliance, module coupling & cohesion, colocation, client/server/isomorphism boundaries, abstraction discipline, naming consistency, type-safety hygiene, and error-handling patterns. Produces one ranked report with severity, finding kind (Violation vs Smell), evidence (file:line), and a concrete remediation. Use whenever the user asks for an "arch review", "architecture audit", "code-quality review", "SOLID check", "code smells", "structure review", "DRY/KISS audit", or similar. Never modifies code — read-only by contract.
Perform a comprehensive read-only security audit of a web/SaaS codebase. Covers authentication & tenant isolation, route/endpoint exposure, secret leaks, SQL/ORM injection, XSS/CSRF/SSRF/open redirect, LLM cost-abuse and prompt injection, dependency supply-chain risks, and OWASP basics. Produces a single ranked report with severity, likelihood, impact, evidence (file:line), and remediation. Use whenever the user asks for a "security audit", "security review", "pentest of the code", "look for vulns", "OWASP review", "LLM abuse surface", or similar. Never modifies files — read-only by contract.
Perform a read-only audit of the codebase's test pyramid. Two halves of equal weight: (1) find critical flows lacking coverage; (2) audit *existing* tests for false confidence — shape-only assertions, tautological mocks, skipped/.only blocks, brittle selectors, tests that pass for the wrong reason. A green suite that doesn't exercise behavior is at least as dangerous as a missing one. Assumes Vitest for unit/integration and Playwright for e2e/smoke. Pragmatic by contract: does NOT chase 100% line coverage — focuses on logic, branching, money math, auth/tenant filters, and user-visible flows. Produces one ranked report with severity, finding kind (Gap vs Defect), evidence (file:line), and a concrete suggested fix. Use whenever the user asks for a "test audit", "test coverage review", "test pyramid health check", "audit our tests", "what should we test", or similar. Never modifies test code — read-only by contract.