fix: skills namespace isolation — install to skills/ccg/ v1.7.75
- Install skills to ~/.claude/skills/ccg/ instead of ~/.claude/skills/ - Uninstall only removes skills/ccg/, preserving user's own skills - Auto-migrate old v1.7.73-74 layout (tools/orchestration at root) - Add 3 tests: namespace, uninstall isolation, migration (134 total)
This commit is contained in:
1 parent
ebc132bc2e
commit
aada7260fc
10 files changed
+133
-10
No files matched your search
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
---
|
||||
|
||||
## [1.7.75] - 2026-03-10
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **Skills 命名空间隔离**:安装路径从 `~/.claude/skills/` 改为 `~/.claude/skills/ccg/`,卸载时不再误删用户自建 skill(如 `brainstorming`、`changelog-generator` 等)
|
||||
- **旧版迁移**:升级时自动将 v1.7.73-74 散落在 `skills/` 根目录的 CCG 文件迁移到 `skills/ccg/`,用户 skill 原地不动
|
||||
|
||||
---
|
||||
|
||||
## [1.7.74] - 2026-03-09
|
||||
|
||||
### 🔄 变更
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> [根目录](../CLAUDE.md) > **skills-v2**
|
||||
|
||||
**Last Updated**: 2026-03-09 (v1.7.74)
|
||||
**Last Updated**: 2026-03-10 (v1.7.75)
|
||||
|
||||
---
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
> 完整变更历史请查看 [CHANGELOG.md](./CHANGELOG.md)
|
||||
|
||||
### 2026-03-10 (v1.7.75)
|
||||
- 🐛 **Skills 命名空间隔离**:`skills/` → `skills/ccg/`,卸载不再误删用户自建 skill + 旧版自动迁移
|
||||
|
||||
### 2026-03-09 (v1.7.74)
|
||||
- 🔄 **spec 模板 guardrail**:`spec-research`/`spec-plan`/`spec-impl` 添加 USER GUIDANCE RULE + TASKS FORMAT RULE,内部 `/opsx:*` 调用标注 internal,失败引导至 `/ccg:spec-*`
|
||||
- 🐛 **Gemini CLI `.env` 隔离**:`cmd.Dir=$HOME` + `--include-directories` 避免项目 `.env` 覆盖全局 API Key
|
||||
|
||||
@@ -293,4 +293,4 @@ MIT
|
||||
|
||||
---
|
||||
|
||||
v1.7.74 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues)
|
||||
v1.7.75 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues)
|
||||
+1
-1
@@ -295,4 +295,4 @@ MIT
|
||||
|
||||
---
|
||||
|
||||
v1.7.74 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues)
|
||||
v1.7.75 | [Issues](https://github.com/fengshao1227/ccg-workflow/issues)
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ccg-workflow",
|
||||
"version": "1.7.74",
|
||||
"version": "1.7.75",
|
||||
"description": "Claude + Codex + Gemini multi-model collaboration system - smart routing development workflow",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.17.1",
|
||||
|
||||
@@ -331,3 +331,85 @@ describe('installWorkflows — prompts installation', () => {
|
||||
expect(geminiFiles.length).toBeGreaterThanOrEqual(5)
|
||||
})
|
||||
})
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// H. Skills namespace isolation (skills/ccg/)
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
describe('skills namespace isolation', () => {
|
||||
const tmpDir = join(tmpdir(), `ccg-test-skills-${Date.now()}`)
|
||||
|
||||
afterAll(async () => {
|
||||
await fs.remove(tmpDir)
|
||||
})
|
||||
|
||||
it('installs skills under skills/ccg/ namespace', async () => {
|
||||
const result = await installWorkflows(['workflow'], tmpDir, true, {
|
||||
mcpProvider: 'skip',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.installedSkills).toBeGreaterThanOrEqual(6)
|
||||
|
||||
// Skills must be under skills/ccg/, not skills/ root
|
||||
expect(fs.existsSync(join(tmpDir, 'skills', 'ccg', 'SKILL.md'))).toBe(true)
|
||||
expect(fs.existsSync(join(tmpDir, 'skills', 'ccg', 'tools'))).toBe(true)
|
||||
expect(fs.existsSync(join(tmpDir, 'skills', 'ccg', 'orchestration'))).toBe(true)
|
||||
})
|
||||
|
||||
it('uninstall only removes skills/ccg/, preserves user skills', async () => {
|
||||
// Simulate a user-created skill at skills/my-custom-skill/SKILL.md
|
||||
const userSkillDir = join(tmpDir, 'skills', 'my-custom-skill')
|
||||
await fs.ensureDir(userSkillDir)
|
||||
await fs.writeFile(join(userSkillDir, 'SKILL.md'), '# My Custom Skill')
|
||||
|
||||
// Uninstall CCG
|
||||
const result = await uninstallWorkflows(tmpDir)
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.removedSkills.length).toBeGreaterThan(0)
|
||||
|
||||
// CCG skills gone
|
||||
expect(fs.existsSync(join(tmpDir, 'skills', 'ccg'))).toBe(false)
|
||||
|
||||
// User skill preserved!
|
||||
expect(fs.existsSync(join(userSkillDir, 'SKILL.md'))).toBe(true)
|
||||
|
||||
// Cleanup
|
||||
await fs.remove(userSkillDir)
|
||||
})
|
||||
|
||||
it('migrates old v1.7.73 layout to skills/ccg/', async () => {
|
||||
const migrateDir = join(tmpdir(), `ccg-test-migrate-${Date.now()}`)
|
||||
|
||||
// Simulate old layout: skills/{tools,orchestration,SKILL.md,run_skill.js}
|
||||
const oldSkills = join(migrateDir, 'skills')
|
||||
await fs.ensureDir(join(oldSkills, 'tools', 'verify-security'))
|
||||
await fs.ensureDir(join(oldSkills, 'orchestration', 'multi-agent'))
|
||||
await fs.writeFile(join(oldSkills, 'SKILL.md'), '# Old Root')
|
||||
await fs.writeFile(join(oldSkills, 'run_skill.js'), '// old')
|
||||
await fs.writeFile(join(oldSkills, 'tools', 'verify-security', 'SKILL.md'), '# Old Security')
|
||||
await fs.writeFile(join(oldSkills, 'orchestration', 'multi-agent', 'SKILL.md'), '# Old Multi-Agent')
|
||||
|
||||
// Also add a user skill that should NOT be migrated
|
||||
await fs.ensureDir(join(oldSkills, 'brainstorming'))
|
||||
await fs.writeFile(join(oldSkills, 'brainstorming', 'SKILL.md'), '# User Brainstorming')
|
||||
|
||||
// Install triggers migration
|
||||
const result = await installWorkflows(['workflow'], migrateDir, true, {
|
||||
mcpProvider: 'skip',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
// CCG skills moved to skills/ccg/
|
||||
expect(fs.existsSync(join(migrateDir, 'skills', 'ccg', 'SKILL.md'))).toBe(true)
|
||||
expect(fs.existsSync(join(migrateDir, 'skills', 'ccg', 'tools'))).toBe(true)
|
||||
expect(fs.existsSync(join(migrateDir, 'skills', 'ccg', 'orchestration'))).toBe(true)
|
||||
|
||||
// User skill untouched at original location
|
||||
expect(fs.existsSync(join(migrateDir, 'skills', 'brainstorming', 'SKILL.md'))).toBe(true)
|
||||
|
||||
// Old CCG items no longer at root level
|
||||
expect(fs.existsSync(join(migrateDir, 'skills', 'tools'))).toBe(false)
|
||||
expect(fs.existsSync(join(migrateDir, 'skills', 'orchestration'))).toBe(false)
|
||||
|
||||
await fs.remove(migrateDir)
|
||||
})
|
||||
})
|
||||
+22
-5
@@ -730,12 +730,29 @@ ${workflow.description}
|
||||
}
|
||||
}
|
||||
|
||||
// Install skills (quality gates, multi-agent orchestration, etc. → ~/.claude/skills/)
|
||||
// Uses recursive copy to handle nested directory structures (tools/verify-*/scripts/*.js)
|
||||
// Install skills (quality gates, multi-agent orchestration, etc. → ~/.claude/skills/ccg/)
|
||||
// Uses 'ccg' namespace to avoid clobbering user's own skills on uninstall
|
||||
const skillsTemplateDir = join(templateDir, 'skills')
|
||||
const skillsDestDir = join(installDir, 'skills')
|
||||
const skillsDestDir = join(installDir, 'skills', 'ccg')
|
||||
if (await fs.pathExists(skillsTemplateDir)) {
|
||||
try {
|
||||
// Migration: move old v1.7.73 layout (skills/{tools,orchestration,SKILL.md,run_skill.js})
|
||||
// into skills/ccg/ namespace, preserving user's own skills in skills/
|
||||
const oldSkillsRoot = join(installDir, 'skills')
|
||||
const ccgLegacyItems = ['tools', 'orchestration', 'SKILL.md', 'run_skill.js']
|
||||
const needsMigration = !await fs.pathExists(skillsDestDir)
|
||||
&& await fs.pathExists(join(oldSkillsRoot, 'tools'))
|
||||
if (needsMigration) {
|
||||
await fs.ensureDir(skillsDestDir)
|
||||
for (const item of ccgLegacyItems) {
|
||||
const oldPath = join(oldSkillsRoot, item)
|
||||
const newPath = join(skillsDestDir, item)
|
||||
if (await fs.pathExists(oldPath)) {
|
||||
await fs.move(oldPath, newPath, { overwrite: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursive copy: preserves full directory tree (tools/lib, tools/*/scripts/, orchestration/*)
|
||||
await fs.copy(skillsTemplateDir, skillsDestDir, {
|
||||
overwrite: force,
|
||||
@@ -865,7 +882,7 @@ export async function uninstallWorkflows(installDir: string): Promise<UninstallR
|
||||
const commandsDir = join(installDir, 'commands', 'ccg')
|
||||
const promptsDir = join(installDir, '.ccg', 'prompts')
|
||||
const agentsDir = join(installDir, 'agents', 'ccg')
|
||||
const skillsDir = join(installDir, 'skills')
|
||||
const skillsDir = join(installDir, 'skills', 'ccg')
|
||||
const binDir = join(installDir, 'bin')
|
||||
const ccgConfigDir = join(installDir, '.ccg')
|
||||
|
||||
@@ -907,7 +924,7 @@ export async function uninstallWorkflows(installDir: string): Promise<UninstallR
|
||||
}
|
||||
}
|
||||
|
||||
// Remove CCG skills directory (recursive — matches new install pattern)
|
||||
// Remove CCG skills directory only (skills/ccg/) — preserves user's own skills in skills/
|
||||
if (await fs.pathExists(skillsDir)) {
|
||||
try {
|
||||
// Collect skill names for reporting (SKILL.md files, excluding root)
|
||||
|
||||
@@ -22,13 +22,17 @@ description: '按规范执行 + 多模型协作 + 归档'
|
||||
- Confirm with user which change ID to implement.
|
||||
- Run `openspec status --change "<change_id>" --json` to review tasks.
|
||||
|
||||
2. **Apply OPSX Change**
|
||||
2. **Apply OPSX Change (Pre-flight Check)**
|
||||
- Call `/opsx:apply` internally to enter implementation mode:
|
||||
```
|
||||
/opsx:apply
|
||||
```
|
||||
- This will load the change context and guide you through the tasks defined in `tasks.md`.
|
||||
- **Note**: This is an internal call. If this step fails, guide the user to re-run `/ccg:spec-impl`.
|
||||
- **HARD GATE**: Check the returned `state` field:
|
||||
- If `state: "blocked"` → STOP immediately. Inform the user which artifacts are missing and suggest: "Run `/ccg:spec-plan` to generate missing artifacts first."
|
||||
- If `progress.total === 0` → STOP immediately. Inform: "tasks.md has no parseable tasks. Run `/ccg:spec-plan` to regenerate."
|
||||
- Only proceed to Step 3 when `state: "ready"` and `progress.total > 0`.
|
||||
|
||||
3. **Identify Minimal Verifiable Phase**
|
||||
- Review `tasks.md` and identify the **smallest verifiable phase**.
|
||||
|
||||
@@ -15,6 +15,7 @@ description: '多模型分析 → 消除歧义 → 零决策可执行计划'
|
||||
- Refer to `openspec/config.yaml` for project conventions.
|
||||
- **USER GUIDANCE RULE**: When suggesting next steps to the user, ALWAYS use CCG commands (`/ccg:spec-research`, `/ccg:spec-plan`, `/ccg:spec-impl`, `/ccg:spec-review`). NEVER suggest `/opsx:*` commands to the user. If OpenSpec CLI returns error messages referencing OPSX skills, translate them to CCG equivalents.
|
||||
- **TASKS FORMAT RULE**: When generating or modifying `tasks.md`, ALL tasks MUST use checkbox format (`- [ ] X.Y description`). Heading+bullet format will cause OpenSpec CLI to parse 0 tasks and block the workflow.
|
||||
- **PHASE BOUNDARY**: This phase ONLY generates OPSX artifacts (specs.md, design.md, tasks.md). Do NOT modify any source code. Do NOT proceed to implementation. After artifacts are generated, STOP and inform the user: "Plan complete. Run `/ccg:spec-impl` to start implementation."
|
||||
|
||||
**Steps**
|
||||
1. **Select Change**
|
||||
@@ -114,6 +115,9 @@ description: '多模型分析 → 消除歧义 → 零决策可执行计划'
|
||||
```
|
||||
- The OPSX skill will use the above summary to create specs.md, design.md, and tasks.md.
|
||||
- **Note**: This is an internal call. If this step fails, guide the user to re-run `/ccg:spec-plan`.
|
||||
- **STOP**: After artifacts are generated, verify they exist and inform user:
|
||||
"Plan phase complete. Artifacts generated: specs.md, design.md, tasks.md. Run `/ccg:spec-impl` to start implementation."
|
||||
Do NOT proceed to modify source code.
|
||||
|
||||
6. **Context Checkpoint**
|
||||
- Report current context usage.
|
||||
|
||||
@@ -16,6 +16,7 @@ description: '需求 → 约束集(并行探索 + OPSX 提案)'
|
||||
- Use `{{MCP_SEARCH_TOOL}}` to minimize grep/find operations.
|
||||
- Do not make architectural decisions—surface constraints that guide decisions.
|
||||
- **USER GUIDANCE RULE**: When suggesting next steps to the user, ALWAYS use CCG commands (`/ccg:spec-research`, `/ccg:spec-plan`, `/ccg:spec-impl`, `/ccg:spec-review`). NEVER suggest `/opsx:*` commands to the user. If OpenSpec CLI returns error messages referencing OPSX skills, translate them to CCG equivalents.
|
||||
- **PHASE BOUNDARY**: This phase ONLY generates the OPSX proposal artifact. Do NOT modify any source code. Do NOT proceed to planning or implementation. After the proposal is generated, STOP and inform the user: "Research complete. Run `/ccg:spec-plan` to continue."
|
||||
|
||||
**Steps**
|
||||
0. **MANDATORY: Enhance Requirement FIRST**
|
||||
@@ -137,6 +138,9 @@ description: '需求 → 约束集(并行探索 + OPSX 提案)'
|
||||
```
|
||||
- The OPSX skill will use the above summary to write proposal.md.
|
||||
- **Note**: This is an internal call. If this step fails, guide the user to re-run `/ccg:spec-research`.
|
||||
- **STOP**: After proposal is generated, verify it exists and inform user:
|
||||
"Research phase complete. Proposal generated. Run `/ccg:spec-plan` to continue planning."
|
||||
Do NOT proceed to planning or implementation.
|
||||
|
||||
8. **Context Checkpoint**
|
||||
- Report current context usage.
|
||||
|
||||
Reference in new issue
Block a user