名称: 技能学习 描述: 扫描代码库中的FIX:/NOTE:/TODO:标签并创建结构化任务,支持交互式选择。用于/learn命令调用。 允许工具: Bash, Grep, Read, Write, Edit, AskUserQuestion
学习技能(直接执行)
直接执行技能,用于扫描文件、交互式展示发现并创建用户选择的任务。替换了之前的基于委派的方法,采用同步执行和AskUserQuestion提示。
关键行为: 用户在创建任何任务之前总是先看到标签扫描结果。用户通过交互式提示选择创建哪些任务类型。
上下文引用
引用(不急于加载):
- 路径:
@specs/TODO.md- 当前任务列表 - 路径:
@specs/state.json- 机器状态
执行
步骤1: 解析参数
从命令输入中提取路径:
# 从命令输入中解析
paths="$ARGUMENTS"
# 如果未指定路径,默认为项目根目录
if [ -z "$paths" ]; then
paths="."
fi
注意: --dry-run 标志不再支持。交互式流程本质上是“先预览” - 用户在创建任务之前总是先看到发现。
步骤2: 生成会话ID
生成会话ID用于跟踪:
session_id="sess_$(date +%s)_$(od -An -N3 -tx1 /dev/urandom | tr -d ' ')"
步骤3: 执行标签提取
使用文件类型特定模式扫描标签。使用Bash和grep以确保输出解析一致。
3.1: 提取FIX:标签
Lua文件(Neovim配置):
grep -rn --include="*.lua" "-- FIX:" $paths 2>/dev/null || true
LaTeX文件:
grep -rn --include="*.tex" "% FIX:" $paths 2>/dev/null || true
Markdown文件:
grep -rn --include="*.md" "<!-- FIX:" $paths 2>/dev/null || true
Python/Shell/YAML文件:
grep -rn --include="*.py" --include="*.sh" --include="*.yaml" --include="*.yml" "# FIX:" $paths 2>/dev/null || true
3.2: 提取NOTE:标签
与上述相同模式,将FIX:替换为NOTE:。
3.3: 提取TODO:标签
与上述相同模式,将FIX:替换为TODO:。
3.4: 解析结果
对于每个grep匹配,提取:
- 文件路径
- 行号
- 标签类型(FIX, NOTE, TODO)
- 标签内容(标签后的文本)
示例原始输出:
nvim/lua/plugins/telescope.lua:67:-- TODO: Add custom picker for git worktrees
docs/KEYMAPS.md:89:<!-- FIX: Update keymap table with new bindings -->
分类为三个数组:
fix_tags[]- 所有FIX:标签note_tags[]- 所有NOTE:标签todo_tags[]- 所有TODO:标签
步骤4: 显示标签摘要
在用户选择之前展示发现:
## 标签扫描结果
**扫描的文件**: {paths}
**找到的标签**: {total_count}
### FIX:标签 ({count})
- `{file}:{line}` - {content}
- ...
### NOTE:标签 ({count})
- `{file}:{line}` - {content}
- ...
### TODO:标签 ({count})
- `{file}:{line}` - {content}
- ...
步骤5: 处理边界情况
未找到标签
如果未找到标签:
## 未找到标签
扫描的文件: {paths}
未检测到FIX:, NOTE:, 或TODO:标签。
无需创建。
优雅退出,无提示。
仅某些标签类型存在
仅显示存在的标签类型的任务类型选项:
- 存在FIX:标签 -> 提供“修复任务”
- 存在NOTE:标签 -> 提供“修复任务”和“学习任务”
- 存在TODO:标签 -> 提供“TODO任务”
步骤6: 任务类型选择
如果找到标签,提示用户选择任务类型:
{
"question": "应该创建哪些任务类型?",
"header": "任务类型",
"multiSelect": true,
"options": [
{
"label": "修复任务",
"description": "将{N}个FIX:/NOTE:标签合并为单个任务"
},
{
"label": "学习任务",
"description": "根据{N}个NOTE:标签更新上下文"
},
{
"label": "TODO任务",
"description": "为{N}个TODO:项创建任务"
}
]
}
重要: 仅包含标签类型存在的选项:
- 仅当FIX:或NOTE:标签存在时包含“修复任务”
- 仅当NOTE:标签存在时包含“学习任务”
- 仅当TODO:标签存在时包含“TODO任务”
如果用户未选择任何内容,优雅退出:
未选择任务类型。未创建任务。
步骤7: 单个TODO选择
如果选择了“TODO任务”并且有TODO:标签:
标准情况(<=20个TODO)
{
"question": "选择要创建为任务的TODO项:",
"header": "TODO选择",
"multiSelect": true,
"options": [
{
"label": "{内容截断至50字符}",
"description": "{file}:{line}"
},
...
]
}
大量TODO(>20个)
在顶部添加“选择所有”选项:
{
"question": "选择要创建为任务的TODO项:",
"header": "TODO选择(多项)",
"multiSelect": true,
"options": [
{
"label": "选择所有({N}项)",
"description": "为每个TODO标签创建任务"
},
{
"label": "{内容截断至50字符}",
"description": "{file}:{line}"
},
...
]
}
如果选择了“选择所有”,包含所有TODO。否则,仅包含选定的项。
步骤7.5: TODO项的主题分组
条件: 用户选择了“TODO任务”并且选择了多于1个TODO项
如果只选择了1个TODO项,跳转到步骤8(无分组优势)。
7.5.1: 提取主题指示符
对于每个选定的TODO项,提取主题指示符:
关键术语: 从TODO内容中提取重要词语(名词、动词)。忽略停用词(the, a, is, to, for, 等)。
文件部分: 按文件路径前缀分组(例如Logos/Layer1/ vs Logos/Shared/)。
操作类型: 识别常见操作模式:
- “添加/实现/创建” → 实现任务
- “修复/处理/纠正” → 修复任务
- “文档/更新文档” → 文档任务
- “测试/验证” → 测试任务
- “重构/优化” → 改进任务
示例提取:
TODO: “为工作树添加自定义选择器” 在 nvim/lua/plugins/telescope.lua:67
→ 关键术语: [“选择器”, “工作树”, “telescope”]
→ 文件部分: “nvim/lua/plugins/”
→ 操作类型: “实现”
TODO: “为工作树添加预览窗口” 在 nvim/lua/plugins/telescope.lua:89
→ 关键术语: [“预览”, “工作树”, “telescope”]
→ 文件部分: “nvim/lua/plugins/”
→ 操作类型: “实现”
TODO: “优化懒加载” 在 nvim/lua/config/lazy.lua:23
→ 关键术语: [“优化”, “懒”, “加载”]
→ 文件部分: “nvim/lua/config/”
→ 操作类型: “改进”
7.5.2: 通过共享术语聚类TODO
分组共享2个或更多重要术语或共享文件部分 + 操作类型的TODO。
聚类算法:
- 以第一个TODO作为初始组
- 对于每个剩余的TODO:
- 如果与现有组共享2+个关键术语 → 添加到组
- 如果与现有组共享文件_section AND action_type → 添加到组
- 否则 → 开始新组
- 从组中最常见的共享术语生成主题标签
示例聚类:
组1: “Telescope 工作树” (共享: 工作树, telescope, nvim/lua/plugins/, 实现)
- 为工作树添加自定义选择器
- 为工作树添加预览窗口
组2: “配置优化” (共享: nvim/lua/config/, 改进)
- 优化懒加载
单项目组: 如果一个TODO未与其他聚类,则成为自己的单项目组。
7.5.3: 存储分组主题
存储主题组以用于步骤7.5.4:
topic_groups = [
{
label: “Telescope 工作树”,
items: [
{file: “nvim/lua/plugins/telescope.lua”, line: 67, content: “为工作树添加自定义选择器”},
{file: “nvim/lua/plugins/telescope.lua”, line: 89, content: “为工作树添加预览窗口”}
],
shared_terms: [“工作树”, “telescope”],
action_type: “实现”
},
{
label: “配置优化”,
items: [
{file: “nvim/lua/config/lazy.lua”, line: 23, content: “优化懒加载”}
],
shared_terms: [],
action_type: “改进”
}
]
步骤7.5.4: 主题组确认
条件: topic_groups包含至少一个拥有2+个项目的组
如果所有组都只有1个项目,跳转到步骤8(无分组优势)。
通过AskUserQuestion展示主题组:
{
"question": "应该如何将TODO项分组到任务中?",
"header": "TODO主题分组",
"multiSelect": false,
"options": [
{
"label": "接受建议的主题组",
"description": "创建{N}个分组任务: {group_summaries}"
},
{
"label": "保持为单独任务",
"description": "创建{M}个单独任务(每个TODO项一个)"
},
{
"label": "创建单个合并任务",
"description": "创建1个任务包含所有{M}个TODO项"
}
]
}
其中:
{N}= 主题组数量{M}= 选定的TODO项总数{group_summaries}= 逗号分隔的列表,如“S5 Theorems (2项), Utility Optimization (1项)”
存储用户选择: grouping_mode = “grouped” | “separate” | “combined”
步骤8: 创建选定的任务
对于每个选定的任务类型,创建任务。重要: 当NOTE:标签存在并且同时选择了修复任务和学习任务时,首先创建学习任务,以便修复任务可以依赖它。
8.1: 获取下一个任务编号
next_num=$(jq -r '.next_project_number' specs/state.json)
8.2: 依赖感知的任务创建顺序
检查NOTE:依赖条件:
has_note_dependency = (存在NOTE:标签) AND (用户同时选择了“修复任务”和“学习任务”)
如果has_note_dependency为TRUE:
- 首先创建学习任务(步骤8.2a)
- 存储学习任务编号为
learn_it_task_num - 其次创建修复任务,带依赖(步骤8.2b)
如果has_note_dependency为FALSE:
- 首先创建修复任务(如果选中)
- 其次创建学习任务(如果选中)
- 无依赖关系
8.2a: 学习任务(当首先创建以用于依赖时)
条件: has_note_dependency为TRUE
{
"title": "根据NOTE:标签更新上下文文件",
"description": "根据学习更新{N}个上下文文件:
{按目标上下文分组}",
"language": "meta",
"effort": "1-2小时"
}
存储任务编号: learn_it_task_num = next_num
递增: next_num = next_num + 1
8.2b: 修复任务(当has_note_dependency时有依赖)
条件: 用户选择了“修复任务”并且(存在FIX:或NOTE:标签)
当has_note_dependency为TRUE时:
{
"title": "修复来自FIX:/NOTE:标签的问题",
"description": "处理来自嵌入标签的{N}个项:
{带文件:行引用的项列表}
**重要**: 进行更改时,从源文件中移除FIX:和NOTE:标签。保留TODO:标签不变(它们创建单独的任务)。",
"language": "{从源文件中检测的主要语言}",
"effort": "2-4小时",
"dependencies": [learn_it_task_num]
}
当has_note_dependency为FALSE时:
{
"title": "修复来自FIX:/NOTE:标签的问题",
"description": "处理来自嵌入标签的{N}个项:
{带文件:行引用的项列表}
**重要**: 进行更改时,从源文件中移除FIX:和NOTE:标签。保留TODO:标签不变(它们创建单独的任务)。",
"language": "{从源文件中检测的主要语言}",
"effort": "2-4小时"
}
语言检测:
如果大多数标签来自.lean文件 -> “lean”
否则如果大多数来自.tex文件 -> “latex”
否则如果大多数来自.claude/文件 -> “meta”
否则 -> “general”
8.3: 学习任务(当创建时无依赖)
条件: 用户选择了“学习任务”并且存在NOTE:标签并且has_note_dependency为FALSE
{
"title": "根据NOTE:标签更新上下文文件",
"description": "根据学习更新{N}个上下文文件:
{按目标上下文分组}",
"language": "meta",
"effort": "1-2小时"
}
8.4: TODO任务(如果选中)
条件: 用户选择了“TODO任务”并且用户选择了特定的TODO项
检查grouping_mode(来自步骤7.5.4,如果跳过了步骤7.5.4,则默认为“separate”):
8.4.1: 分组模式(grouping_mode == “grouped”)
对于topic_groups中的每个主题组:
{
"title": "{topic_label}: {item_count}个TODO项",
"description": "处理与{topic_label}相关的TODO项:
{item_list}
---
共享上下文: {shared_terms_description}",
"language": "{从组中大多数文件类型检测}",
"effort": "{scaled_effort}"
}
其中:
{topic_label}= 生成的标签(例如“Telescope 工作树”){item_count}= 组中项目数{item_list}= 格式化的项目列表:- [ ] {content} (`{file}:{line}`) - [ ] {content} (`{file}:{line}`){shared_terms_description}= 为什么项目被分组的简要描述(例如“与telescope工作树功能相关”)
努力缩放公式:
基础努力 = 1小时
缩放努力 = 基础努力 + (30分钟 * (item_count - 1))
示例:
1项 → 1小时
2项 → 1.5小时 (1h + 30min)
3项 → 2小时 (1h + 60min)
4项 → 2.5小时 (1h + 90min)
8.4.2: 合并模式(grouping_mode == “combined”)
创建包含所有选定TODO项的单个任务:
{
"title": "处理{item_count}个TODO项",
"description": "来自扫描的合并TODO项:
{all_items_list}
---
文件: {unique_files_list}",
"language": "{从大多数文件类型检测}",
"effort": "{scaled_effort}"
}
其中:
{item_count}= 选定的TODO项总数{all_items_list}= 所有项目的格式化列表,带复选框{unique_files_list}= 涉及的唯文件的逗号分隔列表
努力缩放: 与分组模式相同公式。
8.4.3: 单独模式(grouping_mode == “separate”或默认)
对于每个选定的TODO项单独:
{
"title": "{标签内容,截断至60字符}",
"description": "{完整标签内容}
来源: {file}:{line}",
"language": "{从文件类型检测}",
"effort": "1小时"
}
TODO任务的语言检测(所有模式):
.lua (nvim/) -> “neovim”
.tex -> “latex”
.md -> “markdown”
.py/.sh -> “general”
.claude/* -> “meta”
步骤9: 更新状态文件
对于每个创建的任务:
9.1: 更新state.json
读取当前状态,添加新任务条目,递增next_project_number:
# 从标题创建slug
slug=$(echo "$title" | tr '[:upper:]' '[:lower:]' | tr ' ' '_' | tr -cd 'a-z0-9_' | cut -c1-50)
# 读取当前状态
current=$(cat specs/state.json)
# 使用jq添加任务(使用两步模式避免转义问题)
# 步骤1: 将任务数据写入临时文件
# 步骤2: 使用jq和slurpfile
对于当has_note_dependency为TRUE时的修复任务,包含依赖数组:
{
"project_number": {N},
"project_name": "{slug}",
"status": "not_started",
"language": "{language}",
"dependencies": [learn_it_task_num]
}
对于所有其他任务,不需要依赖字段。
9.2: 更新TODO.md
在## 任务部分前添加新任务条目(新任务在顶部):
标准格式(无依赖):
### {N}. {标题}
- **努力**: {估计}
- **状态**: [未开始]
- **语言**: {语言}
- **开始**: {时间戳}
**描述**: {描述}
---
当has_note_dependency为TRUE时的修复任务格式:
### {N}. {标题}
- **努力**: {估计}
- **状态**: [未开始]
- **语言**: {语言}
- **依赖**: {learn_it_task_num}
- **开始**: {时间戳}
**描述**: {描述}
---
步骤10: 显示结果
展示创建的任务摘要:
## 从标签创建的任务
**处理的标签**: {N}个,跨扫描文件
### 创建的任务
| # | 类型 | 标题 | 语言 |
|---|------|-------|----------|
| {N} | 修复 | 修复来自FIX:/NOTE:标签的问题 | {语言} |
| {N+1} | 学习 | 根据NOTE:标签更新上下文文件 | meta |
| {N+2} | TODO | {标题} | {语言} |
---
**下一步**:
1. 在TODO.md中审查任务
2. 运行`/research {first_task}`开始
3. 通过/research -> /plan -> /implement循环进展
步骤11: Git提交(后处理)
如果创建了任务,提交更改:
task_count={创建的任务数量}
git add specs/TODO.md specs/state.json
git commit -m "learn: 从标签创建$task_count个任务
会话: $session_id
共同作者: Claude Opus 4.5 <noreply@anthropic.com>"
错误处理
路径访问错误
当路径不存在或无法访问时:
- 为每个无效路径记录警告
- 继续使用有效路径
- 如果没有有效路径剩余,报告并退出
未找到标签
这不是错误条件:
- 报告信息性消息
- 退出,无提示
state.json更新失败
如果jq失败:
- 记录错误,包括命令和输出
- 尝试两步jq模式
- 如果仍然失败,报告部分成功(找到标签但任务未创建)
TODO.md解析错误
如果TODO.md格式损坏:
- 记录错误
- 跳过TODO.md更新
- State.json更新可能仍然成功
- 报告部分成功
Git提交失败
非阻塞:
- 记录失败
- 任务仍然成功创建
- 报告提交失败但任务存在
标准参考
此技能实现了多任务创建模式。参见.claude/docs/reference/standards/multi-task-creation-standard.md获取完整标准。
合规级别: 完全(所有必需组件)
| 组件 | 状态 | 备注 |
|---|---|---|
| 发现 | 是 | 标签扫描(FIX:, NOTE:, TODO:) |
| 选择 | 是 | AskUserQuestion与multiSelect |
| 分组 | 是 | 主题聚类(步骤7.5) |
| 依赖 | 部分 | 仅内部(学习任务 -> 修复任务在步骤8.2) |
| 排序 | 否 | 顺序创建 |
| 可视化 | 否 | 未实现 |
| 确认 | 是 | 通过选择隐式确认 |
| 状态更新 | 是 | 原子更新(步骤9) |
限制: 外部依赖(TODO任务依赖现有任务)未实现。考虑作为未来增强。