Merge feature/kvb-release-skills into master
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
ad80c8a4af
8 files changed
+238
-12
No files matched your search
@@ -9,7 +9,7 @@ Compatible with Claude Code, Codex, Gemini CLI, Cursor, Copilot, Windsurf, Cline
|
|||||||
|
|
||||||
## Naming 🏷️
|
## Naming 🏷️
|
||||||
|
|
||||||
Every first-party skill is named `kvb-<category>-<name>` with one of these categories: `think`, `create`, `code`, `ci`, `git`, `release`, `check`, `org`, `agent`, `skill`, `coach`: `think` thinks without producing a file, `create` produces an artifact, `code` works on code, `ci` on pipelines, `git` on history and forges, `release` announces a release (changelog, article, posts), `check` analyses and reports findings, `org` coordinates agents, `agent` acts on one agent itself: its repository setup (code index, agent instructions) and its state (drain, suspend, resume), `skill` works on skills themselves, `coach` serves the operator's wellbeing. Only `kvb-cleanup`, `kvb-coach`, `kvb-help` and the collectors `kvb-dream`, `kvb-optimize` and `kvb-transcend` stand without a category. Names use `a-z`, `0-9` and single hyphens, at most 64 characters, and equal their directory name. `tests/test_naming.py` enforces all of it, including that the organigram below lists every skill under its category.
|
Every first-party skill is named `kvb-<category>-<name>` with one of these categories: `think`, `create`, `code`, `ci`, `git`, `release`, `check`, `org`, `agent`, `skill`, `coach`: `think` thinks without producing a file, `create` produces an artifact, `code` works on code, `ci` on pipelines, `git` on history and forges, `release` announces a release (changelog, article, posts), `check` analyses and reports findings, `org` coordinates agents, `agent` acts on one agent itself: its repository setup (code index, agent instructions) and its state (drain, suspend, resume), `skill` works on skills themselves, `coach` serves the operator's wellbeing. Only `kvb-cleanup`, `kvb-coach`, `kvb-help` and the collectors `kvb-dream`, `kvb-optimize`, `kvb-release` and `kvb-transcend` stand without a category. Names use `a-z`, `0-9` and single hyphens, at most 64 characters, and equal their directory name. `tests/test_naming.py` enforces all of it, including that the organigram below lists every skill under its category.
|
||||||
|
|
||||||
## Organigram 🏛️
|
## Organigram 🏛️
|
||||||
|
|
||||||
@@ -58,6 +58,8 @@ flowchart LR
|
|||||||
git --> git_branch_gc[kvb-git-branch-gc]
|
git --> git_branch_gc[kvb-git-branch-gc]
|
||||||
git --> git_orphans_find[kvb-git-orphans-find]
|
git --> git_orphans_find[kvb-git-orphans-find]
|
||||||
release --> release_changelog[kvb-release-changelog]
|
release --> release_changelog[kvb-release-changelog]
|
||||||
|
release --> release_article[kvb-release-article]
|
||||||
|
release --> release_post[kvb-release-post]
|
||||||
git --> git_hotfix[kvb-git-hotfix]
|
git --> git_hotfix[kvb-git-hotfix]
|
||||||
git --> git_push[kvb-git-push]
|
git --> git_push[kvb-git-push]
|
||||||
git --> git_diff[kvb-git-diff]
|
git --> git_diff[kvb-git-diff]
|
||||||
@@ -106,6 +108,7 @@ flowchart LR
|
|||||||
none --> transcend_all[kvb-transcend]
|
none --> transcend_all[kvb-transcend]
|
||||||
none --> dream_all[kvb-dream]
|
none --> dream_all[kvb-dream]
|
||||||
none --> optimize_all[kvb-optimize]
|
none --> optimize_all[kvb-optimize]
|
||||||
|
none --> release_all[kvb-release]
|
||||||
none --> coach_all[kvb-coach]
|
none --> coach_all[kvb-coach]
|
||||||
coach --> coach_meditation[kvb-coach-meditation]
|
coach --> coach_meditation[kvb-coach-meditation]
|
||||||
```
|
```
|
||||||
@@ -162,11 +165,15 @@ flowchart LR
|
|||||||
update_branches[kvb-git-update-branches]
|
update_branches[kvb-git-update-branches]
|
||||||
branch_gc[kvb-git-branch-gc]
|
branch_gc[kvb-git-branch-gc]
|
||||||
orphans_find[kvb-git-orphans-find]
|
orphans_find[kvb-git-orphans-find]
|
||||||
changelog[kvb-release-changelog]
|
|
||||||
hotfix[kvb-git-hotfix]
|
hotfix[kvb-git-hotfix]
|
||||||
git_push[kvb-git-push]
|
git_push[kvb-git-push]
|
||||||
git_diff[kvb-git-diff]
|
git_diff[kvb-git-diff]
|
||||||
end
|
end
|
||||||
|
subgraph cat_release [release]
|
||||||
|
changelog[kvb-release-changelog]
|
||||||
|
release_article[kvb-release-article]
|
||||||
|
release_post[kvb-release-post]
|
||||||
|
end
|
||||||
subgraph cat_check [check]
|
subgraph cat_check [check]
|
||||||
advocate[kvb-check-legal]
|
advocate[kvb-check-legal]
|
||||||
medusa[kvb-check-security]
|
medusa[kvb-check-security]
|
||||||
@@ -220,6 +227,7 @@ flowchart LR
|
|||||||
transcend[kvb-transcend]
|
transcend[kvb-transcend]
|
||||||
dream[kvb-dream]
|
dream[kvb-dream]
|
||||||
optimize[kvb-optimize]
|
optimize[kvb-optimize]
|
||||||
|
release_all[kvb-release]
|
||||||
coach[kvb-coach]
|
coach[kvb-coach]
|
||||||
end
|
end
|
||||||
subgraph cat_coach [coach]
|
subgraph cat_coach [coach]
|
||||||
@@ -364,6 +372,17 @@ flowchart LR
|
|||||||
autotune --> essence
|
autotune --> essence
|
||||||
transcend --> skill_transcend
|
transcend --> skill_transcend
|
||||||
transcend --> code_transcend
|
transcend --> code_transcend
|
||||||
|
release_all --> changelog
|
||||||
|
release_all --> release_article
|
||||||
|
release_all --> release_post
|
||||||
|
release_all --> active_listening
|
||||||
|
release_all --> brainstorm
|
||||||
|
release_article --> active_listening
|
||||||
|
release_article --> brainstorm
|
||||||
|
release_post --> active_listening
|
||||||
|
release_post --> brainstorm
|
||||||
|
changelog --> active_listening
|
||||||
|
changelog --> brainstorm
|
||||||
skill_transcend --> active_listening
|
skill_transcend --> active_listening
|
||||||
skill_transcend --> autotune
|
skill_transcend --> autotune
|
||||||
code_transcend --> active_listening
|
code_transcend --> active_listening
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
name: kvb-release-article
|
||||||
|
description: >
|
||||||
|
Write a release's long form from its brief: blog article, LinkedIn article,
|
||||||
|
newsletter.
|
||||||
|
---
|
||||||
|
|
||||||
|
Long form tells one audience why the release matters. Input: `brief.yml` and
|
||||||
|
`listen.yml` from the `kvb-release` skill, the channel, and the project's
|
||||||
|
channel facts (voice, length bounds, links, template) when a project skill
|
||||||
|
passes them. No project facts live here.
|
||||||
|
|
||||||
|
## 1. Listen
|
||||||
|
|
||||||
|
Run the `kvb-think-listen` skill only for what `listen.yml` leaves open for
|
||||||
|
this channel, such as the newsletter's audience or its call to action.
|
||||||
|
|
||||||
|
## 2. Brainstorm
|
||||||
|
|
||||||
|
Run the `kvb-think-brainstorm` skill on this channel's angle and opening hook
|
||||||
|
for its audience; keep the strongest.
|
||||||
|
|
||||||
|
## 3. Write, per language
|
||||||
|
|
||||||
|
1. Title: lead with the headline benefit in the reader's words; a project
|
||||||
|
skill may prefix the version.
|
||||||
|
2. Lead: what the reader can do now, in two or three sentences.
|
||||||
|
3. One section per benefit: the effect first, then the proof from its
|
||||||
|
changelog lines. A benefit the brief cannot prove is left out, never
|
||||||
|
filled with a placeholder.
|
||||||
|
4. Upgrade notes, when the brief has any, as plain steps.
|
||||||
|
5. Close with one call to action and the brief's links.
|
||||||
|
|
||||||
|
The audience shapes the words: customers read benefits, developers read the
|
||||||
|
mechanism and the upgrade path, investors read only what the brief proves.
|
||||||
|
|
||||||
|
## 4. Rules
|
||||||
|
|
||||||
|
- Every number, name and date comes from `brief.yml`.
|
||||||
|
- No em dashes, no superlative the brief does not prove.
|
||||||
|
- Stay inside the channel's length bounds. Without project bounds: a blog
|
||||||
|
article 600 to 1200 words, a LinkedIn article 400 to 900, a newsletter
|
||||||
|
250 to 500.
|
||||||
|
- Write `/tmp/release-<version>/<channel>/<language>.md`, or the format the
|
||||||
|
project's template names, and report each file's word count.
|
||||||
@@ -11,6 +11,13 @@ finding is unclear, and `special.md` for: no tag, a failed previous-tag
|
|||||||
workflow, a test- or CI-only release, no `markdownlint-cli2`, a pasted pkgmgr
|
workflow, a test- or CI-only release, no `markdownlint-cli2`, a pasted pkgmgr
|
||||||
rejection.
|
rejection.
|
||||||
|
|
||||||
|
## 0. Listen, then brainstorm
|
||||||
|
|
||||||
|
Run the `kvb-think-listen` skill for what only the operator knows (the tag,
|
||||||
|
the audience, the highlights, what to leave out), reusing `listen.yml` when
|
||||||
|
the `kvb-release` skill passed one; then the `kvb-think-brainstorm` skill on
|
||||||
|
how each change reads for its user, before step 1.
|
||||||
|
|
||||||
## 1. Find the range
|
## 1. Find the range
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -60,16 +67,7 @@ empty sections left out.
|
|||||||
|
|
||||||
## 4. Lint it the way pkgmgr does
|
## 4. Lint it the way pkgmgr does
|
||||||
|
|
||||||
1. Write `# Changelog`, blank line, `## [X.Y.Z] - <date>`, blank line, the
|
Follow `lint.md` next to this file; print nothing before 0 errors.
|
||||||
message to a scratch file outside the repository.
|
|
||||||
2. From the repository root, so its config applies:
|
|
||||||
`markdownlint-cli2 --no-globs - < <scratch-file>`.
|
|
||||||
3. A finding on line N is message line N - 4: shorten it and lint again;
|
|
||||||
print nothing before 0 errors.
|
|
||||||
4. Lint a scratch copy of CHANGELOG.md with the wrapped entry inserted below
|
|
||||||
its `# Changelog` line the same way. A finding in older entries that the
|
|
||||||
unmodified file lacks comes from the new entry: fix the message, never
|
|
||||||
the older entries.
|
|
||||||
|
|
||||||
## 5. Output
|
## 5. Output
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Lint it the way pkgmgr does
|
||||||
|
|
||||||
|
1. Write `# Changelog`, blank line, `## [X.Y.Z] - <date>`, blank line, the
|
||||||
|
message to a scratch file outside the repository.
|
||||||
|
2. From the repository root, so its config applies:
|
||||||
|
`markdownlint-cli2 --no-globs - < <scratch-file>`.
|
||||||
|
3. A finding on line N is message line N - 4: shorten it and lint again;
|
||||||
|
print nothing before 0 errors.
|
||||||
|
4. Lint a scratch copy of CHANGELOG.md with the wrapped entry inserted below
|
||||||
|
its `# Changelog` line the same way. A finding in older entries that the
|
||||||
|
unmodified file lacks comes from the new entry: fix the message, never
|
||||||
|
the older entries.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: kvb-release-post
|
||||||
|
description: >
|
||||||
|
Write a release's short posts from its brief: LinkedIn, Mastodon, WhatsApp,
|
||||||
|
Instagram.
|
||||||
|
---
|
||||||
|
|
||||||
|
A post earns one click: one hook, one link. Input: `brief.yml`
|
||||||
|
and `listen.yml` from the `kvb-release` skill, the channel, and the project's
|
||||||
|
channel facts (voice, hashtags, links, length bounds) when a project skill
|
||||||
|
passes them. No project facts live here.
|
||||||
|
|
||||||
|
## 1. Listen
|
||||||
|
|
||||||
|
Run the `kvb-think-listen` skill only for what `listen.yml` leaves open for
|
||||||
|
this channel, such as the language of a personal message or a mention.
|
||||||
|
|
||||||
|
## 2. Brainstorm
|
||||||
|
|
||||||
|
Run the `kvb-think-brainstorm` skill on hooks for this channel and audience:
|
||||||
|
at least three first lines; keep the strongest.
|
||||||
|
|
||||||
|
## 3. Write, per language
|
||||||
|
|
||||||
|
A project skill's structure (several benefit lines, an "Also new" line)
|
||||||
|
replaces this one:
|
||||||
|
|
||||||
|
1. Hook: the first line carries the headline benefit and works on its own
|
||||||
|
before "see more".
|
||||||
|
2. One benefit with its proof from the brief.
|
||||||
|
3. The brief's link, then the channel's hashtags or mentions.
|
||||||
|
|
||||||
|
## 4. Bounds without project facts
|
||||||
|
|
||||||
|
| Channel | Limit | Shown before "more" |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| LinkedIn post | 3000 characters | about 210 characters |
|
||||||
|
| Mastodon | 500 characters, a link counts 23 | everything |
|
||||||
|
| WhatsApp | aim under 700 characters | the first line |
|
||||||
|
| Instagram caption | 2200 characters, at most 30 hashtags | about 125 characters |
|
||||||
|
|
||||||
|
## 5. Rules
|
||||||
|
|
||||||
|
- Every number, name and date comes from `brief.yml`.
|
||||||
|
- No em dashes, no placeholder, no claim the brief does not prove.
|
||||||
|
- Write into `/tmp/release-<version>/` under the file name the project skill
|
||||||
|
sets, else `<channel>/<language>.txt`, and report each file's character
|
||||||
|
count against its limit.
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
name: kvb-release
|
||||||
|
description: >
|
||||||
|
Announce a release: write one brief since the last tag, hand each channel
|
||||||
|
its slice.
|
||||||
|
---
|
||||||
|
|
||||||
|
One brief per release, read once: every channel writes from the brief, never
|
||||||
|
from the changelog, so each fact lives in one place. This method holds no
|
||||||
|
project facts; voice, channels, length bounds, links and templates come from
|
||||||
|
the project's own `<prefix>-release-<channel>` skills.
|
||||||
|
|
||||||
|
## 1. Listen once
|
||||||
|
|
||||||
|
Run the `kvb-think-listen` skill once for what only the operator knows: the
|
||||||
|
tag, the languages (default English), the audiences, the highlights, what to
|
||||||
|
leave out and the channels wanted. Write the answers to
|
||||||
|
`/tmp/release-<version>/listen.yml` (`tag`, `languages`, `audience`,
|
||||||
|
`highlights`, `leave_out`, `channels`) and hand that file to every channel,
|
||||||
|
so the operator answers each question once.
|
||||||
|
|
||||||
|
## 2. Collect
|
||||||
|
|
||||||
|
- The range runs from the newest `vX.Y.Z` tag before the release to the
|
||||||
|
release commit: `git describe --tags --abbrev=0 --match
|
||||||
|
'v[0-9]*.[0-9]*.[0-9]*' <release tag>^` once the release is tagged, else
|
||||||
|
the same without `<release tag>^` from HEAD.
|
||||||
|
- Read the release's changelog entry once; without one, write it first with
|
||||||
|
the project's changelog skill, else the `kvb-release-changelog` skill.
|
||||||
|
|
||||||
|
## 3. Brief
|
||||||
|
|
||||||
|
Run the `kvb-think-brainstorm` skill on the headline benefit, then write
|
||||||
|
`/tmp/release-<version>/brief.yml`: version, date, languages, headline
|
||||||
|
benefit, three to five benefits each with the changelog lines that prove it,
|
||||||
|
upgrade notes and links. `languages` is the only answer the brief copies from
|
||||||
|
`listen.yml`: an answer steers what a channel stresses, it never backs a
|
||||||
|
number. Every number comes from the changelog or the repository.
|
||||||
|
|
||||||
|
## 4. Hand each channel its slice
|
||||||
|
|
||||||
|
Per channel in `listen.yml`, or every channel the project's release skills
|
||||||
|
cover when `listen.yml` names none: the project's
|
||||||
|
`<prefix>-release-<channel>` skill when its skills directory has one, else
|
||||||
|
the general method:
|
||||||
|
|
||||||
|
| Form | Channels | Method |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| long | blog, LinkedIn article, newsletter per audience | `kvb-release-article` |
|
||||||
|
| short | LinkedIn post, Mastodon, WhatsApp, Instagram | `kvb-release-post` |
|
||||||
|
| entry | changelog | `kvb-release-changelog` |
|
||||||
|
|
||||||
|
Each gets `brief.yml`, `listen.yml` and its channel, runs its own listen and
|
||||||
|
brainstorm, and writes into `/tmp/release-<version>/` under the file name
|
||||||
|
the project skill sets, else `<channel>/<language>.<ext>`.
|
||||||
|
|
||||||
|
## 5. Check
|
||||||
|
|
||||||
|
Fix in every file under `/tmp/release-<version>/`, subfolders included,
|
||||||
|
before the hand-over: a leftover placeholder (`TODO`, `XXX`, `<...>`), an
|
||||||
|
em dash, a length outside the channel's bounds, a number absent from
|
||||||
|
`brief.yml`.
|
||||||
|
|
||||||
|
## 6. Hand over
|
||||||
|
|
||||||
|
List every file with its channel, language and length. Never publish, post,
|
||||||
|
send, tag or release: the operator does.
|
||||||
@@ -17,6 +17,7 @@ UNCATEGORIZED = frozenset(
|
|||||||
"kvb-dream",
|
"kvb-dream",
|
||||||
"kvb-help",
|
"kvb-help",
|
||||||
"kvb-optimize",
|
"kvb-optimize",
|
||||||
|
"kvb-release",
|
||||||
"kvb-transcend",
|
"kvb-transcend",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
"""Pin the operator order of 2026-11-10: every release skill listens, then brainstorms, before it writes."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SKILLS = Path(__file__).resolve().parents[1] / "skills"
|
||||||
|
RELEASE = ["kvb-release", "kvb-release-article", "kvb-release-changelog", "kvb-release-post"]
|
||||||
|
|
||||||
|
|
||||||
|
def body(name: str) -> str:
|
||||||
|
return (SKILLS / name / "SKILL.md").read_text(encoding="utf-8").split("---", 2)[2]
|
||||||
|
|
||||||
|
|
||||||
|
class TestReleaseSkills(unittest.TestCase):
|
||||||
|
def test_the_release_skills_are_exactly_the_four_methods(self):
|
||||||
|
self.assertEqual(sorted(path.parent.name for path in SKILLS.glob("kvb-release*/SKILL.md")), RELEASE)
|
||||||
|
|
||||||
|
def test_every_release_skill_listens_before_it_brainstorms(self):
|
||||||
|
for name in RELEASE:
|
||||||
|
with self.subTest(skill=name):
|
||||||
|
text = body(name)
|
||||||
|
listen, brainstorm = text.find("kvb-think-listen"), text.find("kvb-think-brainstorm")
|
||||||
|
self.assertGreaterEqual(listen, 0)
|
||||||
|
self.assertGreater(brainstorm, listen)
|
||||||
|
|
||||||
|
def test_the_collector_hands_every_form_to_its_method(self):
|
||||||
|
text = body("kvb-release")
|
||||||
|
for method in RELEASE[1:]:
|
||||||
|
with self.subTest(method=method):
|
||||||
|
self.assertIn(f"`{method}`", text)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in new issue
Block a user