Add cross-tool positioning, Python helpers, tiers, and hygiene docs

Five improvements to position the library as a serious engineering project:

1. Cross-tool compatibility — new README "Works With" section honestly
   documenting where skills run (Claude Code natively; SKILL.md bodies
   port to other agents and chat LLMs as system prompts).

2. Python helper scripts (stdlib-only) for the three strongest skills:
   - sprint-planning: capacity_calculator.py (recommended commitment)
   - rice-prioritisation: rice_calculator.py (ranks, flags quick wins/moonshots)
   - cs-health-scorecard: health_score.py (weighted total + RAG)
   Each is wired into its SKILL.md and synced to the plugin copies.

3. Explicit skill tiering — TIERS.md + README section marking 46
   Production-Ready skills and calling out Experimental (external-dependency)
   ones; everything else is Stable.

4. Repository hygiene — new CHANGELOG.md (Keep a Changelog format) and
   SKILL-AUTHORING-STANDARD.md; refreshed SECURITY.md version table and
   helper-script disclosure; added .gitignore.

5. Related Projects — README section linking to alirezarezvani/claude-skills
   and the major awesome-claude-skills / awesome-claude-code lists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016JWn5jRD5tcEFKrubjQ6Px
This commit is contained in:
Claude
2026-06-17 07:48:48 +00:00
parent 2299e59d72
commit 760f979365
20 changed files with 1514 additions and 5 deletions
+12
View File
@@ -0,0 +1,12 @@
# Python (helper scripts)
__pycache__/
*.py[cod]
*.egg-info/
.venv/
venv/
# OS / editor
.DS_Store
*.swp
.idea/
.vscode/
+93
View File
@@ -0,0 +1,93 @@
# Changelog
All notable changes to this project are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project broadly follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html):
each new wave of skills bumps the **major** version, extensions and fixes bump
**minor** / **patch**.
## [Unreleased]
### Added
- **Programmatic helpers (stdlib Python) for three flagship skills.** Each runs with
zero dependencies and computes part of the work instead of estimating by hand:
- `sprint-planning/scripts/capacity_calculator.py` — recommended sprint commitment
from team size, availability, velocity, and carry-over (caps at 80% of velocity).
- `rice-prioritisation/scripts/rice_calculator.py` — calculates and ranks RICE
scores from JSON/CSV and auto-flags quick wins, moonshots, and low-confidence items.
- `cs-health-scorecard/scripts/health_score.py` — weighted health total out of 100
with RAG banding and weight validation.
- **`CHANGELOG.md`** — this file, back-filled from the release history.
- **`SKILL-AUTHORING-STANDARD.md`** — the canonical structure every SKILL.md follows
(frontmatter, required sections, quality bar, anti-patterns).
- **Skill tiers** — a `TIERS.md` reference and README section marking skills as
**Production-Ready**, **Stable**, or **Experimental** so new users start with the
strongest work.
- **Cross-tool compatibility** — README now documents which platforms the skills work
on (Claude Code natively; the SKILL.md bodies port to other agents and chat LLMs).
- **Related Projects** — README section linking to other community Claude Skills
libraries and the `awesome-claude-skills` / `awesome-claude-code` lists.
### Changed
- `SECURITY.md` supported-versions table updated to the current release line.
## [14.0.0] — Writers & Content Creators + 7 Community Skills
### Added
- New profession **Writers & Content Creators** (`pm-writers`): Instagram Post
Downloader, AEO Optimizer, Thumbnail Creator, Substack Notes Scraper, Notes Humanizer.
- `pm-cross` (+3): Sycophancy Challenger, Last 30 Days Research, NotebookLM Connector.
- `pm-operations` (+2): Email Triage, Morning Intelligence.
- `pm-engineering` (+2): Context Mode, Claude Superpowers.
Library now spans **167 skills** across **18 professions** + 4 agent templates.
## [13.0.0] — Social Media Profession
### Added
- New bundle `pm-social`: Social Media Audit, Influencer Brief, Community Management
Playbook, Social Ad Campaign, Viral Content Framework.
## [12.0.0] — 150 Skills Milestone
### Added
- 15 skills across 10 bundles, including Cohort Analysis, Data Pipeline Spec, Renewal
Playbook, Customer Success Plan, 360-Degree Feedback Template, Team Health Check, Risk
Register, RACI Matrix, Social Media Strategy, Product Positioning Doc, Customer Journey
Map, User Story Writer, AI Ethics Review, Partnership Proposal, Design System Audit.
Library reached **150 skills** across **16 professions**.
## [11.0.0] — Engineering Expansion (500 ⭐)
### Added
- `pm-engineering` expanded to 35 skills — CI/CD, SLOs, capacity planning, DR plans,
threat models, schema/migration design, and more.
## [10.0.0] — Customer Success + Engineering
### Added
- **Customer Success** bundle (`pm-cs`, 250 ⭐ milestone): Customer Health Scorecard,
QBR Deck, Escalation Brief, Churn Analysis.
- **Engineering** (500 ⭐ milestone): CI/CD Playbook, SLO & Error Budget, Developer
Onboarding Doc, On-Call Runbook — plus Debugging Log Analyser, PR Description Writer,
System Design Interview, Changelog Generator, Test Strategy Doc, Runbook Writer.
Library reached **114 skills** across **16 professions**.
## [6.0.0] — 100 Skills Milestone
### Added
- Quality rebuild across all existing skills, plus 10 Figma skills.
- 7 new skills: Teaching Lesson Plan, SEO Content Brief, Media Pitch, Change Management
Plan, Workshop Facilitation Guide, Sales Forecasting Model, Tax Planning Checklist.
---
Earlier releases (v1.0.0 v5.0.0) predate this changelog. See the
[article series](README.md#-the-article-series) for the full history of how the
library grew from the first PM toolkit to 100+ skills.
[Unreleased]: https://github.com/mohitagw15856/pm-claude-skills/compare/v14.0.0...HEAD
[14.0.0]: https://github.com/mohitagw15856/pm-claude-skills/releases
+72
View File
@@ -18,12 +18,15 @@ A community-built library of Claude Skills for professionals across every field
## Contents ## Contents
- [🚀 Quick Install](#-quick-install-2-minutes) - [🚀 Quick Install](#-quick-install-2-minutes)
- [🔌 Works With — Cross-Tool Compatibility](#-works-with--cross-tool-compatibility)
- [🌐 Skill Playground — try any skill in your browser](#-skill-playground--try-any-skill-in-your-browser) - [🌐 Skill Playground — try any skill in your browser](#-skill-playground--try-any-skill-in-your-browser)
- [📦 Plugin Directory](#-plugin-directory) - [📦 Plugin Directory](#-plugin-directory)
- [🤖 Building Blocks for Agent Templates](#-building-blocks-for-agent-templates) - [🤖 Building Blocks for Agent Templates](#-building-blocks-for-agent-templates)
- [🏷️ Skill Tiers — start with the strongest](#-skill-tiers--start-with-the-strongest)
- [🗂️ All 167 Skills](#-all-167-skills) - [🗂️ All 167 Skills](#-all-167-skills)
- [📋 Changelog](#-changelog) - [📋 Changelog](#-changelog)
- [🤝 Contributing](#-contributing--add-your-skill) - [🤝 Contributing](#-contributing--add-your-skill)
- [🔗 Related Projects](#-related-projects)
--- ---
@@ -74,6 +77,31 @@ ln -s ~/pm-claude-skills/skills/* ~/.claude/skills/
--- ---
## 🔌 Works With — Cross-Tool Compatibility
These skills were built for Claude Code, but they aren't locked to it. Each `SKILL.md` is
two portable parts: a small **frontmatter** block (`name` + `description`) and a
**markdown body** that is just a well-structured set of instructions and output templates.
The body is plain English — so it works anywhere a capable model reads instructions.
| Platform | How it works | Auto-trigger? |
|---|---|---|
| **Claude Code** (CLI / desktop / web / IDE) | Native. Install via the plugin marketplace; Claude loads a skill automatically when your request matches its description. | ✅ Yes |
| **Claude.ai & Claude API** | Upload a skill, or paste the body in as a system prompt / project instruction. | ⚙️ Manual |
| **Other coding agents that read the `SKILL.md` format** (e.g. Codex, Gemini CLI, Cursor) | Point the agent at the skill folder, or paste the body. The frameworks are tool-agnostic; only the auto-discovery mechanism differs per tool. | ⚙️ Varies by tool |
| **General chat LLMs** (ChatGPT, Gemini, Copilot, etc.) | Copy the body of any `SKILL.md` into a custom instruction / system prompt / custom GPT. You keep the full framework and output format. | ❌ Paste per use |
**What's verified vs. what varies:** the skill **bodies** — the frameworks, rubrics, and
output templates that do the actual work — are model-agnostic and have been used across
Claude and other chat LLMs. What's **Claude Code-specific** is the convenience layer:
plugin install, automatic skill discovery from the `description`, and the helper-script
invocation flow. On other tools you copy the body in manually and lose only the
auto-triggering, not the substance.
> Prefer ChatGPT? There's also a [companion Custom GPT library](#-companion-repository--chatgpt-custom-gpts) built from the same frameworks.
---
## 🌐 Skill Playground — Try Any Skill in Your Browser ## 🌐 Skill Playground — Try Any Skill in Your Browser
**▶ Live: [mohitagw15856.github.io/pm-claude-skills](https://mohitagw15856.github.io/pm-claude-skills/)** **▶ Live: [mohitagw15856.github.io/pm-claude-skills](https://mohitagw15856.github.io/pm-claude-skills/)**
@@ -225,6 +253,8 @@ More templates will follow. If you want to contribute one, see the [template con
## 📋 Changelog ## 📋 Changelog
The highlights are below. For the structured, [Keep a Changelog](https://keepachangelog.com/)-format history (including unreleased changes), see **[CHANGELOG.md](CHANGELOG.md)**.
<details> <details>
<summary><strong>Release history — v6.0.0 → v14.0.0</strong> (click to expand)</summary> <summary><strong>Release history — v6.0.0 → v14.0.0</strong> (click to expand)</summary>
@@ -398,6 +428,21 @@ This repo was built alongside a published article series. Read the full story:
--- ---
## 🏷️ Skill Tiers — Start With the Strongest
A 170+ skill library doesn't have 170 equally-mature skills, and pretending otherwise
wastes your time. Skills are tiered honestly so you can start with the best work:
- 🟢 **Production-Ready (46)** — battle-tested, stable output, used in real work. Includes the three skills with computed Python helpers (sprint planning, RICE, customer health). **Start here.**
- 🔵 **Stable** — solid, reliable, well-structured; the default for most of the library.
- 🟡 **Experimental** — newer or dependent on an external tool/API/scrape (Gemini, Gmail, browser automation, social scraping). Useful, but more setup and more moving parts.
**👉 Full breakdown: [TIERS.md](TIERS.md)** — every Production-Ready and Experimental skill listed by name.
If you're new, install `pm-essentials` and try a couple of Production-Ready skills before going wide.
---
## 🗂️ All 167 Skills ## 🗂️ All 167 Skills
The [Plugin Directory](#-plugin-directory) above summarises every bundle. Expand below for the full per-skill breakdown with folder paths. The [Plugin Directory](#-plugin-directory) above summarises every bundle. Expand below for the full per-skill breakdown with folder paths.
@@ -773,6 +818,8 @@ description: "One sentence. Use when [trigger condition]. Produces [output descr
- Produces consistent, structured output - Produces consistent, structured output
- Works without needing extensive setup or context - Works without needing extensive setup or context
**Before you submit:** read the **[Skill Authoring Standard](SKILL-AUTHORING-STANDARD.md)** — it documents the exact section structure, frontmatter rules, and quality bar every skill in this library follows (including optional stdlib-only helper scripts).
**Skills wishlist** (most requested — up for grabs): **Skills wishlist** (most requested — up for grabs):
| Skill | Profession | Use Case | | Skill | Profession | Use Case |
@@ -859,6 +906,31 @@ Read the full breakdown: [Part 12 — I Built the Same Skills Library for ChatGP
--- ---
## 🔗 Related Projects
Claude Skills is a fast-growing open ecosystem. If this library doesn't have what you
need, these community projects are worth a look — and if you maintain one of the lists
below, a reciprocal link is always welcome. 🙌
**Other skill libraries**
- **[alirezarezvani/claude-skills](https://github.com/alirezarezvani/claude-skills)** — a large engineering-leaning library (300+ skills, agents, and commands) with explicit multi-tool support across Claude Code, Codex, Gemini CLI, Cursor, and more.
**Curated "awesome" lists** (great for discovery)
- **[hesreallyhim/awesome-claude-code](https://github.com/hesreallyhim/awesome-claude-code)** — the broad list of skills, hooks, slash-commands, and plugins for Claude Code.
- **[travisvn/awesome-claude-skills](https://github.com/travisvn/awesome-claude-skills)** — curated Claude Skills, resources, and tools.
- **[karanb192/awesome-claude-skills](https://github.com/karanb192/awesome-claude-skills)** — verified skills for Claude Code, Claude.ai, and the API.
- **[ComposioHQ/awesome-claude-skills](https://github.com/ComposioHQ/awesome-claude-skills)** — skills and tools for customizing Claude workflows.
**From this author**
- **[professional-gpt-library](https://github.com/mohitagw15856/professional-gpt-library)** — the same frameworks rebuilt as ChatGPT Custom GPTs.
> Maintain a Claude Skills project and want to be listed here? [Open a PR](../../pulls) or an [issue](../../issues).
---
## 🛠️ Custom Skills for Your Team ## 🛠️ Custom Skills for Your Team
The 155 skills in this library are built for general professional workflows. But the most powerful version of Claude Skills is one built specifically for *your* team — your templates, your terminology, your processes, your quality standards. The 155 skills in this library are built for general professional workflows. But the most powerful version of Claude Skills is one built specifically for *your* team — your templates, your terminology, your processes, your quality standards.
+9 -4
View File
@@ -10,9 +10,12 @@ That said, security matters here in two specific ways: **skill file safety** and
| Version | Supported | | Version | Supported |
|---|---| |---|---|
| v4.0.0 (latest) | ✅ Active | | v14.x (latest) | ✅ Active |
| v3.0.0 | ✅ Security fixes only | | v12.x v13.x | ✅ Security fixes only |
| < v3.0.0 | ❌ No longer supported | | < v12.0.0 | ❌ No longer supported |
Because skills are plain markdown, "support" means we review and correct any reported
safety issue (prompt injection, unsafe instructions) in the listed versions.
## Skill File Safety ## Skill File Safety
@@ -24,7 +27,9 @@ All skills in this repo are reviewed before merging to ensure they:
- Do not contain malicious commands disguised as skill instructions - Do not contain malicious commands disguised as skill instructions
- Do not include hardcoded credentials, API keys, or personally identifiable information - Do not include hardcoded credentials, API keys, or personally identifiable information
**If you are installing skills from this repo:** skills are plain text markdown files. They do not execute code, make network requests, or access your file system on their own. Review any skill file before installing if you have concerns. **If you are installing skills from this repo:** the skills themselves are plain markdown instruction files. They do not execute code, make network requests, or access your file system on their own. Review any skill file before installing if you have concerns.
**A few skills ship optional helper scripts** (in a `scripts/` folder, e.g. the sprint, RICE, and customer-health calculators). These are pure Python standard-library programs — no third-party dependencies, no network calls, no file writes outside what you pass them. They only run when you explicitly invoke them. Read any script before running it, exactly as you would any code from the internet.
## Reporting a Vulnerability ## Reporting a Vulnerability
+98
View File
@@ -0,0 +1,98 @@
# Skill Authoring Standard
This is the canonical structure every skill in this library follows. It exists so
that 160+ skills feel like one coherent product rather than a folder of loose prompts,
and so contributors know exactly what "done" looks like. If you are adding or editing a
skill, match this standard.
It complements [CONTRIBUTING.md](CONTRIBUTING.md) (how to submit) — this document is
about *what a good skill contains*.
---
## 1. File layout
```
skills/
your-skill-name/
SKILL.md # required — the skill itself
scripts/ # optional — stdlib-only helper programs
your_helper.py
```
- One skill per folder. Folder name = skill name = `name` in the frontmatter.
- Use lowercase, hyphenated names (`customer-journey-map`, not `CustomerJourneyMap`).
- A skill must be useful with `SKILL.md` alone. Scripts are an enhancement, never a
prerequisite.
## 2. Frontmatter (required)
```yaml
---
name: your-skill-name
description: "One sentence on what it does. Use when [trigger conditions]. Produces [the concrete output]."
---
```
The `description` is the single most important line — it is all the model sees when
deciding whether to load the skill. It must contain three things:
1. **What** the skill does, in one clause.
2. **Use when…** — explicit trigger phrases a user would actually say.
3. **Produces…** — the concrete artifact, so the model knows the payoff.
Keep it under ~3 sentences. Write triggers from the user's vocabulary, not internal jargon.
## 3. Body sections
Use this section order. Not every skill needs every section, but strong skills include
most of them, and the **bold** ones are required.
| Section | Purpose |
|---|---|
| `# Skill Title` + one-line summary | **Required.** Restate the value in plain language. |
| **What This Skill Produces** | Bullet list of the deliverables. Sets expectations. |
| **Required Inputs** | What to ask the user for if it isn't provided. Prevents guessing. |
| Framework / Formula / Scale | The method, rubric, weights, or formula the skill applies. |
| Programmatic Helper | If the skill has a script, show how to run it and what it returns. |
| **Output Format** | A concrete template (headings, tables) of the final artifact. |
| **Quality Checks** | A checklist the output must pass before it's handed over. |
| **Anti-Patterns** | Explicit "Do not…" rules — the mistakes this skill prevents. |
## 4. Quality bar
A skill is ready to merge when:
- [ ] The `description` has all three parts (what / use when / produces).
- [ ] It solves a **recurring** professional workflow, not a one-off task.
- [ ] It asks for missing inputs rather than inventing them.
- [ ] The output format is concrete enough that two runs look like the same product.
- [ ] It includes **Quality Checks** and **Anti-Patterns** — these are what make a skill
trustworthy, not just a prompt.
- [ ] It works with no setup beyond reading the file (scripts excepted, and those are
stdlib-only).
## 5. Helper scripts (optional)
Some skills ship a `scripts/` folder that computes part of the work. Rules:
- **Standard library only.** No `pip install`. No third-party imports.
- **No network access, no surprise file writes.** Read input, print output.
- Accept input via flags *and* JSON (file or stdin); offer `--json` output for chaining.
- Include a module docstring with runnable examples and a `--help` via `argparse`.
- The script augments the skill — the SKILL.md must still produce a good result without it.
See `skills/rice-prioritisation/scripts/rice_calculator.py` for a reference example.
## 6. Tone and safety
- Write instructions *to the model* ("Ask for…", "Flag any…", "Never write…").
- British or American spelling is fine; be consistent within a skill.
- No prompt injection, no instructions to override model guidelines, no requests to
collect or transmit user data. See [SECURITY.md](SECURITY.md).
## 7. Tiering
New skills enter as **Experimental**. Once a skill has a stable output format, quality
checks, and real-world use, it can be promoted to **Stable** or **Production-Ready** in
[TIERS.md](TIERS.md). Tiering is honest signposting, not a value judgement on effort.
+83
View File
@@ -0,0 +1,83 @@
# Skill Tiers
Not every skill in a 170+ library is at the same level of maturity — and pretending
otherwise wastes your time. This page tiers the skills honestly so you can start with the
strongest work and know what to expect from the rest.
| Tier | What it means |
|---|---|
| 🟢 **Production-Ready** | Battle-tested, stable output format, used in real work. Includes the skills with computed helper scripts. Start here. |
| 🔵 **Stable** | Solid and well-structured. Reliable output; smaller track record than Production-Ready. This is the default tier for most of the library. |
| 🟡 **Experimental** | Newer, niche, or dependent on an external tool/API/scrape (Gemini, Gmail, browser automation, social scraping). Useful, but more setup and more moving parts — expect rough edges. |
> ⚙️ = ships a stdlib-only Python helper script that computes part of the work.
---
## 🟢 Production-Ready (46)
These are the skills to reach for first — the most-used, most-refined frameworks in the
library.
**Product core**
`prd-template` · `meeting-notes` · `stakeholder-update` · `user-research-synthesis` · `competitive-analysis`
**Prioritisation & planning**
`rice-prioritisation` ⚙️ · `feature-prioritisation` · `okr-builder` · `roadmap-narrative` · `rice-impact-matrix`
**Delivery**
`sprint-planning` ⚙️ · `sprint-brief` · `user-story-writer` · `retro-analysis` · `ab-test-planner` · `product-launch-checklist` · `technical-spec-template`
**Discovery**
`customer-journey-map` · `assumption-mapper` · `user-interview-synthesis` · `discovery-interview-guide` · `job-story-mapper`
**Data & analytics**
`data-analysis-standard` · `retention-analysis` · `cohort-analysis` · `metrics-framework` · `product-health-analysis`
**Customer success**
`cs-health-scorecard` ⚙️ · `churn-analysis` · `qbr-deck` · `renewal-playbook` · `customer-success-plan` · `cs-escalation-brief`
**Engineering**
`code-review-checklist` · `incident-postmortem` · `architecture-decision-record` · `api-docs-writer` · `runbook-writer` · `changelog-generator` · `pr-description-writer` · `technical-debt-register`
**GTM & strategy**
`go-to-market` · `competitor-teardown` · `product-positioning-doc`
**Cross-profession**
`executive-summary` · `press-release`
---
## 🟡 Experimental
These depend on external services, scraping, or browser/desktop automation. They can be
genuinely useful, but they have more setup and more failure modes than a self-contained
markdown skill — treat output as a strong draft, and expect to adapt them to your
environment.
| Skill | Why it's experimental |
|---|---|
| `instagram-post-downloader` | Depends on Instagram's page structure; can break when the site changes. |
| `substack-notes-scraper` | Scrapes Substack engagement data; fragile to layout changes. |
| `thumbnail-creator` | Requires a Gemini API key and image generation. |
| `notebooklm-connector` | Drives NotebookLM via a Chrome extension / browser automation. |
| `email-triage` | Requires Gmail access and a configured time window. |
| `morning-intelligence` | Designed for scheduled-task / routine setups; depends on your news sources. |
| `last-30-days-research` | Relies on live Reddit / X / web search availability and quality. |
| `competitor-signal-tracker` | Depends on the live sources you point it at. |
| `multi-source-signal-synthesiser` | Quality depends on the breadth/quality of sources supplied. |
---
## 🔵 Stable (everything else)
Every skill not listed above is **Stable**: well-structured, reliable output, broadly
useful — just with a shorter track record than the Production-Ready set. Browse the full
list in the [README](README.md#-all-167-skills).
---
*Tiers are reviewed as skills mature. New skills enter as Experimental and are promoted
once they have a stable output format and real-world use — see
[SKILL-AUTHORING-STANDARD.md](SKILL-AUTHORING-STANDARD.md#7-tiering). Think a skill is
mis-tiered? [Open an issue](../../issues).*
@@ -35,6 +35,20 @@ Score each dimension 15. Weight as shown. Calculate weighted total out of 100
- 6079: Amber (at risk, needs attention) - 6079: Amber (at risk, needs attention)
- 059: Red (high churn risk, escalate) - 059: Red (high churn risk, escalate)
## Programmatic Helper
This skill ships with a stdlib-only Python script that applies the weights above and converts the weighted total to a RAG status — so the headline score is computed identically every time and weights always sum to 100%.
```bash
# Five scores 1-5 in order: adoption engagement outcomes support commercial
python3 scripts/health_score.py --scores 4 3 4 2 5 --account "Acme Corp"
# Or from JSON (lets you override the default weights per account/segment)
python3 scripts/health_score.py --input account.json
```
It returns the per-dimension weighted points, the **total out of 100**, and the **RAG band** (Green ≥80, Amber 6079, Red <60) with a one-line next step. Run it to set the headline number, then write the dimension detail and actions below around it. Add `--json` for downstream tooling.
## Output Format ## Output Format
--- ---
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""Customer health score calculator for the cs-health-scorecard skill.
Takes per-dimension scores (1-5), applies the standard weights, and returns a
weighted total out of 100 plus a RAG status — so the headline number in a health
scorecard is computed the same way every time. Pure Python standard library —
no dependencies, no network access.
Standard dimensions and weights (override with --weights or in the JSON):
Product Adoption 30%
Engagement 20%
Outcomes 20%
Support Health 15%
Commercial 15%
Usage
-----
Quick scoring from flags (order: adoption engagement outcomes support commercial):
python3 health_score.py --scores 4 3 4 2 5
From a JSON file that can also override weights:
python3 health_score.py --input account.json
account.json:
{
"account": "Acme Corp",
"scores": {"Product Adoption": 4, "Engagement": 3, "Outcomes": 4,
"Support Health": 2, "Commercial": 5},
"weights": {"Product Adoption": 0.30, "Engagement": 0.20, "Outcomes": 0.20,
"Support Health": 0.15, "Commercial": 0.15}
}
"""
from __future__ import annotations
import argparse
import json
import sys
DEFAULT_WEIGHTS = {
"Product Adoption": 0.30,
"Engagement": 0.20,
"Outcomes": 0.20,
"Support Health": 0.15,
"Commercial": 0.15,
}
MAX_DIMENSION_SCORE = 5
def rag(total: float) -> str:
if total >= 80:
return "Green"
if total >= 60:
return "Amber"
return "Red"
def compute(scores: dict[str, float], weights: dict[str, float] | None = None) -> dict:
weights = weights or DEFAULT_WEIGHTS
weight_sum = sum(weights.values())
if abs(weight_sum - 1.0) > 0.001:
raise ValueError(f"Weights must sum to 1.0 (got {weight_sum:.3f}).")
breakdown = []
total = 0.0
for dimension, weight in weights.items():
if dimension not in scores:
raise ValueError(f"Missing score for dimension '{dimension}'.")
raw = float(scores[dimension])
if not 1 <= raw <= MAX_DIMENSION_SCORE:
raise ValueError(f"Score for '{dimension}' must be between 1 and {MAX_DIMENSION_SCORE} (got {raw}).")
# Normalise the 1-5 score to a 0-100 contribution weighted by importance.
weighted = (raw / MAX_DIMENSION_SCORE) * weight * 100
total += weighted
breakdown.append({
"dimension": dimension,
"score": raw,
"weight": weight,
"weighted_points": round(weighted, 1),
})
total = round(total, 1)
return {"total": total, "rag": rag(total), "breakdown": breakdown}
def _render(result: dict, account: str | None) -> str:
title = f"Customer Health Scorecard: {account}" if account else "Customer Health Scorecard"
lines = [title, "=" * len(title)]
lines.append(f"{'Dimension':<18} {'Score':>5} {'Weight':>7} {'Weighted':>9}")
lines.append("-" * 41)
for row in result["breakdown"]:
lines.append(
f"{row['dimension']:<18} {row['score']:>5g} {row['weight']*100:>6.0f}% {row['weighted_points']:>9g}"
)
lines.append("-" * 41)
badge = {"Green": "🟢", "Amber": "🟡", "Red": "🔴"}[result["rag"]]
lines.append(f"{'TOTAL':<18} {'':>5} {'100%':>7} {result['total']:>9g}/100")
lines.append("")
lines.append(f"Overall health: {badge} {result['rag']}{result['total']}/100")
guidance = {
"Green": "Healthy — renew likely. Look for expansion signals.",
"Amber": "At risk — needs attention. Build a save/grow plan before renewal.",
"Red": "High churn risk — escalate now and assign an executive sponsor.",
}[result["rag"]]
lines.append(guidance)
return "\n".join(lines)
def _load_inputs(args: argparse.Namespace) -> tuple[dict, dict | None, str | None]:
if args.input:
raw = sys.stdin.read() if args.input == "-" else open(args.input).read()
data = json.loads(raw)
return data["scores"], data.get("weights"), data.get("account")
if args.scores:
dims = list(DEFAULT_WEIGHTS.keys())
if len(args.scores) != len(dims):
raise ValueError(f"--scores needs {len(dims)} values in order: {', '.join(dims)}")
return dict(zip(dims, args.scores)), None, args.account
raise ValueError("Provide --input or --scores.")
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--input", help="Path to a JSON file (or '-' for stdin).")
parser.add_argument("--scores", nargs="+", type=float,
help="Five scores 1-5 in order: adoption engagement outcomes support commercial.")
parser.add_argument("--account", help="Account name for the report header.")
parser.add_argument("--json", action="store_true", dest="as_json", help="Emit JSON instead of a report.")
args = parser.parse_args(argv)
try:
scores, weights, account = _load_inputs(args)
result = compute(scores, weights)
except (ValueError, KeyError, json.JSONDecodeError, OSError) as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
if args.as_json:
result["account"] = account
print(json.dumps(result, indent=2))
else:
print(_render(result, account))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -53,6 +53,22 @@ Availability factor: 0.70.85 depending on holidays/events
Story points to commit = Historical velocity × Availability factor Story points to commit = Historical velocity × Availability factor
``` ```
## Programmatic Helper
This skill ships with a stdlib-only Python script that computes capacity instead of estimating it by hand. Use it whenever the team's numbers are known — it applies the availability and 80% commit-ratio rules consistently.
```bash
# Quick estimate from flags
python3 scripts/capacity_calculator.py --team 5 --days 10 --velocity 30 --availability 0.8 --carryover 5
# Detailed estimate from per-member availability (JSON via stdin or --input file.json)
echo '{"sprint_days":10,"historical_velocity":40,"carryover_points":8,
"members":[{"name":"Ada","available_days":10},{"name":"Linus","available_days":7}]}' \
| python3 scripts/capacity_calculator.py --input -
```
The script returns available focus hours, a velocity figure adjusted for real availability, the **recommended commitment** (capped at 80% of velocity), and the remaining **capacity for new work** after carry-overs. Run it first, then build the sprint backlog to fit the recommended number. Add `--json` to pipe the result into other tooling.
## Output Format ## Output Format
### Sprint [N] — [Start Date] to [End Date] ### Sprint [N] — [Start Date] to [End Date]
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Sprint capacity calculator for the sprint-planning skill.
Turns team and availability inputs into a recommended sprint commitment so the
numbers in a sprint plan are computed, not guessed. Pure Python standard
library — no dependencies, no network access.
Examples
--------
Quick estimate from flags:
python3 capacity_calculator.py --team 5 --days 10 --velocity 30 \
--availability 0.8 --carryover 5
Detailed estimate from a JSON file describing each team member:
python3 capacity_calculator.py --input team.json
Where team.json looks like:
{
"sprint_days": 10,
"focus_hours_per_day": 6,
"historical_velocity": 30,
"carryover_points": 5,
"commit_ratio": 0.8,
"members": [
{"name": "Ada", "available_days": 10},
{"name": "Linus", "available_days": 7, "note": "2 days PTO, 1 day interview"}
]
}
The recommended commitment deliberately leaves slack for unplanned work — it
never commits 100% of theoretical capacity.
"""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import dataclass, field
@dataclass
class Member:
name: str
available_days: float
note: str = ""
@dataclass
class CapacityInputs:
sprint_days: int = 10
focus_hours_per_day: float = 6.0
historical_velocity: float | None = None
carryover_points: float = 0.0
commit_ratio: float = 0.8
team_size: int | None = None
availability_factor: float = 0.8
members: list[Member] = field(default_factory=list)
def _availability_from_members(inp: CapacityInputs) -> float:
"""Return the blended availability factor (0-1) from per-member days."""
if not inp.members:
return inp.availability_factor
theoretical = len(inp.members) * inp.sprint_days
if theoretical == 0:
return 0.0
actual = sum(m.available_days for m in inp.members)
return actual / theoretical
def compute(inp: CapacityInputs) -> dict:
team_size = inp.team_size if inp.team_size is not None else len(inp.members)
if not team_size:
raise ValueError("Provide --team or a non-empty members list.")
availability = _availability_from_members(inp)
focus_hours = team_size * inp.sprint_days * inp.focus_hours_per_day * availability
result: dict = {
"team_size": team_size,
"sprint_days": inp.sprint_days,
"focus_hours_per_day": inp.focus_hours_per_day,
"availability_factor": round(availability, 3),
"available_focus_hours": round(focus_hours, 1),
}
if inp.historical_velocity is not None:
velocity_adjusted = inp.historical_velocity * availability
recommended = velocity_adjusted * inp.commit_ratio
new_work_capacity = max(recommended - inp.carryover_points, 0.0)
result.update(
{
"historical_velocity": inp.historical_velocity,
"velocity_adjusted_for_availability": round(velocity_adjusted, 1),
"commit_ratio": inp.commit_ratio,
"carryover_points": inp.carryover_points,
"recommended_commitment_points": round(recommended, 1),
"capacity_for_new_work_points": round(new_work_capacity, 1),
}
)
if inp.carryover_points > recommended:
result["warning"] = (
"Carry-over alone exceeds the recommended commitment — "
"pull in little or no new work this sprint."
)
return result
def _parse_inputs(args: argparse.Namespace) -> CapacityInputs:
if args.input:
raw = sys.stdin.read() if args.input == "-" else open(args.input).read()
data = json.loads(raw)
members = [
Member(
name=m.get("name", f"member-{i+1}"),
available_days=float(m.get("available_days", data.get("sprint_days", 10))),
note=m.get("note", ""),
)
for i, m in enumerate(data.get("members", []))
]
return CapacityInputs(
sprint_days=int(data.get("sprint_days", 10)),
focus_hours_per_day=float(data.get("focus_hours_per_day", 6.0)),
historical_velocity=(
float(data["historical_velocity"])
if data.get("historical_velocity") is not None
else None
),
carryover_points=float(data.get("carryover_points", 0.0)),
commit_ratio=float(data.get("commit_ratio", 0.8)),
team_size=data.get("team_size"),
availability_factor=float(data.get("availability_factor", 0.8)),
members=members,
)
return CapacityInputs(
sprint_days=args.days,
focus_hours_per_day=args.focus_hours,
historical_velocity=args.velocity,
carryover_points=args.carryover,
commit_ratio=args.commit_ratio,
team_size=args.team,
availability_factor=args.availability,
)
def _render(result: dict) -> str:
lines = ["Sprint Capacity Estimate", "=" * 24]
label = {
"team_size": "Team size",
"sprint_days": "Sprint days",
"focus_hours_per_day": "Focus hours/day",
"availability_factor": "Availability factor",
"available_focus_hours": "Available focus hours",
"historical_velocity": "Historical velocity (pts)",
"velocity_adjusted_for_availability": "Velocity adj. for availability",
"commit_ratio": "Commit ratio",
"carryover_points": "Carry-over (pts)",
"recommended_commitment_points": "RECOMMENDED commitment (pts)",
"capacity_for_new_work_points": "Capacity for NEW work (pts)",
}
for key, text in label.items():
if key in result:
lines.append(f"{text:<32}: {result[key]}")
if "warning" in result:
lines.append("")
lines.append(f"⚠️ {result['warning']}")
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--input", help="Path to a JSON file describing the team (or '-' for stdin).")
parser.add_argument("--team", type=int, help="Number of team members.")
parser.add_argument("--days", type=int, default=10, help="Working days in the sprint (default: 10).")
parser.add_argument("--focus-hours", type=float, default=6.0, dest="focus_hours",
help="Focus hours per person per day (default: 6).")
parser.add_argument("--velocity", type=float, help="Historical average velocity in story points.")
parser.add_argument("--carryover", type=float, default=0.0, help="Carry-over story points from last sprint.")
parser.add_argument("--availability", type=float, default=0.8,
help="Availability factor 0-1 when not using per-member days (default: 0.8).")
parser.add_argument("--commit-ratio", type=float, default=0.8, dest="commit_ratio",
help="Fraction of velocity to commit, leaving slack (default: 0.8).")
parser.add_argument("--json", action="store_true", help="Emit JSON instead of a formatted report.")
args = parser.parse_args(argv)
try:
result = compute(_parse_inputs(args))
except (ValueError, json.JSONDecodeError, OSError) as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
print(json.dumps(result, indent=2) if args.json else _render(result))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -25,6 +25,24 @@ Ask the user for these if not provided:
## RICE Formula ## RICE Formula
RICE Score = (Reach × Impact × Confidence) / Effort RICE Score = (Reach × Impact × Confidence) / Effort
## Programmatic Helper
This skill ships with a stdlib-only Python script that calculates and ranks RICE scores so the maths is consistent and the quick-win / moonshot flags are applied by rule, not by feel. Feed it the initiatives once R, I, C, and E are gathered.
```bash
# From a JSON file (confidence accepts 0.8 or 80)
python3 scripts/rice_calculator.py initiatives.json
# Or from a CSV with header: name,reach,impact,confidence,effort
python3 scripts/rice_calculator.py initiatives.csv --format csv
# Or piped in
echo '[{"name":"Onboarding","reach":5000,"impact":2,"confidence":0.8,"effort":3}]' \
| python3 scripts/rice_calculator.py -
```
It outputs a ranked table with computed RICE scores and auto-flags **quick-win** (strong score, low relative effort), **moonshot** (high impact, high effort), and **low-confidence** (≤50%) items. Use the computed ranking as the starting point, then apply the validation step below — never accept a surprising top rank without checking the estimates behind it.
## Process ## Process
1. For each initiative provided, gather or estimate R, I, C, E values 1. For each initiative provided, gather or estimate R, I, C, E values
2. Flag where estimates are weak and note what data would improve them 2. Flag where estimates are weak and note what data would improve them
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""RICE score calculator for the rice-prioritisation skill.
Computes RICE = (Reach × Impact × Confidence) / Effort for a list of
initiatives, ranks them, and flags quick wins and moonshots so the ranking in a
prioritisation doc is calculated consistently rather than eyeballed. Pure Python
standard library — no dependencies, no network access.
Input
-----
A JSON or CSV list of initiatives. Each needs: name, reach, impact, confidence,
effort.
- impact uses the standard RICE scale (3, 2, 1, 0.5, 0.25) but any number works.
- confidence is a fraction (0.8) or a percentage (80) — both are accepted.
- effort is in person-months and must be > 0.
JSON example (rice.json):
[
{"name": "Onboarding redesign", "reach": 5000, "impact": 2, "confidence": 0.8, "effort": 3},
{"name": "Dark mode", "reach": 8000, "impact": 0.5, "confidence": 1.0, "effort": 1}
]
CSV example (header row required):
name,reach,impact,confidence,effort
Onboarding redesign,5000,2,0.8,3
Dark mode,8000,0.5,1.0,1
Usage
-----
python3 rice_calculator.py rice.json
python3 rice_calculator.py rice.csv --format csv
cat rice.json | python3 rice_calculator.py - --json
"""
from __future__ import annotations
import argparse
import csv
import io
import json
import sys
from dataclasses import dataclass
@dataclass
class Initiative:
name: str
reach: float
impact: float
confidence: float
effort: float
@property
def score(self) -> float:
if self.effort <= 0:
raise ValueError(f"Effort for '{self.name}' must be greater than 0.")
return (self.reach * self.impact * self.confidence) / self.effort
def _normalise_confidence(value: float) -> float:
"""Accept 80 or 0.8; return a fraction between 0 and 1."""
return value / 100.0 if value > 1 else value
def _to_initiative(row: dict) -> Initiative:
try:
return Initiative(
name=str(row["name"]).strip(),
reach=float(row["reach"]),
impact=float(row["impact"]),
confidence=_normalise_confidence(float(row["confidence"])),
effort=float(row["effort"]),
)
except KeyError as exc:
raise ValueError(f"Missing required field {exc} in row: {row}") from None
def load(text: str, fmt: str) -> list[Initiative]:
if fmt == "csv":
rows = list(csv.DictReader(io.StringIO(text)))
else:
rows = json.loads(text)
if not isinstance(rows, list):
raise ValueError("Input must be a list of initiatives.")
return [_to_initiative(r) for r in rows]
def rank(initiatives: list[Initiative]) -> list[dict]:
scored = []
for i in initiatives:
scored.append({
"name": i.name,
"reach": i.reach,
"impact": i.impact,
"confidence": round(i.confidence, 2),
"effort": i.effort,
"rice_score": round(i.score, 1),
})
scored.sort(key=lambda d: d["rice_score"], reverse=True)
if scored:
max_score = max(d["rice_score"] for d in scored) or 1
max_effort = max(d["effort"] for d in scored) or 1
for rank_index, d in enumerate(scored, start=1):
d["rank"] = rank_index
flags = []
# Quick win: strong score relative to the field, low relative effort.
if d["rice_score"] >= 0.5 * max_score and d["effort"] <= 0.33 * max_effort:
flags.append("quick-win")
# Moonshot: high raw impact, high relative effort.
if d["impact"] >= 2 and d["effort"] >= 0.66 * max_effort:
flags.append("moonshot")
# Low-confidence estimates should be revisited before acting.
if d["confidence"] <= 0.5:
flags.append("low-confidence")
d["flags"] = flags
return scored
def _render(scored: list[dict]) -> str:
header = f"{'#':>2} {'Initiative':<32} {'Reach':>8} {'Imp':>4} {'Conf':>5} {'Eff':>5} {'RICE':>8} Flags"
lines = ["RICE Prioritisation", "=" * len(header), header, "-" * len(header)]
for d in scored:
lines.append(
f"{d['rank']:>2} {d['name'][:32]:<32} {d['reach']:>8g} {d['impact']:>4g} "
f"{d['confidence']:>5.2f} {d['effort']:>5g} {d['rice_score']:>8g} {', '.join(d['flags'])}"
)
quick = [d["name"] for d in scored if "quick-win" in d["flags"]]
moon = [d["name"] for d in scored if "moonshot" in d["flags"]]
lowc = [d["name"] for d in scored if "low-confidence" in d["flags"]]
lines.append("")
lines.append(f"Quick wins (do alongside bigger bets): {', '.join(quick) or 'none'}")
lines.append(f"Moonshots (high impact, high effort): {', '.join(moon) or 'none'}")
lines.append(f"Low confidence — revisit estimates: {', '.join(lowc) or 'none'}")
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("input", help="Path to a JSON/CSV file of initiatives, or '-' for stdin.")
parser.add_argument("--format", choices=["json", "csv"], help="Input format (inferred from extension if omitted).")
parser.add_argument("--json", action="store_true", dest="as_json", help="Emit ranked JSON instead of a table.")
args = parser.parse_args(argv)
text = sys.stdin.read() if args.input == "-" else None
fmt = args.format
if text is None:
try:
text = open(args.input).read()
except OSError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
if fmt is None:
fmt = "csv" if args.input.lower().endswith(".csv") else "json"
fmt = fmt or "json"
try:
scored = rank(load(text, fmt))
except (ValueError, json.JSONDecodeError) as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
print(json.dumps(scored, indent=2) if args.as_json else _render(scored))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+2
View File
@@ -55,6 +55,7 @@ cp skills/feature-prioritisation/SKILL.md plugins/pm-planning/skills/feature-
cp skills/roadmap-presentation/SKILL.md plugins/pm-planning/skills/roadmap-presentation/SKILL.md cp skills/roadmap-presentation/SKILL.md plugins/pm-planning/skills/roadmap-presentation/SKILL.md
cp skills/pricing-strategy/SKILL.md plugins/pm-planning/skills/pricing-strategy/SKILL.md cp skills/pricing-strategy/SKILL.md plugins/pm-planning/skills/pricing-strategy/SKILL.md
cp skills/rice-prioritisation/SKILL.md plugins/pm-planning/skills/rice-prioritisation/SKILL.md cp skills/rice-prioritisation/SKILL.md plugins/pm-planning/skills/rice-prioritisation/SKILL.md
cp -r skills/rice-prioritisation/scripts plugins/pm-planning/skills/rice-prioritisation/ 2>/dev/null || true
cp skills/roadmap-narrative/SKILL.md plugins/pm-planning/skills/roadmap-narrative/SKILL.md cp skills/roadmap-narrative/SKILL.md plugins/pm-planning/skills/roadmap-narrative/SKILL.md
cp skills/rice-impact-matrix/SKILL.md plugins/pm-planning/skills/rice-impact-matrix/SKILL.md cp skills/rice-impact-matrix/SKILL.md plugins/pm-planning/skills/rice-impact-matrix/SKILL.md
@@ -73,6 +74,7 @@ mkdir -p plugins/pm-delivery/skills/sprint-brief
mkdir -p plugins/pm-delivery/skills/retro-analysis mkdir -p plugins/pm-delivery/skills/retro-analysis
cp skills/sprint-planning/SKILL.md plugins/pm-delivery/skills/sprint-planning/SKILL.md cp skills/sprint-planning/SKILL.md plugins/pm-delivery/skills/sprint-planning/SKILL.md
cp -r skills/sprint-planning/scripts plugins/pm-delivery/skills/sprint-planning/ 2>/dev/null || true
cp skills/technical-spec-template/SKILL.md plugins/pm-delivery/skills/technical-spec-template/SKILL.md cp skills/technical-spec-template/SKILL.md plugins/pm-delivery/skills/technical-spec-template/SKILL.md
cp skills/ab-test-planner/SKILL.md plugins/pm-delivery/skills/ab-test-planner/SKILL.md cp skills/ab-test-planner/SKILL.md plugins/pm-delivery/skills/ab-test-planner/SKILL.md
cp skills/go-to-market-planner/SKILL.md plugins/pm-delivery/skills/go-to-market-planner/SKILL.md cp skills/go-to-market-planner/SKILL.md plugins/pm-delivery/skills/go-to-market-planner/SKILL.md
+14
View File
@@ -35,6 +35,20 @@ Score each dimension 15. Weight as shown. Calculate weighted total out of 100
- 6079: Amber (at risk, needs attention) - 6079: Amber (at risk, needs attention)
- 059: Red (high churn risk, escalate) - 059: Red (high churn risk, escalate)
## Programmatic Helper
This skill ships with a stdlib-only Python script that applies the weights above and converts the weighted total to a RAG status — so the headline score is computed identically every time and weights always sum to 100%.
```bash
# Five scores 1-5 in order: adoption engagement outcomes support commercial
python3 scripts/health_score.py --scores 4 3 4 2 5 --account "Acme Corp"
# Or from JSON (lets you override the default weights per account/segment)
python3 scripts/health_score.py --input account.json
```
It returns the per-dimension weighted points, the **total out of 100**, and the **RAG band** (Green ≥80, Amber 6079, Red <60) with a one-line next step. Run it to set the headline number, then write the dimension detail and actions below around it. Add `--json` for downstream tooling.
## Output Format ## Output Format
--- ---
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""Customer health score calculator for the cs-health-scorecard skill.
Takes per-dimension scores (1-5), applies the standard weights, and returns a
weighted total out of 100 plus a RAG status — so the headline number in a health
scorecard is computed the same way every time. Pure Python standard library —
no dependencies, no network access.
Standard dimensions and weights (override with --weights or in the JSON):
Product Adoption 30%
Engagement 20%
Outcomes 20%
Support Health 15%
Commercial 15%
Usage
-----
Quick scoring from flags (order: adoption engagement outcomes support commercial):
python3 health_score.py --scores 4 3 4 2 5
From a JSON file that can also override weights:
python3 health_score.py --input account.json
account.json:
{
"account": "Acme Corp",
"scores": {"Product Adoption": 4, "Engagement": 3, "Outcomes": 4,
"Support Health": 2, "Commercial": 5},
"weights": {"Product Adoption": 0.30, "Engagement": 0.20, "Outcomes": 0.20,
"Support Health": 0.15, "Commercial": 0.15}
}
"""
from __future__ import annotations
import argparse
import json
import sys
DEFAULT_WEIGHTS = {
"Product Adoption": 0.30,
"Engagement": 0.20,
"Outcomes": 0.20,
"Support Health": 0.15,
"Commercial": 0.15,
}
MAX_DIMENSION_SCORE = 5
def rag(total: float) -> str:
if total >= 80:
return "Green"
if total >= 60:
return "Amber"
return "Red"
def compute(scores: dict[str, float], weights: dict[str, float] | None = None) -> dict:
weights = weights or DEFAULT_WEIGHTS
weight_sum = sum(weights.values())
if abs(weight_sum - 1.0) > 0.001:
raise ValueError(f"Weights must sum to 1.0 (got {weight_sum:.3f}).")
breakdown = []
total = 0.0
for dimension, weight in weights.items():
if dimension not in scores:
raise ValueError(f"Missing score for dimension '{dimension}'.")
raw = float(scores[dimension])
if not 1 <= raw <= MAX_DIMENSION_SCORE:
raise ValueError(f"Score for '{dimension}' must be between 1 and {MAX_DIMENSION_SCORE} (got {raw}).")
# Normalise the 1-5 score to a 0-100 contribution weighted by importance.
weighted = (raw / MAX_DIMENSION_SCORE) * weight * 100
total += weighted
breakdown.append({
"dimension": dimension,
"score": raw,
"weight": weight,
"weighted_points": round(weighted, 1),
})
total = round(total, 1)
return {"total": total, "rag": rag(total), "breakdown": breakdown}
def _render(result: dict, account: str | None) -> str:
title = f"Customer Health Scorecard: {account}" if account else "Customer Health Scorecard"
lines = [title, "=" * len(title)]
lines.append(f"{'Dimension':<18} {'Score':>5} {'Weight':>7} {'Weighted':>9}")
lines.append("-" * 41)
for row in result["breakdown"]:
lines.append(
f"{row['dimension']:<18} {row['score']:>5g} {row['weight']*100:>6.0f}% {row['weighted_points']:>9g}"
)
lines.append("-" * 41)
badge = {"Green": "🟢", "Amber": "🟡", "Red": "🔴"}[result["rag"]]
lines.append(f"{'TOTAL':<18} {'':>5} {'100%':>7} {result['total']:>9g}/100")
lines.append("")
lines.append(f"Overall health: {badge} {result['rag']}{result['total']}/100")
guidance = {
"Green": "Healthy — renew likely. Look for expansion signals.",
"Amber": "At risk — needs attention. Build a save/grow plan before renewal.",
"Red": "High churn risk — escalate now and assign an executive sponsor.",
}[result["rag"]]
lines.append(guidance)
return "\n".join(lines)
def _load_inputs(args: argparse.Namespace) -> tuple[dict, dict | None, str | None]:
if args.input:
raw = sys.stdin.read() if args.input == "-" else open(args.input).read()
data = json.loads(raw)
return data["scores"], data.get("weights"), data.get("account")
if args.scores:
dims = list(DEFAULT_WEIGHTS.keys())
if len(args.scores) != len(dims):
raise ValueError(f"--scores needs {len(dims)} values in order: {', '.join(dims)}")
return dict(zip(dims, args.scores)), None, args.account
raise ValueError("Provide --input or --scores.")
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--input", help="Path to a JSON file (or '-' for stdin).")
parser.add_argument("--scores", nargs="+", type=float,
help="Five scores 1-5 in order: adoption engagement outcomes support commercial.")
parser.add_argument("--account", help="Account name for the report header.")
parser.add_argument("--json", action="store_true", dest="as_json", help="Emit JSON instead of a report.")
args = parser.parse_args(argv)
try:
scores, weights, account = _load_inputs(args)
result = compute(scores, weights)
except (ValueError, KeyError, json.JSONDecodeError, OSError) as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
if args.as_json:
result["account"] = account
print(json.dumps(result, indent=2))
else:
print(_render(result, account))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+18
View File
@@ -25,6 +25,24 @@ Ask the user for these if not provided:
## RICE Formula ## RICE Formula
RICE Score = (Reach × Impact × Confidence) / Effort RICE Score = (Reach × Impact × Confidence) / Effort
## Programmatic Helper
This skill ships with a stdlib-only Python script that calculates and ranks RICE scores so the maths is consistent and the quick-win / moonshot flags are applied by rule, not by feel. Feed it the initiatives once R, I, C, and E are gathered.
```bash
# From a JSON file (confidence accepts 0.8 or 80)
python3 scripts/rice_calculator.py initiatives.json
# Or from a CSV with header: name,reach,impact,confidence,effort
python3 scripts/rice_calculator.py initiatives.csv --format csv
# Or piped in
echo '[{"name":"Onboarding","reach":5000,"impact":2,"confidence":0.8,"effort":3}]' \
| python3 scripts/rice_calculator.py -
```
It outputs a ranked table with computed RICE scores and auto-flags **quick-win** (strong score, low relative effort), **moonshot** (high impact, high effort), and **low-confidence** (≤50%) items. Use the computed ranking as the starting point, then apply the validation step below — never accept a surprising top rank without checking the estimates behind it.
## Process ## Process
1. For each initiative provided, gather or estimate R, I, C, E values 1. For each initiative provided, gather or estimate R, I, C, E values
2. Flag where estimates are weak and note what data would improve them 2. Flag where estimates are weak and note what data would improve them
@@ -0,0 +1,170 @@
#!/usr/bin/env python3
"""RICE score calculator for the rice-prioritisation skill.
Computes RICE = (Reach × Impact × Confidence) / Effort for a list of
initiatives, ranks them, and flags quick wins and moonshots so the ranking in a
prioritisation doc is calculated consistently rather than eyeballed. Pure Python
standard library — no dependencies, no network access.
Input
-----
A JSON or CSV list of initiatives. Each needs: name, reach, impact, confidence,
effort.
- impact uses the standard RICE scale (3, 2, 1, 0.5, 0.25) but any number works.
- confidence is a fraction (0.8) or a percentage (80) — both are accepted.
- effort is in person-months and must be > 0.
JSON example (rice.json):
[
{"name": "Onboarding redesign", "reach": 5000, "impact": 2, "confidence": 0.8, "effort": 3},
{"name": "Dark mode", "reach": 8000, "impact": 0.5, "confidence": 1.0, "effort": 1}
]
CSV example (header row required):
name,reach,impact,confidence,effort
Onboarding redesign,5000,2,0.8,3
Dark mode,8000,0.5,1.0,1
Usage
-----
python3 rice_calculator.py rice.json
python3 rice_calculator.py rice.csv --format csv
cat rice.json | python3 rice_calculator.py - --json
"""
from __future__ import annotations
import argparse
import csv
import io
import json
import sys
from dataclasses import dataclass
@dataclass
class Initiative:
name: str
reach: float
impact: float
confidence: float
effort: float
@property
def score(self) -> float:
if self.effort <= 0:
raise ValueError(f"Effort for '{self.name}' must be greater than 0.")
return (self.reach * self.impact * self.confidence) / self.effort
def _normalise_confidence(value: float) -> float:
"""Accept 80 or 0.8; return a fraction between 0 and 1."""
return value / 100.0 if value > 1 else value
def _to_initiative(row: dict) -> Initiative:
try:
return Initiative(
name=str(row["name"]).strip(),
reach=float(row["reach"]),
impact=float(row["impact"]),
confidence=_normalise_confidence(float(row["confidence"])),
effort=float(row["effort"]),
)
except KeyError as exc:
raise ValueError(f"Missing required field {exc} in row: {row}") from None
def load(text: str, fmt: str) -> list[Initiative]:
if fmt == "csv":
rows = list(csv.DictReader(io.StringIO(text)))
else:
rows = json.loads(text)
if not isinstance(rows, list):
raise ValueError("Input must be a list of initiatives.")
return [_to_initiative(r) for r in rows]
def rank(initiatives: list[Initiative]) -> list[dict]:
scored = []
for i in initiatives:
scored.append({
"name": i.name,
"reach": i.reach,
"impact": i.impact,
"confidence": round(i.confidence, 2),
"effort": i.effort,
"rice_score": round(i.score, 1),
})
scored.sort(key=lambda d: d["rice_score"], reverse=True)
if scored:
max_score = max(d["rice_score"] for d in scored) or 1
max_effort = max(d["effort"] for d in scored) or 1
for rank_index, d in enumerate(scored, start=1):
d["rank"] = rank_index
flags = []
# Quick win: strong score relative to the field, low relative effort.
if d["rice_score"] >= 0.5 * max_score and d["effort"] <= 0.33 * max_effort:
flags.append("quick-win")
# Moonshot: high raw impact, high relative effort.
if d["impact"] >= 2 and d["effort"] >= 0.66 * max_effort:
flags.append("moonshot")
# Low-confidence estimates should be revisited before acting.
if d["confidence"] <= 0.5:
flags.append("low-confidence")
d["flags"] = flags
return scored
def _render(scored: list[dict]) -> str:
header = f"{'#':>2} {'Initiative':<32} {'Reach':>8} {'Imp':>4} {'Conf':>5} {'Eff':>5} {'RICE':>8} Flags"
lines = ["RICE Prioritisation", "=" * len(header), header, "-" * len(header)]
for d in scored:
lines.append(
f"{d['rank']:>2} {d['name'][:32]:<32} {d['reach']:>8g} {d['impact']:>4g} "
f"{d['confidence']:>5.2f} {d['effort']:>5g} {d['rice_score']:>8g} {', '.join(d['flags'])}"
)
quick = [d["name"] for d in scored if "quick-win" in d["flags"]]
moon = [d["name"] for d in scored if "moonshot" in d["flags"]]
lowc = [d["name"] for d in scored if "low-confidence" in d["flags"]]
lines.append("")
lines.append(f"Quick wins (do alongside bigger bets): {', '.join(quick) or 'none'}")
lines.append(f"Moonshots (high impact, high effort): {', '.join(moon) or 'none'}")
lines.append(f"Low confidence — revisit estimates: {', '.join(lowc) or 'none'}")
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("input", help="Path to a JSON/CSV file of initiatives, or '-' for stdin.")
parser.add_argument("--format", choices=["json", "csv"], help="Input format (inferred from extension if omitted).")
parser.add_argument("--json", action="store_true", dest="as_json", help="Emit ranked JSON instead of a table.")
args = parser.parse_args(argv)
text = sys.stdin.read() if args.input == "-" else None
fmt = args.format
if text is None:
try:
text = open(args.input).read()
except OSError as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
if fmt is None:
fmt = "csv" if args.input.lower().endswith(".csv") else "json"
fmt = fmt or "json"
try:
scored = rank(load(text, fmt))
except (ValueError, json.JSONDecodeError) as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
print(json.dumps(scored, indent=2) if args.as_json else _render(scored))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+16
View File
@@ -53,6 +53,22 @@ Availability factor: 0.70.85 depending on holidays/events
Story points to commit = Historical velocity × Availability factor Story points to commit = Historical velocity × Availability factor
``` ```
## Programmatic Helper
This skill ships with a stdlib-only Python script that computes capacity instead of estimating it by hand. Use it whenever the team's numbers are known — it applies the availability and 80% commit-ratio rules consistently.
```bash
# Quick estimate from flags
python3 scripts/capacity_calculator.py --team 5 --days 10 --velocity 30 --availability 0.8 --carryover 5
# Detailed estimate from per-member availability (JSON via stdin or --input file.json)
echo '{"sprint_days":10,"historical_velocity":40,"carryover_points":8,
"members":[{"name":"Ada","available_days":10},{"name":"Linus","available_days":7}]}' \
| python3 scripts/capacity_calculator.py --input -
```
The script returns available focus hours, a velocity figure adjusted for real availability, the **recommended commitment** (capped at 80% of velocity), and the remaining **capacity for new work** after carry-overs. Run it first, then build the sprint backlog to fit the recommended number. Add `--json` to pipe the result into other tooling.
## Output Format ## Output Format
### Sprint [N] — [Start Date] to [End Date] ### Sprint [N] — [Start Date] to [End Date]
@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Sprint capacity calculator for the sprint-planning skill.
Turns team and availability inputs into a recommended sprint commitment so the
numbers in a sprint plan are computed, not guessed. Pure Python standard
library — no dependencies, no network access.
Examples
--------
Quick estimate from flags:
python3 capacity_calculator.py --team 5 --days 10 --velocity 30 \
--availability 0.8 --carryover 5
Detailed estimate from a JSON file describing each team member:
python3 capacity_calculator.py --input team.json
Where team.json looks like:
{
"sprint_days": 10,
"focus_hours_per_day": 6,
"historical_velocity": 30,
"carryover_points": 5,
"commit_ratio": 0.8,
"members": [
{"name": "Ada", "available_days": 10},
{"name": "Linus", "available_days": 7, "note": "2 days PTO, 1 day interview"}
]
}
The recommended commitment deliberately leaves slack for unplanned work — it
never commits 100% of theoretical capacity.
"""
from __future__ import annotations
import argparse
import json
import sys
from dataclasses import dataclass, field
@dataclass
class Member:
name: str
available_days: float
note: str = ""
@dataclass
class CapacityInputs:
sprint_days: int = 10
focus_hours_per_day: float = 6.0
historical_velocity: float | None = None
carryover_points: float = 0.0
commit_ratio: float = 0.8
team_size: int | None = None
availability_factor: float = 0.8
members: list[Member] = field(default_factory=list)
def _availability_from_members(inp: CapacityInputs) -> float:
"""Return the blended availability factor (0-1) from per-member days."""
if not inp.members:
return inp.availability_factor
theoretical = len(inp.members) * inp.sprint_days
if theoretical == 0:
return 0.0
actual = sum(m.available_days for m in inp.members)
return actual / theoretical
def compute(inp: CapacityInputs) -> dict:
team_size = inp.team_size if inp.team_size is not None else len(inp.members)
if not team_size:
raise ValueError("Provide --team or a non-empty members list.")
availability = _availability_from_members(inp)
focus_hours = team_size * inp.sprint_days * inp.focus_hours_per_day * availability
result: dict = {
"team_size": team_size,
"sprint_days": inp.sprint_days,
"focus_hours_per_day": inp.focus_hours_per_day,
"availability_factor": round(availability, 3),
"available_focus_hours": round(focus_hours, 1),
}
if inp.historical_velocity is not None:
velocity_adjusted = inp.historical_velocity * availability
recommended = velocity_adjusted * inp.commit_ratio
new_work_capacity = max(recommended - inp.carryover_points, 0.0)
result.update(
{
"historical_velocity": inp.historical_velocity,
"velocity_adjusted_for_availability": round(velocity_adjusted, 1),
"commit_ratio": inp.commit_ratio,
"carryover_points": inp.carryover_points,
"recommended_commitment_points": round(recommended, 1),
"capacity_for_new_work_points": round(new_work_capacity, 1),
}
)
if inp.carryover_points > recommended:
result["warning"] = (
"Carry-over alone exceeds the recommended commitment — "
"pull in little or no new work this sprint."
)
return result
def _parse_inputs(args: argparse.Namespace) -> CapacityInputs:
if args.input:
raw = sys.stdin.read() if args.input == "-" else open(args.input).read()
data = json.loads(raw)
members = [
Member(
name=m.get("name", f"member-{i+1}"),
available_days=float(m.get("available_days", data.get("sprint_days", 10))),
note=m.get("note", ""),
)
for i, m in enumerate(data.get("members", []))
]
return CapacityInputs(
sprint_days=int(data.get("sprint_days", 10)),
focus_hours_per_day=float(data.get("focus_hours_per_day", 6.0)),
historical_velocity=(
float(data["historical_velocity"])
if data.get("historical_velocity") is not None
else None
),
carryover_points=float(data.get("carryover_points", 0.0)),
commit_ratio=float(data.get("commit_ratio", 0.8)),
team_size=data.get("team_size"),
availability_factor=float(data.get("availability_factor", 0.8)),
members=members,
)
return CapacityInputs(
sprint_days=args.days,
focus_hours_per_day=args.focus_hours,
historical_velocity=args.velocity,
carryover_points=args.carryover,
commit_ratio=args.commit_ratio,
team_size=args.team,
availability_factor=args.availability,
)
def _render(result: dict) -> str:
lines = ["Sprint Capacity Estimate", "=" * 24]
label = {
"team_size": "Team size",
"sprint_days": "Sprint days",
"focus_hours_per_day": "Focus hours/day",
"availability_factor": "Availability factor",
"available_focus_hours": "Available focus hours",
"historical_velocity": "Historical velocity (pts)",
"velocity_adjusted_for_availability": "Velocity adj. for availability",
"commit_ratio": "Commit ratio",
"carryover_points": "Carry-over (pts)",
"recommended_commitment_points": "RECOMMENDED commitment (pts)",
"capacity_for_new_work_points": "Capacity for NEW work (pts)",
}
for key, text in label.items():
if key in result:
lines.append(f"{text:<32}: {result[key]}")
if "warning" in result:
lines.append("")
lines.append(f"⚠️ {result['warning']}")
return "\n".join(lines)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--input", help="Path to a JSON file describing the team (or '-' for stdin).")
parser.add_argument("--team", type=int, help="Number of team members.")
parser.add_argument("--days", type=int, default=10, help="Working days in the sprint (default: 10).")
parser.add_argument("--focus-hours", type=float, default=6.0, dest="focus_hours",
help="Focus hours per person per day (default: 6).")
parser.add_argument("--velocity", type=float, help="Historical average velocity in story points.")
parser.add_argument("--carryover", type=float, default=0.0, help="Carry-over story points from last sprint.")
parser.add_argument("--availability", type=float, default=0.8,
help="Availability factor 0-1 when not using per-member days (default: 0.8).")
parser.add_argument("--commit-ratio", type=float, default=0.8, dest="commit_ratio",
help="Fraction of velocity to commit, leaving slack (default: 0.8).")
parser.add_argument("--json", action="store_true", help="Emit JSON instead of a formatted report.")
args = parser.parse_args(argv)
try:
result = compute(_parse_inputs(args))
except (ValueError, json.JSONDecodeError, OSError) as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
print(json.dumps(result, indent=2) if args.json else _render(result))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+1 -1
View File
File diff suppressed because one or more lines are too long