diff --git a/skills/bulk-refactor-workflow/SKILL.md b/skills/bulk-refactor-workflow/SKILL.md index 0f20c642..fbb67826 100644 --- a/skills/bulk-refactor-workflow/SKILL.md +++ b/skills/bulk-refactor-workflow/SKILL.md @@ -1,65 +1,109 @@ --- name: bulk-refactor-workflow -description: - "Safe bulk refactors and mass edits across a repo (rename APIs, global - replacements, mechanical changes). Triggers: bulk refactor, mass edit, rename - symbol, global replace, 批量重构, 全局替换, 统一改名, 大范围修改." +description: Use when renaming symbols, migrating APIs, or applying safe mechanical changes across many files in the repository. --- # 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 -- Rename API / symbol / file convention across many files -- Mechanical refactors (imports, formatting, lint fixes, signature migrations) -- Cross-cutting changes touching 10+ files +- Rename APIs, symbols, file conventions, or repeated string patterns +- Apply scripted or structural edits across many files +- Migrate callers to a new interface where the change pattern is repeatable -## Inputs(required) +## When Not to Use -- Scope:目录/文件类型/排除项(include/exclude) -- Transformation:要做的规则(rename A→B、替换模式、接口迁移策略) -- Constraints:是否允许行为变化?是否需要兼容期?是否允许自动格式化? -- Verification:必须通过哪些命令/检查(最少一个) +- One-off logic changes where no repeatable transformation exists +- Exploratory redesign or feature work that needs new architecture decisions +- Cleanup that is only formatting/lint alignment; use `style-cleanup` directly -## Procedure(default) +## 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` - - 跑一个基线验证(至少 build 或核心测试子集),避免“本来就坏” +## Procedure -2. **Enumerate** +1. **Baseline and bound the scope** - - 先搜索再改:用 `rg`/`git grep` 列出全部命中 - - 分类命中:真实调用 vs 注释/文档/样例;避免误改 + - Record `git status --short`. + - 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** - - 优先使用确定性的机械变换(脚本/结构化编辑)而非手工逐个改 - - 每轮改动后立即做小验证(编译/单测子集) - - 复杂迁移优先“两阶段”:先兼容旧接口(deprecated),再清理旧接口 + - Search before editing with `rg`, `git grep`, or a structured query. + - Separate real code hits from comments, docs, generated files, and samples. + - 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 Hint(optional) + - 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 Contract(stable) + - 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:改动覆盖范围(文件/目录/语言) -- Transformation:执行的规则(可复用) -- Changes:关键改动摘要(按类别) -- Verification:命令 + 证据(输出/退出码/产物) -- Risks:高风险点与回滚建议 +6. **Finish with targeted cleanup** + + - If the mechanical change leaves formatting or lint fallout, Use `style-cleanup` for the final formatting/lint pass. + - Keep that cleanup scoped to the refactor set unless the user approves a wider + 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 -- 任何“全局替换”都必须先给出命中清单与排除策略 -- 避免把行为重构与格式化/无关清理混在同一轮 +- 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" diff --git a/skills/commit-message/SKILL.md b/skills/commit-message/SKILL.md index 75173bb9..f180a078 100644 --- a/skills/commit-message/SKILL.md +++ b/skills/commit-message/SKILL.md @@ -1,61 +1,98 @@ --- name: commit-message -description: - "基于 staged diff 生成符合 commit_message.md 的提交信息建议(:emoji: - type(scope): subject)。Triggers: commit message, 提交信息, 写提交说明, - 生成提交信息, emoji commit, git commit." +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. --- # Commit Message(提交信息建议器) -目标:基于 `git diff --cached`(staged -diff)生成 1–3 条提交信息建议:`:emoji: type(scope): subject`(可选 body/footer)。 +## Overview -权威规范(单一真源): +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/standards/playbook/docs/common/commit_message.md` (vendored playbook) -## When to use +## When to Use -- 用户要“写提交信息 / 生成 commit message / 按规范写提交说明 / emoji commit” -- 已经 `git add` 了一批改动,准备 `git commit` +- The user asks to write or improve a commit message +- 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` -## Procedure(default) +## 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` - - `git diff --cached --stat` - - 必要时只看关键文件:`git diff --cached -- ` +## Inputs -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 - - 优先读取就近的 - `commit_message.md`(见上方路径),以其中的 type/emoji/格式为准。 +## Procedure -3. **生成 1 条主建议 + 2 条备选** +1. **Baseline the repo state** - - 格式固定:`:emoji: type(scope): subject`(scope 可省略)。 - - subject 用一句话描述“做了什么”,避免含糊词;尽量 ≤ 72 字符,不加句号。 + - Inspect staged and unstaged state separately. + - 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(按规范建议换行)。 - - footer:任务号或 `BREAKING CHANGE:`(若有)。 + - Read the staged diff at the smallest useful granularity. + - 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(文件数 + 关键路径 + 是否可能需要拆分) -- Proposed: - - Option A(recommended):`:emoji: type(scope): subject` - - Option B:... - - Option C:... -- Optional body/footer(如适用) -- Notes:规范路径命中情况(哪个 `commit_message.md` 被使用) +5. **Finalize safely** + + - Make it explicit whether the output is a suggestion or a final chosen message. + - Do not run `git commit` unless the user explicitly asks for execution after + reviewing the recommendation. + +## 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 diff --git a/skills/style-cleanup/SKILL.md b/skills/style-cleanup/SKILL.md index 64b0a8c9..a41e4ccb 100644 --- a/skills/style-cleanup/SKILL.md +++ b/skills/style-cleanup/SKILL.md @@ -1,94 +1,107 @@ --- name: style-cleanup -description: - "Clean up formatting and code style with the repo’s existing toolchain - (clang-format/black/isort/flake8/pre-commit/etc). Triggers: 整理代码风格, - 格式化, format, fmt, lint fix, clang-format, black, isort." +description: Use when the user asks to format code, fix lint issues, or align style with the repository's existing toolchain without changing behavior. --- # 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 -- “整理代码风格 / 格式化 / format / fmt / lint fix” -- 合并前做一次风格对齐(不做语义级重构) -- 批量改动后,希望把格式化与机械性风格问题收敛到可控 diff +- 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 +- A mechanical change produced noisy diff that should be normalized by the repo's + existing toolchain -## Inputs(required) +## When Not to Use -- Scope:仅本次改动文件(默认)|全仓库|指定目录/文件类型 -- Languages:自动识别;如为多语言仓库请确认优先级 -- Verification:至少一个可执行的验证命令(如未知,先问/再推断) +- This is not for semantic refactors or behavior changes +- This is not for introducing a new formatter or lint configuration +- This is not for full-repo reformatting unless the user explicitly requests that + scope -## Procedure(default) +## 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` - - 明确范围(默认只处理变更文件): - - staged:`git diff --name-only --cached` - - unstaged:`git diff --name-only` - - untracked:`git ls-files -o --exclude-standard` +## Procedure -2. **Detect Toolchain(prefer repo truth)** +1. **Baseline the scope** - - 优先用仓库既有入口脚本 / 配置: - - JS/TS:`package.json` - scripts(`format`/`lint`/`lint:fix`)、prettier/biome/eslint 配置 - - Python:`pyproject.toml` / `.flake8` / `.pylintrc` / - `.pre-commit-config.yaml` - - C/C++:`.clang-format`(唯一真相),可选 `.clang-tidy` - - Shell:`shfmt`/`shellcheck`(若仓库已使用) - - Markdown:prettier/markdownlint(仅在仓库已固定时使用) - - 禁止默认“引入新 formatter/linter 配置”;缺配置时只做最小手工调整,并先确认是否允许落地配置文件。 + - Record `git status --short`. + - Default to changed files only unless the user requested broader scope. + - Resolve the target file set before running any formatter. -3. **Apply(format first, then lint)** +2. **Detect the repo toolchain** - - 先 formatter(会改文件),再 lint(检查),再 lint - --fix(如有),最后再跑一次 check 确认干净。 - - 默认只处理目标文件集合;避免全仓库 reformat(除非用户明确要求)。 - - 典型命令(按仓库实际替换): - - C++:`clang-format -i `;CI 校验:`clang-format --dry-run --Werror ` - - Python:`black ` + `isort `;或 - `pre-commit run --files ` - - JS/TS:`npm run format -- ` / `pnpm ...` / - `npx prettier -w `(以项目脚本为准) + - Prefer repository scripts and checked-in config over ad hoc commands. + - Use the repo's existing formatter/linter stack: + - JS/TS: `package.json`, prettier, biome, eslint + - Python: `pyproject.toml`, `isort`, `black`, `ruff`, `flake8`, + `pre-commit` + - C/C++: `.clang-format`, optional `.clang-tidy` + - Shell: `shfmt`, `shellcheck` if already present + - Markdown: prettier/markdownlint only if the repo already uses them -4. **Guardrails** +3. **Run cleanup in a fixed loop** - - 只做风格与格式:不改变行为、不改 public API、不做重构。 - - 如格式化导致 diff 暴涨(文件数/行数过大):先停下,给出原因与两种方案让用户选: - 1. 仅格式化本次改动文件(推荐默认) - 2. 全仓库统一格式(通常需要单独 PR/提交) + - Use `formatter -> lint/check -> lint --fix -> final check`. + - Prefer repository entrypoints over raw tool invocations. + - Keep execution scoped to the chosen files whenever the toolchain supports it. + +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** - - 跑最小验证命令(仓库已有命令优先)。 - - 若无法运行(缺环境/缺权限/缺依赖):说明原因,并给出替代验证(例如 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/`(或直接包含 -`docs/tsl|cpp|python/...`),风格决策参考: +## Playbook as Authority -- 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` +When the target repo vendors this playbook, prefer these references for style +judgment: -## Output Contract(stable) +- 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:实际处理范围(文件/目录/语言) -- Toolchain:使用了哪些工具与配置依据 -- Commands:实际执行命令(按顺序) -- Changes:修改文件列表 + 改动规模概览 -- Remaining:仍未修复的问题(分类:formatter / lint / style)+ 下一步建议 +## Output Contract + +- `Scope:` actual files/directories/languages processed +- `Toolchain:` repo configs and commands used +- `Commands:` execution order +- `Changes:` modified files plus diff-size summary +- `Remaining:` unresolved lint/style issues plus why they remain ## Success Criteria -- formatter 二次运行无新增 diff -- lint/检查命令通过(或仅剩已确认的例外) -- 未引入语义变更(仅格式/风格) +- Style cleanup stays within the requested or agreed scope +- 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 -- 工具缺失:优先提示安装方式或替代命令;无法解决则退回“最小手工风格修复 + 明确未覆盖项” -- 规则冲突(如 black vs flake8):以仓库配置为准;必要时调整例外配置但需先确认 +- If the repo lacks a formatter/linter, say so and fall back to minimal manual + 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 diff --git a/tests/README.md b/tests/README.md index 85f10b0e..b0dc7777 100644 --- a/tests/README.md +++ b/tests/README.md @@ -10,6 +10,7 @@ tests/ ├── cli/ # Python CLI 测试(unittest) │ └── test_playbook_cli.py # playbook.py 基础功能测试 ├── test_format_md_action.py # format_md 动作测试 +├── test_firstparty_skills_quality.py # first-party skills 元数据与结构质量测试 ├── test_gitattributes_modes.py # gitattr_mode 行为测试 ├── test_no_backup_flags.py # no_backup 行为测试 ├── test_sync_directory_actions.py # sync_memory_bank/sync_prompts 行为测试 diff --git a/tests/test_firstparty_skills_quality.py b/tests/test_firstparty_skills_quality.py new file mode 100644 index 00000000..40bdff1c --- /dev/null +++ b/tests/test_firstparty_skills_quality.py @@ -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()