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 🏷️
|
||||
|
||||
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 🏛️
|
||||
|
||||
@@ -58,6 +58,8 @@ flowchart LR
|
||||
git --> git_branch_gc[kvb-git-branch-gc]
|
||||
git --> git_orphans_find[kvb-git-orphans-find]
|
||||
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_push[kvb-git-push]
|
||||
git --> git_diff[kvb-git-diff]
|
||||
@@ -106,6 +108,7 @@ flowchart LR
|
||||
none --> transcend_all[kvb-transcend]
|
||||
none --> dream_all[kvb-dream]
|
||||
none --> optimize_all[kvb-optimize]
|
||||
none --> release_all[kvb-release]
|
||||
none --> coach_all[kvb-coach]
|
||||
coach --> coach_meditation[kvb-coach-meditation]
|
||||
```
|
||||
@@ -162,11 +165,15 @@ flowchart LR
|
||||
update_branches[kvb-git-update-branches]
|
||||
branch_gc[kvb-git-branch-gc]
|
||||
orphans_find[kvb-git-orphans-find]
|
||||
changelog[kvb-release-changelog]
|
||||
hotfix[kvb-git-hotfix]
|
||||
git_push[kvb-git-push]
|
||||
git_diff[kvb-git-diff]
|
||||
end
|
||||
subgraph cat_release [release]
|
||||
changelog[kvb-release-changelog]
|
||||
release_article[kvb-release-article]
|
||||
release_post[kvb-release-post]
|
||||
end
|
||||
subgraph cat_check [check]
|
||||
advocate[kvb-check-legal]
|
||||
medusa[kvb-check-security]
|
||||
@@ -220,6 +227,7 @@ flowchart LR
|
||||
transcend[kvb-transcend]
|
||||
dream[kvb-dream]
|
||||
optimize[kvb-optimize]
|
||||
release_all[kvb-release]
|
||||
coach[kvb-coach]
|
||||
end
|
||||
subgraph cat_coach [coach]
|
||||
@@ -364,6 +372,17 @@ flowchart LR
|
||||
autotune --> essence
|
||||
transcend --> skill_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 --> autotune
|
||||
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
|
||||
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
|
||||
|
||||
```bash
|
||||
@@ -60,16 +67,7 @@ empty sections left out.
|
||||
|
||||
## 4. 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.
|
||||
Follow `lint.md` next to this file; print nothing before 0 errors.
|
||||
|
||||
## 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-help",
|
||||
"kvb-optimize",
|
||||
"kvb-release",
|
||||
"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