Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
f967f55
docs: design spec for sync mapping persistence + bulk force-sync
NathanTarbert Jul 14, 2026
c5e5dca
docs: implementation plan for sync mapping persistence + bulk force-sync
NathanTarbert Jul 14, 2026
51ae71f
feat(sync): persist mapping config to SystemConfig
NathanTarbert Jul 14, 2026
9d2ffd2
feat(sync): load status map from persisted mapping config
NathanTarbert Jul 14, 2026
9e90946
feat(sync): support statusMapOverride in initializeSyncEngine
NathanTarbert Jul 14, 2026
5934531
feat(sync): implement bulk force-sync for all tickets linked to a plugin
NathanTarbert Jul 14, 2026
a9d95d2
fix(worker): register Linear sync adapter (was never wired up)
NathanTarbert Jul 14, 2026
1c5882e
fix(worker): remove unused createJob import
NathanTarbert Jul 14, 2026
491fb73
docs: fix spec to match no-tags schema (2 jobs, not 3)
NathanTarbert Jul 14, 2026
d3742b6
fix(sync): validate persisted outpostStatus against TicketStatus enum
NathanTarbert Jul 14, 2026
6d67034
fix(sync): don't swallow errors as 400, recognize plugin via links
NathanTarbert Jul 14, 2026
143ebcf
fix(sync): validate mapping config shape before persisting
NathanTarbert Jul 14, 2026
17b99a2
test(sync): cover requireAdmin gate on POST /api/sync/force
NathanTarbert Jul 14, 2026
0499c1f
test(sync): cover malformed-JSON fallback in GET /api/sync/mappings
NathanTarbert Jul 14, 2026
104163a
docs(sync): fix stale/inaccurate docstrings on new sync code
NathanTarbert Jul 14, 2026
0b95178
fix(sync): reject empty mapping objects instead of silently wiping co…
NathanTarbert Jul 14, 2026
fb0986e
fix(sync): validate labelRules shape before persisting
NathanTarbert Jul 14, 2026
8ebc598
fix(sync): don't swallow DB errors as 400 in mappings PUT
NathanTarbert Jul 14, 2026
85b45ef
test(sync): cover invalid outpostPriority rejection in PUT /api/sync/…
NathanTarbert Jul 14, 2026
1dc575d
docs(sync): fix build-sync-engine docstring — jobs fail+retry, not no-op
NathanTarbert Jul 14, 2026
f74c4fa
fix(sync): reject empty labelRules object, matching statusMappings/pr…
NathanTarbert Jul 14, 2026
b61c02a
docs: require cr-loop review before every push
NathanTarbert Jul 15, 2026
964d6cb
fix(sync): unexport MAPPING_CONFIG_KEY to satisfy Next.js route expor…
NathanTarbert Jul 15, 2026
5335397
chore(sync): prettier-format planning docs
NathanTarbert Jul 15, 2026
7cef144
Merge branch 'main' into sync-mapping-persistence-bulk-force-sync
NathanTarbert Jul 15, 2026
93696c7
fix(sync): apply persisted priority + label mappings in worker (#96)
NathanTarbert Jul 17, 2026
3621bf1
Merge pull request #111 from CopilotKit/fix/worker-priority-label-map…
jerelvelarde Jul 22, 2026
8a1f516
docs(community-signal): Reddit REST, created dates, Notion-only rule,…
NathanTarbert Jul 24, 2026
d98466f
ci: enable lint, harden secret ignores, correct migration docs
NathanTarbert Jul 24, 2026
1b7d3ad
fix: close CR round-1 bucket (a) findings
NathanTarbert Jul 25, 2026
85d98d4
fix: close CR round-2 bucket (a) findings
NathanTarbert Jul 25, 2026
46496d2
fix: close CR round-3 bucket (a) findings
NathanTarbert Jul 25, 2026
f9ad8bd
chore: split lint enablement out of this branch
NathanTarbert Jul 25, 2026
72f63d3
fix(web): pass NEXT_PUBLIC_* through to the client bundle build
NathanTarbert Jul 25, 2026
365c71b
fix(web): close CR findings on the NEXT_PUBLIC build-env fix
NathanTarbert Jul 25, 2026
8f0d73a
fix(ai): stop the bot confirming unverified bugs and hedging in public
NathanTarbert Jul 27, 2026
3d22e10
fix(ai): score responses on groundedness and withhold ungrounded ones
NathanTarbert Jul 27, 2026
8580e7f
Merge pull request #142 from CopilotKit/fix/ai-response-epistemic-gua…
jerelvelarde Jul 28, 2026
8aae925
Merge pull request #136 from CopilotKit/fix/next-public-build-env
jerelvelarde Jul 28, 2026
d68338d
Merge pull request #134 from CopilotKit/community-signal/reddit-rest-…
jerelvelarde Jul 28, 2026
19adb1e
Merge branch 'main' into chore/enable-lint-and-secret-hygiene
jerelvelarde Jul 28, 2026
d721a93
Merge pull request #135 from CopilotKit/chore/enable-lint-and-secret-…
jerelvelarde Jul 28, 2026
8846098
Merge branch 'main' into fix/ai-groundedness-gate
NathanTarbert Jul 28, 2026
28fb1fb
fix(ai): apply the groundedness penalty once, and stop suppressing ne…
NathanTarbert Jul 28, 2026
e9ad3cb
fix(ai): rebuild the groundedness text analysis on normalize-then-split
NathanTarbert Jul 28, 2026
da48fc1
fix(ai): gate groundedness suppression on the objective signal only
NathanTarbert Jul 30, 2026
db6c691
fix(ai): enforce the groundedness gate at the pipeline boundary
NathanTarbert Jul 30, 2026
2f501ed
fix(ai): drop the autoSend field and ground the generator's confidenc…
NathanTarbert Jul 30, 2026
da318fe
test(ai,queue): repair guards that passed without testing their claim
NathanTarbert Jul 30, 2026
89cb3d5
fix(ai): escalate every charged claim, not just those the arithmetic …
NathanTarbert Jul 30, 2026
3cf34b3
fix(ai): escalate only claims that assert our own verification
NathanTarbert Jul 31, 2026
f59ebe4
Merge pull request #143 from CopilotKit/fix/ai-groundedness-gate
NathanTarbert Jul 31, 2026
e8f233d
chore(deps): update github actions
renovate[bot] Aug 3, 2026
2bba344
Merge pull request #151 from CopilotKit/renovate/github-actions
renovate[bot] Aug 3, 2026
b1bd3a2
Merge branch 'main' into sync-mapping-persistence-bulk-force-sync
NathanTarbert Aug 5, 2026
1a23322
fix(sync): address review — typed deps, corrupt-config diagnosis, boo…
NathanTarbert Aug 5, 2026
d57aac8
fix(sync): close the three open items from the 2026-07-18 review
NathanTarbert Aug 5, 2026
ad4658e
fix(sync): fix the priority-key blocker and the rest of the d57aac8 r…
NathanTarbert Aug 6, 2026
3ea7f4a
fix(sync): stop bulk force-sync from writing wrong statuses and DLQ-f…
claude Aug 7, 2026
6200ac8
refactor(sync): address self-review findings on the force-sync fix
claude Aug 7, 2026
d21a203
chore: ignore local review-pipeline session state
claude Aug 7, 2026
b90c517
fix(sync): refuse to guess a Linear state instead of only filtering a…
claude Aug 7, 2026
8326621
docs(sync): record that the capability list cannot see env-conditiona…
claude Aug 7, 2026
365aaa9
Merge pull request #160 from CopilotKit/claude/review-pr-95-ga299k
jerelvelarde Aug 7, 2026
a1318a4
Merge pull request #95 from CopilotKit/sync-mapping-persistence-bulk-…
NathanTarbert Aug 7, 2026
e05bae7
docs(community-signal): resolution-verification rules + Aug 07 Reddit…
NathanTarbert Aug 7, 2026
ad359d9
docs(community-signal): enrichment resolves LinkedIn from self-linked…
NathanTarbert Aug 7, 2026
ea05ade
docs(community-signal): skill-audit cleanup — dedup superseded/duplic…
NathanTarbert Aug 7, 2026
e415e31
fix(ai): answer each ticket once, and stop leaking ticket IDs to repo…
NathanTarbert Aug 11, 2026
9efdd66
Merge pull request #166 from CopilotKit/community-signal/resolution-v…
NathanTarbert Aug 11, 2026
ae314de
fix(teams-bot): use real em dash in two card doc comments
NathanTarbert Aug 11, 2026
4dfde3c
fix(teams): drop dead ticketDisplayId from response card payloads
NathanTarbert Aug 11, 2026
ebc503a
test(teams-bot): make the displayId leak guards able to fail
NathanTarbert Aug 11, 2026
747cce0
fix(inbound): one shared reopen-status set for all reply paths
NathanTarbert Aug 11, 2026
202fc77
fix(inbound): build and look up Ticket.sourceId with one shared helper
NathanTarbert Aug 11, 2026
0052d36
fix(inbound): never answer an orphaned reply
NathanTarbert Aug 11, 2026
508e904
fix(ai): escalate when an AI answer never reaches the reporter
NathanTarbert Aug 11, 2026
f03bd56
fix(queue): answer the message that opened the ticket
NathanTarbert Aug 11, 2026
57cbd28
fix(queue): finish the skipped AI_RESPONSE job at 100% progress
NathanTarbert Aug 11, 2026
7bd88b2
test(queue): pin both halves of the one-response-per-ticket guard
NathanTarbert Aug 11, 2026
aa8b98f
test: repoint team-member assertions off the reply path
NathanTarbert Aug 11, 2026
ee502a0
docs: correct the one-response-per-ticket comments to match the code
NathanTarbert Aug 11, 2026
e02f620
Merge branch 'main' into fix/one-ai-response-per-ticket
NathanTarbert Aug 12, 2026
aa95e94
fix(teams): gate replies by monitored channel
NathanTarbert Aug 12, 2026
9180591
fix(inbound): keep the bot silent on an orphaned reply
NathanTarbert Aug 12, 2026
24addde
fix(worker): repair the missing SystemConfig table and make boot fail…
NathanTarbert Aug 12, 2026
5da02eb
fix(worker): make the boot-failure path actually reachable
jerelvelarde Aug 13, 2026
14278a8
fix(worker): make shutdown survive a signal during boot
jerelvelarde Aug 13, 2026
b8a68e3
fix(worker): harden boot, /health honesty, and the drift guard
jerelvelarde Aug 13, 2026
d0c6f99
fix(worker): reduce to the verified hotfix; defer the /health rework
jerelvelarde Aug 13, 2026
6f1e61b
fix(worker): address round-3 review; add the CI gate that would have …
jerelvelarde Aug 13, 2026
4c3a5f6
fix(worker): address round-4 review; test the guard itself in CI
jerelvelarde Aug 13, 2026
36b263b
ci: supply PGPASSWORD to the migration-verification step
jerelvelarde Aug 13, 2026
f2673ca
ci: invoke prisma directly so exit codes survive
jerelvelarde Aug 13, 2026
90b61de
ci: match the migration's actual RAISE text
jerelvelarde Aug 13, 2026
08417d4
ci: stop the guard harness aborting on expected failures
jerelvelarde Aug 13, 2026
e349407
Merge pull request #180 from CopilotKit/hotfix/create-missing-systemc…
jerelvelarde Aug 13, 2026
509c359
Merge pull request #172 from CopilotKit/fix/one-ai-response-per-ticke…
jerelvelarde Aug 13, 2026
34f4ba9
feat(queue): make one-answer-per-ticket hold under concurrency and de…
NathanTarbert Aug 14, 2026
f17f140
fix(queue,web): close the three blockers on the delivery-durability d…
NathanTarbert Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/deep-read-issue/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ For EACH issue number, run ALL of these — do not stop at the body:
Then deep-read every PR you find (section B) — a fix usually lives here, even when the issue thread is empty.

Capture per issue:
- **STATUS AS OF <today>:** one line — open / closed(reason) / fixed-in-vX / merged-PR#N — derived from state + maintainer comment + linked PR, NOT from the body.
- **STATUS AS OF <today>:** one line — open / closed(reason) / fixed-in-vX / merged-PR#N — derived from state + maintainer comment + linked PR, NOT from the body. **If the issue is closed OR its closing/linked PR is MERGED, emit an explicit `RESOLVED` verdict (with PR# + merge/close date)** so the orchestrator files it under ✅ Resolved, never as an open Pain/Top issue. Reading the linked PR's live merged state is a required check — a merged fix outranks any earlier "this is a blocker" comment in the thread.
- **TYPE — bug vs feature-request (MANDATORY, run the checks BEFORE calling anything a bug).** Don't assume an issue is a bug. Classify it first, from signals in this order:
- **Feature request** if ANY of: the issue title says `Feature Request` / `[Feature]` / `Proposal` / `RFC` / `Enhancement`; the issue has a `feature` / `enhancement` / `proposal` label; **its fix PR is `feat(...)` (conventional-commit) or its title/body says "proposal"** (a `feat` or "proposal" PR is the strongest signal — treat as feature). Feature requests belong in **🔥 Demand**, never in 💢 Pain or 🔝 Top issues (unless a front-door break).
- **Bug** if: `[Bug]` title / `bug` label / a `fix(...)` PR / the reporter says something errors, crashes, throws, 404s, regressed, or returns wrong output.
Expand Down
32 changes: 15 additions & 17 deletions .claude/skills/enrich-prospect/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: enrich-prospect
description: Deep enrichment for community-sourced enterprise PROSPECTS (the 🎯 Prospective enterprise customers subsection). For each prospect, find their LinkedIn profile, verify the LinkedIn employer matches the company on their GitHub (keep searching if it doesn't), then list the company website + company size (ARR / latest funding round / employee count). Runs as a general-purpose subagent (many web searches) so the orchestrator's context stays small. Invoked by weekly-report (enterprise section) and the enterprise skill.
description: Deep enrichment for community-sourced enterprise PROSPECTS (the 🎯 Prospective enterprise customers subsection). For each prospect, resolve their LinkedIn from their GitHub self-linked accounts FIRST (search only if none is self-linked), verify the employer matches, capture their full name, then list the company website + company size (ARR / latest funding round / employee count). Runs as a general-purpose subagent (many web searches) so the orchestrator's context stays small. Invoked by weekly-report (enterprise section) and the enterprise skill.
---

# Deep-enrich enterprise prospects
Expand All @@ -18,15 +18,18 @@ You are deep-enriching enterprise sales prospects for a community report. Accura

For EACH prospect below (handle + the issue/thread that surfaced them):

1. Seed from GitHub:
1. Seed from GitHub — INCLUDING the self-linked accounts:
gh api users/<login> --jq '{login, name, company, bio, blog, twitter_username, email, location}'
Capture their name, the company field, and any employer clue in bio/blog.
gh api users/<login>/social_accounts # ← the authoritative self-linked LinkedIn / X / etc.
Capture their name, the company field, employer clues in bio/blog, AND the `social_accounts` URLs.

2. Find their LinkedIn profile:
- WebSearch: "<name> <company> LinkedIn", then "<name> <role from bio> LinkedIn", then "<github handle> LinkedIn".
- The candidate profile's CURRENT employer MUST match the GitHub company (or a bio/blog employer clue). This is the match gate.
- IF IT DOESN'T MATCH, KEEP SEARCHING — try the blog/personal site, the company team/about page, the Twitter/X bio, a plain Google-style query ("<name>" "<company>"), a GitHub-email search. Only accept a LinkedIn URL when the employer lines up.
- If after real effort you cannot confirm the same person, DO NOT link a guess. Output `Name: <name> — LinkedIn not confirmed` and say what you tried.
2. **Resolve the LinkedIn URL + full name — work this ladder IN ORDER, stop at the first step that yields a confirmed profile:**
a. **Self-linked = authoritative (try FIRST, before any search).** If `social_accounts` has a `linkedin` URL, that IS the profile — use it verbatim, no search, no guessing. Also check the `blog` field (people put their LinkedIn there). A self-linked URL always beats a search hit. *(Miss precedent: the agent name-searched and linked a different person `in/nchatlapalli` when GitHub `social_accounts` already listed `in/navaifanatic`.)*
b. **Only if nothing is self-linked, search — Google-style web search.** Try, in order: `"<name>" "<company>" LinkedIn`, then `<name> <role from bio> LinkedIn`, then `<github handle> LinkedIn`, then a plain `"<name>" "<company>"`. Also try the company team/about page, the X/Twitter bio, and a GitHub-email search. (`WebSearch` is the tool; it's a general web/Google search.)
c. **Match gate — applies to any SEARCHED profile (skip for a self-linked URL, which is already theirs).** Accept a searched profile ONLY when its CURRENT employer matches the GitHub `company` (or a clear bio/blog employer). If it doesn't line up, it's a different person — keep searching. Also apply the stale-employer rule ("ex-"/"previously" ≠ current).
d. **Full name — from the authoritative source, NOT GitHub `name`.** GitHub `name` is often just a first name or a handle. Read the self-linked blog/site (or the LinkedIn) for the complete first + last name. *(Precedent: GitHub `name` was "Naveen"; his blog gave "Naveen Chatlapalli" — a first name alone is a flub on a sales list.)*
e. **Verify the published name matches the profile.** LinkedIn usually returns HTTP 999 to fetchers, so confirm the full name against a *fetchable* self-owned source — their blog, or a LinkedIn article they authored (byline). The name on the report must match the linked profile.
f. **If still unconfirmed after all of the above, DO NOT link a guess.** Output `Name: <name> — LinkedIn not confirmed` and list what you tried, so a human can finish it.

3. Company website: find the official company site (not a directory page). Prefer the root domain.

Expand All @@ -46,18 +49,13 @@ Return, per prospect, the exact block format in "Output block" below. Cite a rea

## Match gate (identity accuracy)

- **The LinkedIn person's current employer must match the GitHub `company` (or a clear bio/blog employer).** If GitHub says `@commercetools` and the first LinkedIn hit works somewhere else, that's a different person — keep searching.
- **Never link a "maybe".** A wrong LinkedIn link in a sales handoff is a real cost. When unconfirmed, write `LinkedIn not confirmed` and list what was tried, so a human can finish it.
- **Stale-employer rule (same as enrich-reporter):** if the bio says "ex-", "previously", "formerly", that employer does NOT count as current — it disqualifies both the prospect classification and the match.
The identity procedure is the ordered ladder in **step 2 of the subagent prompt above** (self-link first → search → match-gate → full name → verify name↔profile → else not-confirmed). Don't restate it here. This section carries only the one calibration rule the ladder doesn't:

## Company size — what counts
- **Don't over-hedge a lead that checks out.** When the self-linked profile, the GitHub `company` field, and a corroborating web search all point to the SAME current employer, mark the prospect **confirmed** — don't leave it "verify before outreach." Reserve `LinkedIn not confirmed` / `employer unconfirmed` for a genuine gap (no self-linked profile AND search can't line the employer up). Precedent: Parker Roan self-linked his LinkedIn, GitHub `company` said Shipt, and a search returned "Software Engineer at Shipt" — three matching signals = confirmed, not a maybe.

Report the single best available signal, most-recent only:
## Company size — what counts

- **ARR** — only if publicly stated (rare for private co's).
- **Funding — latest round ONLY.** "Series C, $120M, Oct 2024." Do not list the full round history; the current stage is what sales needs.
- **Employees** — a real count or a LinkedIn size band.
- **Unknown** — `size unknown (private, no public figures)`. Honest beats invented.
See **step 4 of the subagent prompt above** for the rule. One signal, most-recent only: ARR (only if publicly stated) → latest funding round ONLY (e.g. "Series C, $120M, Oct 2024", not the full history) → employee count / LinkedIn size band → else `size unknown (private, no public figures)`. Never fabricate a number.

## Output block (the required format)

Expand Down
13 changes: 10 additions & 3 deletions .claude/skills/enrich-reporter/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,17 @@ Spawn an `Explore` subagent (or any read-only general-purpose) with this prompt:
## Subagent prompt template

```
For each GitHub username below, run:
For each GitHub username below, run BOTH:
gh api users/<login> --jq '{login, name, company, bio, blog, twitter_username}'
gh api users/<login>/social_accounts # ← the person's OWN self-linked LinkedIn / X / site — authoritative

Return a compact one-line-per-user table:
login | name | company | bio | blog | twitter
login | name | company | profile_url | company_url | linkedin_url | bio | blog | twitter

- linkedin_url = the `linkedin` URL from `social_accounts` if present (the self-linked, authoritative profile — never a search guess); else blank. The prospect pass reuses this instead of searching.

- profile_url = `https://github.com/<login>` (always — used to link the handle on every card).
- company_url = the company's website for enterprise reporters (e.g. Amazon → https://www.amazon.com, Nvidia → https://www.nvidia.com); blank for indie. Used to link the 🏢 Company badge. Don't guess a URL — leave blank if unsure.

Then two bulleted lists:
- Enterprise reporters (company field populated, OR bio/blog clearly identifies an employer — mark inferred ones as "(inferred)")
Expand All @@ -32,7 +38,8 @@ Run them in parallel via xargs or a small loop. Under 350 words total.

## Classification rules

- **Direct enterprise:** `company` field populated → use that as canonical affiliation.
- **Direct enterprise (confirmed):** `company` field populated AND corroborated by the bio/blog/a verifiable identity → use that as canonical affiliation, mark `confirmed`.
- **Self-declared only (UNCONFIRMED):** the `company` field names an employer but nothing else corroborates it — no bio mention, no verifiable name/LinkedIn, throwaway-looking account. Still surface it, but mark it **`unconfirmed (self-declared)`** so the report can flag "⚠️ Company unconfirmed" in 🏢 Enterprise. Never present it as fact. (Precedent: `GeauxEric` → `company: Nvidia`, no verifiable identity → unconfirmed.)
- **Inferred enterprise:** `company` empty, but bio or blog clearly identifies an employer (e.g. "Engineer @AcmeCorp", LinkedIn profile naming a current role) → label as `<Company> (inferred)`. Still treat as enterprise signal.
- **Indie / no affiliation:** no company, no employer clues. Default classification.
- **404 / nonexistent user:** note explicitly. Sometimes handles get renamed; check if the issue still resolves.
Expand Down
37 changes: 21 additions & 16 deletions .claude/skills/loom-walkthrough/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: loom-walkthrough
description: Generate the 5-7 minute Loom walkthrough script for a completed Weekly Community Signal report — a plain spoken briefing that notifies the team of the week's highlights and what the host needs to flag. Straightforward and factual, NOT a radio show or a performance. Runs after every report (invoked by weekly-report) and on "loom script", "walkthrough script", "record the loom", "narrate the report".
description: Generate the ≤10-minute Loom walkthrough script for a completed Weekly Community Signal report — a plain spoken briefing that dives straight into what the team needs to know, walking the report top to bottom (CopilotKit page first, then AG-UI in the same order). Straightforward and factual, NOT a radio show or a performance; no hype opener, no blame framing. Runs after every report (invoked by weekly-report) and on "loom script", "walkthrough script", "record the loom", "narrate the report".
---

# Loom walkthrough script
Expand All @@ -19,7 +19,8 @@ This is an internal report-out. The host is telling the team the week's highligh

- **Straightforward and plain.** State the thing, its status, and whether it needs attention. The host is notifying, not narrating a story.
- **Still easy to read aloud** — contractions and short sentences are fine (it's spoken, not a memo), but the register is a calm colleague giving an update, not a presenter.
- **Open with the single most important thing** — the biggest highlight or the biggest flag, said plainly. No "hook," no rotating gimmick, no callback bit.
- **Dive straight in — no hype opener.** One short orienting sentence (week + "I'll walk CopilotKit top to bottom, then AG-UI"), then start at the top of the report. No "hook," no rotating gimmick, no callback bit, no "biggest thing first" reordering — the page order IS the order.
- **Never imply the engineering team isn't doing its job.** State status neutrally: an in-progress fix is "a fix is in review" / "in testing," an unowned item "needs an owner assigned," a shipped-broken item is just described by what broke + the fix — never "stalled," "neglected," "dropped the ball," or "how did this ship." Keep every fact; frame it as a status, not a failing.
- **No on-camera meta.** Don't narrate the document's structure — no "in this report," "next section," "as you can see." Just say what happened.
- **No catchphrase / no through-line slogan.** Don't invent a theme to repeat. If there's a genuine pattern worth naming, state it once, plainly, where it's relevant.
- **Numbers spoken, not written.** "about two months," "fifty out of a hundred" — never "~2mo" or "50/100" in the spoken lines.
Expand All @@ -31,22 +32,26 @@ The Top issues are the core of the briefing. Deliver them as a **numbered list m

## Length

**5–7 minutes** (~800–1100 spoken words). Mark rough time stamps so the host can pace. Always include a "to hit 5 minutes, cut these" note listing the 2–3 most trimmable lines.
**10 minutes or less** (~1,300–1,500 spoken words max; shorter is fine on a quiet week). Mark rough time stamps so the host can pace. Always include a "to trim, cut these" note listing the 2–3 most trimmable lines (usually the Docs beats and the Reddit lines).

## Segment flow (adapt to the week — don't force empty ones)
## Segment flow — walk the report top to bottom

1. **Open** (~15s) — lead with the single most important thing this week (biggest highlight or biggest flag), said plainly. No hook, no gimmick.
2. **The week in one line** — the one-sentence takeaway + anything the team should watch. The TL;DR spoken aloud, plainly.
3. **Top issues — NUMBERED, one beat each** — deliver the report's ranked top issues as a numbered list ("number one … number two …"), each its own short beat with a `[beat]` between, each with its own time marker. Per issue: what it means for a user + status (fixed / fix in progress / not started). Never how it broke internally, and never blur two into one paragraph. (See "Top issues are numbered" above.)
4. **Pain — the high-level read (the CEO segment — slow down here).** NOT issue-by-issue. Name *where people are struggling* as a pattern, per community, plus the one structural pain. The CEO wants the shape of the hurt, not a bug list. (See "Pain segment" below.)
5. **Enterprise** — count + trend + the one-line "how they showed up / what to do."
6. **CopilotKit Reddit** — score + one-phrase vibe (trimmable).
7. **AG-UI — top issues** — same plain treatment; flag the one that matters most. **This is a SEPARATE page** — the report is two pages (main = CopilotKit, sub-page = AG-UI), so open segment 7 with a `[SWITCH to the AG-UI sub-page]` stage cue and a spoken transition that signals the shift ("switching over to AG-UI…") so the viewer knows they've moved to the other page.
8. **AG-UI pain** — where it hurts + the structural constraint (e.g. review bandwidth).
9. **AG-UI Reddit / momentum** — the public win, end the body on an up note.
10. **Close** (~15s) — the week in a sentence, name the 1–2 things the host needs the team to action, point to the linked report. Plain sign-off — no flourish.
**Dive straight in and follow the report's own order, top to bottom — the CopilotKit page first, then the AG-UI sub-page in the same order.** The page order IS the order — no curated narrative that reorders it (opener/hype rules live in `## Tone`). One short orienting sentence ("This is the community signal for the week of X — I'll walk CopilotKit top to bottom, then AG-UI"), then go. Cover each section in the order it appears on the page; skip a section only when it's empty (say nothing, move on).

**Both pages get airtime — never skip AG-UI.** The routine always produces two pages; even a thin AG-UI week gets segments 7–9 and the `[SWITCH to the AG-UI sub-page]` cue. If AG-UI is genuinely quiet, compress 7–9 into a shorter beat — but cover it and name the page switch. A script that only walks the CopilotKit page is incomplete.
**CopilotKit page (in page order):**
1. **Trends** — one or two sentences: heavy or quiet week, and are we keeping up. Note capped bulk-close sweeps so the resolved number isn't misread.
2. **Top issues — NUMBERED, one beat each** ("number one … number two …"), a `[beat]` between, each with a time marker. Per issue: what it means for a user + status (fixed / fix in review / needs an owner). Never how it broke internally; never blur two into one paragraph. (See "Top issues are numbered.")
3. **Product surface contradictions** (only if present) — the page-vs-page conflicts and whose job it is to reconcile the pages.
4. **Enterprise** — who showed up + the one or two to hand to sales + any enterprise-surface questions.
5. **Demand** — the notable feature asks, briefly (these are asks, not bugs).
6. **Pain — the high-level read.** NOT issue-by-issue. Name *where people are struggling* as a pattern + the one structural pain. The shape of the hurt, not a bug list. (See "Pain segment.")
7. **Docs** — the doc gaps, one line each (trimmable).
8. **Resolved** — what closed this week.
9. **Reddit Pulse** — score + one-phrase vibe (trimmable).

Then `[SWITCH to the AG-UI sub-page]` with a plain spoken transition ("now the AG-UI page, same walk"), and cover it in the **same page order**: Trends → Top issues (numbered) → Pain (pattern read) → Demand → Docs → Resolved → Reddit Pulse.

**Close** (~15s) — the week in a sentence, the one or two things the host needs the team to action, point to the linked report. Plain sign-off, no flourish.

## Pain segment (what the CEO wants)

Expand All @@ -70,7 +75,7 @@ Two columns / two blocks so the host can hide the cues:

- **The spoken script** — what to say, top to bottom, with `[SCROLL to …]` / `[beat]` / `[~m:ss]` cues in brackets the host reads silently. **Bold the one anchor line per segment** so a host who blanks can just read the bold and move on. Top issues appear as a numbered list, one beat each.
- **A cue card** (≤12 lines) — scroll cues + bold anchors only, for off-screen glancing while recording. List the top issues numbered.
- **Pacing notes** — which segment to slow down on (the Pain read), and what to cut for 5 min. No through-line/catchphrase note.
- **Pacing notes** — which segment to slow down on (the Pain read), and which lines to cut to stay under 10 minutes (per the Length section). No through-line/catchphrase note.

## Hand-off

Expand Down
Loading