♻️ refactor(skills): standardize first-party skill contracts

upgrade commit-message, style-cleanup, and bulk-refactor-workflow
to use trigger-focused frontmatter and a shared contract structure.

add a first-party skill quality test gate and register it in tests
documentation.
This commit is contained in:
csh 2026-05-19 09:38:32 +08:00
parent 2c5050de09
commit e0b1c3ab1b
5 changed files with 335 additions and 136 deletions

View File

@ -1,65 +1,109 @@
--- ---
name: bulk-refactor-workflow name: bulk-refactor-workflow
description: description: Use when renaming symbols, migrating APIs, or applying safe mechanical changes across many files in the repository.
"Safe bulk refactors and mass edits across a repo (rename APIs, global
replacements, mechanical changes). Triggers: bulk refactor, mass edit, rename
symbol, global replace, 批量重构, 全局替换, 统一改名, 大范围修改."
--- ---
# Bulk Refactor Workflow批量重构 / 大范围修改) # Bulk Refactor Workflow批量重构 / 大范围修改)
## Overview
Guide repository-wide mechanical edits without losing scope control or damaging
unrelated work. Core principle: enumerate first, transform mechanically, verify
in loops, and keep every batch auditable.
## When to Use ## When to Use
- Rename API / symbol / file convention across many files - Rename APIs, symbols, file conventions, or repeated string patterns
- Mechanical refactors (imports, formatting, lint fixes, signature migrations) - Apply scripted or structural edits across many files
- Cross-cutting changes touching 10+ files - Migrate callers to a new interface where the change pattern is repeatable
## Inputsrequired ## When Not to Use
- Scope目录/文件类型/排除项include/exclude - One-off logic changes where no repeatable transformation exists
- Transformation要做的规则rename A→B、替换模式、接口迁移策略 - Exploratory redesign or feature work that needs new architecture decisions
- Constraints是否允许行为变化是否需要兼容期是否允许自动格式化 - Cleanup that is only formatting/lint alignment; use `style-cleanup` directly
- Verification必须通过哪些命令/检查(最少一个)
## Proceduredefault ## Inputs
1. **Baseline** - Scope: directories, file types, include/exclude rules
- Transformation: rename, replace, codemod, scripted edit, migration strategy
- Constraints: behavior-preserving vs. compatibility window vs. staged rollout
- Verification commands: smallest useful loop plus final gate
- 确保工作区干净:`git status --porcelain` ## Procedure
- 跑一个基线验证(至少 build 或核心测试子集),避免“本来就坏”
2. **Enumerate** 1. **Baseline and bound the scope**
- 先搜索再改:用 `rg`/`git grep` 列出全部命中 - Record `git status --short`.
- 分类命中:真实调用 vs 注释/文档/样例;避免误改 - Dirty worktrees are allowed.
- Do not revert unrelated changes.
- Mark the exact target file set before editing.
- Run a baseline verification command so existing failures are not misattributed
to the refactor.
3. **Apply Mechanical Change** 2. **Enumerate every intended hit**
- 优先使用确定性的机械变换(脚本/结构化编辑)而非手工逐个改 - Search before editing with `rg`, `git grep`, or a structured query.
- 每轮改动后立即做小验证(编译/单测子集) - Separate real code hits from comments, docs, generated files, and samples.
- 复杂迁移优先“两阶段”先兼容旧接口deprecated再清理旧接口 - Write down exclusions before running the transformation.
4. **Format & Lint按项目约定** 3. **Choose the safest mechanical transform**
- 仅在确认“会破坏 diff 可读性”前提下分批格式化(避免把重构和格式揉在一起) - Prefer deterministic transforms: codemods, scripts, structured edits, or
narrow search/replace with explicit scope.
- If compatibility matters, use a staged migration: adapt providers first,
migrate callers second, remove compatibility layer last.
5. **Verify & Report** 4. **Apply in bounded batches**
- 跑约定的验证命令并记录输出
- 汇总影响范围:改动文件数、主要改动点、潜在风险
## Execution Hintoptional - First, apply the transformation in bounded batches.
- Verify each batch before widening scope.
- Stop immediately if output differs from the expected hit class.
如果环境支持“执行型批量处理”(例如脚本执行),优先用脚本完成批量修改,然后只把**最小 diff + 摘要**交付,避免上下文膨胀与漏改。 5. **Verify in loops**
## Output Contractstable - Run the smallest relevant verification after each batch.
- Re-check the hit list after edits to confirm the intended pattern is gone and
unintended patterns were not introduced.
- Scope改动覆盖范围文件/目录/语言) 6. **Finish with targeted cleanup**
- Transformation执行的规则可复用
- Changes关键改动摘要按类别 - If the mechanical change leaves formatting or lint fallout, Use `style-cleanup` for the final formatting/lint pass.
- Verification命令 + 证据(输出/退出码/产物) - Keep that cleanup scoped to the refactor set unless the user approves a wider
- Risks高风险点与回滚建议 pass.
7. **Report**
- Summarize files touched, transform rules used, verification evidence, and any
residual risk.
## Output Contract
- `Scope:` files, directories, and exclusions actually used
- `Transformation:` exact rule or script applied
- `Batches:` how the work was partitioned
- `Verification:` commands, exit status, and what each loop proved
- `Risks:` remaining ambiguous areas, compatibility debt, or deferred cleanup
## Success Criteria
- Every changed file belongs to the declared scope
- The transformation is reproducible and explainable
- Verification passes at both batch level and final gate
- Unrelated work in the repository is preserved
## Failure Handling
- If the hit list is ambiguous, stop and refine scope before editing
- If the transform cannot be expressed mechanically, downgrade to a normal
refactor plan instead of pretending it is safe bulk work
- If verification fails mid-batch, stop, inspect the smallest failing delta, and
correct the transform before continuing
- If compatibility risk is higher than expected, split the migration into staged
commits or phases
## Guardrails ## Guardrails
- 任何“全局替换”都必须先给出命中清单与排除策略 - Never run repository-wide replacement without an explicit hit review
- 避免把行为重构与格式化/无关清理混在同一轮 - Never mix unrelated cleanup into the same batch
- Never hide a large-scope behavior change behind the label "mechanical refactor"

View File

@ -1,61 +1,98 @@
--- ---
name: commit-message name: commit-message
description: description: Use when the user asks for help writing a commit message, wants emoji/type(scope): subject formatting, or asks to review staged changes before committing.
"基于 staged diff 生成符合 commit_message.md 的提交信息建议(:emoji:
type(scope): subject。Triggers: commit message, 提交信息, 写提交说明,
生成提交信息, emoji commit, git commit."
--- ---
# Commit Message提交信息建议器 # Commit Message提交信息建议器
目标:基于 `git diff --cached`staged ## Overview
diff生成 13 条提交信息建议:`:emoji: type(scope): subject`(可选 body/footer
权威规范(单一真源): Turn a staged diff into a commit recommendation that matches repository policy.
Core principle: understand staged intent and commit boundaries before drafting the
message.
Authority source:
- `docs/common/commit_message.md` - `docs/common/commit_message.md`
- `docs/standards/playbook/docs/common/commit_message.md` (vendored playbook)
## When to use ## When to Use
- 用户要“写提交信息 / 生成 commit message / 按规范写提交说明 / emoji commit” - The user asks to write or improve a commit message
- 已经 `git add` 了一批改动,准备 `git commit` - The user wants emoji/type(scope): subject formatting
- The user asks whether current staged changes should be split before commit
- The user asks to review staged changes before running `git commit`
## Proceduredefault ## When Not to Use
1. **收集 staged 概览(尽量小上下文)** - No staged changes exist and the user expects a final commit message based on the
actual staged diff
- The task is writing a PR title, release note, or changelog instead of a git
commit message
- The user already provided an exact commit message and only wants it executed
- `git diff --cached --name-status` ## Inputs
- `git diff --cached --stat`
- 必要时只看关键文件:`git diff --cached -- <path>`
2. **读取并遵循权威规范** - Current staging state (`git status --short`, `git diff --cached`)
- Nearest applicable `commit_message.md`
- Whether the user wants suggestions only or a command-ready final message
- 优先读取就近的 ## Procedure
`commit_message.md`(见上方路径),以其中的 type/emoji/格式为准。
3. **生成 1 条主建议 + 2 条备选** 1. **Baseline the repo state**
- 格式固定:`:emoji: type(scope): subject`scope 可省略)。 - Inspect staged and unstaged state separately.
- subject 用一句话描述“做了什么”,避免含糊词;尽量 ≤ 72 字符,不加句号。 - If nothing is staged, stop. Explain that a staged-diff-based recommendation
is unavailable and ask whether files should be staged first.
- If only unstaged changes exist, stop before proposing a final message. Offer
a draft only if the user explicitly wants one.
4. **判断是否建议拆分提交** 2. **Load the authority spec**
- 当 staged 同时包含多个不相关模块/目的时:建议拆分,并给出拆分方式(按目录/功能点/风险)。 - Prefer the nearest `commit_message.md`.
- Reuse its type, emoji, scope, and body/footer conventions exactly.
5. **可选:补充 body/footer如需要** 3. **Summarize staged intent**
- body说明 why/impact/verify按规范建议换行 - Read the staged diff at the smallest useful granularity.
- footer任务号或 `BREAKING CHANGE:`(若有)。 - Identify the main logical change, affected areas, and whether the staged set
mixes unrelated work.
- If staged changes combine unrelated goals, strongly recommend splitting the commit
before drafting a single message. In other words, strongly recommend splitting
the commit instead of hiding multiple intents behind one subject.
6. **只给建议,不默认执行 `git commit`** 4. **Draft message options**
- 仅当用户明确要求时,才根据选定方案生成最终提交信息。
## Output contract固定输出 - Produce one recommended option and up to two alternatives.
- Keep the subject concrete, specific, and under the repo limit.
- Use body/footer only when they add real context such as motivation, impact,
verification, issue links, or breaking-change notes.
- Detected: staged files summary文件数 + 关键路径 + 是否可能需要拆分) 5. **Finalize safely**
- Proposed:
- Option Arecommended`:emoji: type(scope): subject` - Make it explicit whether the output is a suggestion or a final chosen message.
- Option B... - Do not run `git commit` unless the user explicitly asks for execution after
- Option C... reviewing the recommendation.
- Optional body/footer如适用
- Notes规范路径命中情况哪个 `commit_message.md` 被使用) ## Output Contract
- `Detected:` staged files summary, dominant intent, and split/no-split judgment
- `Spec:` which `commit_message.md` was used
- `Proposed:` Option A (recommended), Option B, Option C
- `Optional body/footer:` only when justified
- `Notes:` risks, ambiguity, or split advice
## Success Criteria
- The recommendation matches the staged diff rather than guessed intent
- The selected format complies with the nearest commit policy
- Split advice appears when staged changes are logically mixed
- No commit command is executed unless the user asked for it
## Failure Handling
- If no staged diff exists, stop and explain the limitation instead of inventing a
final message
- If the staged set is too broad or ambiguous, recommend splitting and explain why
- If no local policy file is found, state the fallback convention before drafting
- If repo state changes during review, rerun the staged summary before finalizing

View File

@ -1,94 +1,107 @@
--- ---
name: style-cleanup name: style-cleanup
description: description: Use when the user asks to format code, fix lint issues, or align style with the repository's existing toolchain without changing behavior.
"Clean up formatting and code style with the repos existing toolchain
(clang-format/black/isort/flake8/pre-commit/etc). Triggers: 整理代码风格,
格式化, format, fmt, lint fix, clang-format, black, isort."
--- ---
# Style Cleanup Workflow整理代码风格 / 格式化) # Style Cleanup Workflow整理代码风格 / 格式化)
## Overview
Apply the repository's existing formatter and lint toolchain without changing
behavior. Core principle: trust repo truth, keep scope tight, and verify with a
rerun.
## When to Use ## When to Use
- “整理代码风格 / 格式化 / format / fmt / lint fix” - The user asks for formatting, `fmt`, `format`, or lint cleanup
- 合并前做一次风格对齐(不做语义级重构) - A code change is done and needs a final style pass before review or commit
- 批量改动后,希望把格式化与机械性风格问题收敛到可控 diff - A mechanical change produced noisy diff that should be normalized by the repo's
existing toolchain
## Inputsrequired ## When Not to Use
- Scope仅本次改动文件默认全仓库指定目录/文件类型 - This is not for semantic refactors or behavior changes
- Languages自动识别如为多语言仓库请确认优先级 - This is not for introducing a new formatter or lint configuration
- Verification至少一个可执行的验证命令如未知先问/再推断) - This is not for full-repo reformatting unless the user explicitly requests that
scope
## Proceduredefault ## Inputs
1. **Baseline** - Target scope: staged files, unstaged files, explicit file list, or explicit
directory
- Relevant languages in scope
- Verification commands available in the repository
- 记录当前状态:`git status --porcelain` ## Procedure
- 明确范围(默认只处理变更文件):
- staged`git diff --name-only --cached`
- unstaged`git diff --name-only`
- untracked`git ls-files -o --exclude-standard`
2. **Detect Toolchainprefer repo truth** 1. **Baseline the scope**
- 优先用仓库既有入口脚本 / 配置: - Record `git status --short`.
- JS/TS`package.json` - Default to changed files only unless the user requested broader scope.
scripts`format`/`lint`/`lint:fix`、prettier/biome/eslint 配置 - Resolve the target file set before running any formatter.
- Python`pyproject.toml` / `.flake8` / `.pylintrc` /
`.pre-commit-config.yaml`
- C/C++`.clang-format`(唯一真相),可选 `.clang-tidy`
- Shell`shfmt`/`shellcheck`(若仓库已使用)
- Markdownprettier/markdownlint仅在仓库已固定时使用
- 禁止默认“引入新 formatter/linter 配置”;缺配置时只做最小手工调整,并先确认是否允许落地配置文件。
3. **Applyformat first, then lint** 2. **Detect the repo toolchain**
- 先 formatter会改文件再 lint检查再 lint - Prefer repository scripts and checked-in config over ad hoc commands.
--fix如有最后再跑一次 check 确认干净。 - Use the repo's existing formatter/linter stack:
- 默认只处理目标文件集合;避免全仓库 reformat除非用户明确要求 - JS/TS: `package.json`, prettier, biome, eslint
- 典型命令(按仓库实际替换): - Python: `pyproject.toml`, `isort`, `black`, `ruff`, `flake8`,
- C++`clang-format -i <files...>`CI 校验:`clang-format --dry-run --Werror <files...>` `pre-commit`
- Python`black <files...>` + `isort <files...>`;或 - C/C++: `.clang-format`, optional `.clang-tidy`
`pre-commit run --files <files...>` - Shell: `shfmt`, `shellcheck` if already present
- JS/TS`npm run format -- <files...>` / `pnpm ...` / - Markdown: prettier/markdownlint only if the repo already uses them
`npx prettier -w <files...>`(以项目脚本为准)
4. **Guardrails** 3. **Run cleanup in a fixed loop**
- 只做风格与格式:不改变行为、不改 public API、不做重构。 - Use `formatter -> lint/check -> lint --fix -> final check`.
- 如格式化导致 diff 暴涨(文件数/行数过大):先停下,给出原因与两种方案让用户选: - Prefer repository entrypoints over raw tool invocations.
1. 仅格式化本次改动文件(推荐默认) - Keep execution scoped to the chosen files whenever the toolchain supports it.
2. 全仓库统一格式(通常需要单独 PR/提交)
4. **Control blast radius**
- If the formatter expands the diff far beyond the requested scope, stop and
explain the tradeoff.
- Offer a scoped pass vs. a full-repo normalization pass instead of silently
widening scope.
5. **Verify** 5. **Verify**
- 跑最小验证命令(仓库已有命令优先)。
- 若无法运行(缺环境/缺权限/缺依赖):说明原因,并给出替代验证(例如 formatter 二次运行无 diff、lint 输出为 0
## Playbook as Authority如果项目 vendoring 了本 Playbook - Re-run the lightweight checks after fixes.
- Confirm that the second formatter run produces no additional diff.
- If the repo has a canonical lint or test command for style verification, run
that command and report the result.
当目标仓库包含 `docs/standards/playbook/docs/`(或直接包含 ## Playbook as Authority
`docs/tsl|cpp|python/...`),风格决策参考:
- TSL`docs/tsl/code_style.md`、`docs/tsl/naming.md`、`docs/tsl/toolchain.md` When the target repo vendors this playbook, prefer these references for style
- C++`docs/cpp/code_style.md`、`docs/cpp/naming.md`、`docs/cpp/toolchain.md` judgment:
- Python`docs/python/style_guide.md`、`docs/python/tooling.md`、`docs/python/configuration.md`
## Output Contractstable - TSL: `docs/tsl/code_style.md`, `docs/tsl/naming.md`, `docs/tsl/toolchain.md`
- C++: `docs/cpp/code_style.md`, `docs/cpp/naming.md`, `docs/cpp/toolchain.md`
- Python: `docs/python/style_guide.md`, `docs/python/tooling.md`,
`docs/python/configuration.md`
- Scope实际处理范围文件/目录/语言) ## Output Contract
- Toolchain使用了哪些工具与配置依据
- Commands实际执行命令按顺序 - `Scope:` actual files/directories/languages processed
- Changes修改文件列表 + 改动规模概览 - `Toolchain:` repo configs and commands used
- Remaining仍未修复的问题分类formatter / lint / style+ 下一步建议 - `Commands:` execution order
- `Changes:` modified files plus diff-size summary
- `Remaining:` unresolved lint/style issues plus why they remain
## Success Criteria ## Success Criteria
- formatter 二次运行无新增 diff - Style cleanup stays within the requested or agreed scope
- lint/检查命令通过(或仅剩已确认的例外) - The second formatter run produces no additional diff
- 未引入语义变更(仅格式/风格) - Verification commands pass, or any remaining exception is explicitly explained
- No semantic behavior change is introduced
## Failure Handling ## Failure Handling
- 工具缺失:优先提示安装方式或替代命令;无法解决则退回“最小手工风格修复 + 明确未覆盖项” - If the repo lacks a formatter/linter, say so and fall back to minimal manual
- 规则冲突(如 black vs flake8以仓库配置为准必要时调整例外配置但需先确认 cleanup only with clear limits
- If tools are missing locally, report the missing dependency and stop before
inventing a new toolchain
- If two configured tools conflict, follow checked-in repo config and surface the
conflict instead of papering over it
- If cleanup would require wider scope than requested, stop and ask for approval

View File

@ -10,6 +10,7 @@ tests/
├── cli/ # Python CLI 测试unittest ├── cli/ # Python CLI 测试unittest
│ └── test_playbook_cli.py # playbook.py 基础功能测试 │ └── test_playbook_cli.py # playbook.py 基础功能测试
├── test_format_md_action.py # format_md 动作测试 ├── test_format_md_action.py # format_md 动作测试
├── test_firstparty_skills_quality.py # first-party skills 元数据与结构质量测试
├── test_gitattributes_modes.py # gitattr_mode 行为测试 ├── test_gitattributes_modes.py # gitattr_mode 行为测试
├── test_no_backup_flags.py # no_backup 行为测试 ├── test_no_backup_flags.py # no_backup 行为测试
├── test_sync_directory_actions.py # sync_memory_bank/sync_prompts 行为测试 ├── test_sync_directory_actions.py # sync_memory_bank/sync_prompts 行为测试

View File

@ -0,0 +1,104 @@
import re
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SKILLS_ROOT = ROOT / "skills"
FIRST_PARTY_SKILLS = {
"commit-message": SKILLS_ROOT / "commit-message" / "SKILL.md",
"style-cleanup": SKILLS_ROOT / "style-cleanup" / "SKILL.md",
"bulk-refactor-workflow": SKILLS_ROOT / "bulk-refactor-workflow" / "SKILL.md",
}
def read_text(path: Path) -> str:
return path.read_text(encoding="utf-8")
def normalize_space(text: str) -> str:
return " ".join(text.split())
def parse_frontmatter(text: str) -> dict[str, str]:
match = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL)
if match is None:
raise AssertionError("missing YAML frontmatter")
block = match.group(1)
data: dict[str, str] = {}
current_key: str | None = None
current_value: list[str] = []
for raw_line in block.splitlines():
if raw_line.startswith(" ") and current_key is not None:
current_value.append(raw_line.strip())
continue
if current_key is not None:
data[current_key] = " ".join(current_value).strip().strip('"')
current_key = None
current_value = []
key, value = raw_line.split(":", 1)
current_key = key.strip()
current_value = [value.strip()]
if current_key is not None:
data[current_key] = " ".join(current_value).strip().strip('"')
return data
class FirstPartySkillsQualityTests(unittest.TestCase):
def test_first_party_skill_frontmatter_is_minimal_and_named_consistently(self):
for name, path in FIRST_PARTY_SKILLS.items():
with self.subTest(skill=name):
frontmatter = parse_frontmatter(read_text(path))
self.assertEqual(set(frontmatter), {"name", "description"})
self.assertEqual(frontmatter["name"], name)
self.assertRegex(frontmatter["name"], r"^[a-z0-9-]+$")
def test_first_party_skill_descriptions_are_trigger_focused(self):
for name, path in FIRST_PARTY_SKILLS.items():
with self.subTest(skill=name):
description = parse_frontmatter(read_text(path))["description"]
self.assertTrue(description.startswith("Use when"))
self.assertLessEqual(len(description), 500)
self.assertNotIn("Triggers:", description)
def test_first_party_skills_have_required_sections(self):
required_sections = (
"## Overview",
"## When to Use",
"## When Not to Use",
"## Inputs",
"## Procedure",
"## Output Contract",
"## Success Criteria",
"## Failure Handling",
)
for name, path in FIRST_PARTY_SKILLS.items():
text = read_text(path)
with self.subTest(skill=name):
for section in required_sections:
self.assertIn(section, text)
def test_commit_message_skill_handles_missing_or_mixed_staging_states(self):
text = normalize_space(read_text(FIRST_PARTY_SKILLS["commit-message"]))
self.assertIn("If nothing is staged", text)
self.assertIn("If only unstaged changes exist", text)
self.assertIn("strongly recommend splitting the commit", text)
self.assertIn("Do not run `git commit`", text)
def test_style_cleanup_skill_has_clear_non_goals_and_verification_loop(self):
text = normalize_space(read_text(FIRST_PARTY_SKILLS["style-cleanup"]))
self.assertIn("not for semantic refactors", text)
self.assertIn("not for introducing a new formatter or lint configuration", text)
self.assertIn("formatter -> lint/check -> lint --fix -> final check", text)
self.assertIn("second formatter run produces no additional diff", text)
def test_bulk_refactor_skill_is_dirty_aware_and_delegates_final_cleanup(self):
text = normalize_space(read_text(FIRST_PARTY_SKILLS["bulk-refactor-workflow"]))
self.assertIn("Dirty worktrees are allowed", text)
self.assertIn("Do not revert unrelated changes", text)
self.assertIn("Use `style-cleanup` for the final formatting/lint pass", text)
self.assertIn("apply the transformation in bounded batches", text)
if __name__ == "__main__":
unittest.main()