[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M741YPXBMVBAZPTPX82STEMM":3,"skill-same:01M741YPXBMVBAZPTPX82STEMM":68},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":28,"commitSha":29,"skillDirectory":30,"forksCount":31,"repositoryHtmlUrl":32,"repositoryHomepageUrl":33,"ownerHtmlUrl":34,"ownerBlogUrl":35,"ownerDescription":6,"updatedAt":36,"install":37,"resources":50},"01M741YPXBMVBAZPTPX82STEMM","retirement-shepherd",null,"Shepherd the retirement of a stalled Apache Incubator podling. Use this skill whenever a user asks to retire a podling, assess whether a podling should be retired, check for stalled evidence, draft a retirement announcement email, or produce a project shutdown checklist. Triggers on phrases like \"retire podling\", \"retirement shepherd\", \"stalled podling\", \"shut down project\", \"retirement email\", \"shutdown checklist\", \"podling is inactive\", or \"move to Attic\". This skill is the counterpart to the graduation-packet-builder — it handles podlings leaving the Incubator through retirement rather than graduation.","apache\u002Fincubator","apache","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F47359?v=4","01M6AC83E6K4FDKK7HZ8WNMQKE","The Apache Software Foundation","01M6ACHK42QHHPFXEPXJG4B72D",58,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fretirement-shepherd\u002Fskills\u002Fretirement-shepherd\u002FSKILL.md",[],[],"2026-11-11 15:11:14",false,"---\nname: retirement-shepherd\ndescription: Shepherd the retirement of a stalled Apache Incubator podling. Use this skill whenever a user asks to retire a podling, assess whether a podling should be retired, check for stalled evidence, draft a retirement announcement email, or produce a project shutdown checklist. Triggers on phrases like \"retire podling\", \"retirement shepherd\", \"stalled podling\", \"shut down project\", \"retirement email\", \"shutdown checklist\", \"podling is inactive\", or \"move to Attic\". This skill is the counterpart to the graduation-packet-builder — it handles podlings leaving the Incubator through retirement rather than graduation.\nallowed-tools:\n  - Bash\n  - Read\n  - Write\n---\n\n# Retirement Shepherd\n\nProduce a complete retirement dossier for one Apache Incubator podling:\nstalled-evidence assessment, retirement announcement email, and a step-by-step\nshutdown checklist.\n\nRun this skill when a podling's mentors, the IPMC chair, or a shepherd\nbelieve a podling has stalled and should be retired rather than graduated.\n\n## Server locations\n\nAll server code lives here — use these absolute paths; never assume relative\npaths:\n\n| Role | Path |\n|------|------|\n| IPMC oversight tools | `~\u002FIncubatorMCP` |\n| Per-podling health metrics | `~\u002FHealthMCP` |\n| Podling roster & schedule | `~\u002FPodlingsMCP` |\n| Report cache | `~\u002FReportMCP` |\n\nHealth report Markdown files (input to HealthMCP): `~\u002Fincubator\u002Freports`\n\nThe harness has no MCP transport — invoke server Python functions directly via\n`Bash` with the PYTHONPATH below. Never call MCP tool endpoints over stdio.\n\n```bash\nexport PYTHONPATH=~\u002FIncubatorMCP:\\\n~\u002FHealthMCP\u002Fsrc:\\\n~\u002FPodlingsMCP:\\\n~\u002FReportMCP\u002Fsrc\n```\n\n---\n\n## Trigger conditions\n\nRun this workflow when:\n\n- A mentor or IPMC member reports that a podling has had no meaningful commits,\n  releases, or community activity for an extended period.\n- `tool_stalled_podlings` or `tool_ipmc_watchlist` returns a podling with\n  severity `\"high\"` and the recommended action involves retirement.\n- The IPMC chair asks for a retirement dossier ahead of a board meeting.\n- A user asks whether a specific podling should be retired.\n\nThe podling name is **required**. If the user has not supplied one, ask.\nIf the user asks to find stalled podlings first, run Step 1 across all current\npodlings before targeting a specific one.\n\n---\n\n## Workflow\n\nWork through these five steps in order. Each step writes a file under `scratch\u002F`\n(create the directory first if it does not exist: `mkdir -p scratch`). Later\nsteps depend on earlier ones, so do not skip ahead.\n\n### Step 1 — Stalled evidence assessment\n\nCollect objective evidence that the podling has stalled. The goal is to replace\nsubjective impressions with specific, sourced metrics that can be cited in the\nretirement discussion.\n\n#### 1a — Configure inactivity windows and thresholds\n\nAll three inactivity signals — commits, mailing-list posts, and releases —\nare checked against configurable windows. Adjust these before running if the\npodling is in an unusual situation (e.g., very new, recently donated large\ncodebase, seasonal project).\n\n```python\nimport sys, json\nsys.path.insert(0, '~\u002FIncubatorMCP')\nsys.path.insert(0, '~\u002FHealthMCP\u002Fsrc')\nsys.path.insert(0, '~\u002FPodlingsMCP')\nsys.path.insert(0, '~\u002FReportMCP\u002Fsrc')\n\nPODLING     = '\u003Cpodling-name>'   # replace with the target podling\nREPORTS_DIR = '~\u002Fincubator\u002Freports'\n\n# --- Configurable inactivity detection ---\n# Each signal has:\n#   window   : health-report window to read from (\"3m\", \"6m\", \"12m\")\n#   threshold: value at-or-below which the signal is FLAGGED\n#   metric   : field name in the health report window\n#   label    : human-readable name for reporting\nINACTIVITY_CONFIG = {\n    \"commits\": {\n        \"window\":    \"3m\",   # short window — catches recent freeze quickly\n        \"metric\":    \"commits\",\n        \"threshold\": 5,       # ≤ 5 commits in 3 months = effectively inactive\n        \"label\":     \"Commits (3m)\",\n        \"rationale\": \"Fewer than 5 commits in the past 3 months indicates\"\n                     \" the codebase is not being actively developed.\",\n    },\n    \"mail_posts\": {\n        \"window\":    \"6m\",   # longer window — mailing lists can be quiet seasonally\n        \"metric\":    \"dev_messages\",\n        \"threshold\": 5,       # ≤ 5 messages in 6 months = community is silent\n        \"label\":     \"dev@ messages (6m)\",\n        \"rationale\": \"Fewer than 5 messages on dev@ in 6 months indicates\"\n                     \" the community is no longer actively discussing the project.\",\n    },\n    \"releases\": {\n        \"window\":    \"12m\",  # annual window — one release per year is the minimum\n        \"metric\":    \"releases\",\n        \"threshold\": 0,       # 0 releases in 12 months = no Apache release made\n        \"label\":     \"Releases (12m)\",\n        \"rationale\": \"Zero releases in the past 12 months means the podling has\"\n                     \" not produced any Apache-branded software deliverable.\",\n    },\n}\n# Optional overrides — uncomment and adjust as needed:\n# INACTIVITY_CONFIG[\"commits\"][\"window\"]    = \"6m\"   # use 6m if 3m data is noisy\n# INACTIVITY_CONFIG[\"commits\"][\"threshold\"] = 10      # stricter for large repos\n# INACTIVITY_CONFIG[\"mail_posts\"][\"threshold\"] = 10   # stricter for active domains\n# INACTIVITY_CONFIG[\"releases\"][\"window\"]  = \"6m\"    # tighter for older podlings\n```\n\n#### 1b — Collect health metrics across all windows\n\n```python\nfrom apache_health_mcp.tools import get_report_summary\n\ntry:\n    health = get_report_summary(PODLING, REPORTS_DIR)\n    all_windows = health.get(\"latest_metrics\", {})\n    # Keep all windows for context; flag signals use only the configured window\n    activity_by_window = {}\n    for w, m in all_windows.items():\n        if m is None:\n            continue\n        activity_by_window[w] = {\n            \"date_range\":        m.get(\"date_range\"),\n            \"commits\":           m.get(\"commits\"),\n            \"unique_committers\": m.get(\"unique_committers\"),\n            \"unique_authors\":    m.get(\"unique_authors\"),\n            \"releases\":          m.get(\"releases\"),\n            \"new_contributors\":  m.get(\"new_contributors\"),\n            \"dev_messages\":      m.get(\"dev_messages\"),\n            \"dev_unique_posters\":m.get(\"dev_unique_posters\"),\n            \"bus50\":             m.get(\"bus50\"),\n            \"avg_mentor_signoffs\": m.get(\"avg_mentor_signoffs\"),\n        }\nexcept FileNotFoundError:\n    health = None\n    activity_by_window = {}\n```\n\n#### 1c — Evaluate inactivity signals against configured thresholds\n\n```python\ninactivity_flags = {}   # signal_name -> flag dict\n\nfor signal_name, cfg in INACTIVITY_CONFIG.items():\n    window_key = cfg[\"window\"]\n    metric     = cfg[\"metric\"]\n    threshold  = cfg[\"threshold\"]\n\n    window_data = activity_by_window.get(window_key)\n    if window_data is None:\n        # Window not present in health report — treat as unknown, not flagged\n        inactivity_flags[signal_name] = {\n            \"flagged\":    None,       # None = data not available\n            \"window\":     window_key,\n            \"metric\":     metric,\n            \"observed\":   None,\n            \"threshold\":  threshold,\n            \"label\":      cfg[\"label\"],\n            \"rationale\":  cfg[\"rationale\"],\n            \"note\":       f\"Window '{window_key}' not available in health report.\",\n        }\n        continue\n\n    observed = window_data.get(metric)\n    if observed is None:\n        flagged = None   # metric absent — cannot evaluate\n    else:\n        flagged = observed \u003C= threshold\n\n    inactivity_flags[signal_name] = {\n        \"flagged\":   flagged,    # True = stalled signal triggered\n        \"window\":    window_key,\n        \"metric\":    metric,\n        \"observed\":  observed,\n        \"threshold\": threshold,\n        \"label\":     cfg[\"label\"],\n        \"rationale\": cfg[\"rationale\"],\n        \"note\":      None,\n    }\n\n# Count confirmed flags (True) vs unknown (None) vs passing (False)\nconfirmed_flags = [k for k, v in inactivity_flags.items() if v[\"flagged\"] is True]\nunknown_flags   = [k for k, v in inactivity_flags.items() if v[\"flagged\"] is None]\npassing_signals = [k for k, v in inactivity_flags.items() if v[\"flagged\"] is False]\n```\n\nPrint a detection table immediately after evaluation so the shepherd can\nreview the signals before proceeding:\n\n```python\nprint(f\"\\n=== Inactivity Detection: Apache {PODLING} ===\\n\")\nprint(f\"{'Signal':\u003C15} {'Window':\u003C6} {'Observed':>10}  {'Threshold':>10}  {'Flagged'}\")\nprint(\"-\" * 60)\nfor name, f in inactivity_flags.items():\n    obs = str(f[\"observed\"]) if f[\"observed\"] is not None else \"N\u002FA\"\n    thr = str(f[\"threshold\"])\n    flag_str = \"*** YES ***\" if f[\"flagged\"] else (\"UNKNOWN\" if f[\"flagged\"] is None else \"no\")\n    print(f\"  {f['label']:\u003C13} {f['window']:\u003C6} {obs:>10}  {thr:>10}  {flag_str}\")\nprint()\nprint(f\"Confirmed flags: {len(confirmed_flags)} \u002F {len(INACTIVITY_CONFIG)}\"\n      f\" ({', '.join(confirmed_flags) or 'none'})\")\n```\n\n#### 1d — IPMC stalled-podling and watchlist signals\n\n```python\nfrom ipmc.tools import tool_stalled_podlings, tool_ipmc_watchlist, tool_podling_brief\n\nstalled_all = tool_stalled_podlings({\"health_source\": REPORTS_DIR})\nstalled_entry = next(\n    (item for item in stalled_all.get(\"items\", [])\n     if item[\"podling\"].lower() == PODLING.lower()),\n    None,\n)\n# stalled_entry fields when present:\n#   severity: \"high\" \u002F \"medium\" \u002F \"low\"\n#   definition_matched: list of signals matched by the IPMC strict definition\n#   observed: {commits, unique_committers, dev_messages, releases_12m}\n#   summary, recommended_ipmc_action\n\nwatchlist = tool_ipmc_watchlist({\n    \"health_source\": REPORTS_DIR,\n    \"severity_at_least\": \"medium\",\n})\nwatchlist_entry = next(\n    (item for item in watchlist.get(\"items\", [])\n     if item.get(\"podling\", \"\").lower() == PODLING.lower()),\n    None,\n)\n\nbrief = tool_podling_brief({\n    \"podling\": PODLING,\n    \"health_source\": REPORTS_DIR,\n    \"focus\": [\"status\", \"health\", \"risk\"],\n    \"brief_format\": \"detailed\",\n})\n```\n\nThe IPMC strict stalled definition (from `tool_stalled_podlings`) requires\n*all* of low commits, low committers, and no releases to fire together. The\nconfigurable flags above are *independent* — each can fire alone. Both\nperspectives appear in the final evidence record.\n\n#### 1e — Podling lifecycle and incubation duration\n\n```python\nfrom podlings.tools import tool_get_podling\nfrom datetime import date\n\npodling_meta      = tool_get_podling({\"name\": PODLING})\np                 = podling_meta[\"podling\"]\nmentors           = p.get(\"mentors\", [])\nstart_date        = p.get(\"startdate\")\nresource          = p.get(\"resource\", PODLING.lower())\nmonths_incubating = None\nif start_date:\n    start             = date.fromisoformat(start_date)\n    today             = date.today()\n    months_incubating = (today.year - start.year) * 12 + (today.month - start.month)\n\n# A podling incubating > 24 months with all three signals flagged is a strong\n# retirement candidate regardless of the IPMC strict stalled definition.\nlong_incubation = months_incubating is not None and months_incubating > 24\n```\n\n#### 1f — Report history: missed reports and chronic issues\n\n```python\nfrom apache_incubator_reports_mcp.tools import get_podling_reports\n\nCACHE_DIR = '~\u002FReportMCP\u002F.cache\u002Fagenda-incubator-reports'\ntry:\n    report_history = get_podling_reports(podling=PODLING, cache_dir=CACHE_DIR)\n    all_reports    = report_history.get(\"reports\", [])   # newest-first\n    total_reports  = len(all_reports)\n    report_periods = [r[\"report_period\"] for r in all_reports]\n\n    # Tally issues across all reports; chronic = appears in ≥ 3 reports\n    issue_counts = {}\n    for r in all_reports:\n        for issue in (r.get(\"issues\") or []):\n            issue_counts[issue] = issue_counts.get(issue, 0) + 1\n    chronic_issues = [i for i, cnt in issue_counts.items() if cnt >= 3]\nexcept Exception:\n    total_reports, report_periods, chronic_issues = 0, [], []\n```\n\nChronic issues (in 3+ reports unresolved) corroborate the inactivity flags\nand should be quoted in the retirement email.\n\n#### 1g — Write the stalled-evidence record\n\n```python\nimport os\nos.makedirs(\"scratch\", exist_ok=True)\n\n# Overall retirement case strength:\n# \"strong\"   — all three configurable flags triggered, or IPMC stalled signal at high severity\n# \"moderate\" — one or two flags triggered, or watchlist entry present\n# \"weak\"     — no flags triggered and not on watchlist\nall_three_flagged = len(confirmed_flags) == 3\nipmc_high         = stalled_entry and stalled_entry.get(\"severity\") == \"high\"\n\nif all_three_flagged or ipmc_high:\n    retirement_case_strength = \"strong\"\nelif confirmed_flags or stalled_entry or watchlist_entry:\n    retirement_case_strength = \"moderate\"\nelse:\n    retirement_case_strength = \"weak\"\n\nstalled_evidence = {\n    \"podling\":                  PODLING,\n    \"resource\":                 resource,\n    \"mentors\":                  mentors,\n    \"start_date\":               start_date,\n    \"months_incubating\":        months_incubating,\n    \"long_incubation\":          long_incubation,\n\n    # Configurable inactivity detection results\n    \"inactivity_config\":        INACTIVITY_CONFIG,\n    \"inactivity_flags\":         inactivity_flags,\n    \"confirmed_flags\":          confirmed_flags,    # signals that fired\n    \"unknown_flags\":            unknown_flags,      # signals with missing data\n    \"passing_signals\":          passing_signals,    # signals that did NOT fire\n\n    # IPMC tool signals\n    \"stalled_signal\":           stalled_entry,\n    \"watchlist_entry\":          watchlist_entry,\n    \"brief_summary\":            brief.get(\"status_summary\"),\n    \"active_concerns\":          brief.get(\"active_concerns\", []),\n    \"outlook\":                  brief.get(\"outlook\"),\n\n    # Raw health data for all windows\n    \"activity_by_window\":       activity_by_window,\n\n    # Board report history\n    \"report_history\": {\n        \"total_reports_filed\":  total_reports,\n        \"report_periods\":       report_periods,\n        \"chronic_issues\":       chronic_issues,\n    },\n\n    \"retirement_case_strength\": retirement_case_strength,\n}\n\nwith open(\"scratch\u002Fstalled_evidence.json\", \"w\") as f:\n    json.dump(stalled_evidence, f, indent=2, default=str)\n\nprint(f\"\\nRetirement case strength: {retirement_case_strength.upper()}\")\nprint(f\"Confirmed inactivity flags: {confirmed_flags}\")\nif unknown_flags:\n    print(f\"Flags with missing data:   {unknown_flags}\")\n```\n\nWrite to `scratch\u002Fstalled_evidence.json`.\n\nIf `retirement_case_strength == \"weak\"`, report this to the user and ask\nwhether to continue — retiring a healthy podling is a serious, irreversible\naction. The configurable thresholds can be tightened (lower values) to\nproduce a stricter assessment, or loosened if the project has a known\nslow period.\n\n---\n\n### Step 2 — Mentor consultation record\n\nBefore posting a retirement discussion, the shepherding mentor must have\nattempted to re-engage the PPMC and documented the outcome. Record this\nmanually:\n\n```python\nconsultation = {\n    \"podling\": PODLING,\n    \"contact_attempts\": [\n        # Each entry: date, method (email\u002FIRC\u002Fcall), summary of response\n        # e.g. {\"date\": \"2026-07-01\", \"method\": \"email to dev@\",\n        #        \"response\": \"no reply after 14 days\"}\n    ],\n    \"ppmc_willing_to_continue\": None,  # True \u002F False \u002F None (no response)\n    \"mentor_recommendation\":    None,  # \"retire\" \u002F \"continue\" \u002F \"defer\"\n    \"notes\": \"\",\n}\n\nwith open(\"scratch\u002Fconsultation.json\", \"w\") as f:\n    json.dump(consultation, f, indent=2, default=str)\n```\n\nThe shepherd must fill `contact_attempts`, `ppmc_willing_to_continue`, and\n`mentor_recommendation` before drafting the retirement email. If there has been\nno contact attempt, make one and wait at least 14 days for a response before\nproceeding.\n\nWrite to `scratch\u002Fconsultation.json`.\n\n---\n\n### Step 3 — Draft the retirement emails\n\nThis step produces **two** emails that are sent at different points in the\nretirement process:\n\n| File | Template | When to send | To |\n|------|----------|-------------|-----|\n| `scratch\u002Fretire_discuss.txt` | `templates\u002Fretire-email.md` | Before the vote | `general@incubator.apache.org` |\n| `scratch\u002Fretire_announce.txt` | `templates\u002Fannounce-email.md` | After vote passes | `dev@`, `announce@`, CC `general@` |\n\nDraft both now; send only the DISCUSS email immediately. Keep the announcement\ndraft until the vote closes successfully.\n\n#### 3a — Load shared data\n\n```python\nimport os, json\n\ndef _load(path):\n    if os.path.exists(path):\n        with open(path) as f:\n            return json.load(f)\n    return {}\n\nevidence     = _load(\"scratch\u002Fstalled_evidence.json\")\nconsultation = _load(\"scratch\u002Fconsultation.json\")\n\nmentors       = evidence.get(\"mentors\", [])\nmonths        = evidence.get(\"months_incubating\", \"?\")\nstalled_sig   = evidence.get(\"stalled_signal\") or {}\nconcerns      = evidence.get(\"active_concerns\", [])\nchronic       = evidence.get(\"report_history\", {}).get(\"chronic_issues\", [])\nresource      = evidence.get(\"resource\", PODLING.lower())\niflags        = evidence.get(\"inactivity_flags\", {})\nconfirmed     = evidence.get(\"confirmed_flags\", [])\n\nSHEPHERD_NAME = \"\u003CYour Name>\"   # set before rendering\n\ndef _flag_row(signal_name):\n    \"\"\"Format one inactivity signal as a table row.\"\"\"\n    f = iflags.get(signal_name, {})\n    obs = f.get(\"observed\")\n    obs_str = str(obs) if obs is not None else \"N\u002FA\"\n    flag_str = \"YES\" if f.get(\"flagged\") else (\"unknown\" if f.get(\"flagged\") is None else \"no\")\n    return (f\"  {f.get('label', signal_name):\u003C28}\"\n            f\"observed={obs_str:\u003C6} threshold≤{f.get('threshold','?'):\u003C4} flagged={flag_str}\")\n\ndef _flag_detail(signal_name):\n    \"\"\"Format a signal for the announcement email (concise, factual).\"\"\"\n    f = iflags.get(signal_name, {})\n    obs = f.get(\"observed\")\n    return (f\"  {f.get('label', signal_name):\u003C28}\"\n            f\"{str(obs) if obs is not None else 'N\u002FA':>6}  \"\n            f\"(threshold: ≤ {f.get('threshold', '?')})\")\n```\n\n#### 3b — DISCUSS email (send before vote)\n\n```python\nsubject_discuss = f\"[DISCUSS] Retire Apache {PODLING} (incubating)\"\n\ndiscuss_body = f\"\"\"\\\nTo: general@incubator.apache.org\nSubject: {subject_discuss}\n\nHi all,\n\nAfter reviewing the evidence below, I would like to propose that we retire\nApache {PODLING} (incubating) and move it to the Apache Attic.\n\n== Background ==\n\nApache {PODLING} has been in incubation since {evidence.get(\"start_date\", \"unknown\")}\n({months} months). Despite mentoring efforts, the project has not demonstrated\nthe sustained activity or release cadence needed to graduate as a Top-Level\nProject.\n\n== Stalled Evidence ==\n\nInactivity signals (each checked against its own configurable window):\n{_flag_row(\"commits\")}\n{_flag_row(\"mail_posts\")}\n{_flag_row(\"releases\")}\n\nSignals flagged: {len(confirmed)} \u002F 3  ({\", \".join(confirmed) if confirmed else \"none\"})\n\nIPMC stalled signal: {\"YES — \" + stalled_sig.get(\"summary\", \"\") if stalled_sig else \"not triggered (see concerns below)\"}\n\nIPMC concerns:\n{\"\".join(\"  - \" + c + chr(10) for c in concerns) or \"  (see stalled_evidence.json)\"}\n\n{\"Chronic unresolved issues (3+ consecutive reports):\" + chr(10) + \"\".join(\"  - \" + i + chr(10) for i in chronic) if chronic else \"\"}\n\n== Community Engagement ==\n\nMentor contact attempts: {len(consultation.get(\"contact_attempts\", []))}\nPPMC willing to continue: {consultation.get(\"ppmc_willing_to_continue\", \"unknown\")}\nMentor recommendation: {consultation.get(\"mentor_recommendation\", \"pending\")}\n\nMentor list: {\", \".join(mentors) if mentors else \"see podlings.xml\"}\n\n== Proposed Action ==\n\nWe propose to retire Apache {PODLING} and move the project to the Apache Attic\n(https:\u002F\u002Fattic.apache.org). This involves:\n\n  1. A [DISCUSS] thread on this list (this email) — 72-hour open window.\n  2. A [VOTE] thread on this list requiring 3 binding +1s and no unresolved -1s.\n  3. Archival steps listed in the shutdown checklist (see scratch\u002Fshutdown_checklist.md).\n\nIf the PPMC wishes to continue instead, please respond in this thread within\n72 hours with a concrete plan and a committed timeline.\n\nWe invite all community members, mentors, and IPMC members to share their views:\n\n  - Do you agree this podling meets the criteria for retirement?\n  - Is there evidence of activity or plans we have not considered?\n  - Do you support moving to a [VOTE] thread?\n\nThanks,\n{SHEPHERD_NAME}\n\"\"\"\n\nwith open(\"scratch\u002Fretire_discuss.txt\", \"w\") as f:\n    f.write(discuss_body)\nprint(f\"DISCUSS email written to: scratch\u002Fretire_discuss.txt\")\n```\n\n#### 3c — Retirement announcement email (send after vote passes)\n\nBased on `templates\u002Fannounce-email.md`. This email goes to the\npodling's own `dev@` list, the ASF-wide `announce@apache.org` (moderated),\nand CCs `general@incubator.apache.org`.\n\n```python\n# vote_result is filled in after the vote closes; leave as placeholders for now\nVOTE_START    = \"\u003CYYYY-MM-DD>\"\nVOTE_CLOSE    = \"\u003CYYYY-MM-DD>\"\nVOTE_PLUS1    = \"\u003CN>\"\nVOTE_ZERO     = \"\u003CN>\"\nVOTE_MINUS1   = \"\u003CN>\"\nVOTE_THREAD   = \"\u003Chttps:\u002F\u002Flists.apache.org\u002Fthread\u002F...>\"\nISSUE_TRACKER = f\"https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002F{PODLING.upper()}\"\nWEBSITE_URL   = f\"https:\u002F\u002F{resource}.apache.org\"\n\nsubject_announce = f\"Apache {PODLING} (incubating) has been retired\"\n\nannounce_body = f\"\"\"\\\nTo: dev@{resource}.apache.org\nCC: general@incubator.apache.org\nBCC: announce@apache.org\nSubject: {subject_announce}\n\nDear Apache {PODLING} community,\n\nThe Apache Incubator PMC has voted to retire Apache {PODLING} (incubating)\nand move the project to the Apache Attic. This email is the official\nannouncement of that decision.\n\n== Vote Result ==\n\nA [VOTE] thread was held on general@incubator.apache.org from\n{VOTE_START} to {VOTE_CLOSE}. The result was:\n\n  Binding +1:  {VOTE_PLUS1}\n  Binding  0:  {VOTE_ZERO}\n  Binding -1:  {VOTE_MINUS1}\n\nThe vote thread is archived at: {VOTE_THREAD}\n\n== Reason for Retirement ==\n\nApache {PODLING} entered the Apache Incubator on {evidence.get(\"start_date\", \"unknown\")}\nand has been incubating for approximately {months} months. The retirement was\nbased on the following evidence of sustained inactivity:\n\n{_flag_detail(\"commits\")}\n{_flag_detail(\"mail_posts\")}\n{_flag_detail(\"releases\")}\n\n{\"Chronic unresolved issues (3+ board reports):\" + chr(10) + \"\".join(\"  - \" + i + chr(10) for i in chronic) if chronic else \"\"}\n\n== What Happens Next ==\n\nProject resources are being preserved in the Apache Attic:\n  https:\u002F\u002Fattic.apache.org\u002Fprojects\u002F{resource}.html\n\n  Source code (read-only):  https:\u002F\u002Fgitbox.apache.org\u002Frepos\u002Fasf\u002F{resource}.git\n  dev@ archive:             https:\u002F\u002Flists.apache.org\u002Flist.html?dev@{resource}.apache.org\n  commits@ archive:         https:\u002F\u002Flists.apache.org\u002Flist.html?commits@{resource}.apache.org\n  Issue tracker:            {ISSUE_TRACKER}\n  Website:                  {WEBSITE_URL} (redirected to the Attic project page)\n\nThe mailing lists, issue tracker, and website will be closed or made\nread-only as part of the shutdown checklist in scratch\u002Fshutdown_checklist.md.\n\n== Thank You ==\n\nWe sincerely thank everyone who contributed to Apache {PODLING} — committers,\ncontributors, mentors, and users. All code and discussions remain available\nthrough the Attic for future reference.\n\nQuestions? Contact general@incubator.apache.org.\n\n{SHEPHERD_NAME}\nApache Incubator PMC\n\"\"\"\n\nwith open(\"scratch\u002Fretire_announce.txt\", \"w\") as f:\n    f.write(announce_body)\nprint(f\"Announcement email written to: scratch\u002Fretire_announce.txt\")\nprint()\nprint(\"Next steps:\")\nprint(\"  1. Review and send scratch\u002Fretire_discuss.txt to general@incubator.apache.org\")\nprint(\"  2. After 72h, post a [VOTE] thread\")\nprint(\"  3. After vote passes, fill in vote placeholders in scratch\u002Fretire_announce.txt\")\nprint(\"  4. Send retire_announce.txt to dev@, announce@, CC general@\")\n```\n\nWrite to `scratch\u002Fretire_discuss.txt` and `scratch\u002Fretire_announce.txt`.\n\n---\n\n### Step 4 — Shutdown checklist\n\nProduce a step-by-step checklist of every action required to shut down the\npodling cleanly. The shepherd works through this list after a successful\nretirement vote.\n\n```python\nresource = evidence.get(\"resource\", PODLING.lower())\n\nchecklist = f\"\"\"# Shutdown Checklist — Apache {PODLING} (incubating)\n\nGenerated for: {PODLING}\nResource slug: {resource}\nMentors: {\", \".join(evidence.get(\"mentors\", []))}\n\nComplete each item in order. Tick when done.\n\n## Phase 1 — Vote and announcement (before archival)\n\n  [ ] Post [DISCUSS] thread on general@incubator.apache.org and wait 72 h.\n  [ ] Post [VOTE] thread on general@incubator.apache.org; requires ≥ 3 binding\n      +1s and more +1s than -1s, with no unresolved -1s.\n  [ ] Record vote result in scratch\u002Fvote_result.txt (vote count, thread URL).\n  [ ] Notify dev@{resource}.apache.org and any user@ list of the retirement\n      decision, including where archived code can be found.\n  [ ] Post a [RESULT][VOTE] message on general@incubator.apache.org.\n\n## Phase 2 — Apache Attic transfer\n\n  [ ] Open an Attic JIRA ticket at https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002FATTIC\n      with subject \"Move Apache {PODLING} to Attic\".\n      Include: mailing list names, git repo URL, website URL, JIRA project key.\n  [ ] Coordinate with the Attic PMC to transfer or archive the project website.\n  [ ] Redirect {resource}.apache.org (or {resource}.incubator.apache.org) to\n      the Attic project page once the Attic entry is live.\n\n## Phase 3 — Infrastructure retirement\n\n  [ ] Close or archive mailing lists:\n        dev@{resource}.apache.org\n        commits@{resource}.apache.org\n        user@{resource}.apache.org  (if active)\n      Coordinate with ASF Infra (https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002FINFRA).\n  [ ] Archive the source repository:\n        https:\u002F\u002Fgitbox.apache.org\u002Frepos\u002Fasf\u002F{resource}.git\n      Request read-only archival via INFRA JIRA.\n  [ ] Close or archive the issue tracker:\n        JIRA project: https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002F{PODLING.upper()}\n        OR GitHub Issues: https:\u002F\u002Fgithub.com\u002Fapache\u002F{resource}\n  [ ] Remove active CI\u002Fbuild pipelines (GitHub Actions, Jenkins) from the repo.\n  [ ] Remove any release artifacts from dist.apache.org\u002Frepos\u002Fdist\u002Frelease\n      that must not remain available; confirm with the Attic PMC first.\n\n## Phase 4 — Roster and metadata\n\n  [ ] Update podlings.xml: set status=\"retired\" and enddate=\u003CYYYY-MM-DD>.\n      The podlings.xml change is made by the Incubator PMC secretary.\n  [ ] Remove the podling from the active reporting schedule in the Incubator\n      board report for the next board meeting.\n  [ ] Update the Incubator website podlings page if it caches active podlings.\n\n## Phase 5 — Final board report\n\n  [ ] The Incubator PMC includes a final retirement note in the next board\n      report, citing the vote thread URL and the retirement date.\n  [ ] The podling's mentors are released from mentoring duties.\n\n## Contacts\n\n  ASF Infra JIRA:  https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002FINFRA\n  Apache Attic:    https:\u002F\u002Fattic.apache.org \u002F https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002FATTIC\n  Incubator PMC:   general@incubator.apache.org\n\n## Notes\n\n(add any podling-specific notes here — custom infra, active downstream users,\n donated IP that needs special handling, etc.)\n\"\"\"\n\nwith open(\"scratch\u002Fshutdown_checklist.md\", \"w\") as f:\n    f.write(checklist)\n\nprint(\"Written to: scratch\u002Fshutdown_checklist.md\")\n```\n\nWrite to `scratch\u002Fshutdown_checklist.md`.\n\n---\n\n### Step 5 — Summary report\n\nPrint a one-page summary for the shepherd before they post anything.\n\n```python\nstrength = evidence.get(\"retirement_case_strength\", \"unknown\")\nmentor_rec = consultation.get(\"mentor_recommendation\", \"pending\")\n\nprint(f\"\"\"\n=== Retirement Dossier: Apache {PODLING} ===\n\nIncubating since: {evidence.get(\"start_date\", \"?\")} ({months} months)\nRetirement case:  {strength.upper()}\nMentor recommendation: {mentor_rec}\n\nKey stalled signals:\n{\"\".join(\"  - \" + s + chr(10) for s in (stalled_sig.get(\"definition_matched\") or concerns[:3]))}\n\nNext actions:\n  1. Fill in SHEPHERD_NAME in scratch\u002Fretire_discuss.txt and scratch\u002Fretire_announce.txt.\n  2. Complete consultation.json if mentor contact is not yet done.\n  3. Post [DISCUSS] email (scratch\u002Fretire_discuss.txt) to general@incubator.apache.org.\n  4. After 72 h, post [VOTE] thread.\n  5. After vote passes, fill vote placeholders in scratch\u002Fretire_announce.txt and send.\n  6. Work through scratch\u002Fshutdown_checklist.md after a successful vote.\n\nScratch files written:\n  scratch\u002Fstalled_evidence.json\n  scratch\u002Fconsultation.json\n  scratch\u002Fretire_discuss.txt\n  scratch\u002Fretire_announce.txt\n  scratch\u002Fshutdown_checklist.md\n\"\"\")\n```\n\n---\n\n## Output checklist\n\nBefore declaring the dossier complete, confirm all five files exist and are\nnon-empty:\n\n- [ ] `scratch\u002Fstalled_evidence.json` — stalled signals, health metrics, lifecycle data\n- [ ] `scratch\u002Fconsultation.json` — mentor contact attempts and recommendation\n- [ ] `scratch\u002Fretire_discuss.txt` — ready-to-post [DISCUSS] email to `general@incubator.apache.org`\n- [ ] `scratch\u002Fretire_announce.txt` — post-vote announcement to `dev@`, CC `general@`, BCC `announce@`\n- [ ] `scratch\u002Fshutdown_checklist.md` — ordered shutdown steps with checkboxes\n\n---\n\n## Common issues\n\n**Adjusting the configurable windows** — the defaults (commits 3m ≤ 5,\nmail_posts 6m ≤ 5, releases 12m = 0) are calibrated for a typical active\npodling. For projects with known seasonal patterns or a single large initial\ncommit, raise the commit threshold or widen the window. For fast-moving\necosystems where one release per year is genuinely insufficient, lower the\nreleases threshold to check 6m. Always record any override in the\n`INACTIVITY_CONFIG` comment so the rationale is visible in the evidence record.\n\n**`flagged = None` means data is missing, not that the signal is clear** —\nif a health report window is absent, that signal is recorded as `unknown`.\nCount `unknown_flags` separately from `confirmed_flags` in the email; do not\ntreat a missing window as evidence the project is healthy.\n\n**`stalled_entry` is None but the podling seems inactive** — the IPMC strict\nstalled definition requires low commits, low committers, AND no 12-month\nreleases simultaneously. A podling with mailing-list traffic but no commits may\nnot trigger it. Independent inactivity flags (from `INACTIVITY_CONFIG`) can\nstill fire on individual signals; use them and `watchlist_entry` as\nsupplementary evidence in the retirement email.\n\n**PPMC responds and commits to a revival plan** — do not proceed to a vote.\nUpdate `consultation.json` with the response, close the dossier, and schedule a\nfollow-up check in 90 days (use the monthly report review skill to monitor).\n\n**`get_podling_reports` has no `limit` parameter** — it returns all cached\nreports. Sort by `report_period` descending after retrieval; do not pass a\n`limit` keyword argument.\n\n**Podling has no health report** — `get_report_summary` raises `FileNotFoundError`.\nSet `activity_by_window = {}` and note in the retirement email that health data\nis unavailable; rely on `stalled_entry` and report-history evidence instead.\n\n**PYTHONPATH must include all four repos** in the same Python process. Re-export\n`PYTHONPATH` in every `subprocess` call or use `sys.path.insert` at the top of\neach script.\n","# Retirement Shepherd\n\nProduce a complete retirement dossier for one Apache Incubator podling:\nstalled-evidence assessment, retirement announcement email, and a step-by-step\nshutdown checklist.\n\nRun this skill when a podling's mentors, the IPMC chair, or a shepherd\nbelieve a podling has stalled and should be retired rather than graduated.\n\n## Server locations\n\nAll server code lives here — use these absolute paths; never assume relative\npaths:\n\n| Role | Path |\n|------|------|\n| IPMC oversight tools | `~\u002FIncubatorMCP` |\n| Per-podling health metrics | `~\u002FHealthMCP` |\n| Podling roster & schedule | `~\u002FPodlingsMCP` |\n| Report cache | `~\u002FReportMCP` |\n\nHealth report Markdown files (input to HealthMCP): `~\u002Fincubator\u002Freports`\n\nThe harness has no MCP transport — invoke server Python functions directly via\n`Bash` with the PYTHONPATH below. Never call MCP tool endpoints over stdio.\n\n```bash\nexport PYTHONPATH=~\u002FIncubatorMCP:\\\n~\u002FHealthMCP\u002Fsrc:\\\n~\u002FPodlingsMCP:\\\n~\u002FReportMCP\u002Fsrc\n```\n\n---\n\n## Trigger conditions\n\nRun this workflow when:\n\n- A mentor or IPMC member reports that a podling has had no meaningful commits,\n  releases, or community activity for an extended period.\n- `tool_stalled_podlings` or `tool_ipmc_watchlist` returns a podling with\n  severity `\"high\"` and the recommended action involves retirement.\n- The IPMC chair asks for a retirement dossier ahead of a board meeting.\n- A user asks whether a specific podling should be retired.\n\nThe podling name is **required**. If the user has not supplied one, ask.\nIf the user asks to find stalled podlings first, run Step 1 across all current\npodlings before targeting a specific one.\n\n---\n\n## Workflow\n\nWork through these five steps in order. Each step writes a file under `scratch\u002F`\n(create the directory first if it does not exist: `mkdir -p scratch`). Later\nsteps depend on earlier ones, so do not skip ahead.\n\n### Step 1 — Stalled evidence assessment\n\nCollect objective evidence that the podling has stalled. The goal is to replace\nsubjective impressions with specific, sourced metrics that can be cited in the\nretirement discussion.\n\n#### 1a — Configure inactivity windows and thresholds\n\nAll three inactivity signals — commits, mailing-list posts, and releases —\nare checked against configurable windows. Adjust these before running if the\npodling is in an unusual situation (e.g., very new, recently donated large\ncodebase, seasonal project).\n\n```python\nimport sys, json\nsys.path.insert(0, '~\u002FIncubatorMCP')\nsys.path.insert(0, '~\u002FHealthMCP\u002Fsrc')\nsys.path.insert(0, '~\u002FPodlingsMCP')\nsys.path.insert(0, '~\u002FReportMCP\u002Fsrc')\n\nPODLING     = '\u003Cpodling-name>'   # replace with the target podling\nREPORTS_DIR = '~\u002Fincubator\u002Freports'\n\n# --- Configurable inactivity detection ---\n# Each signal has:\n#   window   : health-report window to read from (\"3m\", \"6m\", \"12m\")\n#   threshold: value at-or-below which the signal is FLAGGED\n#   metric   : field name in the health report window\n#   label    : human-readable name for reporting\nINACTIVITY_CONFIG = {\n    \"commits\": {\n        \"window\":    \"3m\",   # short window — catches recent freeze quickly\n        \"metric\":    \"commits\",\n        \"threshold\": 5,       # ≤ 5 commits in 3 months = effectively inactive\n        \"label\":     \"Commits (3m)\",\n        \"rationale\": \"Fewer than 5 commits in the past 3 months indicates\"\n                     \" the codebase is not being actively developed.\",\n    },\n    \"mail_posts\": {\n        \"window\":    \"6m\",   # longer window — mailing lists can be quiet seasonally\n        \"metric\":    \"dev_messages\",\n        \"threshold\": 5,       # ≤ 5 messages in 6 months = community is silent\n        \"label\":     \"dev@ messages (6m)\",\n        \"rationale\": \"Fewer than 5 messages on dev@ in 6 months indicates\"\n                     \" the community is no longer actively discussing the project.\",\n    },\n    \"releases\": {\n        \"window\":    \"12m\",  # annual window — one release per year is the minimum\n        \"metric\":    \"releases\",\n        \"threshold\": 0,       # 0 releases in 12 months = no Apache release made\n        \"label\":     \"Releases (12m)\",\n        \"rationale\": \"Zero releases in the past 12 months means the podling has\"\n                     \" not produced any Apache-branded software deliverable.\",\n    },\n}\n# Optional overrides — uncomment and adjust as needed:\n# INACTIVITY_CONFIG[\"commits\"][\"window\"]    = \"6m\"   # use 6m if 3m data is noisy\n# INACTIVITY_CONFIG[\"commits\"][\"threshold\"] = 10      # stricter for large repos\n# INACTIVITY_CONFIG[\"mail_posts\"][\"threshold\"] = 10   # stricter for active domains\n# INACTIVITY_CONFIG[\"releases\"][\"window\"]  = \"6m\"    # tighter for older podlings\n```\n\n#### 1b — Collect health metrics across all windows\n\n```python\nfrom apache_health_mcp.tools import get_report_summary\n\ntry:\n    health = get_report_summary(PODLING, REPORTS_DIR)\n    all_windows = health.get(\"latest_metrics\", {})\n    # Keep all windows for context; flag signals use only the configured window\n    activity_by_window = {}\n    for w, m in all_windows.items():\n        if m is None:\n            continue\n        activity_by_window[w] = {\n            \"date_range\":        m.get(\"date_range\"),\n            \"commits\":           m.get(\"commits\"),\n            \"unique_committers\": m.get(\"unique_committers\"),\n            \"unique_authors\":    m.get(\"unique_authors\"),\n            \"releases\":          m.get(\"releases\"),\n            \"new_contributors\":  m.get(\"new_contributors\"),\n            \"dev_messages\":      m.get(\"dev_messages\"),\n            \"dev_unique_posters\":m.get(\"dev_unique_posters\"),\n            \"bus50\":             m.get(\"bus50\"),\n            \"avg_mentor_signoffs\": m.get(\"avg_mentor_signoffs\"),\n        }\nexcept FileNotFoundError:\n    health = None\n    activity_by_window = {}\n```\n\n#### 1c — Evaluate inactivity signals against configured thresholds\n\n```python\ninactivity_flags = {}   # signal_name -> flag dict\n\nfor signal_name, cfg in INACTIVITY_CONFIG.items():\n    window_key = cfg[\"window\"]\n    metric     = cfg[\"metric\"]\n    threshold  = cfg[\"threshold\"]\n\n    window_data = activity_by_window.get(window_key)\n    if window_data is None:\n        # Window not present in health report — treat as unknown, not flagged\n        inactivity_flags[signal_name] = {\n            \"flagged\":    None,       # None = data not available\n            \"window\":     window_key,\n            \"metric\":     metric,\n            \"observed\":   None,\n            \"threshold\":  threshold,\n            \"label\":      cfg[\"label\"],\n            \"rationale\":  cfg[\"rationale\"],\n            \"note\":       f\"Window '{window_key}' not available in health report.\",\n        }\n        continue\n\n    observed = window_data.get(metric)\n    if observed is None:\n        flagged = None   # metric absent — cannot evaluate\n    else:\n        flagged = observed \u003C= threshold\n\n    inactivity_flags[signal_name] = {\n        \"flagged\":   flagged,    # True = stalled signal triggered\n        \"window\":    window_key,\n        \"metric\":    metric,\n        \"observed\":  observed,\n        \"threshold\": threshold,\n        \"label\":     cfg[\"label\"],\n        \"rationale\": cfg[\"rationale\"],\n        \"note\":      None,\n    }\n\n# Count confirmed flags (True) vs unknown (None) vs passing (False)\nconfirmed_flags = [k for k, v in inactivity_flags.items() if v[\"flagged\"] is True]\nunknown_flags   = [k for k, v in inactivity_flags.items() if v[\"flagged\"] is None]\npassing_signals = [k for k, v in inactivity_flags.items() if v[\"flagged\"] is False]\n```\n\nPrint a detection table immediately after evaluation so the shepherd can\nreview the signals before proceeding:\n\n```python\nprint(f\"\\n=== Inactivity Detection: Apache {PODLING} ===\\n\")\nprint(f\"{'Signal':\u003C15} {'Window':\u003C6} {'Observed':>10}  {'Threshold':>10}  {'Flagged'}\")\nprint(\"-\" * 60)\nfor name, f in inactivity_flags.items():\n    obs = str(f[\"observed\"]) if f[\"observed\"] is not None else \"N\u002FA\"\n    thr = str(f[\"threshold\"])\n    flag_str = \"*** YES ***\" if f[\"flagged\"] else (\"UNKNOWN\" if f[\"flagged\"] is None else \"no\")\n    print(f\"  {f['label']:\u003C13} {f['window']:\u003C6} {obs:>10}  {thr:>10}  {flag_str}\")\nprint()\nprint(f\"Confirmed flags: {len(confirmed_flags)} \u002F {len(INACTIVITY_CONFIG)}\"\n      f\" ({', '.join(confirmed_flags) or 'none'})\")\n```\n\n#### 1d — IPMC stalled-podling and watchlist signals\n\n```python\nfrom ipmc.tools import tool_stalled_podlings, tool_ipmc_watchlist, tool_podling_brief\n\nstalled_all = tool_stalled_podlings({\"health_source\": REPORTS_DIR})\nstalled_entry = next(\n    (item for item in stalled_all.get(\"items\", [])\n     if item[\"podling\"].lower() == PODLING.lower()),\n    None,\n)\n# stalled_entry fields when present:\n#   severity: \"high\" \u002F \"medium\" \u002F \"low\"\n#   definition_matched: list of signals matched by the IPMC strict definition\n#   observed: {commits, unique_committers, dev_messages, releases_12m}\n#   summary, recommended_ipmc_action\n\nwatchlist = tool_ipmc_watchlist({\n    \"health_source\": REPORTS_DIR,\n    \"severity_at_least\": \"medium\",\n})\nwatchlist_entry = next(\n    (item for item in watchlist.get(\"items\", [])\n     if item.get(\"podling\", \"\").lower() == PODLING.lower()),\n    None,\n)\n\nbrief = tool_podling_brief({\n    \"podling\": PODLING,\n    \"health_source\": REPORTS_DIR,\n    \"focus\": [\"status\", \"health\", \"risk\"],\n    \"brief_format\": \"detailed\",\n})\n```\n\nThe IPMC strict stalled definition (from `tool_stalled_podlings`) requires\n*all* of low commits, low committers, and no releases to fire together. The\nconfigurable flags above are *independent* — each can fire alone. Both\nperspectives appear in the final evidence record.\n\n#### 1e — Podling lifecycle and incubation duration\n\n```python\nfrom podlings.tools import tool_get_podling\nfrom datetime import date\n\npodling_meta      = tool_get_podling({\"name\": PODLING})\np                 = podling_meta[\"podling\"]\nmentors           = p.get(\"mentors\", [])\nstart_date        = p.get(\"startdate\")\nresource          = p.get(\"resource\", PODLING.lower())\nmonths_incubating = None\nif start_date:\n    start             = date.fromisoformat(start_date)\n    today             = date.today()\n    months_incubating = (today.year - start.year) * 12 + (today.month - start.month)\n\n# A podling incubating > 24 months with all three signals flagged is a strong\n# retirement candidate regardless of the IPMC strict stalled definition.\nlong_incubation = months_incubating is not None and months_incubating > 24\n```\n\n#### 1f — Report history: missed reports and chronic issues\n\n```python\nfrom apache_incubator_reports_mcp.tools import get_podling_reports\n\nCACHE_DIR = '~\u002FReportMCP\u002F.cache\u002Fagenda-incubator-reports'\ntry:\n    report_history = get_podling_reports(podling=PODLING, cache_dir=CACHE_DIR)\n    all_reports    = report_history.get(\"reports\", [])   # newest-first\n    total_reports  = len(all_reports)\n    report_periods = [r[\"report_period\"] for r in all_reports]\n\n    # Tally issues across all reports; chronic = appears in ≥ 3 reports\n    issue_counts = {}\n    for r in all_reports:\n        for issue in (r.get(\"issues\") or []):\n            issue_counts[issue] = issue_counts.get(issue, 0) + 1\n    chronic_issues = [i for i, cnt in issue_counts.items() if cnt >= 3]\nexcept Exception:\n    total_reports, report_periods, chronic_issues = 0, [], []\n```\n\nChronic issues (in 3+ reports unresolved) corroborate the inactivity flags\nand should be quoted in the retirement email.\n\n#### 1g — Write the stalled-evidence record\n\n```python\nimport os\nos.makedirs(\"scratch\", exist_ok=True)\n\n# Overall retirement case strength:\n# \"strong\"   — all three configurable flags triggered, or IPMC stalled signal at high severity\n# \"moderate\" — one or two flags triggered, or watchlist entry present\n# \"weak\"     — no flags triggered and not on watchlist\nall_three_flagged = len(confirmed_flags) == 3\nipmc_high         = stalled_entry and stalled_entry.get(\"severity\") == \"high\"\n\nif all_three_flagged or ipmc_high:\n    retirement_case_strength = \"strong\"\nelif confirmed_flags or stalled_entry or watchlist_entry:\n    retirement_case_strength = \"moderate\"\nelse:\n    retirement_case_strength = \"weak\"\n\nstalled_evidence = {\n    \"podling\":                  PODLING,\n    \"resource\":                 resource,\n    \"mentors\":                  mentors,\n    \"start_date\":               start_date,\n    \"months_incubating\":        months_incubating,\n    \"long_incubation\":          long_incubation,\n\n    # Configurable inactivity detection results\n    \"inactivity_config\":        INACTIVITY_CONFIG,\n    \"inactivity_flags\":         inactivity_flags,\n    \"confirmed_flags\":          confirmed_flags,    # signals that fired\n    \"unknown_flags\":            unknown_flags,      # signals with missing data\n    \"passing_signals\":          passing_signals,    # signals that did NOT fire\n\n    # IPMC tool signals\n    \"stalled_signal\":           stalled_entry,\n    \"watchlist_entry\":          watchlist_entry,\n    \"brief_summary\":            brief.get(\"status_summary\"),\n    \"active_concerns\":          brief.get(\"active_concerns\", []),\n    \"outlook\":                  brief.get(\"outlook\"),\n\n    # Raw health data for all windows\n    \"activity_by_window\":       activity_by_window,\n\n    # Board report history\n    \"report_history\": {\n        \"total_reports_filed\":  total_reports,\n        \"report_periods\":       report_periods,\n        \"chronic_issues\":       chronic_issues,\n    },\n\n    \"retirement_case_strength\": retirement_case_strength,\n}\n\nwith open(\"scratch\u002Fstalled_evidence.json\", \"w\") as f:\n    json.dump(stalled_evidence, f, indent=2, default=str)\n\nprint(f\"\\nRetirement case strength: {retirement_case_strength.upper()}\")\nprint(f\"Confirmed inactivity flags: {confirmed_flags}\")\nif unknown_flags:\n    print(f\"Flags with missing data:   {unknown_flags}\")\n```\n\nWrite to `scratch\u002Fstalled_evidence.json`.\n\nIf `retirement_case_strength == \"weak\"`, report this to the user and ask\nwhether to continue — retiring a healthy podling is a serious, irreversible\naction. The configurable thresholds can be tightened (lower values) to\nproduce a stricter assessment, or loosened if the project has a known\nslow period.\n\n---\n\n### Step 2 — Mentor consultation record\n\nBefore posting a retirement discussion, the shepherding mentor must have\nattempted to re-engage the PPMC and documented the outcome. Record this\nmanually:\n\n```python\nconsultation = {\n    \"podling\": PODLING,\n    \"contact_attempts\": [\n        # Each entry: date, method (email\u002FIRC\u002Fcall), summary of response\n        # e.g. {\"date\": \"2026-07-01\", \"method\": \"email to dev@\",\n        #        \"response\": \"no reply after 14 days\"}\n    ],\n    \"ppmc_willing_to_continue\": None,  # True \u002F False \u002F None (no response)\n    \"mentor_recommendation\":    None,  # \"retire\" \u002F \"continue\" \u002F \"defer\"\n    \"notes\": \"\",\n}\n\nwith open(\"scratch\u002Fconsultation.json\", \"w\") as f:\n    json.dump(consultation, f, indent=2, default=str)\n```\n\nThe shepherd must fill `contact_attempts`, `ppmc_willing_to_continue`, and\n`mentor_recommendation` before drafting the retirement email. If there has been\nno contact attempt, make one and wait at least 14 days for a response before\nproceeding.\n\nWrite to `scratch\u002Fconsultation.json`.\n\n---\n\n### Step 3 — Draft the retirement emails\n\nThis step produces **two** emails that are sent at different points in the\nretirement process:\n\n| File | Template | When to send | To |\n|------|----------|-------------|-----|\n| `scratch\u002Fretire_discuss.txt` | `templates\u002Fretire-email.md` | Before the vote | `general@incubator.apache.org` |\n| `scratch\u002Fretire_announce.txt` | `templates\u002Fannounce-email.md` | After vote passes | `dev@`, `announce@`, CC `general@` |\n\nDraft both now; send only the DISCUSS email immediately. Keep the announcement\ndraft until the vote closes successfully.\n\n#### 3a — Load shared data\n\n```python\nimport os, json\n\ndef _load(path):\n    if os.path.exists(path):\n        with open(path) as f:\n            return json.load(f)\n    return {}\n\nevidence     = _load(\"scratch\u002Fstalled_evidence.json\")\nconsultation = _load(\"scratch\u002Fconsultation.json\")\n\nmentors       = evidence.get(\"mentors\", [])\nmonths        = evidence.get(\"months_incubating\", \"?\")\nstalled_sig   = evidence.get(\"stalled_signal\") or {}\nconcerns      = evidence.get(\"active_concerns\", [])\nchronic       = evidence.get(\"report_history\", {}).get(\"chronic_issues\", [])\nresource      = evidence.get(\"resource\", PODLING.lower())\niflags        = evidence.get(\"inactivity_flags\", {})\nconfirmed     = evidence.get(\"confirmed_flags\", [])\n\nSHEPHERD_NAME = \"\u003CYour Name>\"   # set before rendering\n\ndef _flag_row(signal_name):\n    \"\"\"Format one inactivity signal as a table row.\"\"\"\n    f = iflags.get(signal_name, {})\n    obs = f.get(\"observed\")\n    obs_str = str(obs) if obs is not None else \"N\u002FA\"\n    flag_str = \"YES\" if f.get(\"flagged\") else (\"unknown\" if f.get(\"flagged\") is None else \"no\")\n    return (f\"  {f.get('label', signal_name):\u003C28}\"\n            f\"observed={obs_str:\u003C6} threshold≤{f.get('threshold','?'):\u003C4} flagged={flag_str}\")\n\ndef _flag_detail(signal_name):\n    \"\"\"Format a signal for the announcement email (concise, factual).\"\"\"\n    f = iflags.get(signal_name, {})\n    obs = f.get(\"observed\")\n    return (f\"  {f.get('label', signal_name):\u003C28}\"\n            f\"{str(obs) if obs is not None else 'N\u002FA':>6}  \"\n            f\"(threshold: ≤ {f.get('threshold', '?')})\")\n```\n\n#### 3b — DISCUSS email (send before vote)\n\n```python\nsubject_discuss = f\"[DISCUSS] Retire Apache {PODLING} (incubating)\"\n\ndiscuss_body = f\"\"\"\\\nTo: general@incubator.apache.org\nSubject: {subject_discuss}\n\nHi all,\n\nAfter reviewing the evidence below, I would like to propose that we retire\nApache {PODLING} (incubating) and move it to the Apache Attic.\n\n== Background ==\n\nApache {PODLING} has been in incubation since {evidence.get(\"start_date\", \"unknown\")}\n({months} months). Despite mentoring efforts, the project has not demonstrated\nthe sustained activity or release cadence needed to graduate as a Top-Level\nProject.\n\n== Stalled Evidence ==\n\nInactivity signals (each checked against its own configurable window):\n{_flag_row(\"commits\")}\n{_flag_row(\"mail_posts\")}\n{_flag_row(\"releases\")}\n\nSignals flagged: {len(confirmed)} \u002F 3  ({\", \".join(confirmed) if confirmed else \"none\"})\n\nIPMC stalled signal: {\"YES — \" + stalled_sig.get(\"summary\", \"\") if stalled_sig else \"not triggered (see concerns below)\"}\n\nIPMC concerns:\n{\"\".join(\"  - \" + c + chr(10) for c in concerns) or \"  (see stalled_evidence.json)\"}\n\n{\"Chronic unresolved issues (3+ consecutive reports):\" + chr(10) + \"\".join(\"  - \" + i + chr(10) for i in chronic) if chronic else \"\"}\n\n== Community Engagement ==\n\nMentor contact attempts: {len(consultation.get(\"contact_attempts\", []))}\nPPMC willing to continue: {consultation.get(\"ppmc_willing_to_continue\", \"unknown\")}\nMentor recommendation: {consultation.get(\"mentor_recommendation\", \"pending\")}\n\nMentor list: {\", \".join(mentors) if mentors else \"see podlings.xml\"}\n\n== Proposed Action ==\n\nWe propose to retire Apache {PODLING} and move the project to the Apache Attic\n(https:\u002F\u002Fattic.apache.org). This involves:\n\n  1. A [DISCUSS] thread on this list (this email) — 72-hour open window.\n  2. A [VOTE] thread on this list requiring 3 binding +1s and no unresolved -1s.\n  3. Archival steps listed in the shutdown checklist (see scratch\u002Fshutdown_checklist.md).\n\nIf the PPMC wishes to continue instead, please respond in this thread within\n72 hours with a concrete plan and a committed timeline.\n\nWe invite all community members, mentors, and IPMC members to share their views:\n\n  - Do you agree this podling meets the criteria for retirement?\n  - Is there evidence of activity or plans we have not considered?\n  - Do you support moving to a [VOTE] thread?\n\nThanks,\n{SHEPHERD_NAME}\n\"\"\"\n\nwith open(\"scratch\u002Fretire_discuss.txt\", \"w\") as f:\n    f.write(discuss_body)\nprint(f\"DISCUSS email written to: scratch\u002Fretire_discuss.txt\")\n```\n\n#### 3c — Retirement announcement email (send after vote passes)\n\nBased on `templates\u002Fannounce-email.md`. This email goes to the\npodling's own `dev@` list, the ASF-wide `announce@apache.org` (moderated),\nand CCs `general@incubator.apache.org`.\n\n```python\n# vote_result is filled in after the vote closes; leave as placeholders for now\nVOTE_START    = \"\u003CYYYY-MM-DD>\"\nVOTE_CLOSE    = \"\u003CYYYY-MM-DD>\"\nVOTE_PLUS1    = \"\u003CN>\"\nVOTE_ZERO     = \"\u003CN>\"\nVOTE_MINUS1   = \"\u003CN>\"\nVOTE_THREAD   = \"\u003Chttps:\u002F\u002Flists.apache.org\u002Fthread\u002F...>\"\nISSUE_TRACKER = f\"https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002F{PODLING.upper()}\"\nWEBSITE_URL   = f\"https:\u002F\u002F{resource}.apache.org\"\n\nsubject_announce = f\"Apache {PODLING} (incubating) has been retired\"\n\nannounce_body = f\"\"\"\\\nTo: dev@{resource}.apache.org\nCC: general@incubator.apache.org\nBCC: announce@apache.org\nSubject: {subject_announce}\n\nDear Apache {PODLING} community,\n\nThe Apache Incubator PMC has voted to retire Apache {PODLING} (incubating)\nand move the project to the Apache Attic. This email is the official\nannouncement of that decision.\n\n== Vote Result ==\n\nA [VOTE] thread was held on general@incubator.apache.org from\n{VOTE_START} to {VOTE_CLOSE}. The result was:\n\n  Binding +1:  {VOTE_PLUS1}\n  Binding  0:  {VOTE_ZERO}\n  Binding -1:  {VOTE_MINUS1}\n\nThe vote thread is archived at: {VOTE_THREAD}\n\n== Reason for Retirement ==\n\nApache {PODLING} entered the Apache Incubator on {evidence.get(\"start_date\", \"unknown\")}\nand has been incubating for approximately {months} months. The retirement was\nbased on the following evidence of sustained inactivity:\n\n{_flag_detail(\"commits\")}\n{_flag_detail(\"mail_posts\")}\n{_flag_detail(\"releases\")}\n\n{\"Chronic unresolved issues (3+ board reports):\" + chr(10) + \"\".join(\"  - \" + i + chr(10) for i in chronic) if chronic else \"\"}\n\n== What Happens Next ==\n\nProject resources are being preserved in the Apache Attic:\n  https:\u002F\u002Fattic.apache.org\u002Fprojects\u002F{resource}.html\n\n  Source code (read-only):  https:\u002F\u002Fgitbox.apache.org\u002Frepos\u002Fasf\u002F{resource}.git\n  dev@ archive:             https:\u002F\u002Flists.apache.org\u002Flist.html?dev@{resource}.apache.org\n  commits@ archive:         https:\u002F\u002Flists.apache.org\u002Flist.html?commits@{resource}.apache.org\n  Issue tracker:            {ISSUE_TRACKER}\n  Website:                  {WEBSITE_URL} (redirected to the Attic project page)\n\nThe mailing lists, issue tracker, and website will be closed or made\nread-only as part of the shutdown checklist in scratch\u002Fshutdown_checklist.md.\n\n== Thank You ==\n\nWe sincerely thank everyone who contributed to Apache {PODLING} — committers,\ncontributors, mentors, and users. All code and discussions remain available\nthrough the Attic for future reference.\n\nQuestions? Contact general@incubator.apache.org.\n\n{SHEPHERD_NAME}\nApache Incubator PMC\n\"\"\"\n\nwith open(\"scratch\u002Fretire_announce.txt\", \"w\") as f:\n    f.write(announce_body)\nprint(f\"Announcement email written to: scratch\u002Fretire_announce.txt\")\nprint()\nprint(\"Next steps:\")\nprint(\"  1. Review and send scratch\u002Fretire_discuss.txt to general@incubator.apache.org\")\nprint(\"  2. After 72h, post a [VOTE] thread\")\nprint(\"  3. After vote passes, fill in vote placeholders in scratch\u002Fretire_announce.txt\")\nprint(\"  4. Send retire_announce.txt to dev@, announce@, CC general@\")\n```\n\nWrite to `scratch\u002Fretire_discuss.txt` and `scratch\u002Fretire_announce.txt`.\n\n---\n\n### Step 4 — Shutdown checklist\n\nProduce a step-by-step checklist of every action required to shut down the\npodling cleanly. The shepherd works through this list after a successful\nretirement vote.\n\n```python\nresource = evidence.get(\"resource\", PODLING.lower())\n\nchecklist = f\"\"\"# Shutdown Checklist — Apache {PODLING} (incubating)\n\nGenerated for: {PODLING}\nResource slug: {resource}\nMentors: {\", \".join(evidence.get(\"mentors\", []))}\n\nComplete each item in order. Tick when done.\n\n## Phase 1 — Vote and announcement (before archival)\n\n  [ ] Post [DISCUSS] thread on general@incubator.apache.org and wait 72 h.\n  [ ] Post [VOTE] thread on general@incubator.apache.org; requires ≥ 3 binding\n      +1s and more +1s than -1s, with no unresolved -1s.\n  [ ] Record vote result in scratch\u002Fvote_result.txt (vote count, thread URL).\n  [ ] Notify dev@{resource}.apache.org and any user@ list of the retirement\n      decision, including where archived code can be found.\n  [ ] Post a [RESULT][VOTE] message on general@incubator.apache.org.\n\n## Phase 2 — Apache Attic transfer\n\n  [ ] Open an Attic JIRA ticket at https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002FATTIC\n      with subject \"Move Apache {PODLING} to Attic\".\n      Include: mailing list names, git repo URL, website URL, JIRA project key.\n  [ ] Coordinate with the Attic PMC to transfer or archive the project website.\n  [ ] Redirect {resource}.apache.org (or {resource}.incubator.apache.org) to\n      the Attic project page once the Attic entry is live.\n\n## Phase 3 — Infrastructure retirement\n\n  [ ] Close or archive mailing lists:\n        dev@{resource}.apache.org\n        commits@{resource}.apache.org\n        user@{resource}.apache.org  (if active)\n      Coordinate with ASF Infra (https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002FINFRA).\n  [ ] Archive the source repository:\n        https:\u002F\u002Fgitbox.apache.org\u002Frepos\u002Fasf\u002F{resource}.git\n      Request read-only archival via INFRA JIRA.\n  [ ] Close or archive the issue tracker:\n        JIRA project: https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002F{PODLING.upper()}\n        OR GitHub Issues: https:\u002F\u002Fgithub.com\u002Fapache\u002F{resource}\n  [ ] Remove active CI\u002Fbuild pipelines (GitHub Actions, Jenkins) from the repo.\n  [ ] Remove any release artifacts from dist.apache.org\u002Frepos\u002Fdist\u002Frelease\n      that must not remain available; confirm with the Attic PMC first.\n\n## Phase 4 — Roster and metadata\n\n  [ ] Update podlings.xml: set status=\"retired\" and enddate=\u003CYYYY-MM-DD>.\n      The podlings.xml change is made by the Incubator PMC secretary.\n  [ ] Remove the podling from the active reporting schedule in the Incubator\n      board report for the next board meeting.\n  [ ] Update the Incubator website podlings page if it caches active podlings.\n\n## Phase 5 — Final board report\n\n  [ ] The Incubator PMC includes a final retirement note in the next board\n      report, citing the vote thread URL and the retirement date.\n  [ ] The podling's mentors are released from mentoring duties.\n\n## Contacts\n\n  ASF Infra JIRA:  https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002FINFRA\n  Apache Attic:    https:\u002F\u002Fattic.apache.org \u002F https:\u002F\u002Fissues.apache.org\u002Fjira\u002Fprojects\u002FATTIC\n  Incubator PMC:   general@incubator.apache.org\n\n## Notes\n\n(add any podling-specific notes here — custom infra, active downstream users,\n donated IP that needs special handling, etc.)\n\"\"\"\n\nwith open(\"scratch\u002Fshutdown_checklist.md\", \"w\") as f:\n    f.write(checklist)\n\nprint(\"Written to: scratch\u002Fshutdown_checklist.md\")\n```\n\nWrite to `scratch\u002Fshutdown_checklist.md`.\n\n---\n\n### Step 5 — Summary report\n\nPrint a one-page summary for the shepherd before they post anything.\n\n```python\nstrength = evidence.get(\"retirement_case_strength\", \"unknown\")\nmentor_rec = consultation.get(\"mentor_recommendation\", \"pending\")\n\nprint(f\"\"\"\n=== Retirement Dossier: Apache {PODLING} ===\n\nIncubating since: {evidence.get(\"start_date\", \"?\")} ({months} months)\nRetirement case:  {strength.upper()}\nMentor recommendation: {mentor_rec}\n\nKey stalled signals:\n{\"\".join(\"  - \" + s + chr(10) for s in (stalled_sig.get(\"definition_matched\") or concerns[:3]))}\n\nNext actions:\n  1. Fill in SHEPHERD_NAME in scratch\u002Fretire_discuss.txt and scratch\u002Fretire_announce.txt.\n  2. Complete consultation.json if mentor contact is not yet done.\n  3. Post [DISCUSS] email (scratch\u002Fretire_discuss.txt) to general@incubator.apache.org.\n  4. After 72 h, post [VOTE] thread.\n  5. After vote passes, fill vote placeholders in scratch\u002Fretire_announce.txt and send.\n  6. Work through scratch\u002Fshutdown_checklist.md after a successful vote.\n\nScratch files written:\n  scratch\u002Fstalled_evidence.json\n  scratch\u002Fconsultation.json\n  scratch\u002Fretire_discuss.txt\n  scratch\u002Fretire_announce.txt\n  scratch\u002Fshutdown_checklist.md\n\"\"\")\n```\n\n---\n\n## Output checklist\n\nBefore declaring the dossier complete, confirm all five files exist and are\nnon-empty:\n\n- [ ] `scratch\u002Fstalled_evidence.json` — stalled signals, health metrics, lifecycle data\n- [ ] `scratch\u002Fconsultation.json` — mentor contact attempts and recommendation\n- [ ] `scratch\u002Fretire_discuss.txt` — ready-to-post [DISCUSS] email to `general@incubator.apache.org`\n- [ ] `scratch\u002Fretire_announce.txt` — post-vote announcement to `dev@`, CC `general@`, BCC `announce@`\n- [ ] `scratch\u002Fshutdown_checklist.md` — ordered shutdown steps with checkboxes\n\n---\n\n## Common issues\n\n**Adjusting the configurable windows** — the defaults (commits 3m ≤ 5,\nmail_posts 6m ≤ 5, releases 12m = 0) are calibrated for a typical active\npodling. For projects with known seasonal patterns or a single large initial\ncommit, raise the commit threshold or widen the window. For fast-moving\necosystems where one release per year is genuinely insufficient, lower the\nreleases threshold to check 6m. Always record any override in the\n`INACTIVITY_CONFIG` comment so the rationale is visible in the evidence record.\n\n**`flagged = None` means data is missing, not that the signal is clear** —\nif a health report window is absent, that signal is recorded as `unknown`.\nCount `unknown_flags` separately from `confirmed_flags` in the email; do not\ntreat a missing window as evidence the project is healthy.\n\n**`stalled_entry` is None but the podling seems inactive** — the IPMC strict\nstalled definition requires low commits, low committers, AND no 12-month\nreleases simultaneously. A podling with mailing-list traffic but no commits may\nnot trigger it. Independent inactivity flags (from `INACTIVITY_CONFIG`) can\nstill fire on individual signals; use them and `watchlist_entry` as\nsupplementary evidence in the retirement email.\n\n**PPMC responds and commits to a revival plan** — do not proceed to a vote.\nUpdate `consultation.json` with the response, close the dossier, and schedule a\nfollow-up check in 90 days (use the monthly report review skill to monitor).\n\n**`get_podling_reports` has no `limit` parameter** — it returns all cached\nreports. Sort by `report_period` descending after retrieval; do not pass a\n`limit` keyword argument.\n\n**Podling has no health report** — `get_report_summary` raises `FileNotFoundError`.\nSet `activity_by_window = {}` and note in the retirement email that health data\nis unavailable; rely on `stalled_entry` and report-history evidence instead.\n\n**PYTHONPATH must include all four repos** in the same Python process. Re-export\n`PYTHONPATH` in every `subprocess` call or use `sys.path.insert` at the top of\neach script.\n",{"name":5,"description":7,"allowed-tools":24},[25,26,27],"Bash","Read","Write","master","7f6898d30f01d4ae3a635e79b83cdac12a5631b8","tools\u002Fskills\u002Fretirement-shepherd\u002Fskills\u002Fretirement-shepherd",48,"https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator","https:\u002F\u002Fincubator.apache.org\u002F","https:\u002F\u002Fgithub.com\u002Fapache","https:\u002F\u002Fwww.apache.org\u002F","2026-11-14 14:20:47",{"source":8,"skillName":5,"commands":38,"prompt":48,"zipAvailable":49},[39,42,45],{"tool":40,"command":41},"npx","npx skills add apache\u002Fincubator --skill retirement-shepherd",{"tool":43,"command":44},"bunx","bunx skills add apache\u002Fincubator --skill retirement-shepherd",{"tool":46,"command":47},"pnpm","pnpm dlx skills add apache\u002Fincubator --skill retirement-shepherd","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fretirement-shepherd\u002Fskills\u002Fretirement-shepherd\u002FSKILL.md",true,[51,57,61,64],{"path":52,"resourceType":53,"resourceTypeDesc":54,"sizeBytes":55,"sourceHtmlUrl":56},"templates\u002Fannounce-email.md","REFERENCE","参考文档",3563,"https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fretirement-shepherd\u002Fskills\u002Fretirement-shepherd\u002Ftemplates\u002Fannounce-email.md",{"path":58,"resourceType":53,"resourceTypeDesc":54,"sizeBytes":59,"sourceHtmlUrl":60},"templates\u002Fretire-email.md",2313,"https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fretirement-shepherd\u002Fskills\u002Fretirement-shepherd\u002Ftemplates\u002Fretire-email.md",{"path":62,"resourceType":53,"resourceTypeDesc":54,"sizeBytes":55,"sourceHtmlUrl":63},"templates\u002Fretirement-email.md","https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fretirement-shepherd\u002Fskills\u002Fretirement-shepherd\u002Ftemplates\u002Fretirement-email.md",{"path":65,"resourceType":53,"resourceTypeDesc":54,"sizeBytes":66,"sourceHtmlUrl":67},"templates\u002Fshutdown-checklist.md",5711,"https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fretirement-shepherd\u002Fskills\u002Fretirement-shepherd\u002Ftemplates\u002Fshutdown-checklist.md",[69,76,83,90,97,104],{"skillNo":70,"name":71,"nameZh":6,"description":72,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":75,"publishedAt":19},"01M741YHV9QA3BCR34KVFBB5FX","podling-release-check","Checks an Apache Incubator podling release candidate before or during its vote: signatures, checksums, naming, DISCLAIMER, LICENSE and NOTICE, headers, bundled third-party code, binaries, and whether issues raised on the podling's earlier votes have been fixed. Uses the ASF Policy MCP for the current rules and the Incubator mail and release MCPs for the podling's history. Use it whenever someone asks to check, review, verify or validate a podling release or RC, asks \"is our release ready to vote on\", is about to call a PPMC or IPMC release vote, is voting on a [VOTE] Release thread on general@incubator, or wants to know why an earlier RC was voted down. It produces findings for a person to review, never a vote.","https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Freleasecheck\u002Fskills\u002Freleasecheck\u002FSKILL.md",[],[],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":82,"publishedAt":19},"01M741YD7YQJT3M9PGZVGZ5SE0","release-vote-reviewer","Review an Apache Incubator release vote and draft the +1 or -1 response. Use this skill whenever a user asks to review a release vote, check a release candidate, verify signatures\u002Fchecksums\u002FDISCLAIMER on an incubating release, tally vote maths on a general@ VOTE thread, or decide how to vote on a podling release. Triggers on phrases like \"review this release vote\", \"check the RC\", \"should I +1\", \"verify the release artifacts\", \"vote thread\", \"release candidate for \u003Cpodling>\", or \"walk the release checks\".","https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Frelease-vote-reviewer\u002Fskills\u002Frelease-vote-reviewer\u002FSKILL.md",[],[],{"skillNo":84,"name":85,"nameZh":6,"description":86,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":89,"publishedAt":19},"01M741Y969YS552176PFFDPYE3","podling-onboarding","Run the new-podling onboarding workflow for an Apache Incubator podling. Use this skill whenever a user asks to onboard a new podling, review a podling proposal against the acceptance checklist, track SGA or ICLA receipt, verify infrastructure setup, or determine a podling's first board report date. Triggers on phrases like \"onboard podling\", \"new podling checklist\", \"proposal review\", \"check SGA status\", \"ICLA coverage\", \"set up podling infra\", \"first report date\", \"bootstrap \u003Cpodling>\", or \"what still needs to be done for \u003Cpodling>\".","https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fpodling-onboarding\u002Fskills\u002Fpodling-onboarding\u002FSKILL.md",[],[],{"skillNo":91,"name":92,"nameZh":6,"description":93,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":94,"categories":95,"keywords":96,"publishedAt":19},"01M741Y44CB7YQWPS7JCABBB1Q","podling-name-clearance","Clear a proposed Apache Incubator podling name and produce the PODLINGNAMESEARCH writeup. Use this skill whenever a user asks to run a name search, clear the name, check a podling name for trademark conflicts, or file\u002Fprepare a PODLINGNAMESEARCH JIRA ticket. Triggers on phrases like \"name search\", \"PODLINGNAMESEARCH\", \"clear the name\", \"check this podling name\", \"is \u003Cname> available\", or \"name clearance for \u003Cpodling>\".","https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fpodling-name-clearance\u002Fskills\u002Fpodling-name-clearance\u002FSKILL.md",[],[],{"skillNo":98,"name":99,"nameZh":6,"description":100,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":101,"categories":102,"keywords":103,"publishedAt":19},"01M741Y01NJ5RS144GX6ECZ3VM","monthly-report-review","Run the IPMC monthly Incubator report review. Use this skill whenever a user asks to review this month's Incubator reports, check who has filed, flag missing reports, cross-check report narratives against health data, or draft shepherd comments for podlings. Triggers on phrases like \"monthly report review\", \"check Incubator reports\", \"who hasn't filed\", \"draft shepherd comments\", or \"report cohort\".","https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fmonthly-report-review\u002Fskills\u002Fmonthly-report-review\u002FSKILL.md",[],[],{"skillNo":105,"name":106,"nameZh":6,"description":107,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":108,"categories":109,"keywords":110,"publishedAt":19},"01M741XSG2HHM31T075RF776AB","maturity-model-assessment","Builds an evidence pack for assessing an Apache Incubator podling against the Apache Project Maturity Model: the 38 items in Code, Licenses and Copyright, Releases, Quality, Community, Consensus Building, Independence and Trademark and Branding. For each item it gathers what the ASF MCP servers, GitHub and the mailing list archives can show, marks it evidenced, proxy or human, and writes the question the PPMC still has to answer. Use it whenever someone asks to assess, evaluate or score a project against the maturity model, asks \"how mature is Apache Foo (incubating)\", wants a maturity self-assessment drafted for graduation or a PPMC review, asks which maturity items a podling falls short on, or asks what the model says about a specific item ID such as CS50 or TB30. It produces evidence and questions for the PPMC to finish, never a maturity verdict or score, because the model has no levels and is a guide rather than a requirements document.","https:\u002F\u002Fgithub.com\u002Fapache\u002Fincubator\u002Fblob\u002Fmaster\u002Ftools\u002Fskills\u002Fmaturity-model-assessment\u002Fskills\u002Fmaturity-model-assessment\u002FSKILL.md",[],[]]