Skip to content

Commit 18468a9

Browse files
committed
Release v2.1.0: Opus 4.8-tuned pm-ai-shipping audits + CHANGELOG-driven release automation
pm-ai-shipping: mandatory Evidence citations verified before reporting, concrete subagent fan-out contract, read-only allowed-tools on both audits, N+1/waterfall detection and a refute pass in the performance audit, untrusted-input hardening across the kit, parallel audits in /ship-check, severity anchors + report consolidation, repo-relative paths. Repo: CHANGELOG.md as release source of truth with auto-tag-and-release on merge to main (adapted from phuryn/claude-usage, minus the .vsix build), Tests workflow on every PR/push, unit + docs-consistency test suite, contributor-credit conventions in CONTRIBUTING, all manifests synced at 2.1.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011URgT9hYuNrXeCvzjnqRxJ
1 parent a0cd730 commit 18468a9

27 files changed

Lines changed: 619 additions & 44 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
33
"name": "pm-skills",
4-
"version": "2.0.0",
4+
"version": "2.1.0",
55
"description": "Structured AI workflows for better product decisions. 68 domain-specific skills and 42 chained workflows across 9 PM plugins — from discovery to strategy, execution, launch, growth, and shipping AI-built software.",
66
"owner": {
77
"name": "Paweł Huryn",

.github/workflows/tag-on-merge.yml

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
name: Tag and release from CHANGELOG
2+
3+
# Runs after each push to main. If CHANGELOG.md gained a new ## vX.Y.Z heading
4+
# anywhere in this push's commit range (compared to the push's `before` SHA):
5+
# 1. gate the release — the version in .claude-plugin/marketplace.json must
6+
# match the new heading, and the validator + test suite must pass
7+
# (the suite also asserts every plugin.json carries the same version), then
8+
# 2. create a lightweight tag with that version name at the pushed commit, and
9+
# 3. publish a GitHub Release for that tag with the matching CHANGELOG
10+
# section as the notes.
11+
#
12+
# CHANGELOG is the source of truth; the tag and the Release are deterministic
13+
# projections of it. Adapted from phuryn/claude-usage's tag-on-merge workflow,
14+
# minus the .vsix build. Added headings whose tag already exists are treated as
15+
# backfilled history and skipped, so importing old releases is safe.
16+
#
17+
# No action when CHANGELOG wasn't touched, when an existing version heading was
18+
# edited (not added), or when the tag/release already exists. Safe to re-run on
19+
# force-pushes and amends.
20+
21+
on:
22+
push:
23+
branches: [main]
24+
25+
permissions:
26+
contents: write
27+
28+
jobs:
29+
release:
30+
runs-on: ubuntu-latest
31+
32+
steps:
33+
- uses: actions/checkout@v5
34+
with:
35+
# Need enough history to diff the whole push range (`before..after`),
36+
# not just the tip commit. Small repo; a full clone is cheap.
37+
fetch-depth: 0
38+
39+
- name: Detect new version heading in CHANGELOG
40+
id: detect
41+
env:
42+
BEFORE: ${{ github.event.before }}
43+
AFTER: ${{ github.sha }}
44+
run: |
45+
set -euo pipefail
46+
47+
# On a brand-new branch (first push), before is all zeros.
48+
zeros="0000000000000000000000000000000000000000"
49+
if [ "$BEFORE" = "$zeros" ] || [ -z "$BEFORE" ]; then
50+
echo "version=" >> "$GITHUB_OUTPUT"
51+
echo "Brand-new branch push; nothing to compare."
52+
exit 0
53+
fi
54+
55+
# Lines added to CHANGELOG.md across the entire pushed range that
56+
# look like a version heading. Format: `## vX.Y.Z` (semver triplet
57+
# required). The trailing-boundary group prevents `## v2.1.0a` from
58+
# matching `v2.1.0`.
59+
added_versions=$(git diff "$BEFORE..$AFTER" -- CHANGELOG.md \
60+
| grep -E '^\+## v[0-9]+\.[0-9]+\.[0-9]+([[:space:]]|$)' \
61+
| sed -E 's/^\+## (v[0-9]+\.[0-9]+\.[0-9]+)([[:space:]]|$).*/\1/' \
62+
|| true)
63+
64+
if [ -z "$added_versions" ]; then
65+
echo "version=" >> "$GITHUB_OUTPUT"
66+
echo "No new ## vX.Y.Z heading added to CHANGELOG; nothing to tag."
67+
exit 0
68+
fi
69+
70+
# Headings whose tag already exists on origin are backfilled history,
71+
# not new releases — skip them.
72+
new_versions=""
73+
for v in $added_versions; do
74+
if git ls-remote --tags origin "refs/tags/$v" | grep -q .; then
75+
echo "$v is already tagged; treating as backfill."
76+
else
77+
new_versions="${new_versions}${v}"$'\n'
78+
fi
79+
done
80+
new_versions=$(printf '%s' "$new_versions" | sed '/^$/d')
81+
82+
if [ -z "$new_versions" ]; then
83+
echo "version=" >> "$GITHUB_OUTPUT"
84+
echo "All added headings are already tagged (backfill); nothing to do."
85+
exit 0
86+
fi
87+
88+
# If multiple new untagged headings were added in one push, fail
89+
# loudly — ambiguous which one to tag, and shipping two releases in
90+
# one merge is almost certainly not intended.
91+
count=$(echo "$new_versions" | wc -l)
92+
if [ "$count" -gt 1 ]; then
93+
echo "::error::Multiple new untagged version headings detected; refusing to auto-tag. Versions: $new_versions"
94+
exit 1
95+
fi
96+
97+
version=$(echo "$new_versions" | head -1)
98+
echo "version=$version" >> "$GITHUB_OUTPUT"
99+
echo "Detected new release: $version"
100+
101+
# ── Release gates ────────────────────────────────────────────────────
102+
# Everything below is gated on a new version being detected, so ordinary
103+
# pushes to main (docs, typo fixes) incur no setup or test cost.
104+
105+
- name: "Gate: marketplace.json version matches the CHANGELOG"
106+
if: steps.detect.outputs.version != ''
107+
env:
108+
VERSION: ${{ steps.detect.outputs.version }}
109+
run: |
110+
set -euo pipefail
111+
want="${VERSION#v}"
112+
have=$(python3 -c "import json; print(json.load(open('.claude-plugin/marketplace.json'))['version'])")
113+
if [ "$have" != "$want" ]; then
114+
echo "::error::marketplace.json is $have but CHANGELOG released $VERSION. Bump the manifests before the release push."
115+
exit 1
116+
fi
117+
echo "marketplace.json at $have."
118+
119+
- name: "Gate: validator + test suite"
120+
if: steps.detect.outputs.version != ''
121+
run: |
122+
set -euo pipefail
123+
python3 validate_plugins.py
124+
python3 -m unittest discover -s tests -v
125+
126+
- name: Create and push tag if it doesn't already exist
127+
if: steps.detect.outputs.version != ''
128+
env:
129+
VERSION: ${{ steps.detect.outputs.version }}
130+
run: |
131+
set -euo pipefail
132+
133+
# Tag may already exist if someone tagged manually before the
134+
# workflow caught up, or on a re-push of the same commit. Idempotent.
135+
if git ls-remote --tags origin "refs/tags/$VERSION" | grep -q .; then
136+
echo "Tag $VERSION already exists on origin; nothing to do."
137+
exit 0
138+
fi
139+
140+
git tag "$VERSION"
141+
git push origin "$VERSION"
142+
echo "Tagged $VERSION at $(git rev-parse HEAD)."
143+
144+
- name: Create GitHub Release with the CHANGELOG section as notes
145+
if: steps.detect.outputs.version != ''
146+
env:
147+
VERSION: ${{ steps.detect.outputs.version }}
148+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
149+
run: |
150+
set -euo pipefail
151+
152+
# Idempotent: a re-push of the same release commit shouldn't error.
153+
if gh release view "$VERSION" >/dev/null 2>&1; then
154+
echo "Release $VERSION already exists; nothing to do."
155+
exit 0
156+
fi
157+
158+
# Extract this version's CHANGELOG section (heading through the line
159+
# before the next `## vX` heading) as the release notes. $2 is the
160+
# version token: `## v2.1.0 — 2026-07-03` → $2 == "v2.1.0".
161+
notes="$(mktemp)"
162+
awk -v ver="$VERSION" '
163+
/^## v[0-9]/ { if (started) exit; if ($2 == ver) started=1 }
164+
started { print }
165+
' CHANGELOG.md > "$notes"
166+
if [ ! -s "$notes" ]; then
167+
echo "::error::No '## $VERSION' section found in CHANGELOG.md."
168+
exit 1
169+
fi
170+
171+
gh release create "$VERSION" \
172+
--title "$VERSION" \
173+
--notes-file "$notes"
174+
echo "Released $VERSION."

.github/workflows/tests.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Tests
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
push:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.11", "3.13"]
15+
16+
steps:
17+
- uses: actions/checkout@v5
18+
19+
- name: Set up Python ${{ matrix.python-version }}
20+
uses: actions/setup-python@v6
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
24+
- name: Plugin validator
25+
run: python validate_plugins.py
26+
27+
- name: Unit + consistency tests
28+
run: python -m unittest discover -s tests -v

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
# Private maintainer-only files — never commit
22
_Internal/
33
CLAUDE.local.md
4+
5+
# Local tooling artifacts
6+
__pycache__/
7+
.claude/

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Changelog
2+
3+
## v2.1.0 — 2026-07-03
4+
5+
### pm-ai-shipping
6+
7+
- `/security-audit-static` findings now carry a mandatory **Evidence** line (`file:line` + verbatim snippet), and every citation is re-verified against the file before the final report ships.
8+
- Subagent fan-out has a concrete trigger (scope over ~30 files / ~5,000 lines) and a structured candidate-record contract, so parallel audit slices merge cleanly into one self-refute pass.
9+
- `/performance-audit-static` now hunts **N+1 queries and request waterfalls** — the most common perf failure in AI-generated code — alongside over-fetching, indexes, and caching, and gained a refute-before-reporting pass (dynamic field access, existing indexes, hot-path evidence).
10+
- Both audit commands pre-approve a read-only toolset (`allowed-tools`): read, search, fan out, and write under `reports/` — never edit the code under audit.
11+
- The audited repo is treated as untrusted input across the kit: instructions embedded in code, comments, or docs are data to analyze — a steering attempt is itself a finding — never directives to follow.
12+
- `/ship-check` runs the security and performance audits as parallel subagents once the docs exist.
13+
- Security reports gained severity anchors (what Critical/High/Medium/Low mean) and a consolidation rule (more than ~12 findings → lead with the worst, group the tail by root cause).
14+
- Docs and reports now use repo-relative paths (`documentation/`, `reports/`) — the old absolute forms (`/documentation`) could resolve to the filesystem root — and reports are always written, with the path announced, instead of "optionally".
15+
16+
### Repo
17+
18+
- Added this `CHANGELOG.md` as the release source of truth with auto-tag-and-release on merge (adapted from [claude-usage](https://github.com/phuryn/claude-usage)): pushing a new `## vX.Y.Z` heading to `main` tags that version and publishes a GitHub Release with the section as notes — gated on the test suite and a version-sync check.
19+
- Added a test suite (`tests/`) and a Tests workflow (every PR and push to `main`): plugin-spec validation plus docs consistency — README skill/command counts vs. disk, marketplace plugin list vs. directories, version sync across all manifests, CHANGELOG format.
20+
- CONTRIBUTING now documents the changelog convention (every user-facing change gets a bullet; contributors credited inline) and the release procedure.
21+
- Docs since v2.0.0: native Codex CLI install path; companion badges (burnstop, claude-usage).
22+
23+
## v2.0.0 — 2026-06-05
24+
25+
- Added the **pm-ai-shipping** plugin (AI Shipping Kit): `/ship-check`, `/document-app`, `/derive-tests`, `/security-audit-static`, `/performance-audit-static`, plus the `shipping-artifacts` and `intended-vs-implemented` skills.
26+
- Added the `strategy-red-team` skill and `/red-team-prd` command to pm-execution.
27+
- Refreshed the root README; added `CLAUDE.md` / `AGENTS.md` agent guidance.

CLAUDE.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ pm-skills/ <- repo root
1616
├── .docs/images/ <- images used by README (webp, gif)
1717
├── .gitattributes
1818
├── .gitignore
19+
├── .github/workflows/ <- CI: tests.yml (every PR/push), tag-on-merge.yml (auto-release)
20+
├── CHANGELOG.md <- release source of truth (new ## vX.Y.Z heading on main = release)
1921
├── CLAUDE.md <- this file (agent guidance, single source of truth)
2022
├── AGENTS.md <- pointer to CLAUDE.md (for non-Claude agents)
2123
├── CONTRIBUTING.md <- contributor guidelines
2224
├── README.md <- public documentation (GitHub)
2325
├── LICENSE <- MIT
2426
├── validate_plugins.py <- plugin validator
27+
├── tests/ <- unit + docs-consistency tests (unittest)
2528
└── pm-{name}/ <- 9 plugin directories
2629
├── .claude-plugin/plugin.json <- per-plugin manifest
2730
├── skills/{skill}/SKILL.md <- one folder per skill
@@ -67,11 +70,12 @@ pm-skills/ <- repo root
6770

6871
Descriptions in `plugin.json` and the repo `README.md` should stay aligned (identical text).
6972

70-
## Versioning
73+
## Versioning & Releases
7174

72-
- All versions are currently **2.0.0**`marketplace.json` and all 9 `plugin.json` files.
73-
- **Keep every version in sync.** There is no independent per-plugin versioning.
74-
- Bump any `plugin.json` → also bump `marketplace.json`, and vice-versa (bump all 9 to match).
75+
- **`CHANGELOG.md` is the source of truth.** The newest `## vX.Y.Z — YYYY-MM-DD` heading is the released version. Pushing a commit to `main` that adds a new heading makes CI (`.github/workflows/tag-on-merge.yml`) verify the version sync and test suite, tag `vX.Y.Z`, and publish a GitHub Release with that section as notes.
76+
- **Keep every version in sync.** `marketplace.json`, all 9 `plugin.json` files, and the newest CHANGELOG heading always carry the same version (enforced by `tests/test_consistency.py`). There is no independent per-plugin versioning.
77+
- Every user-facing change gets a CHANGELOG bullet under `## Unreleased`; contributors are credited inline (`#PR, thanks @handle`). Full procedure: CONTRIBUTING.md § Releases.
78+
- Semver: breaking = major; new skills/commands or changed behavior = minor; fixes/docs = patch.
7579

7680
## Article Links in Skills (Further Reading)
7781

@@ -83,10 +87,11 @@ Descriptions in `plugin.json` and the repo `README.md` should stay aligned (iden
8387
## Operational Procedures
8488

8589
### After any skill/command change
86-
1. Run `python3 validate_plugins.py` from the repo root to check all plugins.
87-
2. If skills/commands were added or removed, update the counts in `README.md`.
90+
1. Run `python3 validate_plugins.py` and `python3 -m unittest discover -s tests` from the repo root.
91+
2. If skills/commands were added or removed, update the counts in `README.md` (headline + per-plugin summary + plugin README section headers — the tests check all three).
8892
3. If totals changed, update the count in the `marketplace.json` description.
89-
4. Bump versions across all manifests (see Versioning).
93+
4. Add a `CHANGELOG.md` bullet under `## Unreleased` for any user-facing change.
94+
5. Bump versions across all manifests at release time (see Versioning & Releases).
9095

9196
### After a description change
9297
- A `plugin.json` description changed → check whether `README.md` needs the same edit (they stay aligned).
@@ -96,8 +101,11 @@ Descriptions in `plugin.json` and the repo `README.md` should stay aligned (iden
96101

97102
`validate_plugins.py` checks: `plugin.json` required fields / name match / semver / author / keywords; skill frontmatter and name-matches-directory; command frontmatter (`description` + `argument-hint`); README presence; and intra-plugin command→skill references.
98103

104+
`tests/` adds the consistency layer: README counts vs. disk, marketplace plugin list vs. directories, version sync across all manifests + CHANGELOG, CHANGELOG heading format, and `/plugin:command` references in plugin READMEs. Both run in CI on every PR and push to `main`, and gate releases.
105+
99106
```
100107
python3 validate_plugins.py
108+
python3 -m unittest discover -s tests
101109
```
102110

103111
## What to Suggest After Completing Work

CONTRIBUTING.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,20 @@ PM Skills Marketplace is maintained by [Paweł Huryn](https://www.productcompass
1414
- Every skill needs frontmatter with `name` and `description`. Every command needs `description` and `argument-hint`.
1515
- Skill `name` must match its directory name.
1616
- No cross-plugin references in commands. Suggest follow-ups in natural language only.
17-
- Every contributor will be listed publicly.
18-
- Run the validator before submitting: `python3 validate_plugins.py`
17+
- Every contributor will be listed publicly (see Changelog & Contributor Credit below).
18+
- Run the checks before submitting: `python3 validate_plugins.py` and `python3 -m unittest discover -s tests`.
19+
20+
## Changelog & Contributor Credit
21+
22+
Every user-facing change gets a bullet in [CHANGELOG.md](CHANGELOG.md). In a PR, add yours under a `## Unreleased` heading at the top (create it if it doesn't exist) and credit yourself at the end of the bullet — `(#123, thanks @your-handle)`. Credits ship verbatim in the GitHub Release notes and stay in the changelog permanently.
23+
24+
## Releases (maintainer)
25+
26+
`CHANGELOG.md` is the source of truth; tags and GitHub Releases are deterministic projections of it (`.github/workflows/tag-on-merge.yml`):
27+
28+
1. Rename `## Unreleased` to `## vX.Y.Z — YYYY-MM-DD`. Semver: breaking changes = major, new skills/commands or changed behavior = minor, fixes and docs = patch.
29+
2. Set the same version in `.claude-plugin/marketplace.json` and every plugin's `plugin.json` — versions stay in sync across the repo (the test suite enforces this).
30+
3. Push to `main`. CI verifies the version sync, runs the validator and test suite, then tags `vX.Y.Z` and publishes a GitHub Release with the changelog section as notes. No new heading → no release; ordinary pushes are unaffected.
1931

2032
## License
2133

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
![GitHub stars](https://img.shields.io/github/stars/phuryn/pm-skills)
22
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](https://github.com/phuryn/pm-skills/blob/main/LICENSE)
33
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen?style=flat-square)](https://github.com/phuryn/pm-skills/blob/main/CONTRIBUTING.md)
4+
[![Tests](https://github.com/phuryn/pm-skills/actions/workflows/tests.yml/badge.svg)](https://github.com/phuryn/pm-skills/actions/workflows/tests.yml)
45
[![Companion: pm-skills](https://img.shields.io/badge/companion-pm--brain-blue)](https://github.com/phuryn/pm-brain)
56
[![Companion: burnstop](https://img.shields.io/badge/companion-burnstop-blue)](https://github.com/phuryn/burnstop)
67
[![Companion: claude-usage](https://img.shields.io/badge/companion-claude--usage-blue)](https://github.com/phuryn/claude-usage)
@@ -452,7 +453,7 @@ For PMs and founders accountable for AI-built code. AI agents write code fast bu
452453
- `/document-app` — Reverse-engineer a codebase into the system documents reviewers and auditors need — a core set (architecture, flows, permissions, variables) plus conditional docs (emails, cron, SEO, automation) when they apply
453454
- `/derive-tests` — Turn documented intent into a test-coverage map: inventory the tests that exist today, separate them from proposed tests and unverified gaps, and recommend a green-before-merge CI gate
454455
- `/security-audit-static` — Static security audit: map trust boundaries, cross-reference documented intent, self-refute every finding, and report only evidence-backed risks
455-
- `/performance-audit-static` — Static performance audit: find over-fetching, missing indexes, and caching opportunities, ranked by effort and impact
456+
- `/performance-audit-static` — Static performance audit: find N+1 queries and request waterfalls, over-fetching, missing indexes, and caching opportunities, ranked by effort and impact
456457

457458
**Examples:**
458459

pm-ai-shipping/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pm-ai-shipping",
3-
"version": "2.0.0",
3+
"version": "2.1.0",
44
"description": "AI Shipping Kit — for PMs and founders accountable for AI-built code. Document a vibe-coded app, audit it for intended-vs-implemented security gaps and performance issues, and produce a reviewer-ready shipping packet.",
55
"author": {
66
"name": "Paweł Huryn",

0 commit comments

Comments
 (0)