From 4b562974984998375f600fad45170f27c3fa8201 Mon Sep 17 00:00:00 2001 From: wonder Date: Thu, 3 Sep 2026 21:06:21 +0800 Subject: [PATCH] =?UTF-8?q?refactor(todo):=20=E5=8E=BB=E9=99=A4=20MySQL=20?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=EF=BC=8C=E6=94=B9=E4=B8=BA=20JSON=20?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 数据存储: ~/.local/share/todo/tasks.json 纯字符串数组 - 操作简化: 仅保留 add/list/done,无状态/优先级/归档 - 新增 scripts/validate.py 校验脚本 (格式/结构/去重) - 新增 schema/tasks.schema.json - 删除 references/setup.md (MySQL 配置指南) Co-Authored-By: Claude Code --- todo/SKILL.md | 249 +++++++++++----------------------- todo/references/setup.md | 106 --------------- todo/schema/tasks.schema.json | 16 +++ todo/scripts/validate.py | 142 +++++++++++++++++++ 4 files changed, 240 insertions(+), 273 deletions(-) delete mode 100644 todo/references/setup.md create mode 100644 todo/schema/tasks.schema.json create mode 100644 todo/scripts/validate.py diff --git a/todo/SKILL.md b/todo/SKILL.md index 226bcd2..37cdbee 100644 --- a/todo/SKILL.md +++ b/todo/SKILL.md @@ -1,197 +1,112 @@ --- name: todo description: | - 任务待办管理 skill。通过自然语言操作 MySQL 数据库中的待办事项。 - 当用户提到待办、任务管理、todo list、添加任务、查看任务、标记完成、 - 清理任务、归档旧任务时,使用此 skill。 - 支持增删改查和基于 LRU 策略的自动归档。 + 极简任务管理 skill。通过 JSON 文件本地存储任务列表,仅记录任务的有与无。 + 当用户提到待办、任务管理、todo list、添加任务、查看任务、标记完成时,使用此 skill。 + 支持添加、查看、完成三个操作,参数歧义时与用户确认。 --- -# 任务待办管理 +# 极简任务管理 -通过自然语言管理 MySQL 数据库中的待办事项,支持 LRU 自动淘汰。 +通过自然语言管理本地 JSON 文件中的任务列表。任务只有「存在」和「不存在」两种状态。 -## 工具说明 +## 数据存储 -本 skill 依赖 **MySQL MCP 工具** `mysql_query`。所有数据库操作都通过该工具执行。 +- 文件路径:`~/.local/share/todo/tasks.json` +- 数据格式: -调用方式:使用 `execute_shell_command` 执行 MySQL MCP 查询,或直接使用 MCP 工具(如可用)。 - -如果 MCP 工具不可用,引导用户参考 `references/setup.md` 完成配置。 - -## 表结构 - -```sql -CREATE TABLE IF NOT EXISTS todos ( - id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(500) NOT NULL, - description TEXT, - status ENUM('pending', 'in_progress', 'done', 'archived') NOT NULL DEFAULT 'pending', - priority ENUM('low', 'medium', 'high', 'urgent') NOT NULL DEFAULT 'medium', - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - last_accessed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - access_count INT UNSIGNED NOT NULL DEFAULT 1 -); +```json +{ + "tasks": [ + "买菜", + "写周报", + "锻炼30分钟" + ] +} ``` +- 任务为纯字符串数组,无 ID、无状态、无优先级、无时间戳 + +## 校验脚本 + +每次读写 JSON 前后,使用 `/scripts/validate.py` 校验数据完整性。 + +```bash +# 校验数据文件 +python3 /scripts/validate.py + +# 校验指定文件 +python3 /scripts/validate.py <文件路径> +``` + +校验内容:JSON 格式合法性、结构合规(tasks 数组 + 字符串元素)、任务去重(大小写不敏感)。 + +退出码 0 = 通过,1 = 失败。失败时 stderr 输出具体错误。 + ## 操作指南 ### 添加任务 -触发词:添加任务 / 新建待办 / 提醒我做 / 记一下 +触发词:添加任务 / 新建待办 / 提醒我做 / 记一下 / add -```sql -INSERT INTO todos (title, description, priority) VALUES ('<标题>', '<描述>', '<优先级>'); +流程: +1. 确认数据文件存在,不存在则创建 `{"tasks": []}` +2. 运行 `validate.py` 校验当前数据 +3. 将新任务追加到 `tasks` 数组 +4. 运行 `validate.py` 校验写入后的数据(去重检查) +5. 校验失败则回滚,输出错误信息 + +### 查看任务 + +触发词:我的待办 / 有什么任务 / 今天要做什么 / 列出任务 / list + +流程: +1. 运行 `validate.py` 校验数据 +2. 读取并展示所有任务,带序号 + +输出格式: + +``` +📋 任务列表 +1. 买菜 +2. 写周报 +3. 锻炼30分钟 ``` -- 优先级默认 `medium` -- 用户说"紧急/urgent/马上/ASAP" → `urgent` -- 用户说"重要" → `high` -- 用户说"不急/有空/low" → `low` -- 添加后执行 LRU 检查(见下方淘汰策略) +### 完成任务(移除) -### 查看待办 +触发词:完成 XXX / 做完了 / 搞定 / done -触发词:我的待办 / 有什么任务 / 今天要做什么 / 列出任务 - -```sql -SELECT id, title, status, priority, DATE_FORMAT(updated_at, '%m-%d') AS updated -FROM todos -WHERE status IN ('pending', 'in_progress') -ORDER BY - FIELD(priority, 'urgent', 'high', 'medium', 'low'), - last_accessed_at DESC; -``` - -查看后刷新访问时间: - -```sql -UPDATE todos SET last_accessed_at = NOW(), access_count = access_count + 1 -WHERE id IN (); -``` - -### 查看已完成 - -触发词:已完成的任务 / 做完了哪些 - -```sql -SELECT id, title, priority, DATE_FORMAT(updated_at, '%m-%d %H:%i') AS completed -FROM todos WHERE status = 'done' -ORDER BY updated_at DESC; -``` - -### 标记完成 - -触发词:完成 XXX / 标记为完成 / 做完了 - -```sql -UPDATE todos SET status = 'done', last_accessed_at = NOW(), access_count = access_count + 1 -WHERE id = ; -``` - -按标题匹配: - -```sql -UPDATE todos SET status = 'done', last_accessed_at = NOW(), access_count = access_count + 1 -WHERE title LIKE '%<关键词>%' AND status != 'done'; -``` +流程: +1. 运行 `validate.py` 校验数据 +2. 按序号或模糊匹配找到目标任务 +3. 从 `tasks` 数组中移除 +4. 运行 `validate.py` 校验写入后的数据 +5. 校验失败则回滚,输出错误信息 - 匹配多条时列出匹配项让用户确认 -- 用 `SELECT` 先查再改,避免误操作 +- 完成即移除,不做归档 -### 标记进行中 +## 参数歧义处理 -触发词:开始做 XXX / 进行中 +以下情况必须与用户确认,不可自行猜测: + +- **无参数调用**:`/todo` 不带任何参数 → 询问用户意图(查看 / 添加 / 完成) +- **仅操作词无目标**:`/todo done` 未指定具体任务 → 列出任务列表让用户选择 +- **语义模糊**:`/todo 买菜` 不明确是「添加」还是「完成」→ 默认理解为添加,但需确认 +- **模糊匹配多条**:关键词匹配到多个任务 → 列出候选项让用户确认 + +确认时使用简洁的提问,例如: -```sql -UPDATE todos SET status = 'in_progress', last_accessed_at = NOW(), access_count = access_count + 1 -WHERE id = ; ``` - -### 更新任务 - -触发词:修改 XXX 的描述/优先级/标题 - -```sql -UPDATE todos SET <字段> = '<新值>', last_accessed_at = NOW(), access_count = access_count + 1 -WHERE id = ; +你想对「买菜」做什么? +1. 添加为新任务 +2. 标记完成(移除) ``` -可更新字段:`title`、`description`、`priority`、`status` - -### 删除任务 - -触发词:删除任务 / 去掉这个待办 - -**软删除(推荐)**: - -```sql -UPDATE todos SET status = 'archived' WHERE id = ; -``` - -**硬删除**(用户明确要求时): - -```sql -DELETE FROM todos WHERE id = ; -``` - -- 硬删除前必须列出将被删除的事项并确认 -- 默认用软删除 - -### 搜索任务 - -触发词:找一下关于 XXX 的任务 / 有没有 XXX - -```sql -SELECT id, title, status, priority -FROM todos -WHERE title LIKE '%<关键词>%' OR description LIKE '%<关键词>%' -ORDER BY FIELD(status, 'in_progress', 'pending', 'done', 'archived'), priority; -``` - -## LRU 淘汰策略 - -每次 INSERT 后检查总数: - -```sql -SELECT COUNT(*) AS total FROM todos WHERE status != 'archived'; -``` - -如果超过 100 条,执行归档: - -```sql --- 1. 归档最久未访问的已完成事项(最旧 10 条) -UPDATE todos SET status = 'archived' -WHERE status = 'done' -ORDER BY last_accessed_at ASC LIMIT 10; - --- 2. 仍超标则归档最久未访问的待办事项 -UPDATE todos SET status = 'archived' -WHERE status = 'pending' -ORDER BY last_accessed_at ASC LIMIT 10; -``` - -**保护规则**:不归档 `in_progress` 状态的事项。 - ## 交互原则 -- **模糊匹配时确认**:关键词匹配多条任务,列出选项让用户选 -- **操作前预览**:删除/归档前,先 `SELECT` 显示受影响的事项 -- **自然语言理解**:用户说"帮我记一下明天要买菜" → 添加任务"买菜" -- **状态转换合理**:不允许 archived → in_progress 等不合理跳转 - -## 输出格式 - -查询结果用表格展示: - -``` -| ID | 标题 | 状态 | 优先级 | 更新时间 | -|----|------|------|--------|----------| -| 1 | xxx | 待办 | 高 | 08-31 | -``` - -操作结果用简洁确认: -- ✓ 已添加任务:XXX -- ✓ 已完成任务:XXX -- ✓ 已归档 5 条旧任务 +- **确认优先**:参数歧义时绝不猜测,主动询问 +- **操作前校验**:每次读写前后运行 validate.py +- **失败回滚**:校验失败时恢复原数据,告知用户错误原因 +- **简洁输出**:操作结果用 ✓ 前缀确认,如 `✓ 已添加任务:买菜` diff --git a/todo/references/setup.md b/todo/references/setup.md deleted file mode 100644 index 9cd9184..0000000 --- a/todo/references/setup.md +++ /dev/null @@ -1,106 +0,0 @@ -# Todo Skill 环境配置指南 - -## 1. 安装 MySQL MCP Server - -推荐使用 `@benborla29/mcp-server-mysql`(支持读写操作)。 - -### QwenPaw 配置方式 - -在 `~/.qwenpaw/config.json` 的 `mcp.clients` 中添加: - -```json -"mysql": { - "name": "mysql", - "description": "MySQL MCP for todo skill", - "enabled": true, - "transport": "stdio", - "command": "npx", - "args": ["-y", "@benborla29/mcp-server-mysql"], - "env": { - "MYSQL_HOST": "<主机地址>", - "MYSQL_PORT": "<端口,默认 3306>", - "MYSQL_USER": "<用户名>", - "MYSQL_PASS": "<密码>", - "MYSQL_DB": "<数据库名>", - "ALLOW_INSERT_OPERATION": "true", - "ALLOW_UPDATE_OPERATION": "true", - "ALLOW_DELETE_OPERATION": "true", - "SCHEMA_DDL_PERMISSIONS": "<数据库名>:true" - } -} -``` - -> **注意**:环境变量名是 `MYSQL_PASS`(不是 `MYSQL_PASSWORD`)和 `MYSQL_DB`(不是 `MYSQL_DATABASE`)。 - -### Claude Code 配置方式 - -```bash -claude mcp add mysql \ - -e MYSQL_HOST="<主机地址>" \ - -e MYSQL_PORT="<端口>" \ - -e MYSQL_USER="<用户名>" \ - -e MYSQL_PASS="<密码>" \ - -e MYSQL_DB="<数据库名>" \ - -e ALLOW_INSERT_OPERATION="true" \ - -e ALLOW_UPDATE_OPERATION="true" \ - -e ALLOW_DELETE_OPERATION="true" \ - -e SCHEMA_DDL_PERMISSIONS="<数据库名>:true" \ - -- npx -y @benborla29/mcp-server-mysql -``` - -### 验证 MCP 可用 - -配置完成后重启客户端,然后尝试: -- 列出数据库中的表 -- 执行一条简单查询 - -如果 MCP 工具可用,会看到 `mysql_query` 工具。 - ---- - -## 2. 创建 todos 表 - -在目标数据库中执行以下 SQL: - -```sql -CREATE TABLE IF NOT EXISTS todos ( - id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, - title VARCHAR(500) NOT NULL COMMENT '任务标题', - description TEXT COMMENT '任务描述', - status ENUM('pending', 'in_progress', 'done', 'archived') NOT NULL DEFAULT 'pending' COMMENT '状态: pending-待办, in_progress-进行中, done-已完成, archived-已归档', - priority ENUM('low', 'medium', 'high', 'urgent') NOT NULL DEFAULT 'medium' COMMENT '优先级: low-低, medium-中, high-高, urgent-紧急', - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间', - last_accessed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后访问时间(LRU 淘汰依据)', - access_count INT UNSIGNED NOT NULL DEFAULT 1 COMMENT '访问次数', - INDEX idx_status (status), - INDEX idx_lru (last_accessed_at), - INDEX idx_priority_status (priority, status), - INDEX idx_created (created_at) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='待办事项表,支持 LRU 淘汰策略'; -``` - -### 字段说明 - -| 字段 | 类型 | 说明 | -|------|------|------| -| id | BIGINT UNSIGNED | 自增主键 | -| title | VARCHAR(500) | 任务标题,必填 | -| description | TEXT | 任务描述,可选 | -| status | ENUM | pending / in_progress / done / archived | -| priority | ENUM | low / medium / high / urgent | -| created_at | DATETIME | 创建时间,自动填充 | -| updated_at | DATETIME | 更新时间,自动刷新 | -| last_accessed_at | DATETIME | 最后访问时间,LRU 依据 | -| access_count | INT UNSIGNED | 访问计数,每次 +1 | - ---- - -## 3. LRU 淘汰策略 - -基于 `last_accessed_at` 字段实现 LRU(Least Recently Used)淘汰: - -- 每次查看、更新 todo 时刷新 `last_accessed_at` 和 `access_count` -- 待办总数超过 100 条时自动归档 -- 先归档最久未访问的 `done` 事项,再归档 `pending` 事项 -- 不归档 `in_progress` 状态的事项 diff --git a/todo/schema/tasks.schema.json b/todo/schema/tasks.schema.json new file mode 100644 index 0000000..360c70f --- /dev/null +++ b/todo/schema/tasks.schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "tasks": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + } + }, + "required": ["tasks"], + "additionalProperties": false +} diff --git a/todo/scripts/validate.py b/todo/scripts/validate.py new file mode 100644 index 0000000..5b5e406 --- /dev/null +++ b/todo/scripts/validate.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""校验 ~/.local/share/todo/tasks.json 的格式、结构和完整性。 + +退出码: + 0 - 校验通过 + 1 - 校验失败(错误信息输出到 stderr) + +用法: + python3 validate.py [文件路径] + 默认路径: ~/.local/share/todo/tasks.json +""" + +import json +import os +import sys +from pathlib import Path + +DEFAULT_PATH = Path.home() / ".local" / "share" / "todo" / "tasks.json" +SCHEMA_PATH = Path(__file__).parent.parent / "schema" / "tasks.schema.json" + + +def load_json(path: Path) -> tuple[dict | None, str | None]: + """加载并解析 JSON 文件。""" + if not path.exists(): + return None, f"文件不存在: {path}" + try: + with open(path, "r", encoding="utf-8") as f: + data = json.load(f) + return data, None + except json.JSONDecodeError as e: + return None, f"JSON 格式错误: {e}" + + +def load_schema() -> tuple[dict | None, str | None]: + """加载 JSON Schema。""" + if not SCHEMA_PATH.exists(): + return None, f"Schema 文件不存在: {SCHEMA_PATH}" + try: + with open(SCHEMA_PATH, "r", encoding="utf-8") as f: + return json.load(f), None + except json.JSONDecodeError as e: + return None, f"Schema 格式错误: {e}" + + +def validate_structure(data: dict) -> list[str]: + """校验数据结构。""" + errors = [] + + if not isinstance(data, dict): + errors.append("顶层必须是对象") + return errors + + if "tasks" not in data: + errors.append("缺少必需字段 'tasks'") + return errors + + tasks = data["tasks"] + if not isinstance(tasks, list): + errors.append("'tasks' 必须是数组") + return errors + + for i, item in enumerate(tasks): + if not isinstance(item, str): + errors.append(f"tasks[{i}] 必须是字符串,实际类型: {type(item).__name__}") + elif len(item.strip()) == 0: + errors.append(f"tasks[{i}] 不能为空字符串") + + return errors + + +def validate_duplicates(tasks: list[str]) -> list[str]: + """校验任务是否有重复(大小写不敏感)。""" + errors = [] + seen = {} + for i, task in enumerate(tasks): + key = task.strip().lower() + if key in seen: + errors.append(f"tasks[{i}] '{task}' 与 tasks[{seen[key]}] 重复") + else: + seen[key] = i + return errors + + +def validate_schema(data: dict) -> list[str]: + """使用 JSON Schema 校验(如果 jsonschema 可用)。""" + try: + import jsonschema + except ImportError: + # jsonschema 未安装,跳过 schema 校验 + return [] + + schema, err = load_schema() + if err: + return [f"Schema 校验跳过: {err}"] + + errors = [] + try: + jsonschema.validate(instance=data, schema=schema) + except jsonschema.ValidationError as e: + errors.append(f"Schema 校验失败: {e.message}") + except jsonschema.SchemaError as e: + errors.append(f"Schema 定义错误: {e.message}") + + return errors + + +def main() -> int: + path = Path(sys.argv[1]) if len(sys.argv) > 1 else DEFAULT_PATH + + # 1. JSON 格式校验 + data, err = load_json(path) + if err: + print(err, file=sys.stderr) + return 1 + + # 2. 结构校验 + errors = validate_structure(data) + if errors: + for e in errors: + print(f"结构错误: {e}", file=sys.stderr) + return 1 + + # 3. JSON Schema 校验 + schema_errors = validate_schema(data) + if schema_errors: + for e in schema_errors: + print(f"Schema 错误: {e}", file=sys.stderr) + return 1 + + # 4. 去重校验 + dup_errors = validate_duplicates(data["tasks"]) + if dup_errors: + for e in dup_errors: + print(f"重复错误: {e}", file=sys.stderr) + return 1 + + print(f"校验通过: {path} ({len(data['tasks'])} 个任务)") + return 0 + + +if __name__ == "__main__": + sys.exit(main())