diff --git a/.gitea/ci/sync_thirdparty_skills.sh b/.gitea/ci/sync_thirdparty_skills.sh index ddc38970..a2ceda25 100644 --- a/.gitea/ci/sync_thirdparty_skills.sh +++ b/.gitea/ci/sync_thirdparty_skills.sh @@ -30,6 +30,7 @@ for entry in data["sources"]: entry.get("template_root", ""), entry.get("data_dir", ""), entry.get("scripts_dir", ""), + "\x1e".join(entry.get("include_skill_dirs", [])), ] ) ) @@ -105,6 +106,24 @@ tracked_skill_exists() { [ -f "skills/$name/SKILL.md" ] } +skill_dir_included() { + local name="$1" + local include_skill_dirs="$2" + + [ -n "$include_skill_dirs" ] || return 0 + + local IFS=$'\x1e' + local expected + read -r -a included <<< "$include_skill_dirs" + for expected in "${included[@]}"; do + if [ "$name" = "$expected" ]; then + return 0 + fi + done + + return 1 +} + cd "$REPO_DIR" @@ -130,7 +149,7 @@ if ! emit_sources_tsv > "$sources_file"; then exit 1 fi -while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_subdir output_name platform_config template_root data_dir scripts_dir; do +while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_subdir output_name platform_config template_root data_dir scripts_dir include_skill_dirs; do [ -n "$source_id" ] || continue if [ -f "$source_list" ]; then @@ -142,7 +161,7 @@ while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_su done < "$sources_file" declare -A owners=() -while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_subdir output_name platform_config template_root data_dir scripts_dir; do +while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_subdir output_name platform_config template_root data_dir scripts_dir include_skill_dirs; do [ -n "$source_id" ] || continue git archive --format=tar "origin/${THIRDPARTY_BRANCH}" "$snapshot_dir" | tar -xf - -C "$tmp_dir" @@ -160,6 +179,9 @@ while IFS=$'\x1f' read -r source_id snapshot_dir sync_mode source_list skills_su for dir in "$source_skills_dir"/*; do [ -d "$dir" ] || continue name="$(basename "$dir")" + if ! skill_dir_included "$name" "$include_skill_dirs"; then + continue + fi if [ -n "${owners[$name]:-}" ] && [ "${owners[$name]}" != "$source_id" ]; then echo "ERROR: duplicate third-party skill name: $name" >&2 exit 1 diff --git a/.gitea/ci/thirdparty_skills.json b/.gitea/ci/thirdparty_skills.json index 4e313347..5864f5fb 100644 --- a/.gitea/ci/thirdparty_skills.json +++ b/.gitea/ci/thirdparty_skills.json @@ -31,6 +31,44 @@ "sync_mode": "copy_skill_dirs", "source_list": "skills/thirdparty/.sources/andrej-karpathy-skills.list", "skills_subdir": "skills" + }, + { + "id": "brooks-lint", + "upstream_repo": "https://github.com/hyhmrright/brooks-lint.git", + "upstream_ref": "main", + "snapshot_dir": "brooks-lint", + "sync_mode": "copy_skill_dirs", + "source_list": "skills/thirdparty/.sources/brooks-lint.list", + "skills_subdir": "skills", + "include_skill_dirs": [ + "_shared", + "brooks-audit", + "brooks-debt", + "brooks-health", + "brooks-review", + "brooks-sweep", + "brooks-test" + ] + }, + { + "id": "codebase-recon", + "upstream_repo": "https://github.com/outfitter-dev/agents.git", + "upstream_ref": "main", + "snapshot_dir": "outfitter-agents", + "sync_mode": "copy_skill_dirs", + "source_list": "skills/thirdparty/.sources/codebase-recon.list", + "skills_subdir": "plugins/outfitter/skills", + "include_skill_dirs": ["codebase-recon"] + }, + { + "id": "codebase-migrate", + "upstream_repo": "https://github.com/ComposioHQ/awesome-codex-skills.git", + "upstream_ref": "main", + "snapshot_dir": "awesome-codex-skills", + "sync_mode": "copy_skill_dirs", + "source_list": "skills/thirdparty/.sources/codebase-migrate.list", + "skills_subdir": ".", + "include_skill_dirs": ["codebase-migrate"] } ] } diff --git a/README.md b/README.md index 0d975fb5..9102e386 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,6 @@ Layer 1: rulesets/ (≤50 行/语言,模板源) Layer 2: skills/ (按需加载,$skill-name 触发) ├─ commit-message: 提交信息规范 ├─ style-cleanup: 代码风格整理 - ├─ bulk-refactor-workflow: 批量重构流程 └─ thirdparty/: 第三方同步 skills Layer 3: docs/ (权威静态文档) @@ -173,8 +172,8 @@ TSL 相关问题直接查阅 `rulesets/tsl/index.md` 与 `docs/tsl/`。 **通用 Skills**: - `$commit-message`:提交信息规范 +- `$gitea-fix-ci`:诊断 Gitea Actions / PR checks 失败并按批准计划修复 - `$style-cleanup`:整理代码风格 -- `$bulk-refactor-workflow`:批量重构流程 - 更多见 `SKILLS.md` **安装与使用**:详见 `SKILLS.md` diff --git a/SKILLS.md b/SKILLS.md index d2a78659..b7666b2c 100644 --- a/SKILLS.md +++ b/SKILLS.md @@ -174,8 +174,8 @@ python /scripts/playbook.py -config playbook.toml ### 通用工作流 Skills - `commit-message`:基于 staged diff 自动建议提交信息(`:emoji: type(scope): subject`) +- `gitea-fix-ci`:诊断 Gitea Actions / PR checks 失败,先提取日志证据并形成修复计划,再按批准范围改代码 - `style-cleanup`:整理代码风格(优先使用仓库既有 formatter/lint 工具链) -- `bulk-refactor-workflow`:批量重构(安全做法 + 验证契约) --- diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 00000000..d4c81166 --- /dev/null +++ b/skills/README.md @@ -0,0 +1,72 @@ +# Skills Overview + +本目录放置 Playbook 可安装的 AI agent skills。 + +- 一方 skill 位于 `skills//SKILL.md`,由本仓库维护。 +- 第三方 skill 位于 `skills/thirdparty//SKILL.md`,由同步流程生成或更新。 +- 第三方来源登记见 `skills/thirdparty/thirdparty-skills.yml` 与 + `.gitea/ci/thirdparty_skills.json`。 + +## 一方 Skills + +| Skill | 作用 | 典型场景 | +| --- | --- | --- | +| `commit-message` | 根据 staged diff 生成符合仓库规范的提交信息,并判断是否应拆分提交 | 写 commit message、检查 staged 改动是否适合一个提交 | +| `gitea-fix-ci` | 基于 Gitea Actions run/job/log 诊断失败 CI,先形成修复计划再改代码 | Gitea PR checks 失败、远端 CI 红但本地需要定位 | +| `style-cleanup` | 使用仓库既有 formatter/linter 做格式和 lint 收尾,不改变语义 | 格式化、lint cleanup、代码改完后的风格整理 | + +## 已同步第三方 Skills + +### Superpowers Workflow Suite + +这些 skill 来自 `superpowers`,主要提供计划、执行、调试、验证和审查工作流。 + +| Skill | 作用 | +| --- | --- | +| `using-superpowers` | 会话开始时判断并加载适用 skill | +| `brainstorming` | 需求澄清、方案讨论、设计文档产出 | +| `writing-plans` | 把设计或需求拆成可执行 implementation plan | +| `executing-plans` | 按已写好的 plan 执行任务 | +| `subagent-driven-development` | 用子 agent 按任务执行、审查和迭代 plan | +| `dispatching-parallel-agents` | 多个互不依赖问题并行分派 | +| `using-git-worktrees` | 需要隔离工作区时创建或使用 git worktree | +| `finishing-a-development-branch` | 开发分支完成后的合并、PR、保留或丢弃流程 | +| `test-driven-development` | 新功能、bugfix、重构前的 TDD 流程 | +| `systematic-debugging` | bug、测试失败、异常行为的根因定位流程 | +| `verification-before-completion` | 声称完成、修复或通过前的验证门禁 | +| `requesting-code-review` | 主动请求代码审查,通常用于任务完成或合并前 | +| `receiving-code-review` | 处理别人给出的 review 意见,判断采纳、反驳或澄清 | +| `writing-skills` | 创建、修改或验证 skill 的写作规范 | + +### Other Synced Third-party Skills + +| Skill | 来源 | 作用 | +| --- | --- | --- | +| `ui-ux-pro-max` | `ui-ux-pro-max-skill` | UI/UX 设计知识库,包含风格、配色、字体、组件和栈相关建议 | +| `karpathy-guidelines` | `andrej-karpathy-skills` | 写代码、审查、重构时降低 LLM 常见错误:保持简单、聚焦、可验证 | + +## 已登记待同步 Third-party Skills + +这些来源已登记,后续同步后会落到 `skills/thirdparty/`。 + +### brooks-lint Suite + +`brooks-lint` 是一个套件来源,不是单个 skill。同步时会包含多个下游 skill +以及共享资料目录: + +| 下游项 | 隶属 | 作用 | +| --- | --- | --- | +| `brooks-review` | `brooks-lint` | 常规 PR / diff code review | +| `brooks-audit` | `brooks-lint` | architecture audit、系统性结构问题审查 | +| `brooks-debt` | `brooks-lint` | technical debt review | +| `brooks-health` | `brooks-lint` | 项目健康度检查 | +| `brooks-sweep` | `brooks-lint` | 扫描式问题发现 | +| `brooks-test` | `brooks-lint` | 测试质量审查 | +| `_shared` | `brooks-lint` | brooks 系列 skill 共享参考资料,不是独立使用的 skill | + +### Single-skill Sources + +| Skill | 来源 | 作用 | +| --- | --- | --- | +| `codebase-recon` | `outfitter-dev/agents` | 通过 git 历史和代码结构做 risk scan、热点分析、重构前侦察 | +| `codebase-migrate` | `awesome-codex-skills` | 大代码库迁移、多文件 refactor、分批变更与 CI 验证工作流 | diff --git a/skills/bulk-refactor-workflow/SKILL.md b/skills/bulk-refactor-workflow/SKILL.md deleted file mode 100644 index fbb67826..00000000 --- a/skills/bulk-refactor-workflow/SKILL.md +++ /dev/null @@ -1,109 +0,0 @@ ---- -name: bulk-refactor-workflow -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 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 - -## When Not to Use - -- 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 - -## Inputs - -- 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 - -## Procedure - -1. **Baseline and bound the scope** - - - 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. - -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. - - Write down exclusions before running the transformation. - -3. **Choose the safest mechanical transform** - - - 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. - -4. **Apply in bounded batches** - - - First, apply the transformation in bounded batches. - - Verify each batch before widening scope. - - Stop immediately if output differs from the expected hit class. - -5. **Verify in loops** - - - 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. - -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/gitea-fix-ci/SKILL.md b/skills/gitea-fix-ci/SKILL.md new file mode 100644 index 00000000..9f468fc1 --- /dev/null +++ b/skills/gitea-fix-ci/SKILL.md @@ -0,0 +1,148 @@ +--- +name: gitea-fix-ci +description: Use when a user asks to debug or fix failing Gitea Actions, Gitea PR checks, or CI workflow runs for a Gitea-hosted repository. +--- + +# Gitea Fix CI + +## Overview + +Diagnose failing Gitea Actions from the pull request or workflow run, extract the +smallest useful failure context, then propose a fix plan before changing code. +Core principle: CI logs are evidence; do not guess from the red status alone. + +This is not a standalone executor. It guides use of `tea`, local `git`, and the +Gitea API from the current workspace. + +## When to Use + +- A Gitea-hosted repository has failing Gitea Actions or PR checks +- The user asks to inspect a failed workflow run, job, or CI status +- The user asks to fix CI after a push, branch update, or pull request update +- Local tests pass but remote Gitea Actions fail + +## When Not to Use + +- The repository is not hosted on Gitea or Forgejo-compatible infrastructure +- The failure belongs to an external CI provider and only links out from Gitea +- The user only wants a local test or lint run +- Credentials, tokens, or network access are unavailable and the user has not + provided the failing log text + +## Inputs + +- Repository path, defaulting to the current workspace +- Gitea base URL and repository owner/name, from `git remote -v` when possible +- Pull request number, branch, commit SHA, or workflow run ID +- Authentication method: `tea` login profile, `GITEA_TOKEN` for API requests, + or an existing git credential for the Gitea web fallback +- Any pasted CI log if remote access is unavailable + +## Procedure + +1. **Baseline local state** + + - Record `git status --short`, current branch, and latest commit SHA. + - Identify the Gitea remote URL and owner/repo. + - Do not modify files while gathering CI evidence. + +2. **Verify Gitea access** + + - Prefer `tea` if it is installed and authenticated: + - `tea login list` + - `tea actions runs list --status failure --branch ` + - `tea actions runs view ` + - `tea actions runs logs --job ` + - `tea pulls view ` + - If `tea` is unavailable or lacks an Actions command for the installed + version, use the Gitea API directly. + - Check `/api/v1/version` and `/swagger.v1.json` when API behavior is + unclear. Gitea 1.21 exposes Actions pages but not workflow run/job/log API + endpoints. + - Never print tokens. Pass API tokens through environment variables. + +3. **Find failing workflow runs** + + - For a known run ID, fetch that run directly. + - Otherwise list recent workflow runs filtered by branch, event, status, or + commit SHA. + - API patterns: + - `GET /api/v1/repos///actions/runs` + - `GET /api/v1/repos///actions/runs/` + - `GET /api/v1/repos///actions/runs//jobs` + - Web fallback for older Gitea: + - `GET ///actions` + - Parse `///actions/runs/` links and status labels. + - Treat `failure`, `cancelled`, and missing required checks differently. + Cancelled jobs may require rerun or queue investigation rather than code + changes. + +4. **Fetch job logs** + + - For each failed job, download the job logs: + - `GET /api/v1/repos///actions/jobs//logs` + - On Gitea 1.21 web fallback, download logs by UI job index: + - `GET ///actions/runs//jobs//logs` + - Start with job index `0`; if multiple jobs exist, inspect the run page or + downloaded logs to map the failing job. + - Save large logs to a temporary file outside tracked source paths. + - Extract the first actionable error block, surrounding command, job name, + workflow name, run URL, branch, and SHA. + - If logs are missing, report that explicitly instead of inventing causes. + +5. **Classify the failure** + + - Code/test failure: failing assertion, compile error, lint error, type error + - Environment failure: missing secret, runner image, dependency install, + network, cache, permission, or service startup + - Workflow failure: invalid YAML, unsupported syntax, wrong trigger, bad path, + wrong branch/ref assumption + - Infrastructure failure: offline runner, stuck queue, cancelled run, timeout + +6. **Create a fix plan** + + - Summarize the failure evidence with exact job/run identifiers. + - Propose the smallest code or workflow change that matches the evidence. + - Include local verification commands and the remote recheck path. + - Do not implement before the user approves the fix plan. + +7. **Implement after approval** + + - Apply only the approved fix. + - Run the local command that most closely reproduces the failed job. + - If the failure is workflow-only, validate the workflow file syntax and any + referenced paths or scripts. + +8. **Recheck** + + - Tell the user what must be pushed or rerun in Gitea. + - If permitted, use the Gitea API to inspect the rerun status. + - Final output must distinguish local verification from remote CI status. + +## Output Contract + +- `Target:` repo, branch/SHA, PR or run ID +- `Failed CI:` workflow, job, status, run URL or API path +- `Evidence:` concise log snippet and classification +- `Plan:` proposed fix, local verification, remote recheck +- `Changes:` files changed after approval +- `Result:` local checks run and remaining remote status + +## Success Criteria + +- Failure analysis is based on Gitea Actions run/job data or pasted logs +- The fix plan names the exact workflow run or job it addresses +- No code or workflow edits happen before plan approval +- Verification distinguishes local commands from remote Gitea Actions results +- Tokens and private log content are not echoed unnecessarily + +## Failure Handling + +- If authentication fails, ask the user to authenticate `tea` or provide a token + through the environment; do not request secrets in chat +- If the Gitea version lacks Actions API endpoints, ask for the relevant log text + or a browser-copied job log +- If an external CI provider owns the failing check, report the external URL and + stop at evidence collection +- If the failure is infrastructure-only, recommend rerun/runner investigation + instead of editing code diff --git a/skills/thirdparty/thirdparty-skills.yml b/skills/thirdparty/thirdparty-skills.yml new file mode 100644 index 00000000..5abe5122 --- /dev/null +++ b/skills/thirdparty/thirdparty-skills.yml @@ -0,0 +1,55 @@ +# Third-party skill registry. + +version: 1 + +skills: + - id: brooks-lint + sync: enabled + upstream_repo: https://github.com/hyhmrright/brooks-lint + upstream_ref: main + upstream_layout: multi_skill_suite + upstream_paths: + - skills/brooks-audit + - skills/brooks-debt + - skills/brooks-health + - skills/brooks-review + - skills/brooks-sweep + - skills/brooks-test + - skills/_shared + selected_for: + - architecture audit + - technical debt review + - PR review + - test quality review + playbook_fit: architecture and code-quality review support + notes: + - Upstream is a suite of related review and audit skills. + + - id: codebase-recon + sync: enabled + upstream_repo: https://github.com/outfitter-dev/agents + upstream_ref: main + upstream_path: plugins/outfitter/skills/codebase-recon + upstream_layout: single_skill_dir + selected_for: + - risk scan + - git-history hotspot analysis + - codebase reconnaissance before planning + - refactor-risk assessment + playbook_fit: pre-design and pre-refactor codebase risk discovery + notes: + - Upstream skill lives inside a larger agents repository. + + - id: codebase-migrate + sync: enabled + upstream_repo: https://github.com/ComposioHQ/awesome-codex-skills + upstream_ref: main + upstream_path: codebase-migrate + upstream_layout: single_skill_dir + selected_for: + - large codebase migrations + - multi-file refactors + - reviewable refactor batches + - CI-verified migration workflows + notes: + - Sourced from awesome-codex-skills. diff --git a/tests/test_firstparty_skills_quality.py b/tests/test_firstparty_skills_quality.py index 40bdff1c..24819814 100644 --- a/tests/test_firstparty_skills_quality.py +++ b/tests/test_firstparty_skills_quality.py @@ -7,8 +7,8 @@ ROOT = Path(__file__).resolve().parents[1] SKILLS_ROOT = ROOT / "skills" FIRST_PARTY_SKILLS = { "commit-message": SKILLS_ROOT / "commit-message" / "SKILL.md", + "gitea-fix-ci": SKILLS_ROOT / "gitea-fix-ci" / "SKILL.md", "style-cleanup": SKILLS_ROOT / "style-cleanup" / "SKILL.md", - "bulk-refactor-workflow": SKILLS_ROOT / "bulk-refactor-workflow" / "SKILL.md", } @@ -92,13 +92,20 @@ class FirstPartySkillsQualityTests(unittest.TestCase): 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) - + def test_gitea_fix_ci_skill_is_gitea_specific_and_plan_gated(self): + text = normalize_space(read_text(FIRST_PARTY_SKILLS["gitea-fix-ci"])) + self.assertIn("Gitea Actions", text) + self.assertIn("tea", text) + self.assertIn("tea actions runs", text) + self.assertIn("Gitea API", text) + self.assertIn("Gitea 1.21", text) + self.assertIn("workflow runs", text) + self.assertIn("job logs", text) + self.assertIn("/actions/runs//jobs//logs", text) + self.assertIn("Do not implement before the user approves the fix plan", text) + self.assertIn("not a standalone executor", text) + self.assertNotIn("gh pr checks", text) + self.assertNotIn("GitHub Actions", text) if __name__ == "__main__": unittest.main() diff --git a/tests/test_skills_readme.py b/tests/test_skills_readme.py new file mode 100644 index 00000000..82515b89 --- /dev/null +++ b/tests/test_skills_readme.py @@ -0,0 +1,44 @@ +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +README = ROOT / "skills" / "README.md" + + +class SkillsReadmeTests(unittest.TestCase): + def test_readme_describes_first_party_and_thirdparty_skill_groups(self): + text = README.read_text(encoding="utf-8") + + for name in ("commit-message", "gitea-fix-ci", "style-cleanup"): + self.assertIn(name, text) + + for name in ( + "brainstorming", + "executing-plans", + "systematic-debugging", + "test-driven-development", + "requesting-code-review", + "receiving-code-review", + "ui-ux-pro-max", + "karpathy-guidelines", + ): + self.assertIn(name, text) + + def test_readme_explains_suite_membership_for_registered_sources(self): + text = README.read_text(encoding="utf-8") + + self.assertIn("brooks-lint", text) + self.assertIn("brooks-review", text) + self.assertIn("brooks-audit", text) + self.assertIn("brooks-debt", text) + self.assertIn("brooks-test", text) + self.assertIn("_shared", text) + + self.assertIn("codebase-recon", text) + self.assertIn("codebase-migrate", text) + self.assertIn("已登记待同步", text) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_thirdparty_skill_curation.py b/tests/test_thirdparty_skill_curation.py new file mode 100644 index 00000000..e255702e --- /dev/null +++ b/tests/test_thirdparty_skill_curation.py @@ -0,0 +1,33 @@ +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +CURATION = ROOT / "skills" / "thirdparty" / "thirdparty-skills.yml" + + +def read_text(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +class ThirdpartySkillCurationTests(unittest.TestCase): + def test_architecture_skills_are_recorded_for_sync(self): + text = read_text(CURATION) + self.assertIn("id: brooks-lint", text) + self.assertIn("upstream_repo: https://github.com/hyhmrright/brooks-lint", text) + self.assertIn("architecture audit", text) + self.assertIn("sync: enabled", text) + + self.assertIn("id: codebase-recon", text) + self.assertIn("upstream_repo: https://github.com/outfitter-dev/agents", text) + self.assertIn("upstream_path: plugins/outfitter/skills/codebase-recon", text) + self.assertIn("risk scan", text) + + self.assertIn("id: codebase-migrate", text) + self.assertIn("upstream_repo: https://github.com/ComposioHQ/awesome-codex-skills", text) + self.assertIn("upstream_path: codebase-migrate", text) + self.assertIn("large codebase migrations", text) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_thirdparty_skills_pipeline.py b/tests/test_thirdparty_skills_pipeline.py index eadb60eb..25f6907a 100644 --- a/tests/test_thirdparty_skills_pipeline.py +++ b/tests/test_thirdparty_skills_pipeline.py @@ -17,6 +17,8 @@ SKILLS_MD = ROOT / "SKILLS.md" SUPERPOWERS_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "superpowers.list" UI_UX_PRO_MAX_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "ui-ux-pro-max.list" UI_UX_PRO_MAX_DIR = ROOT / "skills" / "thirdparty" / "ui-ux-pro-max" +BROOKS_LINT_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "brooks-lint.list" +CODEBASE_RECON_LIST = ROOT / "skills" / "thirdparty" / ".sources" / "codebase-recon.list" def load_manifest() -> dict: @@ -46,7 +48,14 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase): data = load_manifest() self.assertEqual( [entry["id"] for entry in data["sources"]], - ["superpowers", "ui-ux-pro-max", "andrej-karpathy-skills"], + [ + "superpowers", + "ui-ux-pro-max", + "andrej-karpathy-skills", + "brooks-lint", + "codebase-recon", + "codebase-migrate", + ], ) def test_karpathy_manifest_uses_copy_skill_dirs_sync_mode(self): @@ -67,6 +76,39 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase): self.assertEqual(ui_skill["sync_mode"], "render_skill") self.assertEqual(ui_skill["snapshot_dir"], "ui-ux-pro-max") + def test_architecture_skill_sources_use_include_filters(self): + data = load_manifest() + brooks = next(item for item in data["sources"] if item["id"] == "brooks-lint") + self.assertEqual(brooks["sync_mode"], "copy_skill_dirs") + self.assertEqual(brooks["snapshot_dir"], "brooks-lint") + self.assertEqual(brooks["skills_subdir"], "skills") + self.assertEqual( + brooks["source_list"], "skills/thirdparty/.sources/brooks-lint.list" + ) + self.assertIn("brooks-audit", brooks["include_skill_dirs"]) + self.assertIn("brooks-review", brooks["include_skill_dirs"]) + self.assertIn("_shared", brooks["include_skill_dirs"]) + + recon = next(item for item in data["sources"] if item["id"] == "codebase-recon") + self.assertEqual(recon["sync_mode"], "copy_skill_dirs") + self.assertEqual(recon["snapshot_dir"], "outfitter-agents") + self.assertEqual(recon["skills_subdir"], "plugins/outfitter/skills") + self.assertEqual( + recon["source_list"], "skills/thirdparty/.sources/codebase-recon.list" + ) + self.assertEqual(recon["include_skill_dirs"], ["codebase-recon"]) + + migrate = next( + item for item in data["sources"] if item["id"] == "codebase-migrate" + ) + self.assertEqual(migrate["sync_mode"], "copy_skill_dirs") + self.assertEqual(migrate["snapshot_dir"], "awesome-codex-skills") + self.assertEqual(migrate["skills_subdir"], ".") + self.assertEqual( + migrate["source_list"], "skills/thirdparty/.sources/codebase-migrate.list" + ) + self.assertEqual(migrate["include_skill_dirs"], ["codebase-migrate"]) + def test_superpowers_manifest_prunes_non_superpowers_paths(self): data = load_manifest() superpowers = next(item for item in data["sources"] if item["id"] == "superpowers") @@ -138,6 +180,8 @@ class ThirdpartySkillsPipelineTests(unittest.TestCase): text = SYNC_SCRIPT.read_text(encoding="utf-8") self.assertIn('"\\x1f".join(', text) self.assertIn("while IFS=$'\\x1f' read -r", text) + self.assertIn("include_skill_dirs", text) + self.assertIn('skill_dir_included "$name" "$include_skill_dirs"', text) self.assertNotIn("while IFS=$'\\t' read -r", text) self.assertNotIn("exclude_skill_dirs", text) self.assertNotIn("is_excluded_skill_dir", text)