Skill: richenlin/evolving-agent (version 0.1.0)

--- name: evolving-agent description: AI 编程系统协调器。触发词："开发"、"实现"、"创建"、"添加"、"修复"、"报错"、"重构"、"优化"、"review"、"评审"、"继续"、"实现"、"为什么"、"记住"、"保存经验"、"复盘"、"分析"、"学习"、"参考"、"模仿"、"/evolve" license: MIT metadata: triggers: ["开发", "实现", "创建", "添加", "修复", "报错", "重构", "优化", "review", "评审", "继续", "完成", "怎么实现", "为什么", "记住", "保存经验", "复盘", "分析", "学习", "参考", "模仿", "evolve"] --- # Evolving Agent - 协调器 你现在扮演"主进程监督员"的角色，负责管理任务的完整生命周期。 > **渐进披露原则**: 本文件只定义主进程逻辑，详细实现委托给各模块（子进程）。 --- ## 核心流程（强制执行） ``` 步骤1: 设置路径变量 SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills) > 后续所有命令使用 `$SKILLS_DIR` 变量 步骤2: 意图识别 必须使用 `sequential-thinking` 工具进行深度分析和调度，识别用户意图: 编程 / 归纳 / 学习 | 意图 | 触发词 | |------|--------| | 编程 | 开发、实现、创建、添加、修复、重构、优化、完成、review | | 归纳 | 记住、保存、复盘、提取 | | 学习 | 学习、分析、参考、模仿 | 步骤3: 任务拆解与分发（加载对应模块） ├─ 编程意图 → 读取 $SKILLS_DIR/evolving-agent/modules/programming-assistant/README.md ├─ 归纳意图 → 读取 $SKILLS_DIR/evolving-agent/modules/knowledge-base/README.md └─ 学习意图 → 读取 $SKILLS_DIR/evolving-agent/modules/github-to-skills/README.md 步骤4: 子进程按照模块文档执行任务 执行模块中定义的完整流程 > 重要: 识别到意图后立即加载模块执行，不要停止或等待确认！ 步骤5: 健康检查与监控 在子进程运行期间，如果任务支持分步，定期检查中间产物： ├─ 检查 .opencode/progress.txt 的执行进度 ├─ 检查 .opencode/feature_list.json 的任务状态 └─ 如发现执行结果偏离预期（代码不符合规范、测试失败等），中断并重新调整 步骤6: 结果验证 子进程完成后，主进程必须对产出进行最终审计： ├─ 检查所有任务状态是否为 completed ├─ 检查 .opencode/.evolution_mode_active，是否成功完成经验提取 └─ 确保任务闭环，向用户反馈执行结果 ``` --- ## 调度规则 | 意图 | 加载模块 | 核心流程 | |------|----------|----------| | **编程** | `modules/programming-assistant/README.md` | 知识检索 → 状态恢复 → 开发循环 → 进化检查 | | **归纳** | `modules/knowledge-base/README.md` | 提取经验 → 分类 → 存储到知识库 | | **学习** | `modules/github-to-skills/README.md` | fetch → extract → store | --- ## 模块职责（详细实现委托给子进程） | 模块 | 职责 | 文档位置 | |------|------|----------| | **programming-assistant** | 代码生成、修复、重构 | `modules/programming-assistant/` | | **knowledge-base** | 知识存储、查询、归纳 | `modules/knowledge-base/` | | **github-to-skills** | 仓库学习、模式提取 | `modules/github-to-skills/` | --- ## 命令速查 ```bash # 设置路径（每个 shell 会话执行一次） SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills) # 进化模式 python $SKILLS_DIR/evolving-agent/scripts/run.py mode --status|--init|--off # 知识库 python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge query --stats python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge trigger --input "..." # GitHub python $SKILLS_DIR/evolving-agent/scripts/run.py github fetch <url> # 项目 python $SKILLS_DIR/evolving-agent/scripts/run.py project detect . ``` --- ## 健康检查清单 | 检查项 | 检查方式 | 异常处理 | |--------|----------|----------| | 任务进度 | 读取 `.opencode/progress.txt` | 如长时间无更新，检查是否阻塞 | | 任务状态 | 读取 `.opencode/feature_list.json` | 如有 blocked 状态，分析依赖并调整 | | 代码规范 | 运行 linter/formatter | 如有错误，中断并修复 | | 测试通过 | 运行测试命令 | 如失败，中断并修复 | --- ## 结果验证清单 | 验证项 | 验证方式 | 通过条件 | |--------|----------|----------| | 任务完成 | 检查 `feature_list.json` | 所有任务状态为 `completed` | | 经验提取 | 检查 `.evolution_mode_active` | 触发自动提取经验 | | 产出质量 | 代码审查 | 符合项目规范 | --- ## 进化模式 标记文件: `.opencode/.evolution_mode_active` - **激活时**: 已经触发自动提取经验 - **未激活时**: 未触发自动提取，需用户手动提取

path: command/evolve.md
content: ---
name: evolve
description: Start the Evolving Agent coordinator. Supports complete mode initialization, evolution mode switching and status viewing. It is a unified manual entrance to enter the "programming + evolution" closed loop.
metadata:
  command: /evolve
  usage: "/evolve [subcommand]"
---

# Evolve Command

`/evolve` 是 Evolving Programming Agent 的统一手动入口命令。

## 1. 基础用法

### 启动协调器 (Default)
```bash
/evolve
```
**行为**:
1. 执行完整初始化:
   ```bash
   SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)
   python $SKILLS_DIR/evolving-agent/scripts/run.py mode --init
   ```
2. 开启进化模式 (Evolution Mode)
3. 输出交互式引导提示
4. 等待用户输入编程任务

**适用场景**:
- 开始一个新的编程会话
- 需要明确的系统状态反馈时

## 2. 子命令 (Subcommands)

虽然主要通过自然语言交互，但 `/evolve` 也支持一些快捷指令来管理环境状态。

| 子命令 | 对应 run.py 命令 | 说明 |
|--------|-----------------|------|
| `on` | `python $SKILLS_DIR/evolving-agent/scripts/run.py mode --on` | 仅开启进化模式（不输出欢迎语） |
| `off` | `python $SKILLS_DIR/evolving-agent/scripts/run.py mode --off` | 关闭进化模式 |
| `status` | `python $SKILLS_DIR/evolving-agent/scripts/run.py mode --status` | 查看当前状态（激活/未激活） |
| `init` | `python $SKILLS_DIR/evolving-agent/scripts/run.py mode --init` | 强制重新初始化（同无参数运行） |

> **注意**: 执行前需设置 `SKILLS_DIR` 变量：
> ```bash
> SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)
> ```

### 示例

```bash
/evolve status
# Output: Evolution Mode Status: ACTIVE

/evolve off
# Output: Evolution Mode Deactivated.
```

## 3. 交互流程

当用户执行 `/evolve` 后，系统进入**协调模式**：

1. **意图侦听**: 等待用户输入（编程任务 / 知识归纳 / GitHub 学习）。
2. **智能调度**:
   - 输入 "帮我写个登录页面" -> 参考 `modules/programming-assistant/`
   - 输入 "学习这个仓库..." -> 参考 `modules/github-to-skills/`
   - 输入 "保存这次的经验" -> 参考 `modules/knowledge-base/`
3. **进化闭环**:
   - 在后台持续检测 `.opencode/.evolution_mode_active`
   - 自动提取有价值的经验并存入知识库

## 4. 与自然语言的区别

- **自然语言** ("开发一个功能..."): 直接触发 `programming-assistant`，并**隐式**尝试开启进化模式（如果未开启则初始化）。
- **命令** (`/evolve`): **显式**初始化环境，确立"协调器"的主导地位，适合作为会话的"握手"步骤。


path: modules/github-to-skills/README.md
content: # GitHub to Skills

从 GitHub 仓库提取结构化知识。

## 核心命令

```bash
# 设置路径变量
SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)

# 获取仓库信息
python $SKILLS_DIR/evolving-agent/scripts/run.py github fetch <github_url>

# 提取知识
python $SKILLS_DIR/evolving-agent/scripts/run.py github extract --input repo_info.json

# 存储到知识库
python $SKILLS_DIR/evolving-agent/scripts/run.py github store --input extracted.json
```

## 工作流程

```
fetch → extract → store → 进化检查
```

### 1. 获取信息

```bash
python $SKILLS_DIR/evolving-agent/scripts/run.py github fetch https://github.com/user/repo
```

输出：仓库元数据、文件树、README、配置文件。

### 2. 提取知识

```bash
python $SKILLS_DIR/evolving-agent/scripts/run.py github extract --input repo_info.json
```

| 提取内容 | 目标分类 |
|---------|---------|
| 框架配置 | tech-stacks/ |
| 目录结构、分层设计 | patterns/ |
| 代码规范 | skills/ |
| 常见报错修复 | problems/ |

### 3. 存储知识

```bash
python $SKILLS_DIR/evolving-agent/scripts/run.py github store --input extracted.json
```

更新知识库和索引。

## 使用场景

### 主动学习

```
用户: "学习这个仓库 https://github.com/shadcn/ui"

执行:
1. fetch → 获取仓库信息
2. extract → 提取组件模式、Tailwind配置
3. store → 存储到 patterns/, tech-stacks/
4. 进化检查 → 如有额外经验则归纳
```

### 知识复用

```
用户: "用 shadcn/ui 风格写卡片组件"

执行:
1. 检索 "shadcn", "ui"
2. 命中 patterns/shadcn-component.json
3. 加载到上下文
4. 生成符合规范的代码
```

## 约束

1. **Schema 兼容**: 必须符合 `knowledge-base/schema.json`
2. **不存储源码**: 仅提取知识，不克隆完整仓库
3. **原子化**: 拆分存储，不打包成大文件
4. **幂等性**: 重复学习更新现有条目


path: modules/knowledge-base/README.md
content: # Knowledge Base

知识存储、查询、归纳系统。

## 知识分类

| 分类 | 目录 | 触发场景 |
|------|------|----------|
| experience | `experiences/` | 优化、重构、最佳实践 |
| tech-stack | `tech-stacks/` | 框架相关 |
| scenario | `scenarios/` | 创建、实现功能 |
| problem | `problems/` | 修复、调试、报错 |
| testing | `testing/` | 测试相关 |
| pattern | `patterns/` | 架构、设计模式 |
| skill | `skills/` | 通用技巧 |

## 核心命令

```bash
# 设置路径变量
SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)

# 查询
python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge query --stats           # 统计
python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge query --trigger "react,hooks"  # 按触发词
python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge query --category problem       # 按分类
python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge query --search "跨域"          # 全文搜索

# 触发检测
python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge trigger --input "修复CORS问题"
python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge trigger --input "..." --project .

# 归纳存储
echo "内容" | python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store

# 存储
python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge store --category experience --name "xxx"
```

## 工作流程

### 检索流程（任务开始时）

```python
Task(
    subagent_type="general",
    description="Knowledge retrieval",
    prompt="SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills) && python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge trigger --input '...' --format context > .opencode/.knowledge-context.md"
)
```

### 归纳流程（任务结束后）

检查 `.opencode/.evolution_mode_active`，满足条件则：

```python
Task(
    subagent_type="general",
    description="Knowledge summarization",
    prompt="SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills) && echo '{summary}' | python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store"
)
```

## 知识条目 Schema

```json
{
  "id": "category-name-hash",
  "category": "experience|tech-stack|scenario|problem|testing|pattern|skill",
  "name": "名称",
  "triggers": ["触发词"],
  "content": {},
  "sources": ["来源"],
  "created_at": "ISO-8601",
  "effectiveness": 0.5
}
```

## 数据位置

```
~/.config/opencode/knowledge/   # OpenCode
~/.claude/knowledge/            # Claude Code / Cursor
```

## 子代理

| 代理 | 文件 | 用途 |
|------|------|------|
| retrieval | `agents/retrieval-agent.md` | 异步检索 |
| summarize | `agents/summarize-agent.md` | 异步归纳 |


path: modules/knowledge-base/agents/retrieval-agent.md
content: # Knowledge Retrieval Agent

异步检索知识，不阻塞主任务。

## 输入

- `user_input`: 用户原始输入
- `project_dir`: 项目目录（可选）

## 执行

```bash
SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)

python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge trigger \
  --input "{user_input}" \
  --project "{project_dir}" \
  --format context > .opencode/.knowledge-context.md
```

## 输出

写入 `.opencode/.knowledge-context.md`:

```markdown
## 相关知识
### [category] Name
**关键信息**: ...

## 可能相关
- [category] Name
```

## 调用方式

```python
Task(
  subagent_type="general",
  description="Knowledge retrieval",
  prompt="执行知识检索，输出到 .opencode/.knowledge-context.md"
)
```

## 约束

- 快速执行（< 5秒）
- 不询问用户
- 出错时写入空文件


path: modules/knowledge-base/agents/summarize-agent.md
content: # Knowledge Summarize Agent

异步分析会话提取知识，不阻塞主任务。

## ⚠️ 重要：长会话和大任务需要先压缩

对于复杂任务，在归纳经验前**必须先使用 `/compact` 压缩会话**：

**触发条件**：
- `.opencode/feature_list.json` 中任务数 > 10，或
- 会话轮数 > 50

**重要**：`.opencode/progress.txt` 只保存当前任务信息，不包含历史任务。对于多任务项目，需要结合会话历史进行经验提取。

## 输入

- `session_content`: 会话内容
- `session_id`: 会话标识符

## 执行

### 方式1：简单任务（从 progress.txt 提取）

适用于任务数 ≤ 10 的简单项目。`.opencode/progress.txt` 包含当前任务的结构化经验：

```bash
SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)

# 检查任务数
TASK_COUNT=0
if [ -f .opencode/feature_list.json ]; then
  TASK_COUNT=$(jq '.features | length' .opencode/feature_list.json 2>/dev/null || echo 0)
fi

# 简单任务（任务数 ≤ 10）：直接从 progress.txt 提取
if [ "$TASK_COUNT" -le 10 ] && [ -f .opencode/progress.txt ]; then
  {
    echo "## 经验总结"
    sed -n '/## 遇到的问题/,/^$/p' .opencode/progress.txt
    sed -n '/## 关键决策/,/^$/p' .opencode/progress.txt
  } | python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store
else
  echo "任务数 > 10，建议先执行 /compact，然后结合会话历史归纳总结"
fi
```

**`.opencode/progress.txt` 示例格式**：
```
## 当前任务
- [ ] 添加 JWT 中间件

## 遇到的问题
- bcrypt 编译失败 → 改用 @node-rs/bcrypt
- CORS 跨域报错 → Vite配置 server.proxy

## 关键决策
- JWT secret 存储在环境变量
- 使用 httpOnly cookie 提高安全性
```

### 方式2：复杂任务（结合会话历史）

适用于任务数 > 10 或会话轮数 > 50 的复杂项目：

```bash
# 步骤1: 先执行 /compact 压缩会话历史
# 步骤2: 结合压缩后的摘要 + progress.txt 归纳
# 注意：不要只读 progress.txt，因为它只包含当前任务信息

echo "基于压缩后的会话摘要，归纳所有任务的关键经验：
- 问题1: ...
- 问题2: ...
- 决策1: ...
- 决策2: ...
" | python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store
```

### 其他方式

```bash
# 方式4: 从压缩后的摘要归纳
echo "{/compact 后的摘要}" | \
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store

# 方式5: 手动输入关键经验
echo "问题：跨域报错 → 解决：Vite配置proxy" | \
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store

# 方式6: 从文件读取
cat experience.txt | \
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store --session-id "session-123"
```

**建议优先级**:
1. **检查任务复杂度**（任务数 > 10 或会话轮数 > 50）
2. **简单任务**（任务数 ≤ 10）：从 `.opencode/progress.txt` 提取
3. **复杂任务**（任务数 > 10）：先执行 `/compact`，然后结合会话历史 + `.opencode/progress.txt` 归纳
4. **重要**：不要只依赖 `progress.txt`，它只包含当前任务信息

## 知识类型映射

| 内容 | 分类 |
|------|------|
| 问题-解决方案 | problem |
| 最佳实践 | experience |
| 注意事项/坑 | experience |
| 技术模式 | pattern/tech-stack |

## 输出

写入 `.knowledge-summary.md`:

```markdown
## 提取的知识
| 分类 | 名称 | ID |
|------|------|-----|
| problem | CORS问题 | problem-cors-xxx |

## 统计
- 提取条目: 3
- 已存储: 3
```

## 调用方式

```python
Task(
  subagent_type="general",
  description="Knowledge summarization",
  prompt="分析会话并归纳知识"
)
```

## 触发时机

1. **自动**: 进化模式激活时
2. **手动**: 用户说"记住"、"/evolve"

## 约束

- 异步执行
- 不询问用户
- 自动去重


path: modules/knowledge-base/schema.json
content: {
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Unified Knowledge Base Schema",
  "version": "1.0.0",
  "description": "统一知识库 schema 定义，用于存储和查询各类编程知识",
  
  "definitions": {
    "KnowledgeEntry": {
      "type": "object",
      "required": ["id", "category", "name", "triggers", "content", "created_at"],
      "properties": {
        "id": {
          "type": "string",
          "description": "唯一标识符，格式: <category>-<hash>"
        },
        "category": {
          "type": "string",
          "enum": ["experience", "tech-stack", "scenario", "problem", "testing", "pattern", "skill"],
          "description": "知识分类"
        },
        "name": {
          "type": "string",
          "description": "知识条目名称"
        },
        "triggers": {
          "type": "array",
          "items": { "type": "string" },
          "description": "触发加载的关键字列表"
        },
        "content": {
          "type": "object",
          "description": "具体知识内容，结构因 category 而异"
        },
        "sources": {
          "type": "array",
          "items": { "type": "string" },
          "description": "知识来源（GitHub URL、会话 ID 等）"
        },
        "tags": {
          "type": "array",
          "items": { "type": "string" },
          "description": "额外标签，用于精细化检索"
        },
        "created_at": {
          "type": "string",
          "format": "date-time",
          "description": "创建时间"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time",
          "description": "最后更新时间"
        },
        "usage_count": {
          "type": "integer",
          "default": 0,
          "description": "被使用次数"
        },
        "effectiveness": {
          "type": "number",
          "minimum": 0,
          "maximum": 1,
          "default": 0.5,
          "description": "有效性评分 (0-1)，通过反馈更新"
        }
      }
    },
    
    "ExperienceContent": {
      "type": "object",
      "description": "经验类知识内容",
      "properties": {
        "description": { "type": "string" },
        "context": { "type": "string", "description": "适用场景描述" },
        "solution": { "type": "string", "description": "解决方案" },
        "pitfalls": { 
          "type": "array", 
          "items": { "type": "string" },
          "description": "需要避免的坑"
        },
        "related_tech": {
          "type": "array",
          "items": { "type": "string" }
        }
      }
    },
    
    "TechStackContent": {
      "type": "object",
      "description": "技术栈知识内容",
      "properties": {
        "tech_name": { "type": "string" },
        "version": { "type": "string" },
        "best_practices": {
          "type": "array",
          "items": { "type": "string" }
        },
        "conventions": {
          "type": "array",
          "items": { "type": "string" }
        },
        "common_patterns": {
          "type": "array",
          "items": { "type": "string" }
        },
        "gotchas": {
          "type": "array",
          "items": { "type": "string" },
          "description": "常见陷阱"
        }
      }
    },
    
    "ScenarioContent": {
      "type": "object",
      "description": "场景类知识内容",
      "properties": {
        "scenario_name": { "type": "string" },
        "description": { "type": "string" },
        "typical_approach": { "type": "string" },
        "steps": {
          "type": "array",
          "items": { "type": "string" }
        },
        "considerations": {
          "type": "array",
          "items": { "type": "string" }
        },
        "related_tech": {
          "type": "array",
          "items": { "type": "string" }
        }
      }
    },
    
    "ProblemContent": {
      "type": "object",
      "description": "问题类知识内容",
      "properties": {
        "problem_name": { "type": "string" },
        "symptoms": {
          "type": "array",
          "items": { "type": "string" },
          "description": "问题症状"
        },
        "root_causes": {
          "type": "array",
          "items": { "type": "string" },
          "description": "根本原因"
        },
        "solutions": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "description": { "type": "string" },
              "code_example": { "type": "string" },
              "applicable_when": { "type": "string" }
            }
          }
        },
        "prevention": {
          "type": "array",
          "items": { "type": "string" },
          "description": "预防措施"
        }
      }
    },
    
    "TestingContent": {
      "type": "object",
      "description": "测试类知识内容",
      "properties": {
        "testing_type": { 
          "type": "string",
          "enum": ["unit", "integration", "e2e", "performance", "security"]
        },
        "framework": { "type": "string" },
        "best_practices": {
          "type": "array",
          "items": { "type": "string" }
        },
        "patterns": {
          "type": "array",
          "items": { "type": "string" }
        },
        "anti_patterns": {
          "type": "array",
          "items": { "type": "string" }
        },
        "example_structure": { "type": "string" }
      }
    },
    
    "PatternContent": {
      "type": "object",
      "description": "编程范式内容",
      "properties": {
        "pattern_name": { "type": "string" },
        "category": { 
          "type": "string",
          "enum": ["architecture", "design", "behavioral", "creational", "structural"]
        },
        "description": { "type": "string" },
        "when_to_use": { "type": "string" },
        "structure": { "type": "string" },
        "example": { "type": "string" },
        "pros": { "type": "array", "items": { "type": "string" } },
        "cons": { "type": "array", "items": { "type": "string" } }
      }
    },
    
    "SkillContent": {
      "type": "object",
      "description": "编程技能内容",
      "properties": {
        "skill_name": { "type": "string" },
        "level": {
          "type": "string",
          "enum": ["basic", "intermediate", "advanced"]
        },
        "description": { "type": "string" },
        "key_concepts": {
          "type": "array",
          "items": { "type": "string" }
        },
        "practical_tips": {
          "type": "array",
          "items": { "type": "string" }
        },
        "common_mistakes": {
          "type": "array",
          "items": { "type": "string" }
        }
      }
    }
  },
  
  "categories": {
    "experience": {
      "description": "经验积累：从实际编程会话中提取的经验教训",
      "content_schema": { "$ref": "#/definitions/ExperienceContent" }
    },
    "tech-stack": {
      "description": "技术栈积累：特定技术/框架的最佳实践和惯例",
      "content_schema": { "$ref": "#/definitions/TechStackContent" }
    },
    "scenario": {
      "description": "场景积累：特定编程场景的处理方法",
      "content_schema": { "$ref": "#/definitions/ScenarioContent" }
    },
    "problem": {
      "description": "问题积累：常见问题的诊断和解决方案",
      "content_schema": { "$ref": "#/definitions/ProblemContent" }
    },
    "testing": {
      "description": "测试积累：测试相关的最佳实践和模式",
      "content_schema": { "$ref": "#/definitions/TestingContent" }
    },
    "pattern": {
      "description": "编程范式：架构和设计模式",
      "content_schema": { "$ref": "#/definitions/PatternContent" }
    },
    "skill": {
      "description": "编程技能：通用编程技能和技巧",
      "content_schema": { "$ref": "#/definitions/SkillContent" }
    }
  }
}


path: modules/programming-assistant/README.md
content: # Programming Assistant

代码生成与问题修复的执行引擎（子进程）。

> **渐进披露原则**: 本文件定义模块入口和核心原则，详细流程委托给 `workflows/`。

---

## 核心原则

### 1. 状态文件驱动

通过状态文件跟踪任务，**不依赖会话历史**，节省 token。

| 文件 | 用途 | 模板 |
|------|------|------|
| `.opencode/feature_list.json` | 任务清单和状态 | `./template/feature_list.json` |
| `.opencode/progress.txt` | 当前任务进度 | `./template/progress.txt` |


**⚠️ 重要**: 
- `.opencode` 必须创建在**项目根目录**（git 仓库根目录），不是当前工作目录！
> 
> ```bash
> # 获取项目根目录
> PROJECT_ROOT=$(git rev-parse --show-toplevel)
> # 状态文件路径
> $PROJECT_ROOT/.opencode/feature_list.json
> $PROJECT_ROOT/.opencode/progress.txt
> ```
- `progress.txt` 只保存当前任务详情，历史任务查看 `feature_list.json`。

### 2. ⚠️ 禁止中途停止

```
❌ 禁止行为:
- 完成任务后输出"总结报告"然后停止
- 看到 review/文档任务后停止
- 中途询问"是否继续"

✅ 正确行为:
- 完成任务后，立即检查是否还有 pending 任务
- 如有 pending 任务，继续执行下一个
- 循环执行直到所有任务 completed
```

---

## 模式选择（必须执行）

根据用户意图选择对应模式，**立即加载并执行**：

| 场景 | 触发词 | 模式 | 加载文档 |
|------|--------|------|----------|
| 新建/功能 | 创建、实现、添加、开发、继续、完成 | Full Mode | `./workflows/full-mode.md` |
| 修复/重构 | 修复、fix、bug、重构、优化、review | Simple Mode | `./workflows/simple-mode.md` |
| 咨询 | 怎么、为什么、解释 | Direct Answer | 直接回答，无需加载 |

---

## 核心流程概览

```
知识检索 → 状态恢复 → WHILE 有 pending 任务:
    确定任务 → in_progress → 执行 → completed → 健康检查
→ 结果验证 → 知识归纳
```

> **详细流程见各模式文档**，本文件仅提供概览。

---

## 与主进程的协调

| 主进程步骤 | 子进程对应 | 检查内容 |
|------------|------------|----------|
| 步骤5: 健康检查 | 循环中 3.5 | pending 任务数、blocked 状态 |
| 步骤6: 结果验证 | 循环后步骤4 | 所有任务 completed |

---

## 执行规范

1. **知识优先**: 先检索历史经验，避免重复劳动
2. **状态优先**: 先读状态文件，再开始工作
3. **理解优先**: 先读代码，再修改
4. **最小改动**: 选择破坏性最小的方案
5. **状态透明**: 每步完成后立即更新状态文件
6. **经验积累**: 循环结束后执行进化检查

---

## 任务拆解原则

使用 `sequential-thinking` 分析后写入 `feature_list.json`：

- **< 30分钟**可完成
- **可独立测试**验证
- **单一职责**，专注一个问题
- **按依赖顺序**排列

---

## 进化检查（循环结束后必须执行）

按照 `./workflows/evolution-check.md` 执行：

| 场景 | 提取经验 |
|------|----------|
| 修复失败2次后成功 | ✅ |
| 发现隐蔽的 bug 根因 | ✅ |
| 环境特定的 workaround | ✅ |
| 总结出可复用模式 | ✅ |
| 用户说"记住这个" | ✅ |
| 简单修改一行代码 | ❌ |

---

## 命令速查

```bash
# 设置路径（每个 shell 会话执行一次）
SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)

# 知识检索
python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge trigger --input "..."

# 项目检测
python $SKILLS_DIR/evolving-agent/scripts/run.py project detect .

# 进化模式
python $SKILLS_DIR/evolving-agent/scripts/run.py mode --status
```


path: modules/programming-assistant/template/feature_list.json
content: {
  "project": "项目名称",
  "created_at": "2024-01-01T00:00:00Z",
  "updated_at": "2024-01-01T00:00:00Z",
  "features": [
    {
      "id": 1,
      "name": "创建 User 模型",
      "status": "completed",
      "notes": "使用 Prisma ORM"
    },
    {
      "id": 2,
      "name": "实现密码哈希",
      "status": "in_progress",
      "notes": ""
    },
    {
      "id": 3,
      "name": "创建注册 API",
      "status": "pending",
      "dependencies": [1, 2],
      "notes": ""
    }
  ]
}


path: modules/programming-assistant/template/progress.txt
content: 
# Progress Log - 项目名称
# 最后更新: 2024-01-01 10:00

## 当前任务
- [ ] 实现密码哈希 (feature_list.json #2)

## 本次完成
- [x] 创建 User 模型 - 使用 Prisma，包含 id/email/password/createdAt 字段

## 下一步（具体可执行）
1. 安装 bcrypt 依赖: npm install bcrypt @types/bcrypt
2. 创建 src/utils/password.ts
3. 实现 hashPassword() 和 verifyPassword() 函数
4. 编写单元测试

## 遇到的问题
- Prisma 初始化报错 → 需要先运行 npx prisma generate

## 关键决策
- 选择 bcrypt 而非 argon2，因为 bcrypt 更成熟且无需 native 编译

path: modules/programming-assistant/workflows/evolution-check.md
content: # Evolution Check - 进化检查

**必须**在每个开发/修复循环结束后执行。

---

## ⚠️ 重要：知识归纳格式

```
❌ 错误方式（多行文本会返回空结果）:
cat << 'EOF' | python ... knowledge summarize --auto-store
问题1：xxx
问题2：xxx
EOF

✅ 正确方式（每条经验单独存储）:
echo "问题：xxx → 解决：yyy" | python ... knowledge summarize --auto-store
echo "问题：aaa → 解决：bbb" | python ... knowledge summarize --auto-store
```

**原则**: 一条命令存储一条经验，格式为 `问题：xxx → 解决：yyy`

---

## 激活进化模式条件

| 场景                | 激活 |
| ------------------- | ---- |
| 修复失败2次后成功   | ✅ |
| 用户说"记住这个"    | ✅ |
| 发现更优方案        | ✅ |
| 特定环境 workaround | ✅ |
| 简单修改一行代码    | ❌ |
| 用户说"很好"、"ok"  | ❌ |

---

## 检查流程

```
步骤1: 检测任务复杂度
    读取 .opencode/feature_list.json 检查任务数
    ├─ 任务数 > 10 或 会话轮数 > 50 → 需要压缩
    └─ 简单任务 → 直接归纳

步骤2: 会话压缩（如需要）
    执行 /compact 命令压缩会话历史
    保留关键决策、问题解决方案、技术栈选择等

步骤3: 激活进化模式
    根据激活条件判断：
    ├─ 满足条件：执行 python $SKILLS_DIR/evolving-agent/scripts/run.py mode --init
    └─ 不满足：直接返回

步骤4: 知识归纳
    从以下内容提取经验：
    - .opencode/progress.txt 的"遇到的问题"和"关键决策"
    - 会话中的关键发现
    ⚠️ 每条经验单独存储，不要批量存储！
```

---

## 存储命令

```bash
# 设置路径变量（每个 shell 会话执行一次）
SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)

# ⚠️ 每条经验单独存储
echo "问题：xxx → 解决：yyy" | python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store
```

---

## 示例

### 示例1: 单个问题修复

```bash
# 修复跨域问题后存储经验
echo "问题：Vite项目跨域报错 → 解决：配置 server.proxy" | \
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store
```

### 示例2: 多个问题（分开存储）

```bash
# ⚠️ 每条经验单独一行命令
echo "问题：bcrypt 编译报错 → 解决：使用 @node-rs/bcrypt 替代" | \
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store

echo "问题：JWT 中间件顺序错误 → 解决：需要在路由之前注册" | \
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store

echo "决策：选择 bcrypt 而非 argon2，因为 bcrypt 更成熟" | \
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store
```

### 示例3: 快速存储偏好

```bash
echo "偏好：Node.js 项目统一使用 pnpm 作为包管理器" | \
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge summarize --auto-store
```

---

## 注意事项

- **单条存储**: 每条经验单独一个 `echo | python` 命令，不要批量
- **格式规范**: 使用 `问题：xxx → 解决：yyy` 或 `决策：xxx` 格式
- **去重机制**: 知识库会自动去重相似条目
- **静默模式**: 只在存储成功后简短通知，不阻塞主流程


path: modules/programming-assistant/workflows/full-mode.md
content: # Full Mode - 完整开发

适用于新建项目和功能开发。

> **核心原则**: 通过状态文件跟踪进度，不依赖会话历史，节省 token。

---

## ⚠️ 重要约束

```
❌ 禁止行为:
- 完成一个任务后输出"总结报告"然后停止
- 看到 review/文档任务后停止执行
- 完成高优先级任务后等待用户确认
- 中途询问"是否继续"

✅ 正确行为:
- 完成任务后，立即检查 feature_list.json 是否还有 pending 任务
- 如有 pending 任务，继续执行下一个
- 只有所有任务状态都是 completed 时，才输出最终总结
- 循环执行直到没有 pending 任务
```

---

## 核心流程

```
步骤0: 环境准备（必须执行）
  # 设置路径变量
  SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)
  
  # 获取项目根目录（避免在 submodule 中创建 .opencode）
  PROJECT_ROOT=$(git rev-parse --show-toplevel)
  
  # 知识检索
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge trigger \
    --input "用户输入描述" --format context > $PROJECT_ROOT/.opencode/.knowledge-context.md
  读取 $PROJECT_ROOT/.opencode/.knowledge-context.md 获取相关历史经验

步骤1: 状态恢复与任务分析
  使用 `sequential-thinking` 工具进行深度分析
  ├─ 存在 $PROJECT_ROOT/.opencode/feature_list.json → 读取任务列表，恢复上下文
  │   └─ 存在 $PROJECT_ROOT/.opencode/progress.txt → 读取当前进度和"下一步"
  └─ 不存在 → 执行初始化（步骤2）

步骤2: 任务拆解与初始化（仅首次）
  使用 `sequential-thinking` 生成 todos
  ├─ 在项目根目录创建 $PROJECT_ROOT/.opencode/feature_list.json（模板: ../template/feature_list.json）
  ├─ 将 todos 写入 feature_list.json
  └─ 选取第一个 pending 任务，写入 $PROJECT_ROOT/.opencode/progress.txt（模板: ../template/progress.txt）
  
  ⚠️ 注意：.opencode 必须在 $PROJECT_ROOT 下，不是当前目录！

步骤3: 编程循环 [WHILE 有 pending 任务]
  3.1 确定当前任务
      ├─ 从 progress.txt 的"下一步"继续
      └─ 或从 feature_list.json 选择第一个 pending 任务
  
  3.2 更新状态为 in_progress
      ├─ 修改 feature_list.json 中对应任务的 status
      └─ 更新 progress.txt 的"当前任务"
  
  3.3 执行开发
      ├─ 编写代码
      ├─ 编写单元测试
      └─ 运行测试验证
  
  3.4 测试通过，更新状态
      ├─ 修改 feature_list.json: status → completed
      ├─ 更新 progress.txt: 移动到"本次完成"
      └─ 记录"遇到的问题"和"关键决策"
  
  3.5 健康检查（主进程协调点）
      ├─ 检查是否还有 pending 任务
      ├─ 如有 → 回到 3.1
      └─ 如无 → 退出循环

步骤4: 结果验证（主进程协调点）
  ├─ 确认所有任务状态为 completed
  └─ 输出最终执行摘要

步骤5: 知识归纳
  按照 ./evolution-check.md 执行进化检查
```

---

## 任务拆解原则

每个任务必须满足：
- **< 30分钟**可完成
- **可独立测试**验证
- **单一职责**，专注一个问题
- **按依赖顺序**排列

---

## 错误处理

```
错误发生 → 分析原因 → 尝试方案（最多3次）
├─ 成功 → 继续执行，记录到 progress.txt "遇到的问题"
└─ 连续失败 → 回退 + 记录详情 + 标记 blocked + 报告用户
```

---

## 状态文件说明

| 文件 | 用途 | 模板 |
|------|------|------|
| `$PROJECT_ROOT/.opencode/feature_list.json` | 任务清单和状态 | `../template/feature_list.json` |
| `$PROJECT_ROOT/.opencode/progress.txt` | 当前任务进度 | `../template/progress.txt` |

> **注意**: `progress.txt` 只保存当前任务详情，历史任务查看 `feature_list.json`


path: modules/programming-assistant/workflows/simple-mode.md
content: # Simple Mode - 快速修复

适用于问题修复、代码重构、代码审查。

> **核心原则**: 通过状态文件跟踪进度，不依赖会话历史，节省 token。

---

## ⚠️ 重要约束

```
❌ 禁止行为:
- 完成一个修复后输出"总结报告"然后停止
- 中途询问"是否继续"
- 完成主要修复后忽略后续验证步骤

✅ 正确行为:
- 完成修复后，检查是否还有 pending 任务
- 如有 pending 任务，继续执行下一个
- 完成所有修复和验证后才能结束
```

---

## 核心流程

```
步骤0: 环境准备（必须执行）
  # 设置路径变量
  SKILLS_DIR=$([ -d ~/.config/opencode/skills/evolving-agent ] && echo ~/.config/opencode/skills || echo ~/.claude/skills)
  
  # 获取项目根目录（避免在 submodule 中创建 .opencode）
  PROJECT_ROOT=$(git rev-parse --show-toplevel)
  
  # 知识检索
  python $SKILLS_DIR/evolving-agent/scripts/run.py knowledge trigger \
    --input "用户问题描述" --format context > $PROJECT_ROOT/.opencode/.knowledge-context.md
  读取 $PROJECT_ROOT/.opencode/.knowledge-context.md 获取相关历史经验
  > 利用历史经验，快速定位类似问题

步骤1: 状态恢复与问题分析
  使用 `sequential-thinking` 工具进行深度分析
  ├─ 存在 $PROJECT_ROOT/.opencode/progress.txt → 读取当前进度和"下一步"
  │   └─ 存在 $PROJECT_ROOT/.opencode/feature_list.json → 读取任务列表
  └─ 不存在 → 根据用户描述在 $PROJECT_ROOT/.opencode/ 创建新任务

步骤2: 问题理解（修复前必须）
  ├─ 复现问题 - 确认能稳定复现
  ├─ 分析根因 - 定位问题源头
  └─ 制定方案 - 选择最小化修改

步骤3: 修复循环 [WHILE 有 pending 任务]
  3.1 确定当前任务
      ├─ 从 progress.txt 的"下一步"继续
      └─ 或从 feature_list.json 选择第一个 pending 任务
  
  3.2 更新状态为 in_progress
      ├─ 更新 progress.txt 的"当前任务"
      └─ 修改 feature_list.json 中对应任务的 status（如有）
  
  3.3 执行修复
      ├─ 最小化修改代码
      ├─ 运行测试验证
      └─ 确认无回归
  
  3.4 修复完成，更新状态
      ├─ 更新 progress.txt: 移动到"本次完成"
      ├─ 记录"问题根因"和"关键发现"
      ├─ 修改 feature_list.json: status → completed（如有）
      └─ git commit
  
  3.5 健康检查（主进程协调点）
      ├─ 检查是否还有 pending 任务
      ├─ 如有 → 回到 3.1
      └─ 如无 → 退出循环

步骤4: 结果验证（主进程协调点）
  ├─ 确认所有修复任务完成
  ├─ 运行完整测试确认无回归
  └─ 输出修复摘要

步骤5: 知识归纳
  按照 ./evolution-check.md 执行进化检查
```

---

## 何时创建 feature_list.json

| 场景 | 是否创建 |
|------|----------|
| 涉及多个文件 | ✅ |
| 修复范围较大 | ✅ |
| 根因不明确需多步排查 | ✅ |
| 预计需要多个会话完成 | ✅ |
| 单文件简单修复 | ❌ |

---

## 任务拆解原则

```
❌ 模糊: "修复登录失败"

✅ 清晰:
  1. 复现问题 - 记录错误日志和请求参数
  2. 检查前端请求 - 确认请求格式正确
  3. 检查后端响应 - 查看具体错误信息
  4. 定位失败点 - 确定是哪个环节出错
  5. 修复代码 - 最小化改动
  6. 验证修复 - 确认问题解决且无回归
```

---

## 验证方法

| 类型 | 方法 | 时机 |
|------|------|------|
| 编译 | 运行构建命令 | 每次修改后 |
| 单测 | 运行相关测试 | 修复完成后 |
| 功能 | curl/手动测试 | 验证修复 |
| 回归 | 运行完整测试 | 最终验证 |

---

## 错误处理

```
修复失败 → 分析原因 → 尝试方案（最多3次）
├─ 成功 → 继续执行，记录到 progress.txt "问题根因"
└─ 连续失败 → 回退 + 记录详情 + 标记 blocked + 报告用户
```

---

## 状态文件说明

| 文件 | 用途 | 创建时机 |
|------|------|----------|
| `$PROJECT_ROOT/.opencode/progress.txt` | 当前修复进度 | 必须创建 |
| `$PROJECT_ROOT/.opencode/feature_list.json` | 任务清单 | 复杂修复时创建 |


### progress.txt 模板（修复专用）

```
# Progress Log - 修复记录
# 最后更新: 2024-01-01 10:00

## 当前任务
- [ ] 修复登录接口 500 错误

## 本次完成
- [x] 定位问题：数据库连接池耗尽
- [x] 修复：增加连接池大小 10 → 50
- [x] 验证：登录接口恢复正常

## 下一步（如有后续）
1. 添加连接池监控日志
2. 设置连接池告警阈值

## 问题根因
- 现象：登录接口随机 500 错误
- 根因：高并发下数据库连接池耗尽
- 方案：增加连接池大小，添加重试机制

## 关键发现
- 连接泄漏来自未关闭的事务
- 需要添加 finally 块确保连接释放
```

---

## 必须提取经验的场景

| 场景 | 提取 |
|------|------|
| 修复失败2次后成功 | ✅ |
| 发现隐蔽的 bug 根因 | ✅ |
| 环境特定的 workaround | ✅ |
| 用户明确要求记住 | ✅ |
| 简单一行代码修改 | ❌ |


path: scripts/__init__.py
content: # evolving-agent scripts package


path: scripts/core/__init__.py
content: # Core scripts package
from .path_resolver import (
    detect_platform,
    get_skills_dir,
    get_venv_python,
    get_knowledge_base_dir,
    get_script_path,
    get_run_command,
)


path: scripts/core/align_all.py
content: import os
import sys
import subprocess

def align_all(skills_root):
    if not os.path.exists(skills_root):
        print(f"Error: {skills_root} not found")
        return

    stitch_script = os.path.join(os.path.dirname(__file__), "smart_stitch.py")
    
    count = 0
    for item in os.listdir(skills_root):
        skill_dir = os.path.join(skills_root, item)
        if not os.path.isdir(skill_dir):
            continue
            
        evolution_json = os.path.join(skill_dir, "evolution.json")
        if os.path.exists(evolution_json):
            print(f"Aligning {item}...")
            # Run the smart_stitch script for this skill
            subprocess.run([sys.executable, stitch_script, skill_dir])
            count += 1
            
    print(f"\nFinished. Aligned {count} skills.")

if __name__ == "__main__":
    # Use standard skills path
    skills_path = r"C:\Users\20515\.claude\skills"
    if len(sys.argv) > 1:
        skills_path = sys.argv[1]
    align_all(skills_path)


path: scripts/core/merge_evolution.py
content: import os
import sys
import json
import datetime

def merge_evolution(skill_dir, new_data_json_str):
    """
    Merges new evolution data into existing evolution.json.
    Deduplicates list items.
    """
    evolution_json_path = os.path.join(skill_dir, "evolution.json")

    # Load existing or create new
    if os.path.exists(evolution_json_path):
        try:
            with open(evolution_json_path, 'r', encoding='utf-8') as f:
                current_data = json.load(f)
        except FileNotFoundError:
            print(f"Error: evolution.json not found in {skill_dir}", file=sys.stderr)
            current_data = {}
        except json.JSONDecodeError as e:
            print(f"Error: Invalid JSON in evolution.json: {e}", file=sys.stderr)
            current_data = {}
        except Exception as e:
            print(f"Error reading evolution.json: {type(e).__name__}: {e}", file=sys.stderr)
            current_data = {}
    else:
        current_data = {}

    try:
        new_data = json.loads(new_data_json_str)
    except json.JSONDecodeError as e:
        print(f"Error decoding new data JSON: {e}", file=sys.stderr)
        return False
    except TypeError as e:
        print(f"Error: Invalid input type for JSON parsing: {e}", file=sys.stderr)
        return False

    # Merge logic
    # 1. Update timestamp
    current_data['last_updated'] = datetime.datetime.now().isoformat()

    # 2. Merge Lists (preferences, fixes, contexts) with deduplication
    for list_key in ['preferences', 'fixes', 'contexts']:
        if list_key in new_data:
            existing_list = current_data.get(list_key, [])
            new_items = new_data[list_key]
            if isinstance(new_items, list):
                # Simple dedupe by string equality
                for item in new_items:
                    if item not in existing_list:
                        existing_list.append(item)
                current_data[list_key] = existing_list

    # 3. Deep merge nested dicts (patterns, context_triggers)
    for dict_key in ['patterns', 'context_triggers']:
        if dict_key in new_data:
            existing_dict = current_data.get(dict_key, {})
            new_dict = new_data[dict_key]
            if isinstance(new_dict, dict):
                for key, value in new_dict.items():
                    if isinstance(value, list):
                        # Merge lists within nested dict
                        existing_list = existing_dict.get(key, [])
                        for item in value:
                            if item not in existing_list:
                                existing_list.append(item)
                        existing_dict[key] = existing_list
                    else:
                        # Overwrite non-list values
                        existing_dict[key] = value
                current_data[dict_key] = existing_dict
                
    # 3. Overwrite/Append Custom Prompts (Concatenate if exists to preserve history? Or overwrite?)
    # Decision: Overwrite if provided, as prompts usually need to be coherent. 
    # Or, the Agent should have read the old one and combined it before sending here.
    # We assume Agent sends the FINAL desired state for custom_prompts if it wants to merge.
    if 'custom_prompts' in new_data:
        current_data['custom_prompts'] = new_data['custom_prompts']

    # 4. Update last_evolved_hash if provided
    if 'last_evolved_hash' in new_data:
        current_data['last_evolved_hash'] = new_data['last_evolved_hash']

    # Save back
    with open(evolution_json_path, 'w', encoding='utf-8') as f:
        json.dump(current_data, f, indent=2, ensure_ascii=False)
        
    print(f"Successfully merged evolution data for {os.path.basename(skill_dir)}")
    return True

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python merge_evolution.py <skill_dir> <json_string>")
        sys.exit(1)
        
    skill_dir = sys.argv[1]
    json_str = sys.argv[2]
    merge_evolution(skill_dir, json_str)


path: scripts/core/path_resolver.py
content: #!/usr/bin/env python3
"""
Path Resolver - 统一路径解析模块

为 Evolving Programming Agent 提供跨平台的路径解析能力。
支持 OpenCode、Claude Code、Cursor 等多个平台。

使用方式：
    from path_resolver import get_skills_dir, get_venv_python, get_knowledge_base_dir

平台支持：
    - OpenCode: ~/.config/opencode/skills/
    - Claude Code / Cursor: ~/.claude/skills/
"""

import os
import sys
from pathlib import Path
from typing import Optional


# 平台配置
PLATFORM_CONFIGS = {
    'opencode': {
        'skills_dir': Path.home() / '.config' / 'opencode' / 'skills',
        'priority': 1,  # 优先级越小越优先
    },
    'claude': {
        'skills_dir': Path.home() / '.claude' / 'skills',
        'priority': 2,
    },
}

# 共享 venv 所在的 skill 名称
VENV_SKILL = 'evolving-agent'

# 知识数据目录配置 (独立于 skills 目录)
KNOWLEDGE_DIRS = {
    'opencode': Path.home() / '.config' / 'opencode' / 'knowledge',
    'claude': Path.home() / '.claude' / 'knowledge',
}


def detect_platform() -> str:
    """
    自动检测当前运行的平台。
    
    检测逻辑：
    1. 检查环境变量 SKILLS_PLATFORM (显式指定)
    2. 检查哪个 skills 目录存在且包含 evolving-agent
    3. 如果都存在，优先选择 OpenCode
    
    Returns:
        平台名称: 'opencode' 或 'claude'
    """
    # 1. 检查环境变量
    env_platform = os.environ.get('SKILLS_PLATFORM', '').lower()
    if env_platform in PLATFORM_CONFIGS:
        return env_platform
    
    # 2. 检查哪个目录存在 evolving-agent
    platforms_found = []
    for name, config in PLATFORM_CONFIGS.items():
        venv_dir = config['skills_dir'] / VENV_SKILL / '.venv'
        if venv_dir.exists():
            platforms_found.append((name, config['priority']))
    
    # 按优先级排序
    if platforms_found:
        platforms_found.sort(key=lambda x: x[1])
        return platforms_found[0][0]
    
    # 3. 如果都不存在，检查哪个 skills 目录存在
    for name, config in sorted(PLATFORM_CONFIGS.items(), key=lambda x: x[1]['priority']):
        if config['skills_dir'].exists():
            return name
    
    # 4. 默认返回 opencode
    return 'opencode'


def get_skills_dir(platform: Optional[str] = None) -> Path:
    """
    获取 skills 基础目录。
    
    优先级：
    1. 环境变量 SKILLS_BASE_DIR (显式覆盖)
    2. 根据平台自动检测
    
    Args:
        platform: 可选，指定平台名称 ('opencode' 或 'claude')
    
    Returns:
        skills 目录路径
    """
    # 1. 检查环境变量覆盖
    env_dir = os.environ.get('SKILLS_BASE_DIR')
    if env_dir:
        return Path(env_dir)
    
    # 2. 根据平台获取
    if platform is None:
        platform = detect_platform()
    
    if platform not in PLATFORM_CONFIGS:
        raise ValueError(f"Unknown platform: {platform}. Must be one of: {list(PLATFORM_CONFIGS.keys())}")
    
    return PLATFORM_CONFIGS[platform]['skills_dir']


def get_venv_python(platform: Optional[str] = None) -> Path:
    """
    获取共享 venv 的 Python 解释器路径。
    
    Args:
        platform: 可选，指定平台名称
    
    Returns:
        Python 解释器路径
    """
    skills_dir = get_skills_dir(platform)
    return skills_dir / VENV_SKILL / '.venv' / 'bin' / 'python'


def get_knowledge_base_dir(platform: Optional[str] = None) -> Path:
    """
    获取知识库目录。
    
    知识数据存储在独立目录，与 skills 代码分离：
    - OpenCode: ~/.config/opencode/knowledge/
    - Claude: ~/.claude/knowledge/
    
    优先级：
    1. 环境变量 KNOWLEDGE_BASE_PATH (显式覆盖)
    2. 已存在的知识库目录（优先 OpenCode）
    3. 根据平台自动确定
    
    Args:
        platform: 可选，指定平台名称
    
    Returns:
        知识库目录路径
    """
    # 1. 检查环境变量覆盖
    env_path = os.environ.get('KNOWLEDGE_BASE_PATH')
    if env_path:
        kb_path = Path(env_path)
        if kb_path.exists():
            return kb_path
    
    # 2. 检查哪个平台有知识库
    for name, config in sorted(PLATFORM_CONFIGS.items(), key=lambda x: x[1]['priority']):
        kb_dir = KNOWLEDGE_DIRS.get(name)
        if kb_dir and kb_dir.exists():
            return kb_dir
    
    # 3. 根据平台确定（如果不存在则创建）
    if platform:
        kb_dir = KNOWLEDGE_DIRS.get(platform)
    else:
        detected = detect_platform()
        kb_dir = KNOWLEDGE_DIRS.get(detected)
    
    if kb_dir:
        kb_dir.mkdir(parents=True, exist_ok=True)
        return kb_dir
    
    # Fallback to opencode
    fallback_dir = KNOWLEDGE_DIRS['opencode']
    fallback_dir.mkdir(parents=True, exist_ok=True)
    return fallback_dir


def get_script_path(skill_name: str, script_name: str, platform: Optional[str] = None) -> Path:
    """
    获取指定脚本的完整路径。
    
    Args:
        skill_name: skill 名称 (如 'evolving-agent', 'knowledge-base')
        script_name: 脚本名称 (如 'toggle_mode.py', 'knowledge_query.py')
        platform: 可选，指定平台名称
    
    Returns:
        脚本完整路径
    """
    skills_dir = get_skills_dir(platform)
    return skills_dir / skill_name / 'scripts' / script_name


def get_run_command(skill_name: str, script_name: str, *args, platform: Optional[str] = None) -> str:
    """
    获取运行脚本的完整命令。
    
    Args:
        skill_name: skill 名称
        script_name: 脚本名称
        *args: 传递给脚本的参数
        platform: 可选，指定平台名称
    
    Returns:
        完整的运行命令字符串
    """
    python_path = get_venv_python(platform)
    script_path = get_script_path(skill_name, script_name, platform)
    
    cmd_parts = [str(python_path), str(script_path)]
    cmd_parts.extend(str(arg) for arg in args)
    
    return ' '.join(cmd_parts)


def print_paths(platform: Optional[str] = None):
    """打印所有关键路径信息，用于调试。"""
    if platform is None:
        platform = detect_platform()
    
    print(f"Platform: {platform}")
    print(f"Skills Directory: {get_skills_dir(platform)}")
    print(f"Venv Python: {get_venv_python(platform)}")
    print(f"Knowledge Base: {get_knowledge_base_dir(platform)}")
    print()
    print("Script paths:")
    print(f"  toggle_mode.py: {get_script_path('evolving-agent', 'toggle_mode.py', platform)}")
    print(f"  knowledge_query.py: {get_script_path('knowledge-base', 'knowledge_query.py', platform)}")


# 便捷函数：用于其他脚本直接导入
def get_kb_root() -> Path:
    """
    兼容旧接口：获取知识库根目录。
    
    这个函数提供与原有 knowledge_store.py 中 get_kb_root() 相同的接口。
    """
    return get_knowledge_base_dir()


if __name__ == '__main__':
    import argparse
    
    parser = argparse.ArgumentParser(description='Path Resolver - 路径解析工具')
    parser.add_argument('--platform', '-p', choices=['opencode', 'claude'],
                        help='指定平台')
    parser.add_argument('--skills-dir', action='store_true',
                        help='输出 skills 目录')
    parser.add_argument('--venv-python', action='store_true',
                        help='输出 venv Python 路径')
    parser.add_argument('--knowledge-base', action='store_true',
                        help='输出知识库目录')
    parser.add_argument('--script', nargs=2, metavar=('SKILL', 'SCRIPT'),
                        help='输出指定脚本路径')
    parser.add_argument('--run-cmd', nargs='+', metavar='ARG',
                        help='输出运行命令 (格式: SKILL SCRIPT [ARGS...])')
    parser.add_argument('--all', '-a', action='store_true',
                        help='输出所有路径信息')
    
    args = parser.parse_args()
    
    if args.all:
        print_paths(args.platform)
    elif args.skills_dir:
        print(get_skills_dir(args.platform))
    elif args.venv_python:
        print(get_venv_python(args.platform))
    elif args.knowledge_base:
        print(get_knowledge_base_dir(args.platform))
    elif args.script:
        print(get_script_path(args.script[0], args.script[1], args.platform))
    elif args.run_cmd and len(args.run_cmd) >= 2:
        skill, script = args.run_cmd[0], args.run_cmd[1]
        extra_args = args.run_cmd[2:] if len(args.run_cmd) > 2 else []
        print(get_run_command(skill, script, *extra_args, platform=args.platform))
    else:
        # 默认输出检测到的平台
        print(f"Detected platform: {detect_platform()}")
        print(f"Skills directory: {get_skills_dir()}")


path: scripts/core/smart_stitch.py
content: #!/usr/bin/env python3
"""
Smart Stitch v2.0

Updated to work with the progressive experience storage structure.
Instead of dumping all experiences into SKILL.md, it now:
1. Updates the experience index
2. Stores experiences in appropriate category files
3. Only adds a brief summary to SKILL.md (not full content)
"""

import os
import sys
import json
import re
from pathlib import Path
from datetime import datetime

# Add programming-assistant scripts to path
SCRIPT_DIR = Path(__file__).parent
PROG_ASSIST_SCRIPTS = SCRIPT_DIR.parent.parent / 'programming-assistant' / 'scripts'
if str(PROG_ASSIST_SCRIPTS) not in sys.path:
    sys.path.insert(0, str(PROG_ASSIST_SCRIPTS))


def stitch_skill_v2(skill_dir: str):
    """
    New version: Store experiences in progressive structure.
    Only add a summary reference to SKILL.md.
    """
    skill_path = Path(skill_dir)
    skill_md_path = skill_path / "SKILL.md"
    evolution_json_path = skill_path / "evolution.json"
    experience_dir = skill_path / "experience"

    if not skill_md_path.exists():
        print(f"Error: SKILL.md not found in {skill_dir}", file=sys.stderr)
        return False
        
    if not evolution_json_path.exists():
        print(f"Info: No evolution.json found in {skill_dir}. Nothing to stitch.", file=sys.stderr)
        return True

    try:
        with open(evolution_json_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
    except Exception as e:
        print(f"Error parsing evolution.json: {e}", file=sys.stderr)
        return False

    # Check if skill uses progressive loading
    skill_content = skill_md_path.read_text(encoding='utf-8')
    uses_progressive = 'progressive: true' in skill_content or 'progressive_load: true' in skill_content

    if uses_progressive:
        # Use new progressive storage
        return migrate_to_progressive(skill_path, data)
    else:
        # Fall back to legacy behavior
        return stitch_skill_legacy(skill_path, data)


def migrate_to_progressive(skill_path: Path, data: dict) -> bool:
    """Migrate evolution data to progressive storage structure."""
    experience_dir = skill_path / 'experience'
    experience_dir.mkdir(exist_ok=True)
    (experience_dir / 'tech').mkdir(exist_ok=True)
    (experience_dir / 'contexts').mkdir(exist_ok=True)

    # Load or create index
    index_path = experience_dir / 'index.json'
    if index_path.exists():
        with open(index_path, 'r', encoding='utf-8') as f:
            index = json.load(f)
    else:
        index = {
            'version': '2.0.0',
            'last_updated': None,
            'index': {'tech_stacks': [], 'contexts': [], 'total_experiences': 0},
            'preferences': [],
            'fixes': []
        }

    # Migrate preferences
    for pref in data.get('preferences', []):
        if pref not in index['preferences']:
            index['preferences'].append(pref)
            index['index']['total_experiences'] += 1

    # Migrate fixes
    for fix in data.get('fixes', []):
        if fix not in index['fixes']:
            index['fixes'].append(fix)
            index['index']['total_experiences'] += 1

    # Migrate patterns to tech-specific files
    for tech, patterns in data.get('patterns', {}).items():
        if isinstance(patterns, list) and patterns:
            tech_lower = tech.lower()
            tech_file = experience_dir / 'tech' / f'{tech_lower}.json'
            
            if tech_file.exists():
                with open(tech_file, 'r', encoding='utf-8') as f:
                    tech_data = json.load(f)
            else:
                tech_data = {'name': tech, 'patterns': [], 'tips': []}
            
            for pattern in patterns:
                if pattern not in tech_data['patterns']:
                    tech_data['patterns'].append(pattern)
                    index['index']['total_experiences'] += 1
            
            with open(tech_file, 'w', encoding='utf-8') as f:
                json.dump(tech_data, f, indent=2, ensure_ascii=False)
            
            if tech_lower not in index['index']['tech_stacks']:
                index['index']['tech_stacks'].append(tech_lower)

    # Migrate context triggers
    for context, instruction in data.get('context_triggers', {}).items():
        context_lower = context.lower().replace(' ', '_')
        context_file = experience_dir / 'contexts' / f'{context_lower}.json'
        
        if context_file.exists():
            with open(context_file, 'r', encoding='utf-8') as f:
                ctx_data = json.load(f)
        else:
            ctx_data = {'name': context, 'instructions': []}
        
        if instruction not in ctx_data['instructions']:
            ctx_data['instructions'].append(instruction)
            index['index']['total_experiences'] += 1
        
        with open(context_file, 'w', encoding='utf-8') as f:
            json.dump(ctx_data, f, indent=2, ensure_ascii=False)
        
        if context_lower not in index['index']['contexts']:
            index['index']['contexts'].append(context_lower)

    # Save index
    index['last_updated'] = datetime.now().isoformat()
    with open(index_path, 'w', encoding='utf-8') as f:
        json.dump(index, f, indent=2, ensure_ascii=False)

    print(f"Migrated {index['index']['total_experiences']} experiences to progressive structure")
    
    # Optionally remove evolution.json after migration
    # For safety, we'll rename it instead
    evolution_json_path = skill_path / 'evolution.json'
    backup_path = skill_path / 'evolution.json.migrated'
    if evolution_json_path.exists():
        evolution_json_path.rename(backup_path)
        print(f"Backed up evolution.json to evolution.json.migrated")

    return True


def stitch_skill_legacy(skill_path: Path, data: dict) -> bool:
    """Legacy behavior: stitch all experiences into SKILL.md."""
    skill_md_path = skill_path / "SKILL.md"
    
    # Prepare the Markdown content block
    evolution_section = []
    evolution_section.append("\n\n## User-Learned Best Practices & Constraints")
    evolution_section.append("\n> **Auto-Generated Section**: This section is maintained by `evolving-agent`. Do not edit manually.")

    if data.get("preferences"):
        evolution_section.append("\n### User Preferences")
        for item in data["preferences"]:
            evolution_section.append(f"- {item}")

    if data.get("fixes"):
        evolution_section.append("\n### Known Fixes & Workarounds")
        for item in data["fixes"]:
            evolution_section.append(f"- {item}")

    if data.get("patterns"):
        evolution_section.append("\n### Learned Patterns")
        for pattern_type, items in data["patterns"].items():
            if isinstance(items, list) and items:
                evolution_section.append(f"\n#### {pattern_type.capitalize()}")
                for item in items:
                    evolution_section.append(f"- {item}")

    if data.get("context_triggers"):
        evolution_section.append("\n### Context Triggers")
        for trigger, instruction in data["context_triggers"].items():
            evolution_section.append(f"\n- **{trigger}**: {instruction}")

    if data.get("custom_prompts"):
        evolution_section.append("\n### Custom Instruction Injection")
        evolution_section.append(f"\n{data['custom_prompts']}")

    evolution_block = "\n".join(evolution_section)

    # Read original SKILL.md
    content = skill_md_path.read_text(encoding='utf-8')

    # Regex to find existing User-Learned section and replace it
    pattern = r"(\n+## User-Learned Best Practices & Constraints.*$)"
    
    match = re.search(pattern, content, re.DOTALL)
    
    if match:
        print("Updating existing evolution section...", file=sys.stderr)
        new_content = content[:match.start()] + evolution_block
    else:
        print("Appending new evolution section...", file=sys.stderr)
        new_content = content + evolution_block

    skill_md_path.write_text(new_content, encoding='utf-8')
    print(f"Successfully stitched evolution data into {skill_md_path}")
    return True


def stitch_skill(skill_dir):
    """Entry point - use v2 by default."""
    return stitch_skill_v2(skill_dir)


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python smart_stitch.py <skill_dir>")
        sys.exit(1)
        
    target_dir = sys.argv[1]
    stitch_skill(target_dir)


path: scripts/core/toggle_mode.py
content: #!/usr/bin/env python3
"""
Evolution Mode Toggle Script

Controls the persistent evolution mode state for a session.
The marker file is created in the CURRENT WORKING DIRECTORY,
allowing multiple projects to have independent evolution mode states.
"""

import argparse
import os
import subprocess
import sys
from pathlib import Path


def get_workspace_root() -> Path:
    """
    Get the current working directory as workspace root.
    
    This ensures each project directory has its own evolution mode state,
    supporting parallel development across multiple projects.
    
    Returns:
        Path: The current working directory
    """
    return Path.cwd()


def get_mode_marker_path() -> Path:
    """
    Get the path to the evolution mode marker file.
    
    The marker file is always created in the current working directory's
    .opencode subdirectory, ensuring project-level isolation.
    
    Returns:
        Path: Path to .evolution_mode_active file
    """
    root = get_workspace_root()
    return root / '.opencode' / '.evolution_mode_active'


def check_write_permission(path: Path) -> bool:
    """
    Check if we have write permission for the given path.
    
    Args:
        path: The path to check
        
    Returns:
        bool: True if writable, False otherwise
    """
    # Check the path itself or its parent
    check_path = path if path.exists() else path.parent
    if not check_path.exists():
        # Check parent of parent
        check_path = path.parent.parent
        if not check_path.exists():
            check_path = Path.cwd()
    
    return os.access(check_path, os.W_OK)


def run_with_sudo(command: list[str]) -> tuple[bool, str]:
    """
    Run a command with sudo after user confirmation.
    
    Args:
        command: The command to run
        
    Returns:
        tuple: (success, message)
    """
    try:
        # Ask for user confirmation
        print(f"需要管理员权限来写入文件")
        response = input("是否使用 sudo 继续? [y/N]: ").strip().lower()
        
        if response not in ('y', 'yes'):
            return False, "用户取消操作"
        
        # Run with sudo
        result = subprocess.run(
            ['sudo'] + command,
            capture_output=True,
            text=True
        )
        
        if result.returncode == 0:
            return True, "操作成功"
        else:
            return False, f"命令执行失败: {result.stderr}"
            
    except KeyboardInterrupt:
        return False, "用户取消操作"
    except Exception as e:
        return False, f"执行出错: {e}"


def enable_mode() -> str:
    """
    Enable evolution mode by creating the marker file.
    Handles permission issues with sudo if needed.
    
    Returns:
        str: Success message
    """
    marker_path = get_mode_marker_path()
    parent_dir = marker_path.parent
    
    # Try to create directory and file normally first
    try:
        parent_dir.mkdir(parents=True, exist_ok=True)
        marker_path.touch()
        return f"✓ Evolution Mode ENABLED for this session\n  Marker: {marker_path}"
    except PermissionError:
        pass
    
    # Permission denied - try with sudo
    print(f"无法写入 {marker_path}")
    
    # Create directory with sudo if needed
    if not parent_dir.exists():
        success, msg = run_with_sudo(['mkdir', '-p', str(parent_dir)])
        if not success:
            return f"✗ 无法创建目录: {msg}"
    
    # Create marker file with sudo
    success, msg = run_with_sudo(['touch', str(marker_path)])
    if success:
        return f"✓ Evolution Mode ENABLED for this session (with sudo)\n  Marker: {marker_path}"
    else:
        return f"✗ 无法启用进化模式: {msg}"


def disable_mode() -> str:
    """
    Disable evolution mode by removing the marker file.
    Handles permission issues with sudo if needed.
    
    Returns:
        str: Success message
    """
    marker_path = get_mode_marker_path()
    
    if not marker_path.exists():
        return "ℹ Evolution Mode was not active"
    
    # Try to remove normally first
    try:
        marker_path.unlink()
        return f"✓ Evolution Mode DISABLED for this session"
    except PermissionError:
        pass
    
    # Permission denied - try with sudo
    print(f"无法删除 {marker_path}")
    success, msg = run_with_sudo(['rm', '-f', str(marker_path)])
    
    if success:
        return f"✓ Evolution Mode DISABLED for this session (with sudo)"
    else:
        return f"✗ 无法禁用进化模式: {msg}"


def is_mode_active() -> bool:
    """
    Check if evolution mode is currently active.
    
    Returns:
        bool: True if mode is active
    """
    return get_mode_marker_path().exists()


def get_status_message() -> str:
    """
    Get the current status message.
    
    Returns:
        str: Status message
    """
    status = "ACTIVE" if is_mode_active() else "INACTIVE"
    marker_path = get_mode_marker_path()
    return f"Evolution Mode Status: {status}\nMarker file: {marker_path}"


def get_context_prompt() -> str:
    """
    Get the context prompt that should be injected into the AI's context.
    
    Returns:
        str: The reinforcement prompt
    """
    if not is_mode_active():
        return ""
    
    return """
## Evolution Mode Active
This session is in EVOLUTION MODE.
- After completing tasks, automatically check for extractable knowledge
- Run trigger detection even without explicit user commands
- Store valuable experiences to the knowledge base
- Only report to user when new knowledge is extracted
""".strip()


def main():
    parser = argparse.ArgumentParser(
        description='Toggle or check evolution mode status',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python toggle_mode.py                    # Show current status
  python toggle_mode.py --init            # Full init: start coordinator + enable mode
  python toggle_mode.py --on              # Enable evolution mode
  python toggle_mode.py --off              # Disable evolution mode
  python toggle_mode.py --on --inject      # Enable and print context prompt
  python toggle_mode.py --status           # Show detailed status
        """
    )

    parser.add_argument('--init', '-i', action='store_true',
                        help='Full initialization: start coordinator and enable evolution mode')
    parser.add_argument('--on', '-e', action='store_true',
                        help='Enable evolution mode')
    parser.add_argument('--off', '-d', action='store_true',
                        help='Disable evolution mode')
    parser.add_argument('--toggle', '-t', action='store_true',
                        help='Toggle current state')
    parser.add_argument('--inject', action='store_true',
                        help='Print context prompt for injection')
    parser.add_argument('--status', '-s', action='store_true',
                        help='Show detailed status')
    
    args = parser.parse_args()

    # Full initialization (manual trigger /evolve)
    if args.init:
        was_active = is_mode_active()
        result = enable_mode()

        # Only show message if this is a fresh activation
        if not was_active:
            print(result)  # Print the enable message
            print("\n" + "="*60)
            print("🚀 协调器已启动")
            print("="*60)
            print("\n📋 下一步建议：")
            print("   - 输入编程任务（如：帮我实现一个登录功能）")
            print("   - 或直接开始描述您的需求")
            print("\n💡 提示：")
            print("   - programming-assistant 将自动加载")
            print("   - 进化模式已激活，会自动提取有价值经验")
            print("   - 使用 'python toggle_mode.py --off' 可关闭进化模式")
            print("="*60 + "\n")
        return 0

    # Inject context prompt (can be combined with other operations)
    if args.inject:
        if args.on or args.off or args.toggle:
            # Combine with state change
            if args.on:
                print(enable_mode())
            elif args.off:
                print(disable_mode())
            elif args.toggle:
                if is_mode_active():
                    print(disable_mode())
                else:
                    print(enable_mode())
            # Then print context if enabled
            if is_mode_active():
                print("\n--- Context Prompt ---")
                print(get_context_prompt())
            else:
                print("\n--- Context Prompt ---")
                print("(No context prompt - evolution mode is inactive)")
        else:
            # Just print context
            if is_mode_active():
                print("--- Context Prompt ---")
                print(get_context_prompt())
            else:
                print("Evolution mode is not active. No context prompt to inject.")
        return 0

    # Enable
    if args.on:
        print(enable_mode())
        return 0
    
    # Disable
    if args.off:
        print(disable_mode())
        return 0
    
    # Toggle
    if args.toggle:
        if is_mode_active():
            print(disable_mode())
        else:
            print(enable_mode())
        return 0
    
    # Status query
    if args.status:
        print(get_status_message())
        return 0
    
    # Default: show status
    print(get_status_message())
    return 0


if __name__ == '__main__':
    sys.exit(main())


path: scripts/core/trigger_detector.py
content: #!/usr/bin/env python3
"""
Evolution Trigger Detector

Determines when to trigger skill evolution based on session context.
"""


def extract_session_summary(context: str) -> dict:
    """
    Extract session summary from conversation context.

    Args:
        context: Full conversation text

    Returns:
        dict with attempts, success, feedback
    """
    summary = {
        'attempts': 1,
        'success': False,
        'feedback': None
    }

    context_lower = context.lower()

    # Count attempts (look for "attempt", "try", "失败", "错误")
    attempt_keywords = ['attempt', 'try', '尝试', 'fail', '错误', 'error']
    attempts = sum(context_lower.count(keyword) for keyword in attempt_keywords)
    if attempts > 0:
        summary['attempts'] = attempts + 1  # +1 for initial attempt

    # Detect success (look for "成功", "完成", "done", "success", "完成")
    success_keywords = ['success', '成功', '完成', 'done', 'work', 'working']
    if any(keyword in context_lower for keyword in success_keywords):
        summary['success'] = True

    # Extract user feedback (look for user statements with keywords)
    feedback_keywords = ['记住', '以后', '保存', '重要', 'remember', 'save', 'important']
    for line in context.split('\n'):
        line_lower = line.lower()
        if any(keyword in line_lower for keyword in feedback_keywords):
            # Remove "用户:" prefix if present
            feedback = line.strip()
            if feedback.startswith('用户:') or feedback.startswith('user:'):
                feedback = feedback.split(':', 1)[1].strip()
            summary['feedback'] = feedback
            break

    return summary


def should_trigger_evolution(session_summary: dict) -> bool:
    """
    Determine if evolution should be triggered based on session summary.

    Args:
        session_summary: dict with keys:
            - attempts: number of fix attempts made
            - success: whether the task was completed successfully
            - feedback: user's explicit feedback (optional)

    Returns:
        bool: True if evolution should be triggered
    """
    # Condition 1: Complex bug fix (multiple attempts)
    if session_summary.get('attempts', 1) > 1:
        return True

    # Condition 2: User explicit feedback
    feedback = session_summary.get('feedback', '')
    if feedback and any(keyword in feedback.lower() for keyword in ['记住', '以后', '保存', '重要', 'remember', 'save', 'important']):
        return True

    # Condition 3: Successful completion with specific indicators
    if session_summary.get('success', False):
        # Could add more sophisticated conditions here
        pass

    return False


if __name__ == "__main__":
    import json

    # Test extract_session_summary
    print("Testing extract_session_summary():")
    context = '''
用户: 帮我修复这个 bug
助手: 让我看看...第一次尝试失败
用户: 还是不行
助手: 让我换个方法...第二次尝试失败
用户: 再试试
助手: 这次成功了！
用户: 太好了，记住这个解决方案
'''
    summary = extract_session_summary(context)
    print(f"  Extracted: {summary}")
    print()

    # Test should_trigger_evolution
    print("Testing should_trigger_evolution():")
    test_cases = [
        ({'attempts': 3, 'success': True}, True),
        ({'attempts': 1, 'success': True}, False),
        ({'attempts': 1, 'success': True, 'feedback': '记住这个'}, True),
        ({'attempts': 1, 'success': True, 'feedback': '很好'}, False),
    ]

    for test_summary, expected in test_cases:
        result = should_trigger_evolution(test_summary)
        status = "✓" if result == expected else "✗"
        print(f"  {status} {test_summary} -> {result} (expected: {expected})")


path: scripts/github/__init__.py
content: # GitHub learning scripts package


path: scripts/github/create_github_skill.py
content: import sys
import os
import json
import datetime
import subprocess

def create_skill(repo_info, output_dir):
    """
    Scaffolds a new skill directory based on GitHub repository info.
    """
    repo_name = repo_info['name']
    safe_name = "".join(c if c.isalnum() or c in ('-','_') else '-' for c in repo_name).lower()
    skill_path = os.path.join(output_dir, safe_name)
    
    # 1. Create Directory Structure
    os.makedirs(os.path.join(skill_path, "scripts"), exist_ok=True)
    os.makedirs(os.path.join(skill_path, "references"), exist_ok=True)
    os.makedirs(os.path.join(skill_path, "assets"), exist_ok=True)
    
    # 2. Create SKILL.md with Extended Metadata
    skill_md_content = f"""---
name: {safe_name}
description: Skill wrapper for {repo_info['name']}. Generated from {repo_info['url']}.
github_url: {repo_info['url']}
github_hash: {repo_info['latest_hash']}
version: 0.1.0
created_at: {datetime.datetime.now().isoformat()}
entry_point: scripts/wrapper.py
---

# {repo_info['name']} Skill

This skill wraps the capabilities of [{repo_info['name']}]({repo_info['url']}).

## Overview

(Auto-generated context from README)
{repo_info['readme'][:500]}...

## Usage

This skill provides a Python wrapper to interface with the tool. 

### Prerequisites

Ensure the following dependencies are installed:
- [TODO: List dependencies based on requirements.txt]

## Implementation Details

The wrapper script in `scripts/wrapper.py` handles the invocation of the underlying tool.
"""
    
    with open(os.path.join(skill_path, "SKILL.md"), "w", encoding="utf-8") as f:
        f.write(skill_md_content)
        
    # 3. Create Placeholder Wrapper Script
    wrapper_content = f"""import sys
import subprocess

def main():
    print("This is a placeholder wrapper for {repo_name}.")
    # TODO: Implement actual invocation logic here based on {repo_name} usage
    # Example: subprocess.run(['{repo_name}', *sys.argv[1:]])

if __name__ == "__main__":
    main()
"""
    with open(os.path.join(skill_path, "scripts", "wrapper.py"), "w", encoding="utf-8") as f:
        f.write(wrapper_content)

    print(f"Skill scaffolded at: {skill_path}")
    print("Next steps:")
    print("1. Review SKILL.md and refine the description.")
    print("2. Implement the actual logic in scripts/wrapper.py.")
    print(f"3. Run: python package_skill.py {skill_path}")

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python create_github_skill.py <json_info_file> <output_skills_dir>")
        sys.exit(1)
        
    json_file = sys.argv[1]
    output_dir = sys.argv[2]
    
    with open(json_file, 'r', encoding='utf-8') as f:
        repo_info = json.load(f)
        
    create_skill(repo_info, output_dir)


path: scripts/github/extract_patterns.py
content: #!/usr/bin/env python3
"""
Pattern Extractor v2.0

Extract programming patterns and best practices from GitHub repository info.
Now outputs structured JSON for progressive knowledge storage.

Two output modes:
1. --markdown: Generate knowledge-addon Markdown (legacy)
2. --json: Output structured JSON for store_knowledge.py (new)
3. --store: Directly store to knowledge base (new)
"""

import sys
import json
import re
import os
from pathlib import Path
from datetime import datetime
from typing import Optional


def main():
    import argparse
    
    parser = argparse.ArgumentParser(
        description='Extract patterns from GitHub repository',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Legacy: Generate Markdown addon
  python fetch_github_info.py <url> | python extract_patterns.py --markdown
  
  # New: Output JSON for further processing
  python fetch_github_info.py <url> | python extract_patterns.py --json
  
  # New: Directly store to knowledge base
  python fetch_github_info.py <url> | python extract_patterns.py --store
        """
    )
    
    parser.add_argument(
        '--markdown', '-m',
        action='store_true',
        help='Output as knowledge-addon Markdown (legacy)'
    )
    parser.add_argument(
        '--json', '-j',
        action='store_true',
        help='Output as structured JSON'
    )
    parser.add_argument(
        '--store', '-s',
        action='store_true',
        help='Directly store to knowledge base'
    )
    parser.add_argument(
        '--knowledge-dir',
        help='Knowledge directory path (for --store mode)'
    )
    
    args = parser.parse_args()
    
    # Default to markdown if no option specified
    if not args.markdown and not args.json and not args.store:
        args.markdown = True
    
    # Read repo info from stdin
    repo_info = json.load(sys.stdin)

    # Analyze patterns
    architecture_patterns = detect_architecture_patterns(repo_info.get('file_tree', []))
    tech_stack = detect_tech_stack(repo_info.get('readme', ''))
    conventions = detect_conventions(repo_info.get('readme', ''))
    practices = extract_best_practices(repo_info.get('readme', ''))

    if args.json or args.store:
        # New structured output
        output = {
            'name': repo_info.get('name', 'unknown'),
            'url': repo_info.get('url', ''),
            'hash': repo_info.get('latest_hash', ''),
            'extracted_at': datetime.now().isoformat(),
            'architecture_patterns': architecture_patterns,
            'tech_stack': tech_stack,
            'conventions': conventions,
            'practices': practices
        }
        
        if args.store:
            # Directly store to knowledge base
            store_to_knowledge_base(output, args.knowledge_dir)
        else:
            print(json.dumps(output, indent=2, ensure_ascii=False))
    else:
        # Legacy Markdown output
        addon = generate_knowledge_addon(repo_info, architecture_patterns, tech_stack, conventions, practices)
        print(addon)


def store_to_knowledge_base(extracted: dict, knowledge_dir: Optional[str] = None):
    """将提取的知识存储到统一知识库 (knowledge-base)"""
    script_dir = Path(__file__).parent
    sys.path.insert(0, str(script_dir))
    
    try:
        from store_to_knowledge import (
            store_skill,
            store_tech_stack,
            store_pattern,
            get_knowledge_base_dir
        )
    except ImportError as e:
        print(f"导入 store_to_knowledge 失败: {e}", file=sys.stderr)
        sys.exit(1)
    
    kb_dir = Path(knowledge_dir) if knowledge_dir else get_knowledge_base_dir()
    
    source_repo = extracted.get('url', '')
    repo_name = extracted.get('name', 'unknown')
    tech_stack = extracted.get('tech_stack', {})
    frameworks = tech_stack.get('frameworks', [])
    tools = tech_stack.get('tools', [])
    libraries = tech_stack.get('libraries', [])
    patterns = extracted.get('architecture_patterns', [])
    conventions = extracted.get('conventions', [])
    practices = extracted.get('practices', [])
    
    stored_count = {"skill": 0, "tech-stack": 0, "pattern": 0}
    
    # 1. 存储技术栈知识
    for framework in frameworks:
        data = {
            "name": framework,
            "tech_name": framework,
            "triggers": [framework.lower(), repo_name.lower()],
            "best_practices": practices,
            "conventions": conventions,
            "common_patterns": patterns,
            "tags": ["framework", "from-github"]
        }
        store_tech_stack(kb_dir, data, source_repo)
        stored_count["tech-stack"] += 1
    
    # 2. 存储架构模式
    for pattern in patterns:
        data = {
            "name": pattern,
            "pattern_name": pattern,
            "pattern_category": "architecture",
            "triggers": [pattern.lower().replace(' ', '-'), pattern.lower()],
            "description": f"从 {repo_name} 项目提取的架构模式",
            "when_to_use": f"适用于 {', '.join(frameworks) if frameworks else '通用'} 项目",
            "tags": ["architecture", "from-github"] + [f.lower() for f in frameworks]
        }
        store_pattern(kb_dir, data, source_repo)
        stored_count["pattern"] += 1
    
    # 3. 存储综合技能
    if frameworks or tools or practices:
        skill_data = {
            "name": f"{repo_name} 编程技能",
            "skill_name": f"{repo_name} Development",
            "level": "intermediate",
            "triggers": [repo_name.lower()] + [f.lower() for f in frameworks] + [t.lower() for t in tools[:3]],
            "description": f"从 {repo_name} 项目学习的编程技能",
            "key_concepts": frameworks + tools[:5],
            "practical_tips": practices[:5] if practices else ["参考项目 README"],
            "common_mistakes": [],
            "tags": ["from-github", "project-skill"]
        }
        store_skill(kb_dir, skill_data, source_repo)
        stored_count["skill"] += 1
    
    print(f"✓ 成功从 {repo_name} 存储知识到 knowledge-base")
    print(f"  - 技术栈: {stored_count['tech-stack']} 条")
    print(f"  - 架构模式: {stored_count['pattern']} 条")
    print(f"  - 综合技能: {stored_count['skill']} 条")


def detect_conventions(readme: str) -> list:
    """Detect code conventions from README."""
    conventions = []
    readme_lower = readme.lower()

    convention_checks = [
        ('prettier', '代码格式化: Prettier'),
        ('eslint', '代码检查: ESLint'),
        ('typescript', '类型安全: TypeScript'),
        ('husky', '提交前检查: Git Hooks'),
        ('pre-commit', '提交前检查: Git Hooks'),
        ('conventional commits', '提交规范: Conventional Commits'),
        ('semantic versioning', '版本管理: Semantic Versioning'),
        ('golint', '代码检查: golint'),
        ('gofmt', '代码格式化: gofmt'),
        ('checkstyle', '代码检查: Checkstyle'),
        ('spotless', '代码格式化: Spotless'),
        ('black', '代码格式化: Black'),
        ('flake8', '代码检查: Flake8'),
        ('mypy', '类型检查: mypy'),
        ('rustfmt', '代码格式化: rustfmt'),
        ('clippy', '代码检查: Clippy'),
    ]
    
    for keyword, convention in convention_checks:
        if keyword in readme_lower and convention not in conventions:
            conventions.append(convention)

    return conventions


def extract_best_practices(readme: str) -> list:
    """Extract best practices from README."""
    practices = []
    readme_lower = readme.lower()

    practice_checks = [
        (['testing', 'test coverage', 'unit test'], '完整的测试覆盖'),
        (['documentation', 'docs'], '完善的文档说明'),
        (['ci/cd', 'github actions', 'gitlab ci', 'jenkins'], '自动化 CI/CD 流程'),
        (['docker', 'container', 'kubernetes'], '容器化部署支持'),
        (['environment variable', '.env'], '环境变量配置管理'),
        (['logging', 'observability'], '日志和可观测性'),
        (['security', 'authentication', 'authorization'], '安全性设计'),
        (['api versioning', 'backward compatible'], 'API 版本管理'),
        (['error handling', 'graceful'], '错误处理机制'),
        (['dependency injection', 'di'], '依赖注入'),
    ]
    
    for keywords, practice in practice_checks:
        if any(kw in readme_lower for kw in keywords) and practice not in practices:
            practices.append(practice)

    return practices


def detect_tech_stack(readme: str) -> dict:
    """Detect technology stack from README content."""
    readme_lower = readme.lower()

    tech_stack = {
        'frameworks': [],
        'tools': [],
        'libraries': []
    }

    # Frameworks (extended)
    frameworks = {
        # JavaScript/TypeScript
        'react': 'React', 'vue': 'Vue', 'angular': 'Angular', 'svelte': 'Svelte',
        'next.js': 'Next', 'nuxt.js': 'Nuxt', 'express': 'Express', 'fastify': 'Fastify',
        'nest.js': 'NestJS', 'remix': 'Remix', 'astro': 'Astro',
        # Python
        'django': 'Django', 'flask': 'Flask', 'fastapi': 'FastAPI',
        # Go
        'gin': 'Gin', 'fiber': 'Fiber', 'echo': 'Echo', 'chi': 'Chi',
        # Java
        'spring boot': 'Spring Boot', 'spring': 'Spring', 'quarkus': 'Quarkus',
        'micronaut': 'Micronaut',
        # Ruby
        'rails': 'Rails', 'sinatra': 'Sinatra',
        # PHP
        'laravel': 'Laravel', 'symfony': 'Symfony',
        # Rust
        'actix': 'Actix', 'axum': 'Axum', 'rocket': 'Rocket',
    }
    
    for keyword, name in frameworks.items():
        if keyword in readme_lower and name not in tech_stack['frameworks']:
            tech_stack['frameworks'].append(name)

    # Tools (extended)
    tools = {
        # Languages
        'typescript': 'TypeScript', 'javascript': 'JavaScript', 'python': 'Python',
        'golang': 'Go', 'go ': 'Go', 'rust': 'Rust', 'java': 'Java', 'kotlin': 'Kotlin',
        # Package managers
        'npm': 'npm', 'yarn': 'Yarn', 'pnpm': 'pnpm', 'pip': 'pip', 'poetry': 'Poetry',
        'maven': 'Maven', 'gradle': 'Gradle', 'cargo': 'Cargo',
        # DevOps
        'docker': 'Docker', 'kubernetes': 'Kubernetes', 'jenkins': 'Jenkins',
        'github actions': 'GitHub Actions', 'gitlab ci': 'GitLab CI',
        # Build tools
        'webpack': 'Webpack', 'vite': 'Vite', 'parcel': 'Parcel', 'esbuild': 'esbuild',
        # Testing
        'jest': 'Jest', 'vitest': 'Vitest', 'playwright': 'Playwright', 'cypress': 'Cypress',
        'pytest': 'pytest', 'junit': 'JUnit', 'testify': 'testify',
        # Databases
        'mongodb': 'MongoDB', 'postgresql': 'PostgreSQL', 'mysql': 'MySQL',
        'redis': 'Redis', 'sqlite': 'SQLite', 'elasticsearch': 'Elasticsearch',
    }
    
    for keyword, name in tools.items():
        if keyword in readme_lower and name not in tech_stack['tools']:
            tech_stack['tools'].append(name)

    # Libraries (extended)
    libraries = {
        # JavaScript
        'react query': 'React Query', 'zustand': 'Zustand', 'redux': 'Redux',
        'axios': 'Axios', 'zod': 'Zod', 'yup': 'Yup',
        'tailwind': 'Tailwind', 'material-ui': 'Material UI', 'ant design': 'Ant Design',
        'prisma': 'Prisma', 'sequelize': 'Sequelize', 'typeorm': 'TypeORM',
        # Go
        'gorm': 'GORM', 'sqlx': 'sqlx', 'viper': 'Viper', 'cobra': 'Cobra',
        # Java
        'mybatis': 'MyBatis', 'hibernate': 'Hibernate', 'lombok': 'Lombok',
        # Python
        'sqlalchemy': 'SQLAlchemy', 'celery': 'Celery', 'pydantic': 'Pydantic',
    }
    
    for keyword, name in libraries.items():
        if keyword in readme_lower and name not in tech_stack['libraries']:
            tech_stack['libraries'].append(name)

    return tech_stack


def detect_architecture_patterns(file_tree: list) -> list:
    """Detect architecture patterns from file tree."""
    patterns = []
    file_tree_str = ' '.join(file_tree).lower()

    pattern_checks = [
        (['features/', 'feature/'], 'Feature-Based Architecture'),
        (['components/', 'component/'], 'Component-Based Design'),
        (['hooks/', 'hook/'], 'Custom Hooks Pattern'),
        (['services/', 'repositories/'], 'Repository Pattern'),
        (['store/', 'state/'], 'State Management Layer'),
        (['domain/', 'entities/'], 'Domain-Driven Design'),
        (['handlers/', 'controllers/'], 'Handler/Controller Pattern'),
        (['middleware/', 'middlewares/'], 'Middleware Pattern'),
        (['internal/', 'pkg/'], 'Go Standard Layout'),
        (['cmd/', 'internal/'], 'Go CLI Application'),
        (['src/main/java/', 'src/test/java/'], 'Maven Standard Layout'),
        (['layers/', 'layer/'], 'Layered Architecture'),
        (['modules/', 'module/'], 'Modular Architecture'),
        (['api/', 'routes/'], 'API Routes Pattern'),
        (['utils/', 'helpers/'], 'Utility Pattern'),
    ]
    
    for keywords, pattern in pattern_checks:
        if any(kw in file_tree_str for kw in keywords) and pattern not in patterns:
            patterns.append(pattern)

    return patterns


def generate_knowledge_addon(repo_info: dict, patterns: list, tech_stack: dict, conventions: list, practices: list) -> str:
    """Generate knowledge-addon format Markdown (legacy format)."""
    architecture_text = '\n'.join(f"- {p}" for p in patterns) if patterns else "- 未检测到明显架构模式"

    stack_sections = []
    if tech_stack.get('frameworks'):
        stack_sections.append(f"### 框架\n" + '\n'.join(f"- {fw}" for fw in tech_stack['frameworks']))
    if tech_stack.get('tools'):
        stack_sections.append(f"### 工具\n" + '\n'.join(f"- {t}" for t in tech_stack['tools']))
    if tech_stack.get('libraries'):
        stack_sections.append(f"### 库\n" + '\n'.join(f"- {l}" for l in tech_stack['libraries']))

    tech_stack_text = '\n\n'.join(stack_sections) if stack_sections else "- 未检测到明显技术栈"
    conventions_text = '\n'.join(f"- {c}" for c in conventions) if conventions else "- 未检测到明显代码规范"
    practices_text = '\n'.join(f"- {p}" for p in practices) if practices else "- 未检测到明显最佳实践"

    template = f"""---
name: {repo_info['name']}-knowledge
type: knowledge-addon
target_skill: programming-assistant
source_repo: {repo_info['url']}
source_hash: {repo_info['latest_hash']}
created_at: {datetime.now().isoformat()}
---

# {repo_info['name']} 学习笔记

## 项目架构

{architecture_text}

## 代码规范

{conventions_text}

## 技术栈

{tech_stack_text}

## 最佳实践

{practices_text}

## 应用场景
当处理相关技术栈的项目时，自动参考这些范式。
"""
    return template


if __name__ == "__main__":
    main()


path: scripts/github/fetch_info.py
content: import sys
import json
import subprocess
import re
import urllib.request
import os

def extract_file_tree(readme: str) -> list:
    """
    Extract file tree structure from README by looking for directory listings.
    Returns a list of top-level directory paths.
    """
    file_tree = []

    # Pattern 1: Tree structure in README (e.g., src/, components/, etc.)
    tree_patterns = [
        r'([a-zA-Z0-9_-]+/)',  # Matches "src/", "components/", etc.
        r'`([a-zA-Z0-9_-]+)`',   # Matches `src`, `components`
    ]

    for pattern in tree_patterns:
        matches = re.findall(pattern, readme)
        for match in matches:
            if '/' in match or len(match) > 3:  # Only include actual directories
                if match not in file_tree:
                    file_tree.append(match)

        if file_tree:  # Stop if we found directories
            break

    # If no tree found, try extracting from code blocks
    if not file_tree:
        code_block_pattern = r'```.*?\n((?:.|\n)*?)```'
        blocks = re.findall(code_block_pattern, readme)
        for block in blocks:
            for pattern in tree_patterns:
                matches = re.findall(pattern, block)
                for match in matches:
                    if match not in file_tree:
                        file_tree.append(match)

    return file_tree[:50]  # Limit to first 50 entries


def get_repo_info(url):
    """
    Fetches repository information using git ls-remote and direct HTTP requests.
    Returns a dictionary with name, description, latest_hash, and readme content.
    """
    
    # Normalize URL (remove .git suffix if present)
    clean_url = url.rstrip('/')
    if clean_url.endswith('.git'):
        clean_url = clean_url[:-4]
        
    repo_name = clean_url.split('/')[-1]
    
    # 1. Get Latest Commit Hash (using git ls-remote to avoid full clone)
    try:
        result = subprocess.run(
            ['git', 'ls-remote', url, 'HEAD'],
            capture_output=True,
            text=True,
            check=True,
            timeout=30
        )
        # Output format: <hash>\tHEAD
        latest_hash = result.stdout.split()[0]
    except subprocess.TimeoutExpired:
        print(f"Error: Git command timed out for {url}", file=sys.stderr)
        latest_hash = "unknown"
    except subprocess.CalledProcessError as e:
        print(f"Error: Git command failed with exit code {e.returncode} for {url}", file=sys.stderr)
        if e.stderr:
            print(f"Git stderr: {e.stderr.strip()}", file=sys.stderr)
        latest_hash = "unknown"
    except FileNotFoundError:
        print("Error: Git is not installed or not in PATH", file=sys.stderr)
        latest_hash = "unknown"
    except IndexError:
        print(f"Error: Unexpected git ls-remote output format for {url}", file=sys.stderr)
        latest_hash = "unknown"
    except Exception as e:
        print(f"Unexpected error fetching git info: {type(e).__name__}: {e}", file=sys.stderr)
        latest_hash = "unknown"

    # 2. Fetch README (Try main, then master)
    readme_content = ""
    readme_url_base = clean_url.replace("github.com", "raw.githubusercontent.com")
    
    for branch in ["main", "master"]:
        try:
            readme_url = f"{readme_url_base}/{branch}/README.md"
            with urllib.request.urlopen(readme_url) as response:
                readme_content = response.read().decode('utf-8')
                break
        except Exception:
            continue
            
    if not readme_content:
        # Try lowercase readme
        for branch in ["main", "master"]:
            try:
                readme_url = f"{readme_url_base}/{branch}/readme.md"
                with urllib.request.urlopen(readme_url) as response:
                    readme_content = response.read().decode('utf-8')
                    break
            except Exception:
                continue

    # 3. Extract File Tree from README
    file_tree = extract_file_tree(readme_content)

    # 4. Construct Result
    return {
        "name": repo_name,
        "url": url,
        "latest_hash": latest_hash,
        "readme": readme_content[:10000], # Truncate if too huge to avoid context blowup
        "file_tree": file_tree
    }

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python fetch_github_info.py <github_url>")
        sys.exit(1)
        
    url = sys.argv[1]
    info = get_repo_info(url)
    print(json.dumps(info, indent=2))


path: scripts/github/store_to_knowledge.py
content: #!/usr/bin/env python3
"""
Store to Knowledge Base

将从 GitHub 仓库提取的知识存储到统一知识库 (knowledge-base)。
遵循 knowledge-base/schema.json 定义的格式。

用法:
    python store_to_knowledge.py --category skill --input extracted.json
    echo '{"name": "...", "content": {...}}' | python store_to_knowledge.py --category skill
"""

import argparse
import json
import hashlib
import os
import sys
from pathlib import Path
from datetime import datetime
from typing import Dict, Any, Optional, List


# 路径解析器导入
_path_resolver_module = None

def _try_import_path_resolver():
    """尝试导入 path_resolver 模块"""
    global _path_resolver_module
    if _path_resolver_module is not None:
        return _path_resolver_module if _path_resolver_module else None
    
    # 添加 path_resolver 所在目录到 Python path
    script_dir = Path(__file__).parent
    core_scripts = script_dir.parent / 'core'
    if core_scripts.exists() and str(core_scripts) not in sys.path:
        sys.path.insert(0, str(core_scripts))
    
    try:
        import path_resolver
        _path_resolver_module = path_resolver
        return path_resolver
    except ImportError:
        _path_resolver_module = False  # type: ignore
        return None


def get_knowledge_base_dir() -> Path:
    """
    获取知识库根目录
    
    使用统一的 path_resolver 模块进行跨平台路径解析。
    支持 OpenCode (~/.config/opencode/skills/) 和 Claude Code (~/.claude/skills/)。
    """
    # 1. 优先使用 path_resolver
    resolver = _try_import_path_resolver()
    if resolver:
        return resolver.get_knowledge_base_dir()
    
    # 2. Fallback: 内置路径解析逻辑
    env_path = os.environ.get('KNOWLEDGE_BASE_PATH')
    if env_path:
        kb_path = Path(env_path)
        if kb_path.exists():
            return kb_path
    
    opencode_kb = Path.home() / '.config' / 'opencode' / 'knowledge'
    if opencode_kb.exists():
        return opencode_kb
    
    claude_kb = Path.home() / '.claude' / 'knowledge'
    if claude_kb.exists():
        return claude_kb
    
    opencode_kb.mkdir(parents=True, exist_ok=True)
    return opencode_kb


def generate_id(category: str, name: str) -> str:
    """生成唯一 ID"""
    hash_input = f"{category}-{name}-{datetime.now().isoformat()}"
    hash_suffix = hashlib.md5(hash_input.encode()).hexdigest()[:8]
    return f"{category}-{name.lower().replace(' ', '-')}-{hash_suffix}"


def load_category_index(kb_dir: Path, category: str) -> Dict:
    """加载分类索引"""
    category_dir = kb_dir / f"{category}s" if category != "skill" else kb_dir / "skills"
    index_path = category_dir / "index.json"
    
    if index_path.exists():
        with open(index_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {"entries": [], "last_updated": None}


def save_category_index(kb_dir: Path, category: str, index: Dict):
    """保存分类索引"""
    category_dir = kb_dir / f"{category}s" if category != "skill" else kb_dir / "skills"
    category_dir.mkdir(parents=True, exist_ok=True)
    index_path = category_dir / "index.json"
    
    index["last_updated"] = datetime.now().isoformat()
    with open(index_path, 'w', encoding='utf-8') as f:
        json.dump(index, f, ensure_ascii=False, indent=2)


def load_global_index(kb_dir: Path) -> Dict:
    """加载全局索引"""
    index_path = kb_dir / "index.json"
    if index_path.exists():
        with open(index_path, 'r', encoding='utf-8') as f:
            return json.load(f)
    return {
        "version": "1.0.0",
        "trigger_index": {},
        "category_index": {},
        "stats": {}
    }


def save_global_index(kb_dir: Path, index: Dict):
    """保存全局索引"""
    index["last_updated"] = datetime.now().isoformat()
    index_path = kb_dir / "index.json"
    with open(index_path, 'w', encoding='utf-8') as f:
        json.dump(index, f, ensure_ascii=False, indent=2)


def update_trigger_index(global_index: Dict, entry_id: str, triggers: List[str]):
    """更新触发词索引"""
    trigger_index = global_index.setdefault("trigger_index", {})
    for trigger in triggers:
        trigger_lower = trigger.lower()
        if trigger_lower not in trigger_index:
            trigger_index[trigger_lower] = []
        if entry_id not in trigger_index[trigger_lower]:
            trigger_index[trigger_lower].append(entry_id)


def store_knowledge_entry(
    kb_dir: Path,
    category: str,
    name: str,
    triggers: List[str],
    content: Dict[str, Any],
    sources: List[str],
    tags: Optional[List[str]] = None
) -> str:
    """
    存储知识条目到知识库
    
    Args:
        kb_dir: 知识库根目录
        category: 分类 (skill, tech-stack, pattern, problem, testing, experience, scenario)
        name: 条目名称
        triggers: 触发关键词列表
        content: 具体内容 (遵循 schema.json 中对应分类的结构)
        sources: 来源列表 (如 GitHub URL)
        tags: 额外标签
    
    Returns:
        生成的条目 ID
    """
    entry_id = generate_id(category, name)
    
    # 构建知识条目
    entry = {
        "id": entry_id,
        "category": category,
        "name": name,
        "triggers": triggers,
        "content": content,
        "sources": sources,
        "tags": tags or [],
        "created_at": datetime.now().isoformat(),
        "updated_at": datetime.now().isoformat(),
        "usage_count": 0,
        "effectiveness": 0.5
    }
    
    # 确定存储目录
    if category == "tech-stack":
        category_dir = kb_dir / "tech-stacks"
    elif category == "skill":
        category_dir = kb_dir / "skills"
    else:
        category_dir = kb_dir / f"{category}s"
    
    category_dir.mkdir(parents=True, exist_ok=True)
    
    # 保存条目文件
    entry_filename = f"{name.lower().replace(' ', '-')}.json"
    entry_path = category_dir / entry_filename
    
    with open(entry_path, 'w', encoding='utf-8') as f:
        json.dump(entry, f, ensure_ascii=False, indent=2)
    
    # 更新分类索引
    cat_index = load_category_index(kb_dir, category)
    cat_index["entries"].append({
        "id": entry_id,
        "name": name,
        "created_at": entry["created_at"]
    })
    save_category_index(kb_dir, category, cat_index)
    
    # 更新全局索引
    global_index = load_global_index(kb_dir)
    update_trigger_index(global_index, entry_id, triggers)
    
    # 更新分类索引
    category_index = global_index.setdefault("category_index", {})
    if category not in category_index:
        category_index[category] = []
    if entry_id not in category_index[category]:
        category_index[category].append(entry_id)
    
    # 更新统计
    stats = global_index.setdefault("stats", {})
    stats[category] = stats.get(category, 0) + 1
    
    save_global_index(kb_dir, global_index)
    
    return entry_id


def store_skill(kb_dir: Path, data: Dict, source: str) -> str:
    """存储技能类知识"""
    content = {
        "skill_name": data.get("skill_name", data.get("name", "")),
        "level": data.get("level", "intermediate"),
        "description": data.get("description", ""),
        "key_concepts": data.get("key_concepts", []),
        "practical_tips": data.get("practical_tips", []),
        "common_mistakes": data.get("common_mistakes", [])
    }
    
    return store_knowledge_entry(
        kb_dir=kb_dir,
        category="skill",
        name=data.get("name", data.get("skill_name", "Unknown Skill")),
        triggers=data.get("triggers", []),
        content=content,
        sources=[source] if source else [],
        tags=data.get("tags", [])
    )


def store_tech_stack(kb_dir: Path, data: Dict, source: str) -> str:
    """存储技术栈知识"""
    content = {
        "tech_name": data.get("tech_name", data.get("name", "")),
        "version": data.get("version", ""),
        "best_practices": data.get("best_practices", []),
        "conventions": data.get("conventions", []),
        "common_patterns": data.get("common_patterns", []),
        "gotchas": data.get("gotchas", [])
    }
    
    return store_knowledge_entry(
        kb_dir=kb_dir,
        category="tech-stack",
        name=data.get("name", data.get("tech_name", "Unknown Tech")),
        triggers=data.get("triggers", []),
        content=content,
        sources=[source] if source else [],
        tags=data.get("tags", [])
    )


def store_pattern(kb_dir: Path, data: Dict, source: str) -> str:
    """存储模式知识"""
    content = {
        "pattern_name": data.get("pattern_name", data.get("name", "")),
        "category": data.get("pattern_category", "architecture"),
        "description": data.get("description", ""),
        "when_to_use": data.get("when_to_use", ""),
        "structure": data.get("structure", ""),
        "example": data.get("example", ""),
        "pros": data.get("pros", []),
        "cons": data.get("cons", [])
    }
    
    return store_knowledge_entry(
        kb_dir=kb_dir,
        category="pattern",
        name=data.get("name", data.get("pattern_name", "Unknown Pattern")),
        triggers=data.get("triggers", []),
        content=content,
        sources=[source] if source else [],
        tags=data.get("tags", [])
    )


def main():
    parser = argparse.ArgumentParser(description='存储知识到统一知识库')
    parser.add_argument('--category', '-c', required=True,
                       choices=['skill', 'tech-stack', 'pattern', 'problem', 'testing', 'experience', 'scenario'],
                       help='知识分类')
    parser.add_argument('--input', '-i', help='输入 JSON 文件路径')
    parser.add_argument('--source', '-s', help='知识来源 (如 GitHub URL)')
    parser.add_argument('--kb-dir', help='知识库目录路径 (默认自动检测)')
    
    args = parser.parse_args()
    
    # 确定知识库目录
    if args.kb_dir:
        kb_dir = Path(args.kb_dir)
    else:
        kb_dir = get_knowledge_base_dir()
    
    if not kb_dir.exists():
        print(f"错误: 知识库目录不存在: {kb_dir}", file=sys.stderr)
        sys.exit(1)
    
    # 读取输入数据
    if args.input:
        with open(args.input, 'r', encoding='utf-8') as f:
            data = json.load(f)
    else:
        data = json.load(sys.stdin)
    
    # 支持批量存储
    if isinstance(data, list):
        entries = data
    else:
        entries = [data]
    
    stored_ids = []
    for entry_data in entries:
        category = args.category
        source = args.source or entry_data.get("source", "")
        
        if category == "skill":
            entry_id = store_skill(kb_dir, entry_data, source)
        elif category == "tech-stack":
            entry_id = store_tech_stack(kb_dir, entry_data, source)
        elif category == "pattern":
            entry_id = store_pattern(kb_dir, entry_data, source)
        else:
            # 通用存储
            entry_id = store_knowledge_entry(
                kb_dir=kb_dir,
                category=category,
                name=entry_data.get("name", "Unknown"),
                triggers=entry_data.get("triggers", []),
                content=entry_data.get("content", {}),
                sources=[source] if source else [],
                tags=entry_data.get("tags", [])
            )
        
        stored_ids.append(entry_id)
        print(f"已存储: {entry_id}")
    
    print(f"\n共存储 {len(stored_ids)} 条知识到 {kb_dir}")


if __name__ == "__main__":
    main()


path: scripts/knowledge/__init__.py
content: # Knowledge Base Scripts


path: scripts/knowledge/query.py
content: #!/usr/bin/env python3
"""
Unified Knowledge Query

统一知识库查询接口：
- 按关键字触发查询
- 按分类查询
- 按标签查询
- 全文搜索
"""

import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Set


# Category to directory mapping
CATEGORY_DIRS = {
    'experience': 'experiences',
    'tech-stack': 'tech-stacks',
    'scenario': 'scenarios',
    'problem': 'problems',
    'testing': 'testing',
    'pattern': 'patterns',
    'skill': 'skills'
}


# 路径解析器导入
_path_resolver_module = None

def _try_import_path_resolver():
    """尝试导入 path_resolver 模块"""
    global _path_resolver_module
    if _path_resolver_module is not None:
        return _path_resolver_module if _path_resolver_module else None
    
    # 添加 path_resolver 所在目录到 Python path
    script_dir = Path(__file__).parent
    core_scripts = script_dir.parent / 'core'
    if core_scripts.exists() and str(core_scripts) not in sys.path:
        sys.path.insert(0, str(core_scripts))
    
    try:
        import path_resolver
        _path_resolver_module = path_resolver
        return path_resolver
    except ImportError:
        _path_resolver_module = False  # type: ignore
        return None


def get_kb_root() -> Path:
    """
    Get knowledge base root directory.
    
    使用统一的 path_resolver 模块进行跨平台路径解析。
    支持 OpenCode (~/.config/opencode/skills/) 和 Claude Code (~/.claude/skills/)。
    """
    # 1. 优先使用 path_resolver
    resolver = _try_import_path_resolver()
    if resolver:
        return resolver.get_knowledge_base_dir()
    
    # 2. Fallback: 内置路径解析逻辑
    env_path = os.environ.get('KNOWLEDGE_BASE_PATH')
    if env_path:
        kb_path = Path(env_path)
        if kb_path.exists():
            return kb_path
    
    opencode_kb = Path.home() / '.config' / 'opencode' / 'knowledge'
    if opencode_kb.exists():
        return opencode_kb
    
    claude_kb = Path.home() / '.claude' / 'knowledge'
    if claude_kb.exists():
        return claude_kb
    
    opencode_kb.mkdir(parents=True, exist_ok=True)
    return opencode_kb


def load_json(path: Path) -> Dict[str, Any]:
    """Safely load JSON file."""
    if not path.exists():
        return {}
    try:
        with open(path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        return {}


def get_global_index() -> Dict[str, Any]:
    """Load global index."""
    return load_json(get_kb_root() / 'index.json')


def query_by_triggers(triggers: List[str], limit: int = 10) -> List[Dict[str, Any]]:
    """
    根据触发关键字查询知识。
    
    Args:
        triggers: 触发关键字列表
        limit: 返回结果数量限制
    
    Returns:
        匹配的知识条目列表，按匹配度排序
    """
    kb_root = get_kb_root()
    index = get_global_index()
    trigger_index = index.get('trigger_index', {})
    
    # Count matches per entry
    entry_matches: Dict[str, int] = {}
    matched_entry_ids: Set[str] = set()
    
    for trigger in triggers:
        trigger_lower = trigger.lower()
        # Exact match
        if trigger_lower in trigger_index:
            for entry_id in trigger_index[trigger_lower]:
                entry_matches[entry_id] = entry_matches.get(entry_id, 0) + 2  # Higher weight for exact
                matched_entry_ids.add(entry_id)
        
        # Partial match
        for indexed_trigger, entry_ids in trigger_index.items():
            if trigger_lower in indexed_trigger or indexed_trigger in trigger_lower:
                for entry_id in entry_ids:
                    entry_matches[entry_id] = entry_matches.get(entry_id, 0) + 1
                    matched_entry_ids.add(entry_id)
    
    if not matched_entry_ids:
        return []
    
    # Sort by match count
    sorted_entries = sorted(entry_matches.items(), key=lambda x: x[1], reverse=True)
    
    # Load entry details
    results: List[Dict[str, Any]] = []
    for entry_id, match_count in sorted_entries[:limit]:
        # Determine category from entry_id
        category = entry_id.split('-')[0] if '-' in entry_id else 'experience'
        cat_dir = CATEGORY_DIRS.get(category, 'experiences')
        
        entry_path = kb_root / cat_dir / f"{entry_id}.json"
        if entry_path.exists():
            entry = load_json(entry_path)
            entry['_match_score'] = match_count
            results.append(entry)
    
    return results


def query_by_category(category: str, limit: int = 20) -> List[Dict[str, Any]]:
    """
    按分类查询所有知识条目。
    
    Args:
        category: 知识分类
        limit: 返回数量限制
    
    Returns:
        该分类下的知识条目列表
    """
    kb_root = get_kb_root()
    cat_dir = CATEGORY_DIRS.get(category)
    if not cat_dir:
        return []
    
    cat_path = kb_root / cat_dir
    if not cat_path.exists():
        return []
    
    results: List[Dict[str, Any]] = []
    for entry_file in cat_path.glob('*.json'):
        if entry_file.name == 'index.json':
            continue
        entry = load_json(entry_file)
        if entry:
            results.append(entry)
        if len(results) >= limit:
            break
    
    # Sort by effectiveness and usage
    results.sort(key=lambda x: (x.get('effectiveness', 0), x.get('usage_count', 0)), reverse=True)
    return results


def query_by_tags(tags: List[str], limit: int = 10) -> List[Dict[str, Any]]:
    """
    按标签查询知识。
    
    Args:
        tags: 标签列表
        limit: 返回数量限制
    
    Returns:
        匹配标签的知识条目列表
    """
    kb_root = get_kb_root()
    tags_lower = [t.lower() for t in tags]
    results: List[Dict[str, Any]] = []
    
    # Search all categories
    for cat_dir in CATEGORY_DIRS.values():
        cat_path = kb_root / cat_dir
        if not cat_path.exists():
            continue
        
        for entry_file in cat_path.glob('*.json'):
            if entry_file.name == 'index.json':
                continue
            entry = load_json(entry_file)
            if not entry:
                continue
            
            entry_tags = [t.lower() for t in entry.get('tags', [])]
            if any(tag in entry_tags for tag in tags_lower):
                results.append(entry)
                if len(results) >= limit:
                    break
        
        if len(results) >= limit:
            break
    
    return results


def search_content(keyword: str, limit: int = 10) -> List[Dict[str, Any]]:
    """
    全文搜索知识内容。
    
    Args:
        keyword: 搜索关键字
        limit: 返回数量限制
    
    Returns:
        包含关键字的知识条目列表
    """
    kb_root = get_kb_root()
    keyword_lower = keyword.lower()
    results: List[Dict[str, Any]] = []
    
    # Search all categories
    for cat_dir in CATEGORY_DIRS.values():
        cat_path = kb_root / cat_dir
        if not cat_path.exists():
            continue
        
        for entry_file in cat_path.glob('*.json'):
            if entry_file.name == 'index.json':
                continue
            
            # Read raw content for search
            try:
                content_str = entry_file.read_text(encoding='utf-8').lower()
                if keyword_lower in content_str:
                    entry = load_json(entry_file)
                    if entry:
                        results.append(entry)
                        if len(results) >= limit:
                            break
            except IOError:
                continue
        
        if len(results) >= limit:
            break
    
    return results


def get_entry(entry_id: str) -> Optional[Dict[str, Any]]:
    """
    获取单个知识条目。
    
    Args:
        entry_id: 知识条目ID
    
    Returns:
        知识条目，如不存在则返回 None
    """
    kb_root = get_kb_root()
    
    # Try to determine category from ID
    parts = entry_id.split('-')
    if parts:
        category = parts[0]
        cat_dir = CATEGORY_DIRS.get(category)
        if cat_dir:
            entry_path = kb_root / cat_dir / f"{entry_id}.json"
            if entry_path.exists():
                return load_json(entry_path)
    
    # Fallback: search all categories
    for cat_dir in CATEGORY_DIRS.values():
        entry_path = kb_root / cat_dir / f"{entry_id}.json"
        if entry_path.exists():
            return load_json(entry_path)
    
    return None


def get_stats() -> Dict[str, Any]:
    """获取知识库统计信息。"""
    index = get_global_index()
    return {
        'version': index.get('version', 'unknown'),
        'last_updated': index.get('last_updated'),
        'stats': index.get('stats', {}),
        'trigger_count': len(index.get('trigger_index', {})),
        'recent_entries': index.get('recent_entries', [])[:5]
    }


def format_output(data: Any, fmt: str = 'json') -> str:
    """Format output based on type."""
    if fmt == 'json':
        return json.dumps(data, indent=2, ensure_ascii=False)
    elif fmt == 'markdown':
        if isinstance(data, list):
            lines = []
            for entry in data:
                lines.append(f"### {entry.get('name', 'Unknown')}")
                lines.append(f"- **Category**: {entry.get('category', 'N/A')}")
                lines.append(f"- **Triggers**: {', '.join(entry.get('triggers', [])[:5])}")
                content = entry.get('content', {})
                if 'description' in content:
                    lines.append(f"- **Description**: {content['description'][:100]}...")
                lines.append("")
            return '\n'.join(lines)
        elif isinstance(data, dict):
            lines = []
            for key, value in data.items():
                if isinstance(value, (list, dict)):
                    lines.append(f"**{key}**: {json.dumps(value, ensure_ascii=False)}")
                else:
                    lines.append(f"**{key}**: {value}")
            return '\n'.join(lines)
    return str(data)


def main():
    parser = argparse.ArgumentParser(
        description='Query unified knowledge base',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Query by triggers (most common)
  python knowledge_query.py --trigger react,hooks,state
  
  # Query by category
  python knowledge_query.py --category problem
  
  # Search content
  python knowledge_query.py --search "跨域"
  
  # Get single entry
  python knowledge_query.py --id problem-cors-abc123
  
  # Get stats
  python knowledge_query.py --stats
        """
    )
    
    parser.add_argument('--trigger', '-t', help='Comma-separated trigger keywords')
    parser.add_argument('--category', '-c', choices=list(CATEGORY_DIRS.keys()),
                        help='Query by category')
    parser.add_argument('--tags', help='Comma-separated tags')
    parser.add_argument('--search', '-s', help='Full-text search keyword')
    parser.add_argument('--id', help='Get entry by ID')
    parser.add_argument('--stats', action='store_true', help='Show knowledge base stats')
    parser.add_argument('--limit', '-l', type=int, default=10, help='Result limit')
    parser.add_argument('--format', '-f', choices=['json', 'markdown'], default='json',
                        help='Output format')
    
    args = parser.parse_args()
    
    if args.stats:
        result = get_stats()
    elif args.id:
        result = get_entry(args.id)
        if not result:
            print(f"Entry not found: {args.id}", file=sys.stderr)
            sys.exit(1)
    elif args.trigger:
        triggers = [t.strip() for t in args.trigger.split(',')]
        result = query_by_triggers(triggers, args.limit)
    elif args.category:
        result = query_by_category(args.category, args.limit)
    elif args.tags:
        tags = [t.strip() for t in args.tags.split(',')]
        result = query_by_tags(tags, args.limit)
    elif args.search:
        result = search_content(args.search, args.limit)
    else:
        # Default: show stats
        result = get_stats()
    
    print(format_output(result, args.format))


if __name__ == '__main__':
    main()


path: scripts/knowledge/store.py
content: #!/usr/bin/env python3
"""
Unified Knowledge Store

统一知识库存储接口，支持所有分类：
- experience: 经验积累
- tech-stack: 技术栈积累
- scenario: 场景积累
- problem: 问题积累
- testing: 测试积累
- pattern: 编程范式
- skill: 编程技能
"""

import argparse
import hashlib
import json
import os
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional

# 路径解析器导入标志
_path_resolver_module = None

def _try_import_path_resolver():
    """尝试导入 path_resolver 模块"""
    global _path_resolver_module
    if _path_resolver_module is not None:
        return _path_resolver_module
    
    # 添加 path_resolver 所在目录到 Python path
    script_dir = Path(__file__).parent
    core_scripts = script_dir.parent / 'core'
    if core_scripts.exists() and str(core_scripts) not in sys.path:
        sys.path.insert(0, str(core_scripts))
    
    # 尝试导入
    try:
        import path_resolver
        _path_resolver_module = path_resolver
        return path_resolver
    except ImportError:
        _path_resolver_module = False  # type: ignore
        return None


# Category to directory mapping
CATEGORY_DIRS = {
    'experience': 'experiences',
    'tech-stack': 'tech-stacks',
    'scenario': 'scenarios',
    'problem': 'problems',
    'testing': 'testing',
    'pattern': 'patterns',
    'skill': 'skills'
}

VALID_CATEGORIES = list(CATEGORY_DIRS.keys())


def get_kb_root() -> Path:
    """
    Get knowledge base root directory.
    
    使用统一的 path_resolver 模块进行跨平台路径解析。
    支持 OpenCode (~/.config/opencode/skills/) 和 Claude Code (~/.claude/skills/)。
    
    优先级：
    1. 使用 path_resolver 模块（推荐）
    2. 环境变量 KNOWLEDGE_BASE_PATH（如果设置）
    3. 自动检测已存在的知识库目录
    4. 创建默认目录
    """
    # 1. 优先使用 path_resolver
    resolver = _try_import_path_resolver()
    if resolver:
        return resolver.get_knowledge_base_dir()
    
    # 2. Fallback: 内置路径解析逻辑
    # 检查环境变量
    env_path = os.environ.get('KNOWLEDGE_BASE_PATH')
    if env_path:
        kb_path = Path(env_path)
        if kb_path.exists():
            return kb_path
    
    # 检查 OpenCode 知识库位置
    opencode_kb = Path.home() / '.config' / 'opencode' / 'knowledge'
    if opencode_kb.exists():
        return opencode_kb
    
    # 检查 Claude Code 知识库位置
    claude_kb = Path.home() / '.claude' / 'knowledge'
    if claude_kb.exists():
        return claude_kb
    
    # 如果都不存在，创建 OpenCode 默认位置
    opencode_kb.mkdir(parents=True, exist_ok=True)
    return opencode_kb


def generate_id(category: str, name: str) -> str:
    """Generate unique ID for knowledge entry."""
    hash_input = f"{category}:{name}:{datetime.now().isoformat()}"
    hash_suffix = hashlib.md5(hash_input.encode()).hexdigest()[:8]
    name_slug = name.lower().replace(' ', '-').replace('/', '-')[:30]
    return f"{category}-{name_slug}-{hash_suffix}"


def load_json(path: Path) -> Dict[str, Any]:
    """Safely load JSON file."""
    if not path.exists():
        return {}
    try:
        with open(path, 'r', encoding='utf-8') as f:
            return json.load(f)
    except (json.JSONDecodeError, IOError):
        return {}


def save_json(path: Path, data: Dict[str, Any]) -> None:
    """Save JSON file with pretty formatting."""
    path.parent.mkdir(parents=True, exist_ok=True)
    with open(path, 'w', encoding='utf-8') as f:
        json.dump(data, f, indent=2, ensure_ascii=False)


def extract_triggers(name: str, content: Dict[str, Any], tags: Optional[List[str]] = None) -> List[str]:
    """
    自动从知识内容中提取触发关键字。
    
    提取规则：
    1. 名称分词
    2. 相关技术栈
    3. 显式标签
    4. 内容中的关键术语
    """
    triggers: set = set()
    
    # 从名称提取
    name_parts = name.lower().replace('-', ' ').replace('_', ' ').split()
    triggers.update(name_parts)
    
    # 从标签提取
    if tags:
        triggers.update(t.lower() for t in tags)
    
    # 从内容中提取相关技术
    related_tech = content.get('related_tech', [])
    triggers.update(t.lower() for t in related_tech)
    
    # 技术栈名称
    if 'tech_name' in content:
        triggers.add(content['tech_name'].lower())
    
    # 框架名称
    if 'framework' in content:
        triggers.add(content['framework'].lower())
    
    # 问题症状关键字
    if 'symptoms' in content:
        for symptom in content['symptoms']:
            # 提取简短关键字
            words = symptom.lower().split()[:3]
            triggers.update(words)
    
    # 过滤太短或太通用的词
    stop_words = {'a', 'an', 'the', 'is', 'are', 'was', 'were', 'be', 'been', 
                  'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from',
                  'and', 'or', 'but', 'not', 'as', 'if', 'when', 'than'}
    triggers = {t for t in triggers if len(t) > 2 and t not in stop_words}
    
    return sorted(list(triggers))


def update_global_index(kb_root: Path, entry_id: str, category: str, triggers: List[str]) -> None:
    """Update the global index with new entry and trigger mappings."""
    index_path = kb_root / 'index.json'
    index = load_json(index_path)
    
    # Ensure structure
    if 'trigger_index' not in index:
        index['trigger_index'] = {}
    if 'category_index' not in index:
        index['category_index'] = {d: [] for d in CATEGORY_DIRS.values()}
    if 'stats' not in index:
        index['stats'] = {'total_entries': 0, 'by_category': {}}
    
    # Update trigger index
    for trigger in triggers:
        if trigger not in index['trigger_index']:
            index['trigger_index'][trigger] = []
        if entry_id not in index['trigger_index'][trigger]:
            index['trigger_index'][trigger].append(entry_id)
    
    # Update category index
    cat_dir = CATEGORY_DIRS.get(category, category)
    if cat_dir not in index['category_index']:
        index['category_index'][cat_dir] = []
    if entry_id not in index['category_index'][cat_dir]:
        index['category_index'][cat_dir].append(entry_id)
    
    # Update stats
    index['stats']['total_entries'] = sum(
        len(entries) for entries in index['category_index'].values()
    )
    index['stats']['by_category'] = {
        cat: len(entries) for cat, entries in index['category_index'].items()
    }
    
    # Update recent entries (keep last 20)
    if 'recent_entries' not in index:
        index['recent_entries'] = []
    index['recent_entries'].insert(0, entry_id)
    index['recent_entries'] = index['recent_entries'][:20]
    
    index['last_updated'] = datetime.now().isoformat()
    save_json(index_path, index)


def update_category_index(kb_root: Path, category: str, entry_id: str, name: str) -> None:
    """Update the category-specific index."""
    cat_dir = CATEGORY_DIRS.get(category, category)
    index_path = kb_root / cat_dir / 'index.json'
    index = load_json(index_path)
    
    if 'entries' not in index:
        index['entries'] = []
    
    # Check if entry already exists
    existing = next((e for e in index['entries'] if e.get('id') == entry_id), None)
    if existing:
        existing['name'] = name
        existing['updated_at'] = datetime.now().isoformat()
    else:
        index['entries'].append({
            'id': entry_id,
            'name': name,
            'created_at': datetime.now().isoformat()
        })
    
    index['last_updated'] = datetime.now().isoformat()
    save_json(index_path, index)


def store_knowledge(
    category: str,
    name: str,
    content: Dict[str, Any],
    sources: Optional[List[str]] = None,
    tags: Optional[List[str]] = None,
    triggers: Optional[List[str]] = None,
    entry_id: Optional[str] = None
) -> Dict[str, Any]:
    """
    存储知识条目到统一知识库。
    
    Args:
        category: 知识分类 (experience, tech-stack, scenario, problem, testing, pattern, skill)
        name: 知识条目名称
        content: 知识内容 (符合对应分类的 schema)
        sources: 来源列表 (GitHub URL, 会话 ID 等)
        tags: 额外标签
        triggers: 显式指定的触发关键字 (可选，会自动提取)
        entry_id: 已有条目ID (用于更新)
    
    Returns:
        创建/更新的知识条目
    """
    if category not in VALID_CATEGORIES:
        raise ValueError(f"Invalid category: {category}. Must be one of: {VALID_CATEGORIES}")
    
    kb_root = get_kb_root()
    cat_dir = CATEGORY_DIRS[category]
    
    # Generate or use existing ID
    if not entry_id:
        entry_id = generate_id(category, name)
    
    # Auto-extract triggers if not provided
    if triggers is None:
        triggers = extract_triggers(name, content, tags)
    else:
        # Merge auto-extracted with explicit triggers
        auto_triggers = extract_triggers(name, content, tags)
        triggers = list(set(triggers + auto_triggers))
    
    # Build entry
    now = datetime.now().isoformat()
    entry_path = kb_root / cat_dir / f"{entry_id}.json"
    
    # Load existing entry if updating
    existing = load_json(entry_path) if entry_path.exists() else {}
    
    entry: Dict[str, Any] = {
        'id': entry_id,
        'category': category,
        'name': name,
        'triggers': triggers,
        'content': content,
        'sources': sources or existing.get('sources', []),
        'tags': tags or existing.get('tags', []),
        'created_at': existing.get('created_at', now),
        'updated_at': now,
        'usage_count': existing.get('usage_count', 0),
        'effectiveness': existing.get('effectiveness', 0.5)
    }
    
    # Merge sources if updating
    if existing.get('sources'):
        all_sources = list(set(existing['sources'] + (sources or [])))
        entry['sources'] = all_sources
    
    # Save entry
    save_json(entry_path, entry)
    
    # Update indexes
    update_category_index(kb_root, category, entry_id, name)
    update_global_index(kb_root, entry_id, category, triggers)
    
    return entry


def store_experience(
    name: str,
    description: str,
    solution: str,
    context: Optional[str] = None,
    pitfalls: Optional[List[str]] = None,
    related_tech: Optional[List[str]] = None,
    sources: Optional[List[str]] = None,
    tags: Optional[List[str]] = None
) -> Dict[str, Any]:
    """便捷方法：存储经验类知识。"""
    content = {
        'description': description,
        'context': context or '',
        'solution': solution,
        'pitfalls': pitfalls or [],
        'related_tech': related_tech or []
    }
    return store_knowledge('experience', name, content, sources, tags)


def store_tech_stack(
    tech_name: str,
    best_practices: Optional[List[str]] = None,
    conventions: Optional[List[str]] = None,
    common_patterns: Optional[List[str]] = None,
    gotchas: Optional[List[str]] = None,
    version: Optional[str] = None,
    sources: Optional[List[str]] = None
) -> Dict[str, Any]:
    """便捷方法：存储技术栈知识。"""
    content = {
        'tech_name': tech_name,
        'version': version or '',
        'best_practices': best_practices or [],
        'conventions': conventions or [],
        'common_patterns': common_patterns or [],
        'gotchas': gotchas or []
    }
    return store_knowledge('tech-stack', tech_name, content, sources, [tech_name.lower()])


def store_scenario(
    scenario_name: str,
    description: str,
    typical_approach: str,
    steps: Optional[List[str]] = None,
    considerations: Optional[List[str]] = None,
    related_tech: Optional[List[str]] = None,
    sources: Optional[List[str]] = None
) -> Dict[str, Any]:
    """便捷方法：存储场景知识。"""
    content = {
        'scenario_name': scenario_name,
        'description': description,
        'typical_approach': typical_approach,
        'steps': steps or [],
        'considerations': considerations or [],
        'related_tech': related_tech or []
    }
    return store_knowledge('scenario', scenario_name, content, sources)


def store_problem(
    problem_name: str,
    symptoms: List[str],
    root_causes: List[str],
    solutions: List[Dict[str, str]],
    prevention: Optional[List[str]] = None,
    sources: Optional[List[str]] = None,
    tags: Optional[List[str]] = None
) -> Dict[str, Any]:
    """便捷方法：存储问题知识。"""
    content = {
        'problem_name': problem_name,
        'symptoms': symptoms,
        'root_causes': root_causes,
        'solutions': solutions,
        'prevention': prevention or []
    }
    return store_knowledge('problem', problem_name, content, sources, tags)


def store_testing(
    name: str,
    testing_type: str,
    framework: Optional[str] = None,
    best_practices: Optional[List[str]] = None,
    patterns: Optional[List[str]] = None,
    anti_patterns: Optional[List[str]] = None,
    example_structure: Optional[str] = None,
    sources: Optional[List[str]] = None
) -> Dict[str, Any]:
    """便捷方法：存储测试知识。"""
    content = {
        'testing_type': testing_type,
        'framework': framework or '',
        'best_practices': best_practices or [],
        'patterns': patterns or [],
        'anti_patterns': anti_patterns or [],
        'example_structure': example_structure or ''
    }
    tags = [testing_type, 'testing']
    if framework:
        tags.append(framework.lower())
    return store_knowledge('testing', name, content, sources, tags)


def store_pattern(
    pattern_name: str,
    pattern_category: str,
    description: str,
    when_to_use: str,
    structure: Optional[str] = None,
    example: Optional[str] = None,
    pros: Optional[List[str]] = None,
    cons: Optional[List[str]] = None,
    sources: Optional[List[str]] = None
) -> Dict[str, Any]:
    """便捷方法：存储编程范式。"""
    content = {
        'pattern_name': pattern_name,
        'category': pattern_category,
        'description': description,
        'when_to_use': when_to_use,
        'structure': structure or '',
        'example': example or '',
        'pros': pros or [],
        'cons': cons or []
    }
    return store_knowledge('pattern', pattern_name, content, sources, [pattern_category])


def store_skill(
    skill_name: str,
    level: str,
    description: str,
    key_concepts: Optional[List[str]] = None,
    practical_tips: Optional[List[str]] = None,
    common_mistakes: Optional[List[str]] = None,
    sources: Optional[List[str]] = None
) -> Dict[str, Any]:
    """便捷方法：存储编程技能。"""
    content = {
        'skill_name': skill_name,
        'level': level,
        'description': description,
        'key_concepts': key_concepts or [],
        'practical_tips': practical_tips or [],
        'common_mistakes': common_mistakes or []
    }
    return store_knowledge('skill', skill_name, content, sources, [level])


def main():
    parser = argparse.ArgumentParser(
        description='Store knowledge to unified knowledge base',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Store from JSON input
  echo '{"name": "CORS Issue", "content": {...}}' | python knowledge_store.py --category problem
  
  # Store with explicit parameters
  python knowledge_store.py --category tech-stack --name "React" --content '{"best_practices": [...]}'
  
  # Import from stdin
  python knowledge_store.py --from-json --source "https://github.com/..."
        """
    )
    
    parser.add_argument('--category', '-c', choices=VALID_CATEGORIES,
                        help='Knowledge category')
    parser.add_argument('--name', '-n', help='Knowledge entry name')
    parser.add_argument('--content', help='JSON content string')
    parser.add_argument('--source', '-s', help='Source URL or identifier')
    parser.add_argument('--tags', '-t', help='Comma-separated tags')
    parser.add_argument('--from-json', action='store_true',
                        help='Read full entry from stdin as JSON')
    
    args = parser.parse_args()
    
    if args.from_json:
        # Read from stdin
        try:
            data = json.load(sys.stdin)
            entry = store_knowledge(
                category=data.get('category', 'experience'),
                name=data.get('name', 'Unnamed'),
                content=data.get('content', {}),
                sources=[args.source] if args.source else data.get('sources'),
                tags=data.get('tags'),
                triggers=data.get('triggers')
            )
            print(json.dumps(entry, indent=2, ensure_ascii=False))
        except json.JSONDecodeError as e:
            print(f"Error parsing JSON: {e}", file=sys.stderr)
            sys.exit(1)
    elif args.category and args.name:
        content = json.loads(args.content) if args.content else {}
        sources = [args.source] if args.source else None
        tags = args.tags.split(',') if args.tags else None
        
        entry = store_knowledge(
            category=args.category,
            name=args.name,
            content=content,
            sources=sources,
            tags=tags
        )
        print(json.dumps(entry, indent=2, ensure_ascii=False))
    else:
        parser.print_help()


if __name__ == '__main__':
    main()


path: scripts/knowledge/summarizer.py
content: #!/usr/bin/env python3
"""
Knowledge Summarizer

知识归纳总结器 - 分析会话内容，自动提取、分类并存储知识。

功能：
1. 从会话记录中提取有价值的知识
2. 自动分类到正确的 category (experience, problem, scenario, etc.)
3. 生成触发关键字
4. 合并相似知识条目
5. 更新知识有效性评分
"""

import argparse
import json
import re
import sys
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple

from store import (
    store_experience, store_tech_stack, store_scenario,
    store_problem, store_testing, store_pattern, store_skill,
    store_knowledge, get_kb_root, load_json, save_json,
    CATEGORY_DIRS
)
from query import query_by_triggers, search_content


# 知识提取模式
EXTRACTION_PATTERNS = {
    # 问题-解决方案模式
    'problem_solution': [
        r'问题[：:]\s*(.+?)[\n。].*?解决[：:方案]\s*(.+?)[\n。]',
        r'遇到.+?(错误|问题|bug).+?通过(.+?)解决',
        r'(error|issue|bug).+?fixed by (.+)',
        r'修复了(.+?)(?:，|,)(.+)',
    ],
    
    # 最佳实践模式
    'best_practice': [
        r'最佳实践[：:]\s*(.+?)(?:[\n。]|$)',
        r'建议[：:]\s*(.+?)(?:[\n。]|$)',
        r'(?:应该|推荐|最好)(.+?)(?:，|,|。|\n|$)',
        r'best practice[：:]\s*(.+?)(?:[\n.]|$)',
    ],
    
    # 注意事项模式
    'gotcha': [
        r'注意[：:]\s*(.+?)(?:[\n。]|$)',
        r'(?:需要|要)注意(.+?)(?:，|。|\n|$)',
        r'(?:坑|陷阱)[：:]\s*(.+?)(?:[\n。]|$)',
        r'gotcha[：:]\s*(.+?)(?:[\n.]|$)',
        r'(?:don\'t|avoid|不要)(.+?)(?:，|。|\n|$)',
    ],
    
    # 用户反馈/偏好模式 - 使用贪婪匹配捕获完整内容
    'user_feedback': [
        r'(?:记住|保存|重要)[：:]\s*(.+)',
        r'(以后.+)',
        r'((?:总是|一直|统一)(?:使用|用).+)',
        r'(.+(?:项目|工程)(?:都|一直|统一)(?:使用|用).+)',
    ]
}

# 分类推断关键字
CATEGORY_INDICATORS = {
    'experience': ['经验', '教训', '学到', 'learned', '发现'],
    'problem': ['问题', '错误', 'error', 'bug', 'issue', '报错', '失败'],
    'scenario': ['场景', '需求', '实现', 'implement', 'create', '开发'],
    'testing': ['测试', 'test', 'mock', 'jest', 'pytest', '单元测试', 'e2e'],
    'pattern': ['模式', 'pattern', '架构', 'architecture', '设计'],
    'tech-stack': ['框架', 'framework', '库', 'library', '工具', 'tool'],
    'skill': ['技巧', '方法', 'technique', 'skill', '技能']
}


def extract_knowledge_from_text(text: str) -> List[Dict[str, Any]]:
    """
    从文本中提取知识条目。
    
    Args:
        text: 会话文本或代码注释
    
    Returns:
        提取的知识条目列表
    """
    extracted: List[Dict[str, Any]] = []
    text = text.strip()
    
    # 问题-解决方案
    for pattern in EXTRACTION_PATTERNS['problem_solution']:
        matches = re.findall(pattern, text, re.IGNORECASE | re.DOTALL)
        for match in matches:
            if isinstance(match, tuple) and len(match) >= 2:
                problem, solution = match[0].strip(), match[1].strip()
                if len(problem) > 5 and len(solution) > 5:
                    extracted.append({
                        'type': 'problem',
                        'name': problem[:50],
                        'content': {
                            'problem_name': problem,
                            'symptoms': [problem],
                            'root_causes': [],
                            'solutions': [{'description': solution}],
                            'prevention': []
                        }
                    })
    
    # 最佳实践
    for pattern in EXTRACTION_PATTERNS['best_practice']:
        matches = re.findall(pattern, text, re.IGNORECASE)
        for match in matches:
            practice = match.strip() if isinstance(match, str) else match[0].strip()
            if len(practice) > 10:
                extracted.append({
                    'type': 'experience',
                    'name': f"最佳实践: {practice[:30]}",
                    'content': {
                        'description': practice,
                        'context': '',
                        'solution': practice,
                        'pitfalls': [],
                        'related_tech': []
                    }
                })
    
    # 注意事项 (gotchas)
    for pattern in EXTRACTION_PATTERNS['gotcha']:
        matches = re.findall(pattern, text, re.IGNORECASE)
        for match in matches:
            gotcha = match.strip() if isinstance(match, str) else match[0].strip()
            if len(gotcha) > 5:
                extracted.append({
                    'type': 'experience',
                    'name': f"注意: {gotcha[:30]}",
                    'content': {
                        'description': gotcha,
                        'context': '需要特别注意',
                        'solution': '',
                        'pitfalls': [gotcha],
                        'related_tech': []
                    }
                })
    
    # 用户偏好/反馈 - 使用 set 去重
    seen_preferences: Set[str] = set()
    for pattern in EXTRACTION_PATTERNS['user_feedback']:
        matches = re.findall(pattern, text, re.IGNORECASE)
        for match in matches:
            if isinstance(match, tuple):
                # 多组捕获，合并为完整内容
                preference = ' '.join(m.strip() for m in match if m.strip())
            else:
                preference = match.strip()
            
            # 去重：使用小写和去除空格的版本作为 key
            preference_key = preference.lower().replace(' ', '')
            if len(preference) > 5 and preference_key not in seen_preferences:
                seen_preferences.add(preference_key)
                extracted.append({
                    'type': 'experience',
                    'name': f"偏好: {preference[:40]}",
                    'content': {
                        'description': preference,
                        'context': '用户偏好设置',
                        'solution': preference,
                        'pitfalls': [],
                        'related_tech': []
                    }
                })
    
    # 如果没有匹配到任何模式，但输入足够短且有意义，作为通用经验存储
    if not extracted and 5 < len(text) < 200:
        # 简短输入，直接作为经验存储
        extracted.append({
            'type': 'experience',
            'name': f"经验: {text[:40]}",
            'content': {
                'description': text,
                'context': '用户记录的经验',
                'solution': text,
                'pitfalls': [],
                'related_tech': []
            }
        })
    
    return extracted


def infer_category(text: str, default: str = 'experience') -> str:
    """
    根据文本内容推断知识分类。
    
    Args:
        text: 知识内容文本
        default: 默认分类
    
    Returns:
        推断的分类
    """
    text_lower = text.lower()
    scores: Dict[str, int] = {cat: 0 for cat in CATEGORY_INDICATORS}
    
    for category, indicators in CATEGORY_INDICATORS.items():
        for indicator in indicators:
            if indicator in text_lower:
                scores[category] += 1
    
    # 返回得分最高的分类
    max_score = max(scores.values())
    if max_score > 0:
        for cat, score in scores.items():
            if score == max_score:
                return cat
    
    return default


def extract_tech_stack(text: str) -> List[str]:
    """从文本中提取技术栈关键字。"""
    # 常见技术栈列表
    tech_keywords = [
        'react', 'vue', 'angular', 'svelte', 'next', 'nuxt',
        'express', 'fastify', 'nest', 'koa',
        'django', 'flask', 'fastapi',
        'spring', 'springboot', 'quarkus',
        'gin', 'fiber', 'echo',
        'typescript', 'javascript', 'python', 'java', 'go', 'rust',
        'mysql', 'postgres', 'mongodb', 'redis',
        'docker', 'kubernetes', 'k8s',
        'jest', 'vitest', 'pytest', 'junit',
        'webpack', 'vite', 'rollup',
        'graphql', 'rest', 'grpc'
    ]
    
    text_lower = text.lower()
    found: Set[str] = set()
    
    for tech in tech_keywords:
        if tech in text_lower:
            found.add(tech)
    
    return list(found)


def find_similar_entries(name: str, content: Dict[str, Any], threshold: float = 0.5) -> List[Dict[str, Any]]:
    """
    查找相似的知识条目，用于去重和合并。
    
    Args:
        name: 知识名称
        content: 知识内容
        threshold: 相似度阈值
    
    Returns:
        相似条目列表
    """
    # 使用名称关键字搜索
    keywords = name.lower().split()[:3]
    similar: List[Dict[str, Any]] = []
    
    for kw in keywords:
        if len(kw) > 3:
            results = search_content(kw, limit=5)
            similar.extend(results)
    
    # 去重
    seen_ids: Set[str] = set()
    unique: List[Dict[str, Any]] = []
    for entry in similar:
        entry_id = entry.get('id', '')
        if entry_id and entry_id not in seen_ids:
            seen_ids.add(entry_id)
            unique.append(entry)
    
    return unique


def summarize_session(
    session_content: str,
    session_id: Optional[str] = None,
    auto_store: bool = False
) -> Dict[str, Any]:
    """
    分析会话内容，提取并归纳知识。
    
    Args:
        session_content: 会话内容文本
        session_id: 会话ID (作为来源标识)
        auto_store: 是否自动存储到知识库
    
    Returns:
        {
            'extracted': [...],      # 提取的知识条目
            'categorized': {...},    # 按分类整理
            'tech_stack': [...],     # 检测到的技术栈
            'stored': [...]          # 已存储的条目ID (如果 auto_store=True)
        }
    """
    result: Dict[str, Any] = {
        'extracted': [],
        'categorized': {cat: [] for cat in CATEGORY_DIRS.keys()},
        'tech_stack': [],
        'stored': [],
        'similar_found': []
    }
    
    # 1. 提取知识
    extracted = extract_knowledge_from_text(session_content)
    result['extracted'] = extracted
    
    # 2. 检测技术栈
    tech_stack = extract_tech_stack(session_content)
    result['tech_stack'] = tech_stack
    
    # 3. 分类知识
    for entry in extracted:
        entry_type = entry.get('type', 'experience')
        content_text = json.dumps(entry.get('content', {}), ensure_ascii=False)
        
        # 推断更精确的分类
        inferred_cat = infer_category(content_text, entry_type)
        entry['inferred_category'] = inferred_cat
        
        # 添加技术栈标签
        entry['related_tech'] = tech_stack
        
        result['categorized'][inferred_cat].append(entry)
    
    # 4. 查找相似条目
    for entry in extracted:
        similar = find_similar_entries(entry.get('name', ''), entry.get('content', {}))
        if similar:
            result['similar_found'].append({
                'entry': entry.get('name'),
                'similar': [s.get('name') for s in similar[:3]]
            })
    
    # 5. 自动存储
    if auto_store:
        sources = [session_id] if session_id else []
        
        for entry in extracted:
            category = entry.get('inferred_category', 'experience')
            name = entry.get('name', 'Unknown')
            content = entry.get('content', {})
            
            # 添加技术栈到内容
            if 'related_tech' not in content:
                content['related_tech'] = tech_stack
            
            try:
                stored = store_knowledge(
                    category=category,
                    name=name,
                    content=content,
                    sources=sources,
                    tags=tech_stack
                )
                result['stored'].append(stored.get('id'))
            except Exception as e:
                result['stored'].append(f"Error: {str(e)}")
    
    return result


def update_effectiveness(entry_id: str, positive: bool = True) -> bool:
    """
    根据反馈更新知识有效性评分。
    
    Args:
        entry_id: 知识条目ID
        positive: 是否为正面反馈
    
    Returns:
        是否更新成功
    """
    kb_root = get_kb_root()
    
    # 找到条目文件
    for cat_dir in CATEGORY_DIRS.values():
        entry_path = kb_root / cat_dir / f"{entry_id}.json"
        if entry_path.exists():
            entry = load_json(entry_path)
            
            # 更新使用次数
            entry['usage_count'] = entry.get('usage_count', 0) + 1
            
            # 更新有效性评分 (滑动平均)
            current = entry.get('effectiveness', 0.5)
            delta = 0.1 if positive else -0.1
            new_score = max(0, min(1, current + delta))
            entry['effectiveness'] = new_score
            entry['updated_at'] = datetime.now().isoformat()
            
            save_json(entry_path, entry)
            return True
    
    return False


def main():
    parser = argparse.ArgumentParser(
        description='Summarize and extract knowledge from session content',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Analyze session from stdin
  cat session.txt | python knowledge_summarizer.py --session-id "session-123"
  
  # Analyze and auto-store
  cat session.txt | python knowledge_summarizer.py --auto-store
  
  # Update effectiveness
  python knowledge_summarizer.py --feedback positive --entry-id "problem-cors-abc123"
        """
    )
    
    parser.add_argument('--session-id', help='Session identifier for source tracking')
    parser.add_argument('--auto-store', action='store_true', help='Automatically store extracted knowledge')
    parser.add_argument('--feedback', choices=['positive', 'negative'], 
                        help='Update entry effectiveness')
    parser.add_argument('--entry-id', help='Entry ID for feedback update')
    parser.add_argument('--format', '-f', choices=['json', 'summary'], default='json',
                        help='Output format')
    
    args = parser.parse_args()
    
    if args.feedback and args.entry_id:
        success = update_effectiveness(args.entry_id, args.feedback == 'positive')
        print(json.dumps({'success': success, 'entry_id': args.entry_id}))
        return
    
    # Read session content from stdin
    session_content = sys.stdin.read()
    
    if not session_content.strip():
        print("Error: No session content provided via stdin", file=sys.stderr)
        sys.exit(1)
    
    result = summarize_session(
        session_content=session_content,
        session_id=args.session_id,
        auto_store=args.auto_store
    )
    
    if args.format == 'json':
        print(json.dumps(result, indent=2, ensure_ascii=False))
    elif args.format == 'summary':
        print(f"提取的知识条目: {len(result['extracted'])}")
        print(f"检测到的技术栈: {', '.join(result['tech_stack']) or 'None'}")
        print("\n按分类:")
        for cat, entries in result['categorized'].items():
            if entries:
                print(f"  {cat}: {len(entries)}")
        if result['stored']:
            print(f"\n已存储: {len(result['stored'])} 条")
        if result['similar_found']:
            print(f"\n发现相似条目: {len(result['similar_found'])} 组")


if __name__ == '__main__':
    main()


path: scripts/knowledge/trigger.py
content: #!/usr/bin/env python3
"""
Knowledge Trigger Detector

知识触发检测器 - 根据用户输入和项目上下文自动检测并加载相关知识。

触发方式：
1. 项目检测 (package.json, go.mod, pom.xml 等)
2. 用户输入关键字匹配
3. 场景推断 (根据动词和上下文)
"""

import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any, Dict, List, Optional, Set

# Import query functions
from query import (
    get_kb_root, load_json, get_global_index,
    query_by_triggers, query_by_category, get_entry
)


# 场景关键字映射
SCENARIO_KEYWORDS = {
    'api': ['api', 'rest', 'restful', 'graphql', 'endpoint', '接口', '请求'],
    'auth': ['auth', 'login', 'logout', '登录', '认证', '授权', 'jwt', 'oauth', 'session'],
    'database': ['database', 'db', 'sql', 'query', '数据库', 'mysql', 'postgres', 'mongodb', 'redis'],
    'testing': ['test', 'testing', 'unit', 'e2e', 'integration', '测试', 'jest', 'pytest', 'vitest'],
    'deploy': ['deploy', 'deployment', 'ci', 'cd', 'docker', 'k8s', '部署', '上线'],
    'performance': ['performance', 'optimize', 'slow', 'fast', '性能', '优化', '慢'],
    'security': ['security', 'secure', 'xss', 'csrf', 'injection', '安全', '漏洞'],
    'state': ['state', 'store', 'redux', 'vuex', 'zustand', '状态管理'],
    'form': ['form', 'validation', 'input', '表单', '校验'],
    'routing': ['route', 'router', 'navigation', '路由', '导航'],
    'error': ['error', 'bug', 'fix', 'debug', '错误', '报错', 'issue', '问题']
}

# 问题症状关键字
PROBLEM_SYMPTOMS = {
    'cors': ['cors', '跨域', 'cross-origin', 'access-control'],
    'memory': ['memory', 'leak', '内存', '泄露', 'oom'],
    'timeout': ['timeout', '超时', 'hang', '卡住'],
    'crash': ['crash', '崩溃', '闪退'],
    'import': ['import', 'module', 'cannot find', '找不到模块', 'not found'],
    'type': ['type', 'typescript', '类型错误', 'type error'],
    'null': ['null', 'undefined', 'cannot read', 'is not defined'],
    'async': ['async', 'await', 'promise', 'callback', '异步']
}

# 动作关键字映射到场景
ACTION_TO_SCENARIO = {
    'create': 'scenario', 'build': 'scenario', 'implement': 'scenario', 'add': 'scenario',
    '创建': 'scenario', '实现': 'scenario', '开发': 'scenario', '新增': 'scenario',
    'fix': 'problem', 'debug': 'problem', 'solve': 'problem', 'resolve': 'problem',
    '修复': 'problem', '解决': 'problem', '排查': 'problem',
    'optimize': 'experience', 'improve': 'experience', 'refactor': 'experience',
    '优化': 'experience', '重构': 'experience',
    'test': 'testing', 'mock': 'testing', '测试': 'testing', '验证': 'testing'
}


def extract_keywords(text: str) -> List[str]:
    """从文本中提取关键字。"""
    keywords: Set[str] = set()
    
    # 英文单词 (3+ 字符)
    english_words = re.findall(r'\b[a-zA-Z][a-zA-Z0-9\-\.]+\b', text.lower())
    keywords.update(w for w in english_words if len(w) >= 3)
    
    # 中文词组 (2-4字)
    chinese_words = re.findall(r'[\u4e00-\u9fa5]{2,4}', text)
    keywords.update(chinese_words)
    
    # 技术术语 (如 react-query, vue.js)
    tech_terms = re.findall(r'[a-zA-Z]+[\-\.][a-zA-Z]+', text.lower())
    keywords.update(tech_terms)
    
    return list(keywords)


def detect_scenarios(text: str) -> List[str]:
    """检测文本中涉及的场景。"""
    text_lower = text.lower()
    detected: Set[str] = set()
    
    for scenario, keywords in SCENARIO_KEYWORDS.items():
        for kw in keywords:
            if kw in text_lower:
                detected.add(scenario)
                break
    
    return list(detected)


def detect_problems(text: str) -> List[str]:
    """检测文本中描述的问题类型。"""
    text_lower = text.lower()
    detected: Set[str] = set()
    
    for problem, symptoms in PROBLEM_SYMPTOMS.items():
        for symptom in symptoms:
            if symptom in text_lower:
                detected.add(problem)
                break
    
    return list(detected)


def detect_action_type(text: str) -> Optional[str]:
    """检测用户意图的动作类型。"""
    text_lower = text.lower()
    
    for action, category in ACTION_TO_SCENARIO.items():
        if action in text_lower:
            return category
    
    return None


def detect_project_tech(project_dir: str) -> Dict[str, Any]:
    """检测项目技术栈。"""
    detector_path = Path(__file__).parent.parent / 'programming'
    sys.path.insert(0, str(detector_path))
    
    try:
        from detect_project import detect_project
        return detect_project(project_dir)
    except ImportError:
        return {'error': 'Project detector not available'}


def trigger_knowledge(
    user_input: Optional[str] = None,
    project_dir: Optional[str] = None,
    explicit_triggers: Optional[List[str]] = None,
    limit: int = 5
) -> Dict[str, Any]:
    """
    主触发函数 - 根据输入检测并加载相关知识。
    
    Args:
        user_input: 用户输入的文本
        project_dir: 项目目录路径
        explicit_triggers: 显式指定的触发关键字
        limit: 每类知识返回的条目数限制
    
    Returns:
        检测结果和匹配的知识
    """
    result: Dict[str, Any] = {
        'detected': {
            'keywords': [],
            'scenarios': [],
            'problems': [],
            'tech_stack': None,
            'action_type': None
        },
        'knowledge': {
            'high_relevance': [],
            'medium_relevance': [],
            'by_category': {}
        },
        'triggers_used': []
    }
    
    all_triggers: Set[str] = set()
    
    # 1. 处理显式触发关键字
    if explicit_triggers:
        all_triggers.update(explicit_triggers)
    
    # 2. 从用户输入提取
    if user_input:
        keywords = extract_keywords(user_input)
        result['detected']['keywords'] = keywords
        all_triggers.update(keywords)
        
        scenarios = detect_scenarios(user_input)
        result['detected']['scenarios'] = scenarios
        all_triggers.update(scenarios)
        
        problems = detect_problems(user_input)
        result['detected']['problems'] = problems
        all_triggers.update(problems)
        
        action_type = detect_action_type(user_input)
        result['detected']['action_type'] = action_type
    
    # 3. 从项目检测
    if project_dir:
        tech_detection = detect_project_tech(project_dir)
        if 'error' not in tech_detection:
            result['detected']['tech_stack'] = tech_detection
            all_triggers.update(tech_detection.get('base_tech', []))
            all_triggers.update(tech_detection.get('frameworks', []))
            all_triggers.update(tech_detection.get('tools', []))
    
    result['triggers_used'] = sorted(list(all_triggers))
    
    # 4. 查询知识库
    if all_triggers:
        matched = query_by_triggers(list(all_triggers), limit=limit * 2)
        
        high_threshold = 3
        for entry in matched:
            match_score = entry.get('_match_score', 0)
            if match_score >= high_threshold:
                result['knowledge']['high_relevance'].append(entry)
            else:
                result['knowledge']['medium_relevance'].append(entry)
        
        result['knowledge']['high_relevance'] = result['knowledge']['high_relevance'][:limit]
        result['knowledge']['medium_relevance'] = result['knowledge']['medium_relevance'][:limit]
    
    # 5. 根据检测到的场景/问题补充查询
    if result['detected']['scenarios']:
        for scenario in result['detected']['scenarios'][:2]:
            entries = query_by_category('scenario', limit=2)
            if entries:
                result['knowledge']['by_category'][f'scenario:{scenario}'] = entries
    
    if result['detected']['problems']:
        for problem in result['detected']['problems'][:2]:
            entries = query_by_category('problem', limit=2)
            if entries:
                result['knowledge']['by_category'][f'problem:{problem}'] = entries
    
    return result


def format_for_context(knowledge_result: Dict[str, Any]) -> str:
    """将知识结果格式化为可嵌入上下文的简洁格式。"""
    lines: List[str] = []
    
    high_rel = knowledge_result.get('knowledge', {}).get('high_relevance', [])
    if high_rel:
        lines.append("## 相关知识")
        for entry in high_rel[:3]:
            name = entry.get('name', 'Unknown')
            category = entry.get('category', '')
            content = entry.get('content', {})
            
            lines.append(f"\n### [{category}] {name}")
            
            if 'solution' in content:
                lines.append(f"**解决方案**: {content['solution'][:200]}")
            if 'best_practices' in content:
                practices = content['best_practices'][:3]
                lines.append("**最佳实践**:")
                for p in practices:
                    lines.append(f"- {p}")
            if 'symptoms' in content:
                symptoms = content['symptoms'][:2]
                lines.append(f"**症状**: {'; '.join(symptoms)}")
            if 'typical_approach' in content:
                lines.append(f"**典型方法**: {content['typical_approach'][:150]}")
    
    med_rel = knowledge_result.get('knowledge', {}).get('medium_relevance', [])
    if med_rel and len(lines) < 30:
        lines.append("\n## 可能相关")
        for entry in med_rel[:2]:
            name = entry.get('name', 'Unknown')
            category = entry.get('category', '')
            lines.append(f"- [{category}] {name}")
    
    return '\n'.join(lines)


def main():
    parser = argparse.ArgumentParser(
        description='Detect and trigger relevant knowledge based on input',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python knowledge_trigger.py --input "帮我修复这个 CORS 跨域问题"
  python knowledge_trigger.py --project /path/to/react-app
  python knowledge_trigger.py --input "如何优化 API 性能" --project .
  python knowledge_trigger.py --trigger react,hooks,performance
  python knowledge_trigger.py --input "..." --format context
        """
    )
    
    parser.add_argument('--input', '-i', help='User input text')
    parser.add_argument('--project', '-p', help='Project directory path')
    parser.add_argument('--trigger', '-t', help='Comma-separated explicit triggers')
    parser.add_argument('--limit', '-l', type=int, default=5, help='Result limit')
    parser.add_argument('--format', '-f', choices=['json', 'context', 'triggers'],
                        default='json', help='Output format')
    
    args = parser.parse_args()
    
    if not any([args.input, args.project, args.trigger]):
        parser.print_help()
        sys.exit(1)
    
    explicit_triggers = None
    if args.trigger:
        explicit_triggers = [t.strip() for t in args.trigger.split(',')]
    
    result = trigger_knowledge(
        user_input=args.input,
        project_dir=args.project,
        explicit_triggers=explicit_triggers,
        limit=args.limit
    )
    
    if args.format == 'json':
        print(json.dumps(result, indent=2, ensure_ascii=False, default=str))
    elif args.format == 'context':
        print(format_for_context(result))
    elif args.format == 'triggers':
        print(','.join(result.get('triggers_used', [])))


if __name__ == '__main__':
    main()


path: scripts/programming/__init__.py
content: # Programming assistant scripts package


path: scripts/programming/store_experience.py
content: #!/usr/bin/env python3
"""
Experience Storage Tool (v2.0)

Store experiences in a progressive-loading friendly structure.
Experiences are stored by tech stack and context, not all in one file.
"""

import argparse
import json
import os
import sys
from pathlib import Path
from datetime import datetime


def ensure_directories(experience_dir: Path):
    """Ensure experience directory structure exists."""
    experience_dir.mkdir(parents=True, exist_ok=True)
    (experience_dir / 'tech').mkdir(exist_ok=True)
    (experience_dir / 'contexts').mkdir(exist_ok=True)


def load_index(experience_dir: Path) -> dict:
    """Load the experience index."""
    index_path = experience_dir / 'index.json'
    if not index_path.exists():
        return {
            'version': '2.0.0',
            'last_updated': None,
            'index': {'tech_stacks': [], 'contexts': [], 'total_experiences': 0},
            'preferences': [],
            'fixes': []
        }
    
    with open(index_path, 'r', encoding='utf-8') as f:
        return json.load(f)


def save_index(experience_dir: Path, index: dict):
    """Save the experience index."""
    index['last_updated'] = datetime.now().isoformat()
    index_path = experience_dir / 'index.json'
    with open(index_path, 'w', encoding='utf-8') as f:
        json.dump(index, f, indent=2, ensure_ascii=False)


def add_preference(experience_dir: Path, preference: str):
    """Add a user preference."""
    index = load_index(experience_dir)
    if preference not in index['preferences']:
        index['preferences'].append(preference)
        index['index']['total_experiences'] += 1
        save_index(experience_dir, index)
        print(f"Added preference: {preference}")
    else:
        print(f"Preference already exists: {preference}")


def add_fix(experience_dir: Path, fix: str):
    """Add a known fix."""
    index = load_index(experience_dir)
    if fix not in index['fixes']:
        index['fixes'].append(fix)
        index['index']['total_experiences'] += 1
        save_index(experience_dir, index)
        print(f"Added fix: {fix}")
    else:
        print(f"Fix already exists: {fix}")


def add_tech_pattern(experience_dir: Path, tech: str, pattern: str):
    """Add a pattern for a specific tech stack."""
    tech_lower = tech.lower()
    tech_file = experience_dir / 'tech' / f'{tech_lower}.json'
    
    if tech_file.exists():
        with open(tech_file, 'r', encoding='utf-8') as f:
            data = json.load(f)
    else:
        data = {'name': tech, 'patterns': [], 'tips': []}
    
    if pattern not in data['patterns']:
        data['patterns'].append(pattern)
        with open(tech_file, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
        
        # Update index
        index = load_index(experience_dir)
        if tech_lower not in index['index']['tech_stacks']:
            index['index']['tech_stacks'].append(tech_lower)
        index['index']['total_experiences'] += 1
        save_index(experience_dir, index)
        
        print(f"Added pattern to {tech}: {pattern}")
    else:
        print(f"Pattern already exists in {tech}: {pattern}")


def add_context_trigger(experience_dir: Path, context: str, instruction: str):
    """Add a context trigger."""
    context_lower = context.lower().replace(' ', '_')
    context_file = experience_dir / 'contexts' / f'{context_lower}.json'
    
    if context_file.exists():
        with open(context_file, 'r', encoding='utf-8') as f:
            data = json.load(f)
    else:
        data = {'name': context, 'instructions': []}
    
    if instruction not in data['instructions']:
        data['instructions'].append(instruction)
        with open(context_file, 'w', encoding='utf-8') as f:
            json.dump(data, f, indent=2, ensure_ascii=False)
        
        # Update index
        index = load_index(experience_dir)
        if context_lower not in index['index']['contexts']:
            index['index']['contexts'].append(context_lower)
        index['index']['total_experiences'] += 1
        save_index(experience_dir, index)
        
        print(f"Added context trigger: {context} -> {instruction}")
    else:
        print(f"Context trigger already exists: {context}")


def merge_from_json(experience_dir: Path, json_str: str):
    """Merge experiences from JSON string (for backward compatibility)."""
    try:
        data = json.loads(json_str)
    except json.JSONDecodeError as e:
        print(f"Error parsing JSON: {e}", file=sys.stderr)
        return False
    
    # Process preferences
    for pref in data.get('preferences', []):
        add_preference(experience_dir, pref)
    
    # Process fixes
    for fix in data.get('fixes', []):
        add_fix(experience_dir, fix)
    
    # Process patterns (tech-specific)
    for tech, patterns in data.get('patterns', {}).items():
        if isinstance(patterns, list):
            for pattern in patterns:
                add_tech_pattern(experience_dir, tech, pattern)
    
    # Process context triggers
    for context, instruction in data.get('context_triggers', {}).items():
        add_context_trigger(experience_dir, context, instruction)
    
    return True


def main():
    parser = argparse.ArgumentParser(
        description='Store programming experiences progressively',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python store_experience.py --preference "使用 pnpm 而非 npm"
  python store_experience.py --fix "macOS 上 node-gyp 需要 Xcode CLI"
  python store_experience.py --tech react --pattern "使用函数式组件"
  python store_experience.py --context when_testing --instruction "使用 Vitest"
  python store_experience.py --merge '{"preferences": ["..."]}'
        """
    )
    
    parser.add_argument(
        '--preference',
        help='Add a user preference'
    )
    parser.add_argument(
        '--fix',
        help='Add a known fix/workaround'
    )
    parser.add_argument(
        '--tech',
        help='Tech stack name (used with --pattern)'
    )
    parser.add_argument(
        '--pattern',
        help='Add a pattern for a tech stack'
    )
    parser.add_argument(
        '--context',
        help='Context trigger name (used with --instruction)'
    )
    parser.add_argument(
        '--instruction',
        help='Add an instruction for a context'
    )
    parser.add_argument(
        '--merge',
        help='Merge experiences from JSON string'
    )
    parser.add_argument(
        '--experience-dir',
        default=os.path.join(os.path.dirname(__file__), '..', 'experience'),
        help='Experience directory path'
    )
    
    args = parser.parse_args()
    experience_dir = Path(args.experience_dir)
    ensure_directories(experience_dir)
    
    if args.preference:
        add_preference(experience_dir, args.preference)
    elif args.fix:
        add_fix(experience_dir, args.fix)
    elif args.tech and args.pattern:
        add_tech_pattern(experience_dir, args.tech, args.pattern)
    elif args.context and args.instruction:
        add_context_trigger(experience_dir, args.context, args.instruction)
    elif args.merge:
        merge_from_json(experience_dir, args.merge)
    else:
        parser.print_help()


if __name__ == '__main__':
    main()


Security scan verdicts — ClawScan: suspicious | static analysis:  | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)