en un clic
sync-ideas
Match Slack
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
Match Slack
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Basé sur la classification professionnelle SOC
| name | sync-ideas |
| description | Match Slack |
| disable-model-invocation | true |
Scan Slack #ideas for untracked messages. Match each against existing Shortcut stories. Create cards for unmatched ideas. Add reactions, tracked replies, and link-back lines.
Before starting, read these shared sections from box/shortcut-ops.md:
agenterminal.execute_approved or present the
command for the user to run.Retry-After.python3 box/shortcut-cards.py --active --summary
For full JSON with descriptions (needed for semantic matching): drop --summary.
Don't manually curl Shortcut for the card list.
Always refresh the corpus, even if you ran this play earlier in the same session. Cards created during the session won't appear in a cached corpus. Proved 2026-04-22: SC-1726 created in run 1, missing from corpus reused in run 2.
Check the watermark first:
python3 box/watermark.py get play1 --days
If it prints a number, use that as --days. If none, fall back to --days 90.
python3 box/slack-scanner.py --channel C0ADJ4ATJE4 --days N --untracked-only --skip-resolved --summary
Drop --summary for full JSON (including message text for semantic matching).
Drop --skip-resolved to see messages with :white_check_mark: tagged as [RESOLVED].
Don't manually loop over conversations.history + conversations.replies.
Scanner JSON output fields (per message): ts, thread_ts, user,
user_name, date, text, reply_count, has_shortcut_reaction,
has_resolved_reaction, is_tracked, has_shipped_reply,
thread_replies (human replies in the #ideas thread),
bot_replies, human_shortcut_links, files,
cross_channel_links (each with thread_context from the source channel).
Default data window: from watermark, or 90 days if no watermark.
Each top-level Slack message = one idea (threads may contain sub-ideas). Matching is semantic — compare idea text against story titles and descriptions. Title-matching alone is proxy trust. For each idea, search descriptions before proposing a match or declaring no match:
python3 box/shortcut-cards.py --active --description | python3 -c "
import json,sys
terms = ['keyword1', 'keyword2'] # extract from idea text
cards = json.load(sys.stdin)
for c in cards:
blob = (c.get('name','') + ' ' + c.get('description','')).lower()
if any(t in blob for t in terms):
print(f'SC-{c[\"id\"]} | {c[\"name\"][:80]}')
"
Read every candidate that comes back before confirming a match or proposing a
new card. The --summary corpus from step 1 is for orientation, not matching.
Read threads on all messages before proposing skip or match, not just bug reports. Thread replies contain context that changes matching decisions and card content (e.g. "we already have this" or design discussion that shapes the What). The initial message is the headline; real signal is in the thread.
Read all file attachments before presenting analysis. If the scanner output
includes files (screenshots, images), download and read them before forming a
recommendation or proposing a match. Slack text describes what someone observed;
the attachment is the primary record. Proved 2026-04-23: recommendation formed
from text description of keyword save error; screenshot (the actual error toast)
read only after human corrected.
Jam recordings: If an idea or its thread contains a Jam URL (jam.dev/c/...),
note it on the card. For bug reports, pull key findings via Jam MCP (getDetails
first, then getNetworkRequests, getConsoleLogs, getUserEvents) to inform the
"What" section. See the Jam MCP section in box/shortcut-ops.md for tools and pattern.
Thread images: If a message or its thread contains image files (screenshots,
mockups, recordings), download them, upload to Shortcut via POST /api/v3/files
(route through execute_approved), and embed in the card description using
. Pass the file IDs in file_ids when creating the
story. Screenshots are primary evidence for UX issues — a card without the image
forces the reader to chase the Slack thread. Check files on all thread messages,
not just the parent.
Cross-channel links: The scanner detects permalink URLs pointing to other
channels, resolves channel names, and fetches the full source thread content
inline. In summary output these show as [CROSS-LINK: #channel-name]. In JSON
output, each message's cross_channel_links array contains thread_context
(array of {user, user_name, text}) for each link. If the bot isn't in the
linked channel, access_error: "not_in_channel" is set and thread_context
is empty — add the bot to that channel and re-scan.
Slack attachments: Messages may contain quoted/attached content (shared from
private conversations or other channels). Always check attachments and blocks
for the real idea text, not just the top-level text field.
Released story matches: If an idea matches a Released story, still track it
normally (:shortcut: reaction + "Tracked" reply). Play 6 handles "This shipped!"
notifications separately.
Present likely matches to Paul one at a time for confirmation.
Gate question -> TodoWrite before asking. When presenting an item that requires Paul's decision (match/skip/create/rescope), TodoWrite "Process the answer to '[verbatim question]' before acting on it" BEFORE asking the question. Do not make tool calls until the todo is resolved by a human turn. Proved 2026-04-29: 3 gate bypasses in one session, 0 self-caught.
When tracking against an existing card with a distinct idea: Proactively propose
adding a comment to the card capturing the new angle, symptom, or use case. Plain
tracking loses the signal. Comment format:
**[Attribution (who, date)](slack_permalink):** Distinct idea or finding.
Investigation accounting before drafting. Before writing a card draft for any item, produce an accounting line: source thread fetched (Y/N), PR search run (Y/N), closest corpus card read (Y/N). This catches batch momentum — investigation depth compresses on the second and third items when the work feels similar. Proved 2026-04-23: item 3 skipped source thread fetch and PR search that item 2 received.
Check repo for existing work: Before creating a card, search for related
PRs and recent branches: gh pr list --repo tailwind/aero --search "keywords" --state all. If a
PR exists, note it in the card (add the PR URL to external_links) and
adjust the workflow state (Build if PR is open, Test if merged but not
released). This applies to bugs and features equally — card search tells you
what's tracked, not what's in progress. Proved 2026-04-15: billing statements
bug had open PR #3273; card would have gone to In Definition without the check.
Bug cards: grep before prescribing fixes in Architecture Context. For bugs, grep the codebase for the error message string or handler name before writing the Architecture Context section. One targeted search moves "likely" into a sourced claim and prevents prescribing a fix based on how the code probably works. Proved 2026-04-23: SC-1797 prescribed a two-part fix without grepping for "Unable to save keywords".
New cards go to "In Definition" state, owned by Paul, no team assignment.
Always construct JSON payloads with Python json.dumps() — bash string
interpolation breaks on quotes, newlines, and markdown in descriptions.
| Constant | Value |
|---|---|
| Paul's member ID | 698124a4-138b-4743-abd6-ec12c64d9262 |
| In Definition state | 500000022 |
| Need Requirements state | 500000024 |
| Backlog state | 500000019 |
| Product Area field ID | 69812486-120c-4e45-a64e-2662ab423eea |
| Priority field ID | 69812486-9e27-4319-a506-94f3b0516aa8 |
| Severity field ID | 69812486-0261-4fb0-aba6-67ad499b4aee |
| Product Area | value_id | Keywords / signals |
|---|---|---|
| SMARTPIN | 69efc6f0-9d90-47ef-8b72-1f415370476c | smartpin, smart pin, template, pin generation, AI pin, design customization, text overlay, branded color, style tone, URL page, sitemap, bulk activation |
| PIN SCHEDULER | 69efc6f0-e2b9-4b68-a5d2-3d88ac4bd026 | scheduler, schedule pin, bulk edit, bulk delete, pin draft, date range, carousel, image grid, pin from URL, alt text, SEO filename |
| TURBO | 69efc6f0-b643-4968-963a-3ee82cb58fd3 | turbo, boost, engagement, turbo feed, turbo queue, turbo onboarding, auto-queue, auto-renew, moderation |
| KEYWORD RESEARCH | 69efc6f0-0a59-4928-8489-d2346c7a52e3 | keyword, keyword search, keyword plan, commercial intent, CSV import, saved keywords |
| EXTENSION | 69efc6f0-400f-4027-afd2-0351cd23a210 | extension, browser extension, visit site, outbound click, turbo extension |
| CREDITS | 69efc6f0-f99d-46bb-929c-737d7fd46760 | credit, credit cost, credit refresh, credit consumption, credit visibility |
| META | 69efc6f0-282a-4090-958b-83844d537ac2 | instagram, facebook, IG, FB, meta, grid planner |
| NAV | 69efc6f0-66d4-46ce-bf58-edb6ab395cf1 | navigation, nav, product-focused nav |
| BILLING | 69efc6f0-388f-46aa-8c2e-df1a3f4f94ab | billing, subscription, cancel, past-due, invoice |
| API/MCP | 69efc6f0-f762-4f53-9369-d1bb572f1598 | API, MCP, ChatGPT, integration, workflow, app store |
| M4U | 69efc6f0-69d8-4162-87ee-c60586d1a649 | made for you, generate a pin, pin from URL, URL scrape, labs, ghostwriter pin |
| GHOSTWRITER | 69efc6f0-b5e2-47dc-8066-786ce24c3323 | ghostwriter, ghost writer, GW, bulk ghostwriter, AI generation stuck, generation failed |
| ONBOARDING | 69efc6f0-e4cc-49ba-88e9-7c46157cff4e | onboarding, signup, sign up, first run, welcome, use case selection, redirect after signup |
| INSIGHTS | 69efc6f0-f8b5-44ff-8c53-a446a8acd579 | health score, account health, analytics dashboard, performance grade, pinning score, diagnostics |
| CATALOG | 69efc6f0-b03f-4932-aa11-57aedf3f45e0 | homepage, marketing site, landing page, catalog |
| Marketing / Growth / Data | 69efc6f0-82c3-440c-aa73-b15fd0c0cd21 | marketing, growth, data, affiliates, SEO, BuiltWith, leads |
| INTEGRATIONS | 69efc6f0-d7b5-42a0-9cf3-4304de680495 | canva, integration, third-party, import, export |
| INFRA | 69efc6f0-cfbb-497f-b87d-d5ab39047586 | infrastructure, migration, backend, performance, lambda, queue |
| SITE | 69efc6f0-4db5-479b-b20a-d9f668e2faeb | smart.bio, link in bio, profile URL |
| USER | 69efc6f0-0c76-4678-95a5-fa74af51e619 | user settings, profile, account settings, login, password, authentication, timezone, email preferences |
| POSTHOG | 69efc5ee-aa96-4e8a-9782-ed951e5c00b4 | posthog, tracking, instrumentation, event tracking, telemetry, dashboard insight, funnel visibility, tracking gap |
Infer Product Area from idea text using keyword matching.
Note: The
## ...lines inside the code block below are Shortcut card description headers (Shortcut renders markdown). They are template content inside a Python string, not document headings.
# Step 1: Write a .py file that builds the payload
cat > /tmp/ff-$AGENTERMINAL_SESSION_ID/sc_create_payload.py << 'PYEOF'
import json
import os
# The ## headers below are Shortcut card template sections (rendered as H2 in the card)
description = r'''[Link to original idea in Slack](PERMALINK)
## "What"
Description here...
## Evidence
-
## Architecture Context
-
## UI Representation (Wireframes, descriptions of visuals, etc.)
-
## Monetization Angle
-
## Reporting Needs/Measurement Plan
-
## Release Strategy
-'''
payload = {
'name': 'Card title here',
'description': description,
'story_type': 'feature', # or 'bug'
'workflow_state_id': 500000022, # In Definition
'owner_ids': ['698124a4-138b-4743-abd6-ec12c64d9262'], # Paul
'external_links': ['SLACK_PERMALINK'],
'custom_fields': [
{
'field_id': '69812486-120c-4e45-a64e-2662ab423eea',
'value_id': 'PRODUCT_AREA_VALUE_ID'
}
]
}
session_id = os.environ['AGENTERMINAL_SESSION_ID']
with open(f'/tmp/ff-{session_id}/sc_create_payload.json', 'w') as f:
json.dump(payload, f)
PYEOF
python3 /tmp/ff-$AGENTERMINAL_SESSION_ID/sc_create_payload.py
# Step 2: Create via execute_approved using the wrapper
python3 box/sc-create-story.py /tmp/ff-$AGENTERMINAL_SESSION_ID/sc_create_payload.json
r'''...''' for the description string to avoid backslash escapingcustom_fields entirely if no Product Area appliessc-create-story.py handles deduplication and post-create verification internallyTie-break priority (most specific wins): GHOSTWRITER > PIN SCHEDULER (for ghostwriter-specific issues), EXTENSION > TURBO, KEYWORD RESEARCH > SMARTPIN > M4U > PIN SCHEDULER, CREDITS > BILLING. When ambiguous, propose best guess and flag it.
Card settings:
story_type: bug for bug reports, feature for ideas (default)external_links: Slack permalink URL (enables has:external-link filtering).
The markdown link stays in the description too (visible when reading the card).
Both serve different purposes.external_links entry per source
message. The description permalink points to the primary idea.Slack user ID resolution: the scanner resolves names automatically. For IDs
outside scanner output, call users.info?user=UXXXXX
(see reference/tooling-logistics.md). Don't guess names from IDs.
Tracking gap cards: When a #ideas message describes an instrumentation or tracking gap (missing PostHog events, properties without diagnostic value, blind spots in release impact insights), create a standalone card for it.
bug (the instrumentation is wrong or missing)approve_content with content_type: "card-draft",
filename: "scNNN-stub". The user can edit in the modal. Saved file is
compaction insurance.slack-mutate.py via execute_approvedshortcut-mutate.py prepend-description via execute_approvedshortcut-mutate.py add-link via execute_approvedshortcut-mutate.py move via execute_approvedPre-approval checklist (before every approve_content call):
Verify story creation first (mandatory, sequential): After creating a story
via execute_approved, GET /api/v3/stories/{id} and confirm title, workflow state,
and product area before any Slack mutations. Do not parallelize the verification
GET with reactions or thread replies — verification must gate the next step, not run
alongside it. This catches wrong field values, truncated descriptions, missing custom
fields, and partial mutations where the API returned an error but the write committed.
Don't trust the create-response stdout as ground truth.
Run python3 box/ship-gate.py enter before the first production
execute_approved call (the hook will block production mutations until the
plan is declared).
Then for each created/matched card, use slack-mutate.py and
shortcut-mutate.py via execute_approved:
python3 box/slack-mutate.py reply CHANNEL THREAD_TS 'TEXT'
Tracked in new card: <shortcut_url|SC-NNN: Story title>Tracked against existing card: <shortcut_url|SC-NNN: Story title>:shortcut: reaction: python3 box/slack-mutate.py react CHANNEL TS shortcutpython3 box/shortcut-mutate.py prepend-description STORY_ID 'TEXT'
(For cards just created in step 4: verify the link-back line is present in the
post-create GET response, then skip the prepend — the template already includes it.
The script's idempotency check will also catch duplicates by URL, but verifying +
skipping saves an API call and avoids relying solely on the template.)python3 box/shortcut-mutate.py add-link STORY_ID URLHuman-pre-tracked messages: When the scanner shows has_shortcut_url: true
(Shortcut URL in the top-level message) or human_shortcut_links is non-empty
(human posted a Shortcut link in a thread reply), the human's link counts as the
tracked reply:
:shortcut: reaction if missingexternal_links on the story for the Slack permalink — add if missing
using shortcut-mutate.py add-link STORY_ID PERMALINK (read-merge-write safe;
raw PUT on external_links silently deletes existing entries).This is the common pattern when someone creates a card manually and posts the link in #ideas. Proved 2026-04-29: Taylor posted SC-1863 link in message text.
slack-mutate.py handles token selection and built-in read-back verification
(exit 0 = verified, exit 1 = verification failed, exit 2 = mutation failed).
react uses the bot token (user token lacks reactions:write scope). reply
uses the user OAuth token from the macOS keychain — messages appear as
"Paul Yokota — Sent using @Claude", falling back to bot token only if the user
token is unavailable or expired.
Do not use raw chat.postMessage curl for tracked replies. Use
slack-mutate.py reply which handles token selection, attribution, and
verification. Raw curl with the bot token posts under the wrong identity.
Proved 2026-04-22: tracked reply posted as FeedForward Bot, required
delete and re-post.
Slack API quirk: When posting or checking thread replies, always pass the
parent message ts, not a reply's ts. Passing a reply's ts returns only
that one message (no error, no parent, no siblings). This silently breaks
idempotency checks for existing tracked replies.
Post-mutation Slack verification (mandatory): slack-mutate.py handles
per-mutation verification internally. As batch defense-in-depth, re-scan the
tracked messages after all Slack mutations for a batch are complete:
python3 box/slack-scanner.py --channel C0ADJ4ATJE4 --threads TS1,TS2,TS3
Each message should show has_shortcut_reaction: true and a tracked reply with
the correct Shortcut URL. This catches edge cases where individual verification
passed but the combined surface state is wrong.
Before each mutation:
:shortcut: reaction before addinghas_shortcut_url and human_shortcut_links — human-posted links
count as tracked replies (skip posting, focus on story link-back)external_links for existing Slack permalink before addingalready_reacted from Slack = successWhen a new card supersedes an existing one, sequence matters — preserve the relationship chain before archiving:
execute_approved -> verify)duplicates old card. Use verb duplicates
(not blocks or relates to) — it captures "this supersedes that."execute_approvedarchived: true, story link present,
comment presentAll five steps are sequential — each depends on the previous. Don't parallelize.
No automated audit script currently. Thread state verification is manual.
Re-fetch threads at processing time, not just scan time. If the bulk scan ran more than a few minutes ago, re-fetch the specific message's thread before declaring "no thread" or drafting. Thread data goes stale — reply_count: 0 in the scan doesn't mean no replies exist now. Proved 2026-04-20: message 11 showed 0 replies in scan; re-fetch found Paul's reply with a key API constraint.
Verify named attributions before approve_content. Before submitting a card draft, grep the scanner output for each person named in the Evidence section. "Bill confirmed" shipped on SC-1643 with zero data backing — the name felt plausible but appeared nowhere in the thread data. Proved 2026-04-20.
Refresh priority signals, manage active epics, and refresh Near Term pool
Multi-session deliverable play for projects spanning 3+ sessions with concrete outputs (proposals, strategies, wireframes). Provides project-level structure, evidence provenance, and cross-session handoff.
Investigation-driven card grooming — investigate across data sources, synthesize findings into card content, present for approval
Use when acting as a reviewer in an AgenTerminal review conversation. Handles both code reviews (REVIEW_APPROVED) and plan reviews (PLAN_APPROVED).
Weekly release impact review — pull PostHog data for Released cards and tracked PRs, classify, draft observations, post to Slack
Verify instrumentation, build measurement insights, close Slack loop for Released cards