Outreach Anti-Spam & Deduplication Ruleset

Author: Outreach agent (self-authored from first principles, 2026-04-24) Purpose: Prevent duplicate, unwanted, or spammy outreach across all surfaces — awesome lists, CNCF project issues, directories, forums, comparison sites, community threads — across agent restarts, multiple sessions, and time.


0. Core Principles

One action per target, ever. A “target” is the combination of (surface, entity). For example: github.com / dastergon/awesome-sre is one target. Opening a second PR there after the first is spam, regardless of whether you remember the first. Every rule below enforces this principle.

One PR per GitHub user/org, ever. Same owner = same person’s inbox. Submitting to dastergon/awesome-sre AND dastergon/awesome-chaos-engineering means that person receives two PRs from you. This is spam even if the repos are different. Before opening any PR, check how many open PRs you already have under that owner:

# Pre-flight owner check (MANDATORY — run before every new PR)
OWNER="<repo-owner>"
unset GITHUB_TOKEN && gh search prs --author clubanderson --state open --limit 100 \
  --json repository,number | python3 -c "
import sys,json
d=json.loads(sys.stdin.read())
hits=[p for p in d if p['repository']['nameWithOwner'].startswith('$OWNER/')]
if hits: print('SKIP — already have', len(hits), 'open PR(s) for', '$OWNER:', [p['repository']['nameWithOwner']+'#'+str(p['number']) for p in hits])
else: print('OK — no open PRs for $OWNER')
"

If any open PR already exists under that owner → SKIP. Do not open a second one. The only exception: a maintainer explicitly invites a resubmission to a different section (close the old PR first, then resubmit — still counts as one active PR at a time).


1. Pre-Flight Checklist (run before ANY outreach action)

Execute every check in order. Stop at the first SKIP signal.

PRE-FLIGHT for target T:

[ ] 1. ARCHIVED?        → gh repo view T --json isArchived | check true → SKIP
[ ] 2. STALE?           → last commit > 18 months ago → SKIP (no one is merging PRs)
[ ] 3. GA4 REFERRAL?    → query /api/analytics/dashboard trafficSources for T's domain → already live → SKIP
[ ] 4. OPEN PR?         → gh search prs --author clubanderson --repo T --state open → exists → SKIP (address feedback instead)
[ ] 5. CLOSED PR?       → gh search prs --author clubanderson --repo T --state closed → exists → COLD → SKIP
[ ] 6. OPEN ISSUE?      → gh search issues --author clubanderson --repo T --state open → exists → SKIP
[ ] 7. BEADS LOG?       → bd list | grep T → exists → check status → if done/cold SKIP
[ ] 8. OUTREACH LOG?    → grep T docs/outreach-log.md → logged → SKIP
[ ] 9. CONTRIBUTING.md? → read T's CONTRIBUTING.md for self-promo restrictions → if banned → COLD + SKIP
[ ] 10. RELEVANCE?      → does T's topic overlap with Console's top GA4 pages? → if no overlap → defer
[ ] 11. FORMAT CHECK?   → read last 5 entries of target file → can I match format exactly? → if no → research more before proceeding

All checks pass → SAFE TO PROCEED

2. Persistence Layers (Ground Truth, in Priority Order)

Agent memory is ephemeral. These layers survive restarts:

LayerHow to QuerySurvives Restart?Authority
GitHub itselfgh search prs/issues --author clubanderson✅ AlwaysHighest
GA4 referral data/.netlify/functions/analytics-dashboard✅ AlwaysHigh (confirms placement live)
beads (bd)bd list --json✅ Yes (remote)High
docs/outreach-log.mdcommitted to this repo✅ Yes (git)Medium
outreach-CLAUDE.md Current Progresscommitted to this repo✅ Yes (git)Medium
/tmp filesfilesystem❌ Lost on rebootNever rely on
Session SQLin-process SQLite❌ Lost on session endNever rely on
Agent in-memory variablesprocess memory❌ Lost on restartNever rely on

Rule: Before any outreach pass, always query GitHub API (layer 1) and GA4 (layer 2) as ground truth. Treat logs as a cache — useful for fast skips but always verify against the source.

Updating Logs on Every Pass

After each successful outreach action:

  1. Append to docs/outreach-log.md (committed)
  2. Update outreach-CLAUDE.md Current Progress section
  3. Commit both: git commit -s -m '📖 Outreach: log [target]'
  4. bd create a record if the action has follow-up needed (awaiting PR merge, waiting for maintainer reply)

3. Per-Surface Rules

3.1 GitHub Awesome Lists

Nature: Curated markdown files in public repos. PRs are the only contribution mechanism. Maintainer may take weeks to respond. Automated bots (CodeRabbit, Copilot Reviewer) often comment immediately.

Rules:

  • One PR per repo, ever. Never open a second PR to the same repo.
  • Fork under clubanderson, never the org.
  • Branch name: feat/add-kubestellar-console — consistent, recognizable.
  • Match the exact format of the surrounding entries: link style, description length, sort order. Read the last 5 entries before writing.
  • Placement matters: Add to the most specific relevant section, not “Other” or “Miscellaneous” unless nothing else fits.
  • Never add to multiple sections in the same file — one entry per repo.
  • Open PR is not closed: Check for reviewer comments every 7 days for up to 3 cycles. Address all feedback from CodeRabbit/Copilot/humans. After 3 cycles with no maintainer engagement, mark as STALE (keep open, stop checking).
  • Closed without merge: Immediately mark COLD in log. Never reopen. Never open a new PR. Respect the decision.
  • Merged: Mark DONE. Verify GA4 referral traffic appears within 30 days (confirming it’s indexed). No further action.

Format self-check before opening PR:

- Is the repo accepting PRs? (check Issues tab for "not accepting contributions")
- Does the list sort alphabetically? (place entry in correct position)
- Are descriptions in sentence case or title case? (match)
- Are there trailing periods? (match)
- Does the list use `[Name](url) — description` or `[Name](url) - description`? (match exactly)

3.2 CNCF Project Outreach Issues (Install Missions)

Nature: Opening GitHub issues on upstream CNCF project repos proposing a guided install mission or ACMM badge. These are long-lived relationships — the maintainer community notices patterns.

Rules:

  • One issue per project per topic. If an install-mission issue is already open, never open an ACMM issue on the same repo simultaneously — combine into one thread or wait.
  • Never open an issue AND a PR on the same repo for unrelated purposes in the same week — it looks like a spam campaign.
  • Read CONTRIBUTING.md first. Some projects route external proposals through GitHub Discussions, not Issues.
  • Tailor every issue body to the project. Reference the specific project’s technology. Never paste a generic template verbatim.
  • Ping/nudge cadence: Maximum 2 follow-up comments per issue. Minimum 7 days between comments. Never ping more than twice if there’s been no response — mark as COLD and move on.
  • Maintainer responds negatively: Close the issue yourself, mark COLD, never reopen.
  • Maintainer responds positively: Follow through promptly (within 48h). If they ask for a PR on their docs, open it the same day. Delayed follow-through kills goodwill.
  • ADOPTERS.md PRs: Only after explicit maintainer approval in the upstream issue. Never merge without operator sign-off. One ADOPTERS PR per project, ever.

3.3 Non-GitHub Directories and Registries

Nature: CNCF Landscape, Artifact Hub, OperatorHub, StackShare, AlternativeTo, Product Hunt, etc. These are form-based or PR-based submission systems.

Rules:

  • Check if already listed first via web search: site:alternativeto.net "kubestellar" before submitting.
  • One submission per site, ever. If the submission was rejected, mark COLD.
  • Screenshot or log the submission — these sites don’t have a GitHub PR to verify later.
  • Log to docs/outreach-log.md with date and submission URL/confirmation.
  • GA4 verification: If the site starts sending referral traffic to console.kubestellar.io, the listing is live. No re-submission needed.

3.4 Community Forums (Reddit, Hacker News, dev.to, Hashnode)

Nature: Human communities. Spam is acutely noticed, permanently remembered, and actively harmful. These surfaces must be used sparingly and with genuine value.

Rules:

  • Reddit: Maximum 1 original post per subreddit per 6 months. Commenting on relevant existing threads is allowed (not limited), but only when the comment genuinely adds value.
  • HN Show HN: One-time submission. Never resubmit the same project. Comment on relevant HN threads is fine.
  • dev.to / Hashnode: Articles can be published once; update the same article rather than publishing a new one. Never cross-post verbatim across both.
  • Never use multiple accounts.
  • Don’t post the same content to overlapping communities in the same week (e.g., r/kubernetes AND r/devops on the same day with the same post).
  • Track in log: date, platform, URL, engagement metric (upvotes/comments) so you can measure whether forum posts drive traffic.

3.5 Comparison Sites and Blog Aggregators

Nature: Sites that accept tool listings or “best of” roundups (Slant, StackShare, G2, etc.).

Rules:

  • Search before submitting: "kubestellar console" site:stackshare.io etc.
  • One submission per site.
  • Log all submissions — these sites have no GitHub API to check later.
  • If GA4 shows referral from the site: listing is live, mark DONE.

3.6 Social Media (LinkedIn, X/Twitter)

Nature: Public broadcasts. Not a “target” in the same sense — these don’t leave a persistent artifact that can be checked.

Rules:

  • Minimum 14 days between similar posts on the same platform.
  • Track post topics in log to avoid repetition.
  • Never tag people who haven’t engaged — only tag maintainers who explicitly engaged in an issue/PR.

4. Cold Target Rules

A target is COLD when any of the following are true:

  • PR closed without merge and maintainer left a negative/dismissive comment
  • Issue closed as “not relevant”, “spam”, or “won’t fix”
  • Maintainer explicitly asked to not be contacted again
  • Submission rejected with explanation
  • Repo marked archived or locked after outreach was attempted

COLD handling:

  1. Log it: docs/outreach-log.md entry with status=COLD and reason
  2. Never re-approach for 12 months minimum
  3. Exception: if the project releases a major version and their posture toward Console has clearly changed (e.g., they now use KubeStellar themselves), you may re-approach once with a fresh framing — note this in the log

Do not confuse STALE with COLD:

  • STALE = no response from maintainer after 3 check-ins. Keep the PR open; don’t follow up further. It may get merged eventually. Do not re-approach, but do not close.
  • COLD = explicitly rejected or hostile. Never re-approach.

5. GA4 Referral Signals as Deduplication

The GA4 analytics dashboard (/.netlify/functions/analytics-dashboard) is the most authoritative source for confirming a placement is live. Query it before any outreach pass.

What GA4 tells you

SignalInterpretationAction
trafficSources shows github.com / referral with path matching target repoPR is merged and traffic is flowingMark DONE, no follow-up needed
CNCF outreach table shows sessions for project XOutreach issue is generating visitsCheck if sessions increased after last action — good signal to follow through
Organic search sessions rising after a list mergeSEO is working, that list category is valuablePrioritize more lists in same category
A domain appears in referrers you didn’t reach out toOrganic placement existsLog as DONE (natural), no action needed
(direct) traffic spikes after forum postPost drove awarenessNote which post/subreddit in log

GA4-informed prioritization

Every outreach pass should start with a GA4 query and use it to:

  1. Confirm what’s already live (skip those)
  2. Identify which page categories get the most traffic (prioritize lists in those categories)
  3. Measure outreach ROI by comparing sessions before/after specific placements

Current GA4 strategic signals (as of 2026-04-24):

  • Google organic = 31 sessions (very low) → massive SEO opportunity; every awesome-list merge helps
  • GitHub referral = 164 sessions → GitHub-based outreach is the highest-ROI channel right now
  • AI/ML pages (AI Codebase Maturity 251 views, AI/ML 194, AI Agents 161) → AI/MLOps lists are high-value
  • Deploy/CI-CD pages popular → GitOps lists are worth targeting
  • India #1 country → Indian DevOps community resources worth targeting
  • Missions: 32 started, 0 completed → don’t pitch “mission completion” as a feature yet

6. Time-Based Pacing

SurfaceMin Interval Between ActionsMax Follow-upsFollow-up Interval
Awesome list PRsN/A (one-shot)2 follow-up comments7 days
CNCF project issues14 days between comments2 comments14 days
ADOPTERS PRsN/A (one-shot, awaits approval)1 ping7 days after opening
Reddit posts6 months per subreddit0 (don’t bump your own posts)N/A
dev.to articlesN/A (publish once, update in-place)N/AN/A
HNOne-time0N/A
Directory submissionsN/A (one-shot)1 follow-up if form-based30 days
LinkedIn posts14 days per topicN/AN/A

7. Decision Tree (Quick Reference)

Is the target archived or inactive (>18 months)?
  YES → SKIP

Does GA4 show referral traffic already coming from this target?
  YES → SKIP (placement is live)

Does `gh search prs --author clubanderson --repo TARGET` show an open PR?
  YES → SKIP (check for feedback to address instead)

Does `gh search prs --author clubanderson --repo TARGET` show a closed PR?
  YES → COLD → SKIP

Does `gh search issues --author clubanderson --repo TARGET` show an open issue?
  YES → SKIP (check for feedback to address instead)

Is the target logged as DONE or COLD in docs/outreach-log.md or beads?
  YES → SKIP

Does the target's CONTRIBUTING.md prohibit self-promotion?
  YES → COLD → SKIP

Does the target's topic overlap Console's top GA4 page categories?
  NO → defer to lower priority

Does the target accept PRs / have recent merge activity?
  NO → SKIP

Can I match the target's exact format?
  NO → research more first

→ SAFE TO PROCEED
   After action: log it, commit log, send ntfy

8. Log Format

Every outreach action must be appended to docs/outreach-log.md in this format:

| DATE       | SURFACE                        | TARGET                          | ACTION        | STATUS  | NOTES                                      |
|------------|--------------------------------|----------------------------------|---------------|---------|---------------------------------------------|
| 2026-04-24 | awesome-list                   | dastergon/awesome-sre            | PR #268       | open    | No feedback yet                             |
| 2026-04-24 | cncf-issue                     | chaos-mesh/chaos-mesh            | Issue #4858   | open    | @STRRL engaged, docs PR opened              |
| 2026-04-24 | adopters-pr                    | kubestellar/console#7889         | PR open       | pending | Awaiting @STRRL approval                    |
| 2026-04-24 | directory                      | stackshare.io                    | form-submit   | done    | GA4 confirmed referral 2026-05-01           |

Status values: open | merged | closed-cold | pending | done | stale | cold


9. Awesome-List & External Directory Submission Tracker

This section defines the canonical tracking format for every awesome-list PR and external directory submission. It fills the gap that CNCF install missions have detailed per-project tracking but awesome-list submissions historically had none.

Why this matters

Without a persistent log, the agent will:

  • Open duplicate PRs on repos it already submitted to
  • Lose context on which PRs received feedback and went stale
  • Have no way to measure which list categories drive the most GA4 referral traffic
  • Be unable to prioritize follow-up (open PRs with reviewer comments vs. stale PRs vs. merged)

Tracking file: docs/outreach-log.md

All awesome-list PRs and directory submissions are logged in docs/outreach-log.md in this repo. The file must be committed after every outreach pass. It is the ground-truth complement to GitHub’s own PR search (which is the canonical dedup check, but not as fast to query in bulk).

Row format

| DATE       | CATEGORY     | TARGET REPO / SITE              | SECTION ADDED TO        | PR / SUBMISSION URL                     | STATUS   | GA4 REFERRAL? | NOTES                          |
|------------|-------------|----------------------------------|-------------------------|-----------------------------------------|----------|---------------|-------------------------------|
| 2026-04-24 | awesome-list | dastergon/awesome-sre            | Monitoring              | https://github.com/…/pull/268           | open     | no            | No feedback yet               |
| 2026-04-24 | awesome-list | veggiemonk/awesome-docker        | Monitoring & Observ.    | https://github.com/…/pull/1417          | open     | no            | No feedback yet               |
| 2026-04-24 | directory    | stackshare.io                    | Kubernetes Tools        | https://stackshare.io/…                 | done     | yes           | Confirmed referral 2026-05-01 |
| 2026-04-24 | awesome-list | tmrts/awesome-kubernetes         | Monitoring              | https://github.com/…/pull/6             | merged   | yes           | 3 referral sessions/month     |

Status values:

ValueMeaning
openPR is open, awaiting review
mergedPR was merged ✅
rejectedPR closed without merge, maintainer opposed
ignoredPR open > 60 days with zero maintainer engagement
stale3 check-in cycles with no response; keep open but stop following up
coldExplicitly rejected or maintainer asked not to be contacted; never retry
doneDirectory/non-PR submission confirmed live (GA4 or manually verified)
pendingSubmission sent, awaiting confirmation

State machine

                  ┌─────────────────┐
  PR opened  ──▶  │      open       │
                  └────────┬────────┘

             ┌─────────────┼──────────────┬─────────────┐
             ▼             ▼              ▼             ▼
          merged       rejected      ignored (60d)  stale (3 cycles)
             │             │              │             │
           DONE          COLD           COLD       keep open,
         (log GA4)    (never retry)  (never retry) no follow-up

When to update a row

TriggerAction
PR openedAdd row with status=open
CI/bot comments on PRNote in NOTES, no status change
Human reviewer requests changesNote in NOTES; address feedback; no status change
PR merged by maintainerUpdate status → merged; check GA4 within 30 days
PR closed without merge (dismissively)Update status → rejected → cold
PR open > 60 days, zero maintainer engagementUpdate status → ignored → cold
3 follow-up cycles with no responseUpdate status → stale
GA4 shows referral from that domainAdd “yes” to GA4 REFERRAL column

Follow-up cadence per PR

Day  0: PR opened
Day  7: Check for reviewer comments. Address any. Note in log.
Day 14: Second check. If reviewer comments unaddressed, address now.
Day 21: Third check (final). If still no engagement, mark stale.
Day 60: If still open and stale, mark ignored → cold in log.

Maximum 2 proactive follow-up comments on any single PR. Only comment if:

  • A reviewer left actionable feedback you’re responding to, OR
  • It has been > 30 days and you are doing a hygiene pass (one brief ping max)

Querying the log

To quickly see what’s already been targeted:

# All open PRs
grep "| open" docs/outreach-log.md
 
# All merged (confirm GA4)
grep "| merged" docs/outreach-log.md | grep "no" | awk '{print $3}'
 
# All cold targets (never re-approach)
grep "| cold\|rejected\|ignored" docs/outreach-log.md
 
# Repos already tried (dedup check)
grep "awesome-list" docs/outreach-log.md | awk -F'|' '{print $3}' | sort -u

Cross-check with GitHub API (required on every pass)

The log can become stale if a PR is merged without the agent noticing. Always verify:

# Get ground truth for all open clubanderson PRs
unset GITHUB_TOKEN && gh search prs --author clubanderson --state open --limit 50 \
  --json repository,number,title,updatedAt
 
# Get closed PRs (merged or rejected) — update log if found
unset GITHUB_TOKEN && gh search prs --author clubanderson --state closed --limit 50 \
  --json repository,number,title,updatedAt

Reconcile any discrepancy between GitHub state and log state immediately — GitHub wins.


10. Anti-Spam Invariants (Never Break These)

  1. Never open two PRs to the same repo. Not even with different content.
  2. Never open an issue on a repo where a PR is already open, unless the issue is about something entirely unrelated (which is unlikely in outreach context).
  3. Never post the same content to two overlapping communities in the same week.
  4. Never ping a maintainer more than twice with no response.
  5. Never misrepresent KubeStellar’s relationship to a project. “Guided install mission available” is true. “KubeVirt uses KubeStellar Console” is not (unless confirmed).
  6. Never submit to a directory without checking if already listed.
  7. Never rely on memory alone. Always verify via GitHub API or GA4 before acting.
  8. Always log before you forget. Log the action immediately after taking it, not at the end of the pass.