mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-07-05 21:50:46 +08:00
chore: migrate .claude/skills to directory symlinks (#13486)
### What this PR does Before this PR: `.claude/skills/<name>/SKILL.md` files were **copied** from `.agents/skills/<name>/SKILL.md` via `pnpm skills:sync`. A dedicated `skills-check-windows` CI job ran on `windows-latest` to verify cross-platform file-copy compatibility. After this PR: `.claude/skills/<name>` entries are **directory symlinks** pointing to `../../.agents/skills/<name>`, following the Single Source of Truth (SSoT) principle. The Windows-specific CI job is removed; Windows developers are expected to enable symlink support. ### Why we need it and why it was done in this way The following tradeoffs were made: - Windows developers must now manually enable symlink support (Developer Mode + `git config --global core.symlinks true`). This is acceptable because: 1. The existing `AGENTS.md` is already a symlink, so Windows compatibility was never fully enforced. 2. Symlinks eliminate the need for file-copy synchronization, reducing maintenance complexity. 3. Contributors are expected to have sufficient technical capability to configure their environments. The following alternatives were considered: - Keeping file-copy sync: rejected because it duplicates content and requires extra CI to verify consistency. ### Breaking changes Windows developers who clone without symlink support enabled will get plain text files instead of symlinks. They must: 1. Enable Developer Mode or grant `SeCreateSymbolicLinkPrivilege` 2. Run `git config --global core.symlinks true` 3. Re-clone or run `pnpm skills:sync` ### Special notes for your reviewer - The `.github/workflows/ci.yml` diff includes minor quote-style changes (`'` → `"`) from the YAML formatter — these are cosmetic only. ### Checklist - [x] PR: The PR description is expressive enough and will help future contributors - [x] Code: [Write code that humans can understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans) and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle) - [x] Refactor: You have [left the code cleaner than you found it (Boy Scout Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html) - [x] Upgrade: Impact of this change on upgrade flows was considered and addressed if required - [x] Documentation: A [user-guide update](https://docs.cherry-ai.com) was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior. - [ ] Self-review: I have reviewed my own code (e.g., via [`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`, or GitHub UI) before requesting review from others ### Release note ```release-note NONE ``` Signed-off-by: icarus <eurfelux@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -24,10 +24,19 @@ For each new public skill, run:
|
||||
pnpm skills:sync
|
||||
```
|
||||
|
||||
`skills:sync` will create/update `.claude/skills/<skill-name>/SKILL.md` as:
|
||||
`skills:sync` will create/update `.claude/skills/<skill-name>` as a symlink pointing to `../../.agents/skills/<skill-name>`.
|
||||
|
||||
- a copied file from `.agents/skills/<skill-name>/SKILL.md`.
|
||||
- symlinks are not allowed; check enforces regular files for compatibility.
|
||||
## Windows Compatibility
|
||||
|
||||
This project uses symlinks to synchronize files such as AGENTS.md and skills. Windows developers must enable symlink support:
|
||||
|
||||
1. **Enable Developer Mode** (Settings → Update & Security → For developers), or
|
||||
2. **Grant `SeCreateSymbolicLinkPrivilege`** via Local Security Policy (`secpol.msc`).
|
||||
3. **Configure Git** to create symlinks:
|
||||
```bash
|
||||
git config --global core.symlinks true
|
||||
```
|
||||
4. Re-clone the repository (or run `pnpm skills:sync`) after enabling symlink support.
|
||||
|
||||
## White-list Tracking Rules
|
||||
|
||||
@@ -53,4 +62,4 @@ The sync/check scripts manage and verify:
|
||||
|
||||
- `.agents/skills/.gitignore`
|
||||
- `.claude/skills/.gitignore`
|
||||
- `.claude/skills/<skill-name>/SKILL.md` content matches `.agents/skills/<skill-name>/SKILL.md`
|
||||
- `.claude/skills/<skill-name>` is a valid symlink to `.agents/skills/<skill-name>`
|
||||
|
||||
@@ -24,10 +24,19 @@
|
||||
pnpm skills:sync
|
||||
```
|
||||
|
||||
`skills:sync` 会自动创建/更新 `.claude/skills/<skill-name>/SKILL.md`:
|
||||
`skills:sync` 会自动创建/更新 `.claude/skills/<skill-name>` 为指向 `../../.agents/skills/<skill-name>` 的符号链接。
|
||||
|
||||
- 复制 `.agents/skills/<skill-name>/SKILL.md` 的内容。
|
||||
- 不允许使用符号链接;check 会强制要求为普通文件以保证兼容性。
|
||||
## Windows 兼容性
|
||||
|
||||
本项目使用符号链接同步 AGENTS.md、skills 等文件。Windows 开发者需要手动启用符号链接支持:
|
||||
|
||||
1. **启用开发者模式**(设置 → 更新和安全 → 开发者选项),或
|
||||
2. 通过本地安全策略(`secpol.msc`)**授予 `SeCreateSymbolicLinkPrivilege` 权限**。
|
||||
3. **配置 Git** 以创建符号链接:
|
||||
```bash
|
||||
git config --global core.symlinks true
|
||||
```
|
||||
4. 启用后重新克隆仓库(或执行 `pnpm skills:sync`)。
|
||||
|
||||
## 白名单跟踪规则
|
||||
|
||||
@@ -53,4 +62,4 @@ pnpm skills:check
|
||||
|
||||
- `.agents/skills/.gitignore`
|
||||
- `.claude/skills/.gitignore`
|
||||
- `.claude/skills/<skill-name>/SKILL.md` 与 `.agents/skills/<skill-name>/SKILL.md` 的内容一致性
|
||||
- `.claude/skills/<skill-name>` 是指向 `.agents/skills/<skill-name>` 的有效符号链接
|
||||
|
||||
@@ -60,7 +60,7 @@ If the user wants a **public skill**, before validation:
|
||||
pnpm skills:sync
|
||||
```
|
||||
|
||||
This copies the skill to `.claude/skills/<skill-name>/`.
|
||||
This creates a symlink at `.claude/skills/<skill-name>/` pointing to `.agents/skills/<skill-name>/`.
|
||||
|
||||
**Note**: `pnpm skills:check` primarily validates public skills (those in `public-skills.txt`) and also verifies related governance files, so you must sync first before validating.
|
||||
|
||||
|
||||
15
.claude/skills/.gitignore
vendored
15
.claude/skills/.gitignore
vendored
@@ -3,13 +3,8 @@
|
||||
*
|
||||
!.gitignore
|
||||
!README*.md
|
||||
!create-skill/
|
||||
!create-skill/**
|
||||
!gh-create-issue/
|
||||
!gh-create-issue/**
|
||||
!gh-create-pr/
|
||||
!gh-create-pr/**
|
||||
!gh-pr-review/
|
||||
!gh-pr-review/**
|
||||
!prepare-release/
|
||||
!prepare-release/**
|
||||
!create-skill
|
||||
!gh-create-issue
|
||||
!gh-create-pr
|
||||
!gh-pr-review
|
||||
!prepare-release
|
||||
|
||||
@@ -5,4 +5,4 @@ This directory is a synced mirror for Claude-compatible skill files.
|
||||
- Do not create new skills directly under `.claude/skills`.
|
||||
- Create and maintain skills under `.agents/skills` only.
|
||||
- Update `.agents/skills/public-skills.txt`, then run `pnpm skills:sync`.
|
||||
- `pnpm skills:check` verifies `.claude/skills/<skill>/SKILL.md` matches `.agents/skills/<skill>/SKILL.md`.
|
||||
- `pnpm skills:check` verifies `.claude/skills/<skill>` is a valid symlink to `.agents/skills/<skill>`.
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
- 不要直接在 `.claude/skills` 下创建新 skill。
|
||||
- 所有 skill 仅在 `.agents/skills` 中创建和维护。
|
||||
- 更新 `.agents/skills/public-skills.txt` 后,执行 `pnpm skills:sync`。
|
||||
- `pnpm skills:check` 会校验 `.claude/skills/<skill>/SKILL.md` 与 `.agents/skills/<skill>/SKILL.md` 内容一致。
|
||||
- `pnpm skills:check` 会校验 `.claude/skills/<skill>` 是指向 `.agents/skills/<skill>` 的有效符号链接。
|
||||
|
||||
1
.claude/skills/create-skill
Symbolic link
1
.claude/skills/create-skill
Symbolic link
@@ -0,0 +1 @@
|
||||
../../.agents/skills/create-skill
|
||||
@@ -1,111 +0,0 @@
|
||||
---
|
||||
name: create-skill
|
||||
description: Create a new skill in the current repository. Use when the user wants to create/add a new skill, or mentions creating a skill from scratch. This skill follows the workflow defined in .agents/skills/README.md and helps scaffold, validate, and sync new skills.
|
||||
---
|
||||
|
||||
# Create Skill
|
||||
|
||||
Create a new skill in `.agents/skills/<skill-name>/` following the workflow defined in `.agents/skills/README.md`.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Gather Intent
|
||||
|
||||
Before creating anything, ask the user:
|
||||
|
||||
1. **Skill name**: What should the skill be called? (lowercase, digits, hyphens only, e.g., `gh-create-pr`, `prepare-release`)
|
||||
2. **Description**: What should this skill do? Include specific trigger contexts (e.g., "Use when user asks to create PRs")
|
||||
3. **Is this a public skill?**: Should it be synced to `.claude/skills/` for shared use? (default: no, private only)
|
||||
4. **Test cases** (optional): Does the user want to set up evals for this skill?
|
||||
|
||||
If the user provides partial info (e.g., just a name), proceed with reasonable defaults and ask to confirm.
|
||||
|
||||
### Step 2: Read Guidelines
|
||||
|
||||
Always read `.agents/skills/README.md` before creating a new skill to ensure compliance with the current workflow.
|
||||
|
||||
### Step 3: Create Skill Structure
|
||||
|
||||
Create the following directory structure:
|
||||
|
||||
```
|
||||
.agents/skills/<skill-name>/
|
||||
└── SKILL.md
|
||||
```
|
||||
|
||||
**SKILL.md template:**
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: <skill-name>
|
||||
description: <description>
|
||||
---
|
||||
|
||||
# <Skill Name>
|
||||
|
||||
[Instructions for the skill]
|
||||
```
|
||||
|
||||
**Frontmatter fields:**
|
||||
- `name`: Skill identifier (lowercase, digits, hyphens)
|
||||
- `description`: When to trigger (what the skill does + specific contexts)
|
||||
|
||||
### Step 4: Sync (if public)
|
||||
|
||||
If the user wants a **public skill**, before validation:
|
||||
|
||||
1. Add the skill name to `.agents/skills/public-skills.txt` (one per line, no inline comments)
|
||||
2. Run sync:
|
||||
```bash
|
||||
pnpm skills:sync
|
||||
```
|
||||
|
||||
This copies the skill to `.claude/skills/<skill-name>/`.
|
||||
|
||||
**Note**: `pnpm skills:check` primarily validates public skills (those in `public-skills.txt`) and also verifies related governance files, so you must sync first before validating.
|
||||
|
||||
### Step 5: Validate
|
||||
|
||||
Run the validation command:
|
||||
|
||||
```bash
|
||||
pnpm skills:check
|
||||
```
|
||||
|
||||
If there are issues, fix them and re-run.
|
||||
|
||||
### Step 6: Summary
|
||||
|
||||
Present the user with:
|
||||
- Created files
|
||||
- Validation result
|
||||
- Next steps (how to use the skill)
|
||||
|
||||
## Naming Rules
|
||||
|
||||
- Use lowercase letters, digits, and hyphens only
|
||||
- Prefer short, action-oriented names (e.g., `gh-create-pr`)
|
||||
|
||||
## Public vs Private Skills
|
||||
|
||||
| Type | Location | Sync | Requires |
|
||||
|------|----------|------|----------|
|
||||
| Private | `.agents/skills/` | No | Just create the folder |
|
||||
| Public | Both | Yes | Add to `public-skills.txt` + run `pnpm skills:sync` |
|
||||
|
||||
## Commands Reference
|
||||
|
||||
```bash
|
||||
# Validate skill structure
|
||||
pnpm skills:check
|
||||
|
||||
# Sync public skills to Claude
|
||||
pnpm skills:sync
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
- Never create skills outside `.agents/skills/<skill-name>/`
|
||||
- Always run `pnpm skills:check` before completing
|
||||
- Public skills require both adding to `public-skills.txt` AND running `pnpm skills:sync`
|
||||
- If the skill-creator skill is available, you may use it for advanced skill development (evals, iterations), but this skill handles the basic creation workflow.
|
||||
1
.claude/skills/gh-create-issue
Symbolic link
1
.claude/skills/gh-create-issue
Symbolic link
@@ -0,0 +1 @@
|
||||
../../.agents/skills/gh-create-issue
|
||||
@@ -1,94 +0,0 @@
|
||||
---
|
||||
name: gh-create-issue
|
||||
description: Use when user wants to create a GitHub issue for the current repository. Must read and follow the repository's issue template format.
|
||||
---
|
||||
|
||||
# GitHub Create Issue
|
||||
|
||||
Use this skill when the user requests to create an issue. Must follow the repository's issue template format.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Determine Template Type
|
||||
|
||||
Analyze the user's request to determine the issue type:
|
||||
- If the user describes a problem, error, crash, or something not working -> Bug Report
|
||||
- If the user requests a new feature, enhancement, or additional support -> Feature Request
|
||||
- If the user is asking a question or needs help with something -> Questions & Discussion
|
||||
- Otherwise -> Others
|
||||
|
||||
**If unclear**, ask the user which template to use. Do not default to "Others" on your own.
|
||||
|
||||
### Step 2: Read the Selected Template
|
||||
|
||||
1. Read the corresponding template file from `.github/ISSUE_TEMPLATE/` directory.
|
||||
2. Identify required fields (`validations.required: true`), title prefix (`title`), and labels (`labels`, if present).
|
||||
|
||||
### Step 3: Collect Information
|
||||
|
||||
Based on the selected template, ask the user for required information only. Follow the template's required fields and option constraints (for example, Platform and Priority choices).
|
||||
|
||||
### Step 4: Build and Preview Issue Content
|
||||
|
||||
Create a temp file and write the issue content:
|
||||
- Use `issue_body_file="$(mktemp /tmp/gh-issue-body-XXXXXX).md"`
|
||||
- Use the exact title prefix from the selected template.
|
||||
- Fill content following the template body structure and section order.
|
||||
- Apply labels exactly as defined by the template.
|
||||
- Keep all labels when there are multiple labels.
|
||||
- If template has no labels, do not add custom labels.
|
||||
|
||||
Preview the temp file content. **Show the file path** (e.g., `/tmp/gh-issue-body-XXXXXX.md`) and ask for confirmation before creating. **Skip this step if the user explicitly indicates no preview/confirmation is needed** (for example, automation workflows).
|
||||
|
||||
### Step 5: Create Issue
|
||||
|
||||
Use `gh issue create` command to create the issue.
|
||||
|
||||
Use a unique temp file for the body:
|
||||
|
||||
```bash
|
||||
issue_body_file="$(mktemp /tmp/gh-issue-body-XXXXXX).md"
|
||||
cat > "$issue_body_file" <<'EOF'
|
||||
...issue body built from selected template...
|
||||
EOF
|
||||
```
|
||||
|
||||
Create the issue using values from the selected template:
|
||||
|
||||
```bash
|
||||
gh issue create --title "<title_with_template_prefix>" --body-file "$issue_body_file"
|
||||
```
|
||||
|
||||
If the selected template includes labels, append one `--label` per label:
|
||||
|
||||
```bash
|
||||
gh issue create --title "<title_with_template_prefix>" --body-file "$issue_body_file" --label "<label_1_from_template>" --label "<label_2_from_template>"
|
||||
```
|
||||
|
||||
If the selected template has no labels, do not pass `--label`.
|
||||
|
||||
You may use `--template` as a starting point (use the exact template name from the repository):
|
||||
|
||||
```bash
|
||||
gh issue create --template "<template_name>"
|
||||
```
|
||||
|
||||
Use the `--web` flag to open the creation page in browser when complex formatting is needed:
|
||||
|
||||
```bash
|
||||
gh issue create --web
|
||||
```
|
||||
|
||||
Clean up the temp file after creation:
|
||||
|
||||
```bash
|
||||
rm -f "$issue_body_file"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Must read template files under `.github/ISSUE_TEMPLATE/` to ensure following the correct format.
|
||||
- Treat template files as the only source of truth. Do not hardcode title prefixes or labels in this skill.
|
||||
- Title must be clear and concise, avoid vague terms like "a suggestion" or "stuck".
|
||||
- Provide as much detail as possible to help developers understand and resolve the issue.
|
||||
- If user doesn't specify a template type, ask them to choose one first.
|
||||
1
.claude/skills/gh-create-pr
Symbolic link
1
.claude/skills/gh-create-pr
Symbolic link
@@ -0,0 +1 @@
|
||||
../../.agents/skills/gh-create-pr
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
name: gh-create-pr
|
||||
description: Create or update GitHub pull requests using the repository-required workflow and template compliance. Use when asked to create/open/update a PR so the assistant reads `.github/pull_request_template.md`, fills every template section, preserves markdown structure exactly, and marks missing data as N/A or None instead of skipping sections.
|
||||
---
|
||||
|
||||
# GitHub PR Creation
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Read `.github/pull_request_template.md` before drafting the PR body.
|
||||
2. Collect PR context from the current branch (base/head, scope, linked issues, testing status, breaking changes, release note content).
|
||||
3. Check if the current branch has been pushed to remote. If not, push it first:
|
||||
- Default remote is `origin`, but ask the user if they want to use a different remote.
|
||||
```bash
|
||||
git push -u <remote> <head-branch>
|
||||
```
|
||||
4. Determine the base branch:
|
||||
- For official repo(CherryHQ/cherry-studio) as `origin`: default base is `main` from `origin`, but allow the user to explicitly indicate a base branch.
|
||||
- For fork repo as `origin`: check available remotes with `git remote -v`, default base may be `upstream/main` or another remote. Always assume that user wants to merge head to CherryHQ/cherry-studio/main, unless the user explicitly indicates a base branch.
|
||||
- Ask the user to confirm the base branch if it's not the default.
|
||||
5. Create a temp file and write the PR body:
|
||||
- Use `pr_body_file="$(mktemp /tmp/gh-pr-body-XXXXXX).md"`
|
||||
- Fill content using the template structure exactly (keep section order, headings, checkbox formatting).
|
||||
- If not applicable, write `N/A` or `None`.
|
||||
6. Preview the temp file content. **Show the file path** (e.g., `/tmp/gh-pr-body-XXXXXX.md`) and ask for explicit confirmation before creating. **Skip this step if the user explicitly indicates no preview/confirmation is needed** (for example, automation workflows).
|
||||
7. After confirmation, create the PR:
|
||||
```bash
|
||||
gh pr create --base <base> --head <head> --title "<title>" --body-file "$pr_body_file"
|
||||
```
|
||||
8. Clean up the temp file: `rm -f "$pr_body_file"`
|
||||
9. Report the created PR URL and summarize title/base/head and any required follow-up.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Never skip template sections.
|
||||
- Never rewrite the template format.
|
||||
- Keep content concise and specific to the current change set.
|
||||
- PR title and body must be written in English.
|
||||
- Never create the PR before showing the full final body to the user, unless they explicitly waive the preview or confirmation.
|
||||
- Never rely on command permission prompts as PR body preview.
|
||||
- **Release note & Documentation checkbox** — both are driven by whether the change is **user-facing**. Use the table below:
|
||||
|
||||
| Change type | Release note | Docs `[x]` |
|
||||
|---|---|---|
|
||||
| New user-facing feature / setting / UI | Describe the change | ✅ |
|
||||
| Bug fix visible to users | Describe the fix | ✅ if behavior changed |
|
||||
| Behavior change / default value change | Describe + `action required` | ✅ |
|
||||
| Security fix in a user-facing dependency | Describe the fix | ✅ if usage changed |
|
||||
| CI / GitHub Actions changes | `NONE` | ❌ |
|
||||
| Internal refactoring (user cannot tell) | `NONE` | ❌ |
|
||||
| Dev / build tooling changes | `NONE` | ❌ |
|
||||
| Dev-only dependency bump | `NONE` | ❌ |
|
||||
| Test-only / code style changes | `NONE` | ❌ |
|
||||
|
||||
## Command Pattern
|
||||
|
||||
```bash
|
||||
# read template
|
||||
cat .github/pull_request_template.md
|
||||
|
||||
# show this full Markdown body in chat first
|
||||
pr_body_file="$(mktemp /tmp/gh-pr-body-XXXXXX).md"
|
||||
cat > "$pr_body_file" <<'EOF'
|
||||
...filled template body...
|
||||
EOF
|
||||
|
||||
# run only after explicit user confirmation
|
||||
gh pr create --base <base> --head <head> --title "<title>" --body-file "$pr_body_file"
|
||||
rm -f "$pr_body_file"
|
||||
```
|
||||
1
.claude/skills/gh-pr-review
Symbolic link
1
.claude/skills/gh-pr-review
Symbolic link
@@ -0,0 +1 @@
|
||||
../../.agents/skills/gh-pr-review
|
||||
@@ -1,179 +0,0 @@
|
||||
---
|
||||
name: gh-pr-review
|
||||
description: Review GitHub pull requests using the gh-pr-review extension. Use when asked to review a PR, add inline review comments, request changes, approve, or comment on a pull request. Manages the full review lifecycle — start, add inline comments, preview, and submit.
|
||||
---
|
||||
|
||||
# GitHub PR Review
|
||||
|
||||
Use this skill when the user requests to review a pull request. Leverages the `gh-pr-review` CLI extension for structured, inline code reviews via GitHub's pending review API.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
The `gh-pr-review` extension must be installed. If not present, install it:
|
||||
|
||||
```bash
|
||||
gh extension install EurFelux/gh-pr-review
|
||||
```
|
||||
|
||||
Verify with:
|
||||
|
||||
```bash
|
||||
gh extension list | grep pr-review
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Identify the PR
|
||||
|
||||
Determine the target PR from the user's request:
|
||||
- A PR number (e.g., `#123`)
|
||||
- A PR URL (e.g., `https://github.com/owner/repo/pull/123`)
|
||||
- The current branch (use `gh pr view --json number` to find it)
|
||||
|
||||
Determine the repository in `owner/repo` format. Default to the current repo via `gh repo view --json nameWithOwner -q .nameWithOwner`.
|
||||
|
||||
### Step 2: Gather PR Context
|
||||
|
||||
Collect information needed for a thorough review:
|
||||
|
||||
```bash
|
||||
# PR metadata
|
||||
gh pr view <number> --json title,body,files,additions,deletions,baseRefName,headRefName
|
||||
|
||||
# Full diff
|
||||
gh pr diff <number>
|
||||
|
||||
# Changed files with diff hunks (needed for inline comment line numbers)
|
||||
gh api repos/<owner>/<repo>/pulls/<number>/files --jq '.[] | {filename, status, patch}'
|
||||
```
|
||||
|
||||
Read the changed files in the local repo to understand surrounding context beyond the diff.
|
||||
|
||||
### Step 3: Analyze Changes
|
||||
|
||||
Review the code for:
|
||||
- Correctness and logic errors
|
||||
- Security vulnerabilities (OWASP top 10)
|
||||
- Performance issues
|
||||
- Missing error handling at system boundaries
|
||||
- Breaking changes or backward compatibility concerns
|
||||
- Test coverage gaps
|
||||
- Typos and naming inconsistencies
|
||||
- Adherence to project conventions (check `CLAUDE.md` or equivalent)
|
||||
|
||||
Categorize findings by severity:
|
||||
- **Critical**: Bugs, data loss risks, security vulnerabilities
|
||||
- **Significant**: Missing error handling, architectural concerns, incomplete implementations
|
||||
- **Minor/Nit**: Typos, style issues, naming suggestions
|
||||
|
||||
### Step 4: Start a Pending Review
|
||||
|
||||
```bash
|
||||
gh pr-review review start --repo <owner/repo> --pr <number>
|
||||
```
|
||||
|
||||
Save the returned `id` field — this is the `review-id` needed for all subsequent commands.
|
||||
|
||||
### Step 5: Add Inline Comments
|
||||
|
||||
For each finding, add an inline comment at the relevant location:
|
||||
|
||||
```bash
|
||||
gh pr-review review add-comment --repo <owner/repo> --pr <number> \
|
||||
--review-id "<review-id>" \
|
||||
--path "<file-path>" \
|
||||
--line <line-number> \
|
||||
--body "<comment-body>"
|
||||
```
|
||||
|
||||
For multi-line comments (highlighting a range of code):
|
||||
|
||||
```bash
|
||||
gh pr-review review add-comment --repo <owner/repo> --pr <number> \
|
||||
--review-id "<review-id>" \
|
||||
--path "<file-path>" \
|
||||
--line <end-line> \
|
||||
--start-line <start-line> \
|
||||
--body "<comment-body>"
|
||||
```
|
||||
|
||||
**Line number rules:**
|
||||
- `--line` is the absolute line number in the **new file** (RIGHT side by default).
|
||||
- The line must fall within a diff hunk range. Check hunk headers: `@@ -oldStart,oldCount +newStart,newCount @@` — valid range for RIGHT side is `newStart` to `newStart + newCount - 1`.
|
||||
- For comments on deleted lines, use `--side LEFT` and line numbers from the old file.
|
||||
- Use `gh api repos/<owner>/<repo>/pulls/<number>/files --jq '.[].patch'` to verify valid line ranges.
|
||||
|
||||
**Comment body guidelines:**
|
||||
- Lead with a bold severity label (e.g., `**Bug:**`, `**Critical:**`, `**Nit:**`, `**Perf:**`).
|
||||
- Explain the problem clearly.
|
||||
- Provide a concrete suggestion with code snippet when applicable.
|
||||
|
||||
### Step 6: Preview the Review
|
||||
|
||||
Before submitting, optionally preview all pending comments:
|
||||
|
||||
```bash
|
||||
gh pr-review review preview --repo <owner/repo> --pr <number> --review-id "<review-id>"
|
||||
```
|
||||
|
||||
Show the preview to the user and ask for confirmation before submitting. **Skip this step if the user explicitly indicates no preview/confirmation is needed.**
|
||||
|
||||
### Step 7: Submit the Review
|
||||
|
||||
```bash
|
||||
gh pr-review review submit --repo <owner/repo> --pr <number> \
|
||||
--review-id "<review-id>" \
|
||||
--event "<APPROVE|COMMENT|REQUEST_CHANGES>" \
|
||||
--body "<review-summary>"
|
||||
```
|
||||
|
||||
Choose the event based on findings:
|
||||
- `APPROVE` — No issues found, or only minor nits.
|
||||
- `COMMENT` — Observations and suggestions, but nothing blocking.
|
||||
- `REQUEST_CHANGES` — Critical or significant issues that must be addressed before merging.
|
||||
|
||||
**Review summary body guidelines:**
|
||||
- Start with a brief overall assessment.
|
||||
- Group findings by severity (Critical, Significant, Minor).
|
||||
- Include a Positives section to acknowledge good patterns.
|
||||
- Keep it concise but comprehensive.
|
||||
|
||||
### Step 8: Report Results
|
||||
|
||||
Summarize to the user:
|
||||
- Review event type (approved / commented / requested changes)
|
||||
- Number of inline comments added
|
||||
- Key findings by category
|
||||
- Link to the PR
|
||||
|
||||
## Managing Existing Reviews
|
||||
|
||||
### Reply to Review Threads
|
||||
|
||||
```bash
|
||||
gh pr-review comments --repo <owner/repo> --pr <number> --reply-to <thread-id> --body "<reply>"
|
||||
```
|
||||
|
||||
### Resolve/Unresolve Threads
|
||||
|
||||
```bash
|
||||
gh pr-review threads --repo <owner/repo> --pr <number> --resolve <thread-id>
|
||||
gh pr-review threads --repo <owner/repo> --pr <number> --unresolve <thread-id>
|
||||
```
|
||||
|
||||
### Edit or Delete Pending Comments
|
||||
|
||||
```bash
|
||||
gh pr-review review edit-comment --repo <owner/repo> --pr <number> --review-id "<review-id>" --comment-id "<comment-id>" --body "<new-body>"
|
||||
gh pr-review review delete-comment --repo <owner/repo> --pr <number> --review-id "<review-id>" --comment-id "<comment-id>"
|
||||
```
|
||||
|
||||
## Constraints
|
||||
|
||||
- Always start a pending review before adding comments — never use single-comment review APIs.
|
||||
- Never submit a review without showing the summary to the user first, unless they explicitly waive preview.
|
||||
- Never fabricate line numbers — always verify against the actual diff hunk ranges.
|
||||
- Review summary and inline comments must be written in English.
|
||||
- Do not add inline comments outside of diff hunk ranges — they will fail silently or error.
|
||||
- Respect the repository's contribution guidelines and coding conventions.
|
||||
- When reviewing, read the full changed files for context, not just the diff hunks.
|
||||
1
.claude/skills/prepare-release
Symbolic link
1
.claude/skills/prepare-release
Symbolic link
@@ -0,0 +1 @@
|
||||
../../.agents/skills/prepare-release
|
||||
@@ -1,175 +0,0 @@
|
||||
---
|
||||
name: prepare-release
|
||||
description: Prepare a new release by collecting commits, generating bilingual release notes, updating version files, and creating a release branch with PR. Use when asked to prepare/create a release, bump version, or run `/prepare-release`.
|
||||
---
|
||||
|
||||
# Prepare Release
|
||||
|
||||
Automate the Cherry Studio release workflow: collect changes → generate bilingual release notes → update files → create release branch + PR → trigger CI/CD.
|
||||
|
||||
## Arguments
|
||||
|
||||
Parse the version intent from the user's message. Accept any of these forms:
|
||||
- Bump type keyword: `patch`, `minor`, `major`
|
||||
- Exact version: `x.y.z` or `x.y.z-pre.N` (e.g. `1.8.0`, `1.8.0-beta.1`, `1.8.0-rc.1`)
|
||||
- Natural language: "prepare a beta release", "bump to 1.8.0-rc.2", etc.
|
||||
|
||||
Defaults to `patch` if no version is specified. Always echo the resolved target version back to the user before proceeding with any file edits.
|
||||
|
||||
- `--dry-run`: Preview only, do not create branch or PR.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Determine Version
|
||||
|
||||
1. Get the latest tag:
|
||||
```bash
|
||||
git describe --tags --abbrev=0
|
||||
```
|
||||
2. Read current version from `package.json`.
|
||||
3. Compute the new version based on the argument:
|
||||
- `patch` / `minor` / `major`: bump from the current tag version.
|
||||
- `x.y.z` or `x.y.z-pre.N`: use as-is after validating it is valid semver.
|
||||
|
||||
### Step 2: Collect Commits
|
||||
|
||||
1. List all commits since the last tag:
|
||||
```bash
|
||||
git log <last-tag>..HEAD --format="%H %s" --no-merges
|
||||
```
|
||||
2. For each commit, get the full body:
|
||||
```bash
|
||||
git log <hash> -1 --format="%B"
|
||||
```
|
||||
3. Extract the content inside `` ```release-note `` code blocks from each commit body.
|
||||
4. Extract the conventional commit type from the title (`feat`, `fix`, `refactor`, `perf`, `docs`, etc.).
|
||||
5. **Skip** these commits:
|
||||
- Titles starting with `🤖 Daily Auto I18N`
|
||||
- Titles starting with `Merge`
|
||||
- Titles starting with `chore(deps)`
|
||||
- Titles starting with `chore: release`
|
||||
- Commits where the release-note block says `NONE`
|
||||
|
||||
### Step 3: Generate Bilingual Release Notes
|
||||
|
||||
Using the collected commit information, generate release notes in **both English and Chinese**.
|
||||
|
||||
**Format** (must match exactly):
|
||||
|
||||
```
|
||||
<!--LANG:en-->
|
||||
Cherry Studio {version} - {Brief English Title}
|
||||
|
||||
✨ New Features
|
||||
- [Component] Description
|
||||
|
||||
🐛 Bug Fixes
|
||||
- [Component] Description
|
||||
|
||||
💄 Improvements
|
||||
- [Component] Description
|
||||
|
||||
⚡ Performance
|
||||
- [Component] Description
|
||||
|
||||
<!--LANG:zh-CN-->
|
||||
Cherry Studio {version} - {简短中文标题}
|
||||
|
||||
✨ 新功能
|
||||
- [组件] 描述
|
||||
|
||||
🐛 问题修复
|
||||
- [组件] 描述
|
||||
|
||||
💄 改进
|
||||
- [组件] 描述
|
||||
|
||||
⚡ 性能优化
|
||||
- [组件] 描述
|
||||
<!--LANG:END-->
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Only include categories that have entries (omit empty categories).
|
||||
- Each commit appears as exactly ONE line item in the appropriate category.
|
||||
- Use the `release-note` field if present; otherwise summarize from the commit title.
|
||||
- Component tags should be short: `[Chat]`, `[Models]`, `[Agent]`, `[MCP]`, `[Settings]`, `[Data]`, `[Build]`, etc.
|
||||
- Chinese translations should be natural, not machine-literal.
|
||||
- Do NOT include commit hashes or PR numbers.
|
||||
- Read the **existing** release notes in `electron-builder.yml` as a style reference before writing.
|
||||
|
||||
**IMPORTANT: User-Focused Content Only**
|
||||
|
||||
Release notes are for **end users**, not developers. Exclude anything users don't care about:
|
||||
|
||||
- **EXCLUDE** internal refactoring, code cleanup, or architecture changes
|
||||
- **EXCLUDE** CI/CD, build tooling, or test infrastructure changes
|
||||
- **EXCLUDE** dependency updates (unless they add user-visible features)
|
||||
- **EXCLUDE** documentation updates
|
||||
- **EXCLUDE** developer experience improvements
|
||||
- **EXCLUDE** technical debt fixes with no user-visible impact
|
||||
- **EXCLUDE** overly technical descriptions (e.g., "fix race condition in Redux middleware")
|
||||
|
||||
**INCLUDE** only changes that users will notice:
|
||||
- New features they can use
|
||||
- Bug fixes that affected their workflow
|
||||
- UI/UX improvements they can see
|
||||
- Performance improvements they can feel
|
||||
- Security fixes (simplified, without implementation details)
|
||||
|
||||
**Keep descriptions simple and non-technical:**
|
||||
- ❌ "Fix streaming race condition causing partial tool response status in Redux state"
|
||||
- ✅ "Fix tool status not stopping when aborting"
|
||||
- ❌ "Auto-convert reasoning_effort to reasoningEffort for OpenAI-compatible providers"
|
||||
- ✅ "Fix deep thinking mode not working with some providers"
|
||||
|
||||
### Step 4: Update Files
|
||||
|
||||
1. **`package.json`**: Update the `"version"` field to the new version.
|
||||
2. **`electron-builder.yml`**: Replace the content under `releaseInfo.releaseNotes: |` with the generated notes. Preserve the 4-space YAML indentation for the block scalar content.
|
||||
|
||||
### Step 5: Present for Review
|
||||
|
||||
Show the user:
|
||||
- The new version number.
|
||||
- The full generated release notes.
|
||||
- A summary of which files were modified.
|
||||
|
||||
If `--dry-run` was specified, stop here.
|
||||
|
||||
Otherwise, ask the user to confirm before proceeding to Step 6.
|
||||
|
||||
### Step 6: Create Branch and PR
|
||||
|
||||
1. Create and push the release branch:
|
||||
```bash
|
||||
git checkout -b release/v{version}
|
||||
git add package.json electron-builder.yml
|
||||
git commit -m "chore: release v{version}"
|
||||
git push -u origin release/v{version}
|
||||
```
|
||||
2. Create the PR using the `gh-create-pr` skill. If the skill tool is unavailable, read `.agents/skills/gh-create-pr/SKILL.md` and follow it manually. In CI (non-interactive) mode, skip interactive confirmation steps and create the PR directly after filling the template.
|
||||
- Use title: `chore: release v{version}`
|
||||
- Use base branch: `main`
|
||||
- When filling the PR template, incorporate:
|
||||
- The generated release notes (English section only, for readability).
|
||||
- A list of included commits.
|
||||
- A review checklist:
|
||||
- [ ] Review generated release notes in `electron-builder.yml`
|
||||
- [ ] Verify version bump in `package.json`
|
||||
- [ ] CI passes
|
||||
- [ ] Merge to trigger release build
|
||||
3. Report the PR URL and next steps.
|
||||
|
||||
## CI Trigger Chain
|
||||
|
||||
Creating a PR from `release/v*` to `main` automatically triggers:
|
||||
- **`release.yml`**: Builds on macOS, Windows, Linux and creates a draft GitHub Release.
|
||||
- **`ci.yml`**: Runs lint, typecheck, and tests.
|
||||
|
||||
## Constraints
|
||||
|
||||
- Always read `electron-builder.yml` before modifying it to understand the current format.
|
||||
- Never modify files other than `package.json` and `electron-builder.yml`.
|
||||
- Never push directly to `main`.
|
||||
- Always show the generated release notes to the user before creating the branch/PR (unless running in CI with no interactive user).
|
||||
48
.github/workflows/ci.yml
vendored
48
.github/workflows/ci.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
node-version-file: ".node-version"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
node-version-file: ".node-version"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
node-version-file: ".node-version"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
@@ -197,46 +197,10 @@ jobs:
|
||||
- name: Renderer Test
|
||||
run: pnpm test:renderer
|
||||
|
||||
skills-check-windows:
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
CI: true
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.draft == false
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Get pnpm store directory
|
||||
id: pnpm-cache
|
||||
shell: bash
|
||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache pnpm dependencies
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Skills Check
|
||||
run: pnpm skills:check
|
||||
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [basic-checks, general-test, render-test, skills-check-windows]
|
||||
if: always() && (needs.basic-checks.result == 'failure' || needs.general-test.result == 'failure' || needs.render-test.result == 'failure' || needs.skills-check-windows.result == 'failure') && github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
needs: [basic-checks, general-test, render-test]
|
||||
if: always() && (needs.basic-checks.result == 'failure' || needs.general-test.result == 'failure' || needs.render-test.result == 'failure') && github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v6
|
||||
@@ -244,7 +208,7 @@ jobs:
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
node-version-file: ".node-version"
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
@@ -16,6 +16,17 @@
|
||||
```
|
||||
3. Customize `.zed/settings.json` as needed (it is git-ignored).
|
||||
|
||||
## Windows: Enable Symlinks
|
||||
|
||||
This project uses symlinks to synchronize files such as AGENTS.md and skills. Windows developers must enable symlink support before cloning:
|
||||
|
||||
1. **Enable Developer Mode** (Settings → Update & Security → For developers), or grant `SeCreateSymbolicLinkPrivilege` via `secpol.msc`.
|
||||
2. **Configure Git**:
|
||||
```bash
|
||||
git config --global core.symlinks true
|
||||
```
|
||||
3. Clone (or re-clone) the repository after enabling symlink support.
|
||||
|
||||
## Project Setup
|
||||
|
||||
### Install
|
||||
|
||||
@@ -16,6 +16,17 @@
|
||||
```
|
||||
3. 按需自定义 `.zed/settings.json`(该文件已被 git 忽略)。
|
||||
|
||||
## Windows:启用符号链接
|
||||
|
||||
本项目使用符号链接同步 AGENTS.md、skills 等文件。Windows 开发者在克隆前需启用符号链接支持:
|
||||
|
||||
1. **启用开发者模式**(设置 → 更新和安全 → 开发者选项),或通过 `secpol.msc` 授予 `SeCreateSymbolicLinkPrivilege` 权限。
|
||||
2. **配置 Git**:
|
||||
```bash
|
||||
git config --global core.symlinks true
|
||||
```
|
||||
3. 启用后重新克隆仓库。
|
||||
|
||||
## 项目配置
|
||||
|
||||
### 安装 Node.js
|
||||
|
||||
@@ -34,52 +34,31 @@ function checkGitignore(filePath: string, expected: string, displayPath: string,
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies `.claude/skills/<skillName>/SKILL.md` is correctly synced with
|
||||
* `.agents/skills/<skillName>/SKILL.md`.
|
||||
* Requires regular files (symlinks are disallowed for cross-platform compatibility).
|
||||
* Verifies `.claude/skills/<skillName>` is a symlink pointing to
|
||||
* `../../.agents/skills/<skillName>`.
|
||||
*/
|
||||
function checkClaudeSkillFile(skillName: string, errors: string[]) {
|
||||
const skillDir = path.join(CLAUDE_SKILLS_DIR, skillName)
|
||||
const skillFile = path.join(skillDir, 'SKILL.md')
|
||||
const agentsSkillFile = path.join(AGENTS_SKILLS_DIR, skillName, 'SKILL.md')
|
||||
|
||||
if (!fs.existsSync(skillDir)) {
|
||||
errors.push(`.claude/skills/${skillName} is missing`)
|
||||
return
|
||||
}
|
||||
|
||||
if (!fs.statSync(skillDir).isDirectory()) {
|
||||
errors.push(`.claude/skills/${skillName} is not a directory`)
|
||||
return
|
||||
}
|
||||
function checkClaudeSkillSymlink(skillName: string, errors: string[]) {
|
||||
const claudeSkillDir = path.join(CLAUDE_SKILLS_DIR, skillName)
|
||||
const expectedTarget = path.join('..', '..', '.agents', 'skills', skillName)
|
||||
|
||||
let stat: fs.Stats
|
||||
try {
|
||||
stat = fs.lstatSync(skillFile)
|
||||
stat = fs.lstatSync(claudeSkillDir)
|
||||
} catch {
|
||||
errors.push(`.claude/skills/${skillName}/SKILL.md is missing`)
|
||||
errors.push(`.claude/skills/${skillName} is missing (run pnpm skills:sync)`)
|
||||
return
|
||||
}
|
||||
|
||||
if (stat.isSymbolicLink()) {
|
||||
errors.push(`.claude/skills/${skillName}/SKILL.md must be a regular file, not a symlink`)
|
||||
if (!stat.isSymbolicLink()) {
|
||||
errors.push(
|
||||
`.claude/skills/${skillName} must be a symlink, not a ${stat.isDirectory() ? 'directory' : 'file'} (run pnpm skills:sync)`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (!stat.isFile()) {
|
||||
errors.push(`.claude/skills/${skillName}/SKILL.md is not a regular file`)
|
||||
return
|
||||
}
|
||||
|
||||
const expectedContent = readFileSafe(agentsSkillFile)
|
||||
const actualContent = readFileSafe(skillFile)
|
||||
if (expectedContent === null || actualContent === null) {
|
||||
errors.push(`failed to read .claude/skills/${skillName}/SKILL.md for content verification`)
|
||||
return
|
||||
}
|
||||
|
||||
if (actualContent !== expectedContent) {
|
||||
errors.push(`.claude/skills/${skillName}/SKILL.md content differs from .agents/skills/${skillName}/SKILL.md`)
|
||||
const actualTarget = fs.readlinkSync(claudeSkillDir)
|
||||
if (actualTarget !== expectedTarget) {
|
||||
errors.push(`.claude/skills/${skillName} symlink points to '${actualTarget}', expected '${expectedTarget}'`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +66,7 @@ function checkTrackedFilesAgainstWhitelist(skillNames: string[], errors: string[
|
||||
const sharedAgentsFiles = new Set(['.agents/skills/.gitignore', '.agents/skills/public-skills.txt'])
|
||||
const sharedClaudeFiles = new Set(['.claude/skills/.gitignore'])
|
||||
const allowedAgentsPrefixes = skillNames.map((skillName) => `.agents/skills/${skillName}/`)
|
||||
const allowedClaudeSymlinks = new Set(skillNames.map((skillName) => `.claude/skills/${skillName}`))
|
||||
const allowedClaudePrefixes = skillNames.map((skillName) => `.claude/skills/${skillName}/`)
|
||||
|
||||
let trackedFiles: string[]
|
||||
@@ -121,7 +101,7 @@ function checkTrackedFilesAgainstWhitelist(skillNames: string[], errors: string[
|
||||
if (sharedClaudeFiles.has(file) || isClaudeReadmeFile(file)) {
|
||||
continue
|
||||
}
|
||||
if (allowedClaudePrefixes.some((prefix) => file.startsWith(prefix))) {
|
||||
if (allowedClaudeSymlinks.has(file) || allowedClaudePrefixes.some((prefix) => file.startsWith(prefix))) {
|
||||
continue
|
||||
}
|
||||
errors.push(`tracked file is outside public skill whitelist: ${file}`)
|
||||
@@ -151,13 +131,13 @@ function main() {
|
||||
checkGitignore(CLAUDE_SKILLS_GITIGNORE, buildClaudeSkillsGitignore(skillNames), '.claude/skills/.gitignore', errors)
|
||||
|
||||
for (const skillName of skillNames) {
|
||||
const agentSkillPath = path.join(AGENTS_SKILLS_DIR, skillName, 'SKILL.md')
|
||||
if (!fs.existsSync(agentSkillPath)) {
|
||||
errors.push(`.agents/skills/${skillName}/SKILL.md is missing`)
|
||||
const agentSkillDir = path.join(AGENTS_SKILLS_DIR, skillName)
|
||||
if (!fs.existsSync(agentSkillDir)) {
|
||||
errors.push(`.agents/skills/${skillName} is missing`)
|
||||
continue
|
||||
}
|
||||
|
||||
checkClaudeSkillFile(skillName, errors)
|
||||
checkClaudeSkillSymlink(skillName, errors)
|
||||
}
|
||||
checkTrackedFilesAgainstWhitelist(skillNames, errors)
|
||||
|
||||
|
||||
@@ -78,8 +78,7 @@ export function buildClaudeSkillsGitignore(skillNames: string[]): string {
|
||||
]
|
||||
|
||||
for (const skillName of skillNames) {
|
||||
lines.push(`!${skillName}/`)
|
||||
lines.push(`!${skillName}/**`)
|
||||
lines.push(`!${skillName}`)
|
||||
}
|
||||
|
||||
return `${lines.join('\n')}\n`
|
||||
|
||||
@@ -12,26 +12,21 @@ import {
|
||||
} from './skills-common'
|
||||
|
||||
/**
|
||||
* Ensures `.claude/skills/<skillName>/SKILL.md` is synchronized with
|
||||
* `.agents/skills/<skillName>/SKILL.md`.
|
||||
* Uses file copy to keep cross-platform compatibility.
|
||||
* Ensures `.claude/skills/<skillName>` is a symlink pointing to
|
||||
* `../../.agents/skills/<skillName>` (relative to `.claude/skills/`).
|
||||
*/
|
||||
function ensureClaudeSkillFile(skillName: string): boolean {
|
||||
const agentsSkillFile = path.join(AGENTS_SKILLS_DIR, skillName, 'SKILL.md')
|
||||
function ensureClaudeSkillSymlink(skillName: string): boolean {
|
||||
const agentsSkillDir = path.join(AGENTS_SKILLS_DIR, skillName)
|
||||
const claudeSkillDir = path.join(CLAUDE_SKILLS_DIR, skillName)
|
||||
const claudeSkillFile = path.join(claudeSkillDir, 'SKILL.md')
|
||||
const expectedTarget = path.join('..', '..', '.agents', 'skills', skillName)
|
||||
|
||||
if (!fs.existsSync(agentsSkillFile)) {
|
||||
throw new Error(`.agents/skills/${skillName}/SKILL.md is missing`)
|
||||
if (!fs.existsSync(agentsSkillDir)) {
|
||||
throw new Error(`.agents/skills/${skillName} is missing`)
|
||||
}
|
||||
|
||||
fs.mkdirSync(claudeSkillDir, { recursive: true })
|
||||
|
||||
const expectedContent = fs.readFileSync(agentsSkillFile, 'utf-8')
|
||||
|
||||
let existing: fs.Stats | null = null
|
||||
try {
|
||||
existing = fs.lstatSync(claudeSkillFile)
|
||||
existing = fs.lstatSync(claudeSkillDir)
|
||||
} catch (error) {
|
||||
const nodeError = error as NodeJS.ErrnoException
|
||||
if (nodeError.code !== 'ENOENT') {
|
||||
@@ -39,17 +34,17 @@ function ensureClaudeSkillFile(skillName: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
if (existing !== null && !existing.isFile()) {
|
||||
fs.rmSync(claudeSkillFile, { force: true, recursive: true })
|
||||
existing = null
|
||||
} else if (existing?.isFile()) {
|
||||
const currentContent = fs.readFileSync(claudeSkillFile, 'utf-8')
|
||||
if (currentContent === expectedContent) {
|
||||
return false
|
||||
if (existing !== null) {
|
||||
if (existing.isSymbolicLink()) {
|
||||
const currentTarget = fs.readlinkSync(claudeSkillDir)
|
||||
if (currentTarget === expectedTarget) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
fs.rmSync(claudeSkillDir, { force: true, recursive: true })
|
||||
}
|
||||
|
||||
fs.writeFileSync(claudeSkillFile, expectedContent, 'utf-8')
|
||||
fs.symlinkSync(expectedTarget, claudeSkillDir)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -81,8 +76,8 @@ function main() {
|
||||
changedFiles.push('.claude/skills/.gitignore')
|
||||
}
|
||||
for (const skillName of skillNames) {
|
||||
if (ensureClaudeSkillFile(skillName)) {
|
||||
changedSkillFiles.push(`.claude/skills/${skillName}/SKILL.md`)
|
||||
if (ensureClaudeSkillSymlink(skillName)) {
|
||||
changedSkillFiles.push(`.claude/skills/${skillName}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user