send-it
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesesend-it
send-it
Bundle uncommitted work into atomic commits (via the
skill), run the change-gated lint
, author or update the dated
entry (via the
skill), compose a Conventional Commits PR title (the squash subject
release-please reads to decide the version bump), push the branch, open or update
a pull request against the base branch, and transition any linked Linear issues to
In Review (via the skill).
commitpreflightchangelog/<ts>-<slug>.mdchangeloglinear-syncThis skill is the single source of truth for the ship flow. It is a thin
orchestrator: it owns only the glue no sibling skill does — the branch guard,
worktree resolution, the release-type decision (by category), PR-title
composition, push, and the PR — and delegates the rest:
- Commit → the skill (classify in-scope vs out-of-scope, atomic Conventional Commits, out-of-scope guard).
commit - Lint gate → the skill (change-gated; no-ops when nothing lint-relevant changed).
preflight - Changelog → the skill (author/update + validate; an entry for every PR, skipped entirely only when
changelogsetsconfig.json).changelog: false - Linear In Review → the skill (resolve state by team name, idempotent transition).
linear-sync
The delegated skills auto-detect their own scope, so monorepo features
(per-workspace ESLint fan-out, changelog ) no-op cleanly in a
single-package repo. send-it configures nothing about them.
affected_packagesInstall the delegated skills alongside. This bundle invokes and links its siblings by relative path (send-it,../commit/SKILL.md,../preflight/SKILL.md,../changelog/SKILL.md), so a../linear-sync/SKILL.md-only install leaves the commit, lint, changelog, and Linear steps unavailable and those links dangling. Install them together:--skill send-itbashnpx skills add https://github.com/acme-skunkworks/agent-skills \ --skill send-it --skill commit --skill preflight --skill changelog --skill linear-sync \ --agent claude-code --agent cursor --copy
This flow intentionally does not run typecheck, tests, or format checks — CI
handles those. The only gate it runs is the change-gated lint.
preflight将未提交的工作打包为原子提交(通过
技能),运行变更触发的lint
预检(),编写或更新带日期的
条目(通过
技能),生成符合Conventional Commits规范的PR标题(release-please会读取该合并提交主题来决定版本升级幅度),推送分支,针对基准分支创建或更新
PR,并将所有关联的Linear工单切换至In Review状态(通过技能)。
commitpreflightchangelog/<ts>-<slug>.mdchangeloglinear-sync该技能是代码交付流程的唯一可信来源。它是一个轻量协调器:仅负责其他兄弟技能未覆盖的衔接工作——分支防护、工作区解析、发布类型决策(基于分类)、PR标题生成、代码推送和PR管理——其余工作均委托给其他技能:
- 提交 → 技能(区分范围内与范围外变更、生成符合Conventional Commits规范的原子提交、范围外变更防护)。
commit - Lint检查 → 技能(变更触发;当无lint相关变更时自动跳过)。
preflight - 变更日志 → 技能(编写/更新 + 验证;每个PR都对应一条条目,仅当
changelog中设置config.json时才完全跳过)。changelog: false - Linear工单状态同步至In Review → 技能(按团队名称解析状态、幂等状态转换)。
linear-sync
被委托的技能会自动检测自身作用范围,因此单体仓库的特性(如每个工作区的ESLint扩展、变更日志的)在单包仓库中会自动无操作运行。send-it不会对这些技能进行任何配置。
affected_packages请将send-it与被委托的技能一同安装。 该工具会通过相对路径(、../commit/SKILL.md、../preflight/SKILL.md、../changelog/SKILL.md)调用并关联其兄弟技能,因此仅安装../linear-sync/SKILL.md会导致提交、lint、变更日志和Linear同步步骤无法使用,且这些关联会失效。请一起安装它们:--skill send-itbashnpx skills add https://github.com/acme-skunkworks/agent-skills \ --skill send-it --skill commit --skill preflight --skill changelog --skill linear-sync \ --agent claude-code --agent cursor --copy
本流程刻意不运行类型检查、测试或格式检查——这些由CI负责。它仅运行变更触发的 lint检查。
preflightConfiguration
配置
A few knobs live in beside this file; edit your
copied to match the consuming repo (a neutral
ships as a template):
config.jsonconfig.jsonconfig.example.json| Key | Meaning | Default |
|---|---|---|
| The trunk the branch diff is taken against ( | |
| Path prefixes that make up the published surface — a documentation hint for reviewers, not the release decision (A-598; see Step 6). Release-type is decided by the change's semantic category, so these no longer gate the title. Kept for the optional publish-surface cross-check note. | |
| | |
| Whether to author a dated | |
| Enables the per-bundle version-bump check (Step 6) for repos that ship many independently-versioned skill bundles. An object | unset (disabled) |
The team name, issue-ID prefixes, and workspace slug are not configured here —
they live in the and skills' own files,
read by the delegated steps.
linear-syncchangelogconfig.jsonChangelog scope (was). send-it authors a dated entry for every PR — the "record everything, filter later" model. Release notes come from filtering the changelog to the version-stamped (release-triggering) entries at release time, not from gating authoring at write time. ThechangelogScopeknob (added in 0.4.0) is gone (A-600); only thechangelogScopemaster switch remains.changelog: true|false
本文件旁的中有一些可配置项;请编辑你复制的以匹配目标仓库(附带一个中性的作为模板):
config.jsonconfig.jsonconfig.example.json| 键名 | 含义 | 默认值 |
|---|---|---|
| 分支差异对比的基准主干( | |
| 构成发布内容的路径前缀——供评审者参考的文档提示,并非发布决策依据(A-598;见步骤6)。发布类型由变更的语义分类决定,因此这些路径不再作为标题的判断依据。保留该配置是为了可选的发布内容交叉检查备注。 | |
| | |
| 是否编写带日期的 | |
| 为发布多个独立版本技能包的仓库启用每个包的版本升级检查(步骤6)。该配置为一个对象 | 未设置(禁用) |
团队名称、工单ID前缀和工作区别名不在这里配置——它们位于和技能各自的文件中,由被委托的步骤读取。
linear-syncchangelogconfig.json变更日志范围(原)。 send-it会为每个PR编写一条带日期的条目——采用“记录所有内容,后续过滤”的模式。发布说明来自于在发布时将变更日志过滤为带有版本标记(触发发布)的条目,而非在编写时限制内容。changelogScope配置项(0.4.0版本新增)已移除(A-600);仅保留changelogScope这个总开关。changelog: true|false
Prerequisites
前置条件
- CLI installed and authenticated (
gh).gh auth status - The sibling skills (,
commit,preflight,changelog) installed.linear-sync
- 已安装并认证CLI(运行
gh验证)。gh auth status - 已安装兄弟技能(、
commit、preflight、changelog)。linear-sync
Process
流程
Step 0: Worktree resolution (only if --worktree=
is set)
--worktree=步骤0:工作区解析(仅当设置--worktree=
时执行)
--worktree=If was passed, resolve and into that worktree
before any other step runs. Skip this step otherwise.
--worktree=<branch-or-path>cd-
Runto list worktrees with their paths and branches.
git worktree list --porcelain -
Resolve the argument:
- Absolute path (starts with ): match against the
/field.worktree <path> - Otherwise: treat as a branch name and match against the
field.
branch refs/heads/<name>
- Absolute path (starts with
-
No match — exit immediately with:.
No worktree found for <arg>. Available: <comma-separated paths> -
Match —into the resolved worktree path. The
cdpersists for the rest of the workflow, so all subsequentcwdandgitcalls operate on the worktree.gh -
Ensure dependencies are present. A freshly-created worktree has no. If it is absent, run
node_modulesnow — before any step that invokes a bundled script or a validator — sopnpm install --frozen-lockfileis self-sufficient:--worktreebash[ -d node_modules ] || pnpm install --frozen-lockfile -
Continue to Step 1.
This step does nothing when is omitted — no-arg send-it keeps working
unchanged from whatever directory the session is in.
--worktree如果传入了,则在执行其他步骤前先解析并切换到该工作区。否则跳过此步骤。
--worktree=<branch-or-path>-
运行列出所有工作区的路径和分支。
git worktree list --porcelain -
解析参数:
- 绝对路径(以开头):匹配
/字段。worktree <path> - 其他情况:视为分支名称,匹配字段。
branch refs/heads/<name>
- 绝对路径(以
-
无匹配项——立即退出并提示:。
No worktree found for <arg>. Available: <逗号分隔的路径> -
匹配成功——切换到解析后的工作区路径。当前工作目录会在后续流程中保持不变,因此所有后续的和
git命令都会在该工作区执行。gh -
确保依赖已安装。新创建的工作区没有。如果不存在该目录,则立即运行
node_modules——在执行任何调用打包脚本或验证器的步骤之前,确保pnpm install --frozen-lockfile模式可以独立运行:--worktreebash[ -d node_modules ] || pnpm install --frozen-lockfile -
继续执行步骤1。
当省略时,此步骤不执行——无参数的send-it会在当前会话所在的目录中正常运行。
--worktreeStep 1: Branch guard
步骤1:分支防护
- Get the current branch: .
git branch --show-current - If on the base branch (from
baseBranch; defaultconfig.json):main- Run . If clean, exit with: "Nothing to ship from the base branch. Create a feature branch first."
git status --porcelain - If there are uncommitted changes:
- Inspect the diff (and
git diff) and the changed file paths.git diff --cached - Derive a short kebab-case slug summarising the change (~3 words, lowercase,
max ~40 chars). Examples: ,
add-readme-section.fix-config-typo - Branch name resolution (in order):
- — use as-is.
--branch=<name> - — use
--issue=<ID>lower-cased (e.g.<ID>-<slug>), matching Linear'sa-7-as-acquired.gitBranchName - Otherwise — just (no
<slug>prefix).wip/
- If the chosen branch already exists locally or on , append
origin,-2, … until unused.-3 - Run to move the working tree onto it.
git checkout -b <branch> - Inform the user: "Was on the base branch with uncommitted changes; created
and continuing."
<branch>
- Inspect the diff (
- Continue with the rest of the workflow on the new branch.
- Run
- If on a feature branch: continue.
- 获取当前分支:。
git branch --show-current - 如果当前在基准分支(中的
config.json;默认baseBranch):main- 运行。如果工作区干净,则退出并提示:"Nothing to ship from the base branch. Create a feature branch first."
git status --porcelain - 如果存在未提交的变更:
- 检查差异(和
git diff)以及变更文件的路径。git diff --cached - 生成一个简短的烤肉串格式(kebab-case)别名,总结变更内容(约3个单词,小写,最多约40个字符)。示例:、
add-readme-section。fix-config-typo - 分支名称解析顺序:
- ——直接使用该名称。
--branch=<name> - ——使用
--issue=<ID>(全小写,例如<ID>-<slug>),与Linear的a-7-as-acquired格式匹配。gitBranchName - 其他情况——仅使用(无
<slug>前缀)。wip/
- 如果选定的分支已在本地或上存在,则追加
origin、-2……直到找到未使用的名称。-3 - 运行将工作区切换到新分支。
git checkout -b <branch> - 通知用户:"Was on the base branch with uncommitted changes; created
and continuing."
<branch>
- 检查差异(
- 在新分支上继续执行后续流程。
- 运行
- 如果当前在功能分支:继续执行。
Step 2: Refresh lockfile if package.json
drifted
package.json步骤2:如果package.json
发生变更则更新锁文件
package.jsonSkip this step if no was touched on the branch.
package.json-
. If empty, skip.
git diff --name-only origin/<base>...HEAD | grep -E '(^|/)package\.json$' -
Run. If it succeeds, the lockfile is already in sync — continue.
pnpm install --frozen-lockfile -
If it fails, runto update the lockfile.
pnpm install -
If the lockfile changed, stage and commit it before any other commits go in:bash
git add pnpm-lock.yaml git commit -m "chore: update lockfile"
This keeps CI's install green. (Skip silently in repos that
don't use pnpm.)
--frozen-lockfile如果分支上未修改,则跳过此步骤。
package.json-
运行。如果结果为空,则跳过。
git diff --name-only origin/<base>...HEAD | grep -E '(^|/)package\.json$' -
运行。如果执行成功,说明锁文件已同步——继续执行。
pnpm install --frozen-lockfile -
如果执行失败,运行更新锁文件。
pnpm install -
如果锁文件发生变更,则在提交其他内容之前暂存并提交锁文件:bash
git add pnpm-lock.yaml git commit -m "chore: update lockfile"
这可以确保CI的安装步骤正常通过。(在不使用pnpm的仓库中会自动静默跳过。)
--frozen-lockfileStep 3: Commit uncommitted changes — delegate to the commit
skill
commit步骤3:提交未提交的变更——委托给commit
技能
commitsend-it is the all-in-one finisher: whatever's uncommitted should be committed
before the changelog/PR work begins — but only what belongs to this branch.
Follow the skill to do this: classify uncommitted
files in-scope vs out-of-scope against the merge base (), show a staging plan flagging any out-of-scope files (never ; stray files from another branch/worktree are never staged silently), and
create logical atomic Conventional Commits (type + optional scope +
British-English body; / for breaking changes). If clean,
skip this step. Direct the skill to classify against this send-it
run's resolved base — is (from ), or
when passed — not the skill's own , which
differs on a run (the stacked-PR case). The scope classification and the
out-of-scope guard must be computed against the same base send-it ships against,
or a stacked PR would mis-classify files.
commitgit merge-base HEAD origin/<base>git add -A!BREAKING CHANGE:commit<base>baseBranchconfig.json--basecommitconfig.jsonbaseBranch--baseThe Conventional-Commit types this step writes are the input to Step 6's release
decision ( reads them back out of the commits), so the honest
types and / markers matter.
derive-bump.mjs!BREAKING CHANGE:This delegation covers only the initial commit of uncommitted work. send-it's own
later, targeted commits stay here: the lockfile refresh (Step 2), the optional
bundle-version bump (Step 6), and the changelog entry (Step 8).
send-it是一站式收尾工具:在开始变更日志/PR工作之前,所有未提交的内容都应该被提交——但仅提交属于当前分支的内容。
按照技能的流程执行:根据合并基准()区分未提交文件的范围内与范围外,展示暂存计划并标记任何范围外的文件(绝不会自动执行;绝不会静默暂存来自其他分支/工作区的无关文件),并创建符合逻辑的原子Conventional Commits(类型 + 可选范围 + 英式英文描述;使用 / 标记破坏性变更)。如果工作区干净,则跳过此步骤。指定技能根据本次send-it运行解析出的基准进行分类——是中的,或者传入的参数——而非技能自身中的,因为在使用的场景中(堆叠PR),后者可能不同。范围分类和范围外防护必须与send-it交付时使用的基准保持一致,否则堆叠PR会错误分类文件。
commitgit merge-base HEAD origin/<base>git add -A!BREAKING CHANGE:commit<base>config.jsonbaseBranch--basecommitconfig.jsonbaseBranch--base此步骤生成的Conventional Commit类型是步骤6中发布决策的输入(会从提交记录中读取这些类型),因此准确的类型和 / 标记非常重要。
derive-bump.mjs!BREAKING CHANGE:此委托仅覆盖未提交工作的初始提交。send-it自身后续的针对性提交仍在此处处理:锁文件更新(步骤2)、可选的包版本升级(步骤6)和变更日志条目(步骤8)。
Step 4: Fetch the base branch and confirm there's something to ship
步骤4:拉取基准分支并确认有内容可交付
bash
git fetch origin <base>If is empty, exit with: "No commits ahead of the base
branch. Nothing to ship."
git log origin/<base>..HEADbash
git fetch origin <base>如果为空,则退出并提示:"No commits ahead of the base
branch. Nothing to ship."
git log origin/<base>..HEADStep 5: Lint gate — delegate to the preflight
skill
preflight步骤5:Lint检查——委托给preflight
技能
preflightbypasses this whole step. Print a clear--skip-preflightwarning and jump to Step 6. Use it only when the gate misfires; CI still runs the repo's real linting.⚠️ lint gate bypassed (--skip-preflight)
Run the change-gated lint preflight, following the
skill:
preflightbash
node skills/preflight/scripts/preflight.mjsAct on its exit-code contract, reading to interpret a
non-zero exit:
.preflight-summary.json- Exit 0 — pass. No introduced violations; continue.
- Exit 1 with — introduced violations (blocking). Run
violations.introducedCount > 0, re-run preflight, and repeat until introduced violations clear. Commit the fixes (anode skills/preflight/scripts/lint-fix.mjs/style:commit, or fold into the relevant Step 3 commit if not yet pushed) before continuing.fix: - Exit 1 with and
introducedCount == 0non-empty — a linter could not run (its binary is absent), not a real violation. This is expected in a repo that doesn't use that toolchain (e.g. a docs/skills repo with no ESLint or markdownlint installed). Treat it as a skip, not a block: warn thatresults.failedLinterswas unavailable and continue. The repo's own CI owns whatever linting it actually runs.<linter> - Exit 2 — pre-existing violations only. Not introduced by this branch — do not block shipping. Surface them and continue (optionally offer a debt issue per the preflight skill).
Preflight is change-gated: it lints only the categories the branch touched, so
it no-ops when nothing lint-relevant changed. Skip this step entirely only if
isn't installed.
preflight****会跳过整个步骤。打印清晰的--skip-preflight警告并跳至步骤6。仅当检查出现异常时使用;CI仍会运行仓库的完整lint检查。⚠️ lint gate bypassed (--skip-preflight)
按照技能的流程运行变更触发的lint预检:
preflightbash
node skills/preflight/scripts/preflight.mjs根据其退出码约定执行操作,读取来解释非零退出码:
.preflight-summary.json- 退出码0——通过。未引入新的违规;继续执行。
- 退出码1且——引入新违规(阻塞)。运行
violations.introducedCount > 0,重新运行预检,重复直到新违规被修复。在继续执行之前提交修复内容(使用node skills/preflight/scripts/lint-fix.mjs/style:类型的提交,或者如果尚未推送则合并到步骤3的相关提交中)。fix: - 退出码1且且
introducedCount == 0非空——linter无法运行(其二进制文件不存在),并非真正的违规。这在不使用该工具链的仓库中是预期情况(例如没有安装ESLint或markdownlint的文档/技能仓库)。将其视为跳过而非阻塞:警告results.failedLinters不可用并继续执行。仓库自身的CI负责运行实际需要的lint检查。<linter> - 退出码2——仅存在预先违规。并非当前分支引入的——不阻塞交付。提示这些违规并继续执行(可根据preflight技能的建议选择创建技术债务工单)。
预检是变更触发的:仅检查分支涉及的分类,因此当无lint相关变更时会自动无操作运行。仅当未安装时才完全跳过此步骤。
preflightStep 6: Decide release-type by category and compose the Conventional Commits PR title
步骤6:根据分类决定发布类型并生成符合Conventional Commits规范的PR标题
Versioning is driven by release-please
reading Conventional Commits. The repo squash-merges, so the squash subject is
the PR title — and that single conventional title is what release-please parses to
decide the bump. send-it composes a correct conventional title and writes the dated
changelog entry (for every PR — see Step 7). It does not bump versions, write any
, or tag.
CHANGELOG.mdRelease-type is decided by the change's semantic category — the Conventional-Commit
type of the work send-it itself committed — not by which paths the diff touches
(A-598). A docs-only edit is (no release) even when it lives under a published
path like ; a is a release wherever its files sit. (Earlier versions
keyed this off , which mis-titled a docs edit inside a published path
as / and cut a spurious release.)
docs:skills/feat:shippablePathsfeat:fix:-
Derive the slug, body, type, and category from the branch commits via the bundled helper (zero-dep — no tsx):bash
node skills/send-it/scripts/derive-bump.mjsIt prints JSON::{ "slug", "bump", "body", "type", "breaking", "category", "releaseTriggering" }- — the Conventional-Commit type of the lead commit (
type/feat/fix/perf/docs/refactor/chore/…); this is the PR-title prefix.ci - —
breakingif any commit carries atrueor a!trailer.BREAKING CHANGE: - — the dated changelog
categoryenum value (category→feat,feature→fix,fix→perf,perf→docs,docs→refactor, everything else →refactor).chore - —
releaseTriggeringifftrueorbreaking. This is the release decision:type ∈ {feat, fix, perf}cuts a release,truedoes not.false - —
bump/major/minor, the release magnitude whenpatch(areleaseTriggering/BREAKING CHANGE:→ major; lead!→ minor; else patch). Ignored whenfeat:isreleaseTriggering.false
-
(Advisory) publish-surface cross-check./
shippablePathsinshippableManifestKeysare a documentation hint of the published surface — they do not decide release-type any more. Optionally sanity-check the category against them: ifconfig.jsonisreleaseTriggeringbut the diff (true) touches nogit diff --name-only origin/<base>...HEADprefix (nor ashippablePathskey inshippableManifestKeys), note it in the PR body so a reviewer can confirm the release was intended — and likewise if a change touching a published path ispackage.json. This is a soft note only; never let it override the category decision or block.releaseTriggering: false -
Check per-bundle version bumps — only whensets
config.json(multi-artefact repos; skip this step entirely when it's unset). Each skill bundle carries its own version in itsbundleVersioning+package.json, bumped by hand and decoupled from the repo release. CI enforces that the two agree, but nothing enforces they were bumped when the bundle's content changed — so an edited bundle can ship with a stale version label. Close that gap:SKILL.md metadata.versionbashnode skills/send-it/scripts/check-skill-bumps.mjsIt prints. For each{ "configured", "unbumped": [{ name, currentVersion, suggestedBump, suggestedVersion, manifestPath, skillPath }], "bumped" }entry, surface the proposal and apply it on confirmation:unbumpedchanged but its version is stillskills/<name>. Suggested bump:<currentVersion>→<suggestedBump>(matches the PR-title bump). Apply? (yes / no / patch / minor / major)<suggestedVersion>On(or an explicit level), edit bothyes(manifestPath) andversion(skillPath) to the chosen version — in lockstep, so the parity invariant CI checks still holds — then stage and commit just those two files:metadata.version. Ongit commit -m "chore(<name>): release <name>@<version>", leave it and continue. Underno, print the proposal and edit nothing.--dry-run -
Compose the PR title as a single Conventional Commits subject — this is the release-please bump signal and is enforced by CI's PR-title lint. Ifwas passed, use it verbatim (still run
--titleabove for the changelogderive-bump, and warn — don't block — if the supplied type contradicts the derivedcategory/type). Otherwise build it straight from the derived fields:releaseTriggering- Prefix = (add a scope when one is obvious, e.g.
type), plusfeat(<scope>):when!— sobreaking,feat: <body>,fix: <body>,perf: <body>,docs: <body>,refactor: <body>,chore: <body>, etc.feat!: <body> - Release-triggering () → the prefix is already a release type (
releaseTriggering: true/feat/fix, or anyperf); release-please cuts the bump from it. Add the scope; that's it.! - Non-release () → the prefix is a non-release type (
releaseTriggering: false/docs/refactor/chore/ci/build/test); release-please cuts nothing.style
⚠️ The PR title is the version. A mistyped prefix silently ships the wrong semver — aon a docs PR cuts a needless release; afeat:on a real fix ships nothing. There is no changeset file to cross-check against: the title is the declaration. It comes straight from the change's semantic category (the commit types) — keep the commit types honest and the title follows.chore:WhenisreleaseTriggering, notefalsein the PR body so reviewers can confirm the non-release type was intentional.no release (<type>-only) - Prefix =
版本控制由release-please通过读取Conventional Commits来驱动。仓库采用合并提交(squash merge)方式,因此合并提交主题即为PR标题——这个符合规范的标题是release-please用来决定版本升级幅度的依据。send-it会生成正确的规范标题并编写带日期的变更日志条目(每个PR都有——见步骤7)。它不会升级版本、编写或打标签。
CHANGELOG.md发布类型由变更的语义分类——即send-it自身提交的工作的Conventional Commit类型——而非差异涉及的路径决定(A-598)。仅修改文档的提交属于类型(不触发发布),即使它位于这样的发布路径下;类型的提交无论文件位置如何都会触发发布。(早期版本基于判断,这会导致发布路径内的文档编辑被错误标记为/类型并触发不必要的发布。)
docs:skills/feat:shippablePathsfeat:fix:-
通过内置工具从分支提交中提取别名、描述、类型和分类(零依赖——无需tsx):bash
node skills/send-it/scripts/derive-bump.mjs该工具会输出JSON::{ "slug", "bump", "body", "type", "breaking", "category", "releaseTriggering" }- ——首个提交的Conventional Commit类型(
type/feat/fix/perf/docs/refactor/chore/…);这是PR标题的前缀。ci - ——如果任何提交带有
breaking或!标记,则为BREAKING CHANGE:。true - ——带日期的变更日志的
category枚举值(category→feat、feature→fix、fix→perf、perf→docs、docs→refactor,其他类型→refactor)。chore - ——当
releaseTriggering为breaking或true时为type ∈ {feat, fix, perf}。这是发布决策:true会触发发布,true则不会。false - ——
bump/major/minor,当patch为releaseTriggering时的发布幅度(true/BREAKING CHANGE:→major;首个提交为!→minor;否则为patch)。当feat:为releaseTriggering时忽略该值。false
-
(参考性)发布内容交叉检查。中的
config.json/shippablePaths是发布内容的文档提示——它们不再决定发布类型。可选择将分类与这些配置进行 sanity 检查:如果shippableManifestKeys为releaseTriggering但分支差异(true)未涉及任何git diff --name-only origin/<base>...HEAD前缀(也未修改shippablePaths中的package.json键),则在PR正文中添加备注,供评审者确认是否确实需要发布——反之,如果涉及发布路径的变更被标记为shippableManifestKeys,也添加备注。这仅为软提示;绝不会覆盖分类决策或阻塞流程。releaseTriggering: false -
检查每个包的版本升级——仅当中设置了
config.json时执行(多产物仓库;未设置时完全跳过此步骤)。每个技能包在其bundleVersioning+package.json中都有自己的版本,需手动升级且与仓库发布版本解耦。CI会强制这两个版本保持一致,但不会强制在包内容变更时升级版本——因此编辑后的包可能会带着过时的版本标签交付。此步骤填补了这个空白:SKILL.md metadata.versionbashnode skills/send-it/scripts/check-skill-bumps.mjs该工具会输出。对于每个{ "configured", "unbumped": [{ name, currentVersion, suggestedBump, suggestedVersion, manifestPath, skillPath }], "bumped" }条目,展示升级建议并在确认后应用:unbumped已变更但版本仍为skills/<name>。建议升级幅度:<currentVersion>→<suggestedBump>(与PR标题的升级幅度匹配)。 是否应用?(yes / no / patch / minor / major)<suggestedVersion>当选择(或明确指定升级幅度)时,同步编辑yes中的manifestPath和version中的skillPath——确保两者一致,以便CI检查的一致性约束仍然有效——然后暂存并提交这两个文件:metadata.version。选择git commit -m "chore(<name>): release <name>@<version>"则保留当前版本并继续执行。在no模式下,仅打印建议而不进行任何编辑。--dry-run -
生成PR标题为符合Conventional Commits规范的单行主题——这是release-please的版本升级信号,且会被CI的PR标题lint检查强制要求。如果传入了参数,则直接使用该标题(仍需运行上述
--title来获取变更日志的derive-bump,如果传入的类型与推导的category/type不一致,会警告但不阻塞)。否则直接根据推导的字段生成标题:releaseTriggering- 前缀 = (当范围明确时添加范围,例如
type),如果feat(<scope>):为breaking则追加true——例如!、feat: <body>、fix: <body>、perf: <body>、docs: <body>、refactor: <body>、chore: <body>等。feat!: <body> - 触发发布()→ 前缀已经是发布类型(
releaseTriggering: true/feat/fix,或带有perf的任何类型);release-please会根据该前缀决定升级幅度。添加范围即可。! - 不触发发布()→ 前缀为非发布类型(
releaseTriggering: false/docs/refactor/chore/ci/build/test);release-please不会触发任何发布。style
⚠️ PR标题即版本。 错误的前缀会静默导致错误的语义化版本——将文档PR标记为会触发不必要的发布;将实际修复标记为feat:则不会触发任何发布。没有变更集文件可以交叉验证:标题就是声明。它直接来自变更的语义分类(提交类型)——确保提交类型准确,标题自然会正确。chore:当为releaseTriggering时,在PR正文中添加false备注,供评审者确认非发布类型是否符合预期。no release (<type>-only) - 前缀 =
Step 7: Author or update the dated changelog entry — delegate to the changelog
skill
changelog步骤7:编写或更新带日期的变更日志条目——委托给changelog
技能
changelogDisabled entirely? Ifsetsconfig.json, skip Steps 7 and 8 completely — author nothing, run nochangelog: falsescripts, make nochangelogcommit — and note "changelog step disabled (no changelog flow in this repo)" in the run summary. This is for repos with nodocs(changelog)directory and nochangelog/skill installed; the category decision from Step 6 still drives the PR title. Whenchangelogis unset orchangelog, always author an entry (thetrueknob was removed — A-600).changelogScopeAn entry for every PR. send-it authors a datedentry for every PR, release-triggering or not — the "record everything, filter later" model. The dated changelog is the full record of merged work; release notes filter it to the version-stamped (release-triggering) entries at release time, so a non-release entry simply carries nochangelog/.versionis the only thing that suppresses authoring.changelog: false
Follow the skill to author or update the entry:
changelog-
Detect an existing entry for this branch (by thefrontmatter field) → update vs create. On update, preserve the filename and
branch.created_at -
Write/refresh(the
changelog/<YYYYMMDD-HHMMSS>-<slug>.mdfrom Step 6), deriving<slug>/title/release_notefrom the branch. Setissuesandcategorystraight frombreaking's output (Step 6):derive-bumpis itscategoryfield (category/feature/fix/perf/docs/refactor— the changelog enum), andchoreis itsbreakingflag. For a non-release entry (breaking),releaseTriggering: falsemay be blank when there's no user-facing impact.release_noteLeave the post-merge fields (,merged_at,commit,pr,merge_strategy) andstatsas blank placeholders — the release step finalises them (a non-release entry keepsversionblank, as no release is cut for it). This includesversion: no step here writes it back after the PR opens; the release/enrich step resolves it post-merge from the entry'spr.branch: -
Run the enrichment scripts:then
node skills/changelog/scripts/set-affected-packages.mjs.node skills/changelog/scripts/add-links.mjs -
Validate:. It must pass before committing — if it fails, surface the error and abort; don't auto-fix.
node skills/changelog/scripts/validate-changelog.mjs
完全禁用? 如果中设置config.json,则完全跳过步骤7和8——不编写任何内容,不运行任何changelog: false脚本,不创建changelog提交——并在运行摘要中添加备注"changelog step disabled (no changelog flow in this repo)"。这适用于无docs(changelog)目录且未安装changelog/技能的仓库;步骤6的分类决策仍会驱动PR标题。当changelog未设置或为changelog时,始终编写条目(true配置项已移除——A-600)。changelogScope每个PR对应一条条目。 send-it会为每个PR编写一条带日期的条目,无论是否触发发布——采用“记录所有内容,后续过滤”的模式。带日期的变更日志是合并工作的完整记录;发布说明来自于在发布时将变更日志过滤为带有版本标记(触发发布)的条目,因此不触发发布的条目仅保留changelog/为空。只有version会抑制条目编写。changelog: false
按照技能的流程编写或更新条目:
changelog-
检测当前分支是否已有对应的条目(通过前置字段)→ 决定是更新还是创建。如果是更新,保留文件名和
branch字段。created_at -
编写/刷新(
changelog/<YYYYMMDD-HHMMSS>-<slug>.md来自步骤6),从分支中提取<slug>/title/release_note。直接从issues的输出(步骤6)中设置derive-bump和category:breaking为其category字段(category/feature/fix/perf/docs/refactor——变更日志枚举值),chore为其breaking标记。对于不触发发布的条目(breaking),如果没有用户可见的影响,releaseTriggering: false可以为空。release_note保留合并后的字段(、merged_at、commit、pr、merge_strategy)和stats为空占位符——发布步骤会完成这些字段的填充(不触发发布的条目会保持version为空,因为不会为其创建发布)。这包括version字段:PR创建后不会在此步骤回填该字段;发布/丰富步骤会在合并后根据条目的pr字段解析该值。branch: -
运行丰富脚本:,然后运行
node skills/changelog/scripts/set-affected-packages.mjs。node skills/changelog/scripts/add-links.mjs -
验证:运行。必须验证通过才能提交——如果失败,提示错误并终止流程;不自动修复。
node skills/changelog/scripts/validate-changelog.mjs
Step 8: Commit the changelog entry and push
步骤8:提交变更日志条目并推送
If a entry was written in Step 7 (i.e. is not ), commit
only that file:
changelog/changelogfalsebash
git add changelog/<YYYYMMDD-HHMMSS>-<slug>.md
git commit -m "docs(changelog): <one-line summary>"Then push the branch:
bash
git push -u origin <branch>如果步骤7中编写了条目(即不为),则仅提交该文件:
changelog/changelogfalsebash
git add changelog/<YYYYMMDD-HHMMSS>-<slug>.md
git commit -m "docs(changelog): <单行摘要>"然后推送分支:
bash
git push -u origin <branch>Step 9: Create or update the PR
步骤9:创建或更新PR
<title>- Check for an existing PR: .
gh pr view --json number,url 2>/dev/null - If creating: . Use
gh pr create --base <base> --draft --title "<title>" --body "<body>"(the flag) instead of--readyif the user passed--draft.--ready - If updating: .
gh pr edit <number> --title "<title>" --body "<body>" - If was passed: after create/update, run
--merge-when-readyto enable auto-merge once requirements are met.gh pr merge --auto --squash <number> - Return the PR URL via .
gh pr view --json url -q '.url'
PR body template:
markdown
undefined<title>- 检查是否已有PR:。
gh pr view --json number,url 2>/dev/null - 如果创建PR:运行。如果用户传入了
gh pr create --base <base> --draft --title "<title>" --body "<body>"参数,则使用--ready标志而非--ready。--draft - 如果更新PR:运行。
gh pr edit <number> --title "<title>" --body "<body>" - 如果传入了参数:创建/更新PR后,运行
--merge-when-ready以启用满足条件后的自动合并。gh pr merge --auto --squash <number> - 通过返回PR链接。
gh pr view --json url -q '.url'
PR正文模板:
markdown
undefinedSummary
摘要
- Comprehensive summary of all changes on this branch
- What changed and why
- 分支上所有变更的全面总结
- 变更内容及原因
Related Issues
关联工单
<!-- Linear identifiers extracted from the branch and commits -->
- <ISSUE-ID>
<!-- 从分支和提交中提取的Linear标识符 -->
- <工单ID>
Test Plan
测试计划
- <test>
Drop the `## Related Issues` section if no issues were found.- <测试内容>
如果未找到关联工单,则移除`## 关联工单`部分。Step 10: Transition linked Linear issues to In Review — delegate to the linear-sync
skill
linear-sync步骤10:将关联的Linear工单切换至In Review状态——委托给linear-sync
技能
linear-syncFollow the skill with target state In
Review: read its for and , extract issue
IDs from the branch and commits, resolve the live state ID by team name (once),
and apply the transition idempotently (skip any issue already at or past In Review).
Skip silently if or the Linear MCP server is unavailable.
linear-syncconfig.jsonlinearTeamNameissueKeyslinear-sync按照技能的流程,将目标状态设置为In
Review:从其中读取和,从分支和提交中提取工单ID,按团队名称解析实时状态ID(仅解析一次),并幂等应用状态转换(跳过已处于或超过In Review状态的工单)。如果或Linear MCP服务器不可用,则静默跳过。
linear-syncconfig.jsonlinearTeamNameissueKeyslinear-syncFlags
标志
- — print what would be written/submitted (changelog preview, branch, conventional PR title, any version-bump proposals), make no commits, no push, no
--dry-runcalls. Exit 0.gh - — override the auto-derived branch name when running on the base branch with uncommitted changes.
--branch=<name> - — prefix the auto-derived slug with a Linear issue ID (e.g.
--issue=<ID>→--issue=A-7, lower-cased). Ignored ifa-7-<slug>is given.--branch - — override
--base=<branch>'sconfig.jsonfor this run. Applies everywhere the base is used: thebaseBranch, the branch diff (git fetch), the PRorigin/<base>...HEAD, and the--baseenv passed toBASE_REF=origin/<branch>/derive-bump.mjs. Use it for stacked PRs or a non-check-skill-bumps.mjstarget.main - — set the PR title verbatim instead of deriving it (escape hatch for when derivation picks the wrong type). It must still be a valid Conventional Commits subject (CI lints it).
--title="<conventional subject>"still runs (itsderive-bumpdrives the changelog entry); send-it warns if the supplied type contradicts the derivedcategory/type.releaseTriggering - — skip the Step 5 lint gate entirely, printing a bypass warning.
--skip-preflight - — open the PR ready-for-review instead of draft (default is draft).
--ready - — after create/update, enable
--merge-when-ready.gh pr merge --auto --squash - —
--worktree=<branch-or-path>into a worktree before running (Step 0).cd
- ——打印将要编写/提交的内容(变更日志预览、分支名称、符合规范的PR标题、任何版本升级建议),不创建提交、不推送、不调用
--dry-run命令。退出码为0。gh - ——当在基准分支上存在未提交变更时,覆盖自动推导的分支名称。
--branch=<name> - ——在自动推导的别名前添加Linear工单ID(例如
--issue=<ID>→--issue=A-7,全小写)。如果传入了a-7-<slug>参数,则忽略该标志。--branch - ——覆盖本次运行的
--base=<branch>中的config.json。适用于所有使用基准的场景:baseBranch、分支差异(git fetch)、PR的origin/<base>...HEAD参数,以及传递给--base/derive-bump.mjs的check-skill-bumps.mjs环境变量。用于堆叠PR或非BASE_REF=origin/<branch>目标分支的场景。main - ——直接设置PR标题,而非自动推导(当自动推导选择了错误类型时的应急方案)。该标题仍需符合Conventional Commits规范(CI会进行lint检查)。
--title="<符合规范的主题>"仍会运行(其derive-bump会驱动变更日志条目);如果传入的类型与推导的category/type不一致,send-it会警告。releaseTriggering - ——完全跳过步骤5的lint检查,打印绕过警告。
--skip-preflight - ——将PR标记为就绪状态而非草稿(默认是草稿)。
--ready - ——创建/更新PR后,启用
--merge-when-ready。gh pr merge --auto --squash - ——在运行前切换到指定工作区(步骤0)。
--worktree=<branch-or-path>
Notes
注意事项
- Prose follows the host repo's language convention. Author the PR title, PR
body, and commit messages in the consuming repo's documented prose language. Across
this estate that is British English (,
colour,behaviour/-ise); the-yseskill applies the same rule to the entry it writes. This governs prose only — never identifiers, dependency names, or upstream API field names.changelog - Trunk-based: PRs target the base branch (
config.json, orbaseBranchfor this run).--base - send-it bumps only per-bundle versions, never the repo version. The optional
Step 6 bundle-version check moves a changed skill's own ; the repo-level npm release stays owned by release-please via the PR title.
metadata.version - Idempotent: re-running send-it updates the existing PR title and changelog entry; the Linear writeback skips issues already In Review or beyond.
- send-it does not bump versions or write any . release-please reads the merged Conventional-Commit PR title, bumps the manifest in the release PR, and the release workflow publishes + tags. send-it only writes the dated
CHANGELOG.mdentry (Step 7), finalised at release.changelog/<ts>-<slug>.md
- 文案遵循宿主仓库的语言约定。 PR标题、PR正文和提交消息的文案遵循目标仓库的文档语言约定。在本环境中为英式英文(、
colour、behaviour/-ise);-yse技能编写条目时也遵循相同规则。仅适用于文案——标识符、依赖名称或上游API字段名称不受此约束。changelog - 基于主干开发: PR的目标分支为基准分支(中的
config.json,或本次运行的baseBranch参数)。--base - send-it仅升级每个包的版本,绝不升级仓库版本。 步骤6中的可选包版本检查会更新已变更技能的;仓库级别的npm发布仍由release-please通过PR标题管理。
metadata.version - 幂等性: 重新运行send-it会更新现有的PR标题和变更日志条目;Linear状态同步会跳过已处于In Review或更高状态的工单。
- send-it不升级版本或编写。 release-please会读取合并后的符合Conventional Commits规范的PR标题,在发布PR中升级清单版本,发布工作流会完成发布和打标签。send-it仅编写带日期的
CHANGELOG.md条目(步骤7),该条目会在发布时完成最终填充。changelog/<ts>-<slug>.md
Error Handling
错误处理
- fails — run
gh auth statusfirst; abort until authenticated.gh auth login - changelog validation fails — surface the error; don't auto-fix. The user resolves the entry and re-runs.
- No commits ahead of the base — exit "No commits ahead of the base branch. Nothing to ship."
- Branch push fails — verify push access; ensure the remote is configured.
- PR create/update fails — verify the PR isn't closed; verify the branch is pushed.
- 失败——先运行
gh auth status;未认证前终止流程。gh auth login - 变更日志验证失败——提示错误;不自动修复。用户需修复条目后重新运行。
- 无提交领先于基准分支——退出并提示"No commits ahead of the base branch. Nothing to ship."
- 分支推送失败——验证推送权限;确保远程仓库已配置。
- PR创建/更新失败——验证PR未关闭;验证分支已推送。