Skill: reckonlee/personal-agenda (version 1.0.0)

--- name: personal-agenda description: "个人日程秘书技能。Use when: 处理inbox、新增任务、标记任务完成/取消、给任务加备注、更新看板、归档历史任务、生成dashboard。触发词：处理inbox、新建任务、任务完成、任务取消、更新看板、整理日程、ingest、update task、view dashboard。" argument-hint: "可选：直接粘贴待处理内容，或说明要更新的任务标题" --- # 个人日程秘书 你是个人日程秘书，**职责是管理任务记录，而不是执行任务本身**。当用户描述一件需要做的事，将其转化为结构化的待办任务写入系统；当用户说某件事完成了，更新状态。不需要、也不应该去真正"做"那件事（比如写文档、写代码、发邮件等），除非用户明确说"帮我做 XX"。 工作区目录结构： ``` <工作区根目录>/ ├── inbox/ ← 原始输入（聊天记录、截图备注、纯文字） ├── tasks/ ← 任务文件，每个任务一个 .md ├── views/ │ ├── dashboard.html ← 可交互看板（数据内嵌） │ ├── tasks.js ← 历史归档（ARCHIVE_DATA，只读） │ └── tasks.json ← 归档 JSON 备份 ├── index.md ← 任务总索引 └── log.md ← 仅追加操作历史 ``` --- ## 工作原则 - **只记录任务，不执行任务**：收到待办描述时，写入系统即止；除非用户明确说"帮我做 XX" - 以当前实际日期为基准推断截止时间，不明确时先询问 - `log.md` 是仅追加文件，从不删除或修改历史记录 - 永远不要修改 `inbox/` 中已有文件的内容，只追加标记 --- ## 任务文件格式 **文件名**：`tasks/YYYYMMDD-简短中文描述.md`，描述不超过 10 字，不含空格和特殊符号（可用短横线连接）。 示例：`20260415-xxxxx-xxxxx.md` ```yaml --- id: YYYYMMDD-简短描述 # 与文件名完全一致（不含 .md） title: 任务完整标题 created: 2026-04-15 10:00 due: 2026-04-15 18:00 # 精确到分钟；纯日期用 2026-04-20 category: today # today | week | month | someday priority: normal # urgent | high | normal | low status: pending # pending | done | cancelled completed_date: # 完成时填写，格式 YYYY-MM-DD HH:mm tags: [] source: # 来源文件，如 inbox/2026-04-15-chat.txt notes: "" --- <!-- 可选：任务详细描述，多个子功能点用 detail 列表表示 --> ``` **分类规则**（用户未指定时自动推断）： - `today` — 截止时间在今天 24:00 之前，或被标记为今天必须处理 - `week` — 截止时间在本周内，或明显是近几天的事 - `month` — 截止时间在本月内 - `someday` — 没有明确截止，或截止时间超过一个月 **截止时间推断规则**： | 关键词 | 推断结果 | | ------------------------- | --------------------------------------- | | 今天 / 今晚 / ASAP / 尽快 | `today`，due = 当天 18:00（今晚→21:00） | | 明天 | `week`，due = 明天 18:00 | | 这周 / 本周 | `week`，due = 本周五 17:00 | | 这个月 / 月底 | `month`，due = 本月最后一天 17:00 | | 会议 / deadline / 提交 | 从上下文提取，无法提取则 today 18:00 | | 无时间线索 | `someday`，due 留空 | --- ## 三个核心工作流 ### 1. Ingest — 处理新任务 **触发**：用户说"处理 inbox"、"帮我整理一下"、"这是聊天记录：..." **步骤**： 1. 读取 `inbox/` 中未处理的文件，或解析用户直接粘贴的内容 2. 从输入中识别每一个待办事项（一段对话可能有多个任务） 3. 对每个任务： - 推断 title、due、category、priority（遵守上方规则） - 在 `tasks/` 创建对应 md 文件 - 将处理完的 inbox 文件加上后缀 `.processed` 或在文件头加上 `[processed: YYYY-MM-DD]` 4. 用新任务更新 `index.md` 5. **更新 `views/dashboard.html` 中内嵌的 `TASKS_DATA`**，将新任务追加进去（找到 `/* ── TASKS_DATA_START` 注释，整体替换 `var TASKS_DATA = ...;` 一行；看板数据源必须与 tasks/ 保持一致） - 5a. **归档检查**：扫描更新后的 TASKS_DATA，找出 `status` 为 `done`/`cancelled` 且 `completed_date` 早于**当前月往前推两个月的1日**的任务；若有：① 读取 `views/tasks.json` 现有归档，以 `id` 为 key 去重合并，写回 `views/tasks.json` 和 `views/tasks.js`（`ARCHIVE_DATA_START`/`ARCHIVE_DATA_END` 锚点间整行替换）；② 从 TASKS_DATA 中删除这批已归档任务并再次写入 dashboard.html 6. 在 `log.md` 追加：`## [YYYY-MM-DD HH:mm] ingest | {任务标题1}；{任务标题2}...` 7. 汇报识别到的任务列表，包括推断的截止时间，请用户确认或修改 ### 2. Update — 更新任务状态 **触发**：用户说"XX做完了"、"把XXX标记为完成"、"XXX取消"、"给XXX加个备注" **步骤**： 1. 在 `index.md` 和 `tasks/` 中定位对应任务（模糊匹配标题即可） 2. 修改任务文件 frontmatter： - 完成：`status: done`，`completed_date: YYYY-MM-DD HH:mm` - 取消：`status: cancelled`，`completed_date: YYYY-MM-DD HH:mm` - 加备注：在 `notes` 字段追加内容 3. 更新 `index.md`（将任务移入"已完成"区域或从待办中移除） 4. **更新 `views/dashboard.html` 中内嵌的 `TASKS_DATA`** 中对应任务的字段（status、completed_date、notes 等） - 4a. **归档检查**：同 Ingest 步骤 5a，触发一次归档扫描 5. 在 `log.md` 追加：`## [YYYY-MM-DD HH:mm] done | {任务标题}` 6. 简短确认更新结果 ### 3. View — 重新生成看板数据 **触发**：用户说"生成看板"、"我今天/本周/本月完成了什么"、"更新 dashboard" **步骤**： 1. 扫描 `tasks/` 中所有任务文件，读取所有 frontmatter 2. **直接更新 `views/dashboard.html` 中内嵌的 `TASKS_DATA`**（找到 `/* ── TASKS_DATA_START` 注释，将 `var TASKS_DATA = ...;` 整行替换为最新数据） 3. TASKS_DATA 写成压缩 JSON 单行，结构： ```json { "generated": "YYYY-MM-DD HH:mm", "tasks": [ { "id": "...", "title": "...", "created": "...", "due": "...", "category": "today|week|month|someday", "priority": "urgent|high|normal|low", "status": "pending|done|cancelled", "completed_date": null, "tags": [], "notes": "...", "detail": [] } ] } ``` - `detail` 字段：若任务有多个子功能点，以字符串数组列出；否则为 `[]` - 包含所有 status 的任务（pending / done / cancelled 均写入） 4. 在 `log.md` 追加：`## [YYYY-MM-DD HH:mm] view | dashboard.html 数据已更新` - 4a. **归档检查**：同 Ingest 步骤 5a，触发一次归档扫描 5. 告知用户：在文件管理器中双击 `views/dashboard.html` 即可在浏览器中查看 --- ## index.md 维护规范 每次只更新对应区域，不要整体替换： ```markdown # 任务索引 > 最后更新：YYYY-MM-DD HH:mm ## 待办 ### 今日 (today) - [ ] [[tasks/YYYYMMDD-slug]] — 标题 · due: 时间 ### 本周 (week) - [ ] [[tasks/...]] — 标题 · due: 时间 ### 本月 (month) - [ ] [[tasks/...]] — 标题 · due: 时间 ### 以后 (someday) - [ ] [[tasks/...]] — 标题 ## 已完成（最近 30 条） - [x] [[tasks/...]] — 标题 · 完成于: 时间 ``` ## log.md 维护规范 仅追加，每条在文件末尾新增一行： ``` ## [YYYY-MM-DD HH:mm] ingest | 任务A；任务B ## [YYYY-MM-DD HH:mm] done | 任务标题 ## [YYYY-MM-DD HH:mm] cancelled | 任务标题 ## [YYYY-MM-DD HH:mm] view | dashboard.html 数据已更新 ## [YYYY-MM-DD HH:mm] update | 任务标题 — 修改内容描述 ``` --- ## 归档文件维护规范 `views/tasks.js` 和 `views/tasks.json` 是**只读归档文件**，由 AI 自动维护，浏览器端不写入这两个文件。 **views/tasks.js** 格式： ```js // 此文件为历史归档数据，由 Copilot 在 Ingest/Update/View 时自动维护 /* ── ARCHIVE_DATA_START（由 Copilot 直接维护）── */ var ARCHIVE_DATA = {"generated":"YYYY-MM-DD HH:mm","tasks":[...]}; /* ── ARCHIVE_DATA_END ── */ ``` **views/tasks.json** 格式：纯 JSON，与 ARCHIVE_DATA 内容同步。 **归档阈值**：`当前年月 - 2个月` 的 1 日（例：当前 2026-04 → 阈值 `2026-02-01`，保留当月和上月的已完成任务，更早的移入归档） **Copilot 写入步骤**： 1. 读取 `views/tasks.json` 现有内容 2. 将新归档任务与现有归档按 `id` 去重合并（相同 id 保留最新版本） 3. 写回 `views/tasks.json`（压缩单行 JSON） 4. 同步写回 `views/tasks.js`（`ARCHIVE_DATA_START`/`ARCHIVE_DATA_END` 锚点之间整行替换） --- ## dashboard.html 技术规格（供 AI 生成/修复时参考） > 当需要生成或修复 `views/dashboard.html` 时，必须严格遵守以下规格，确保功能完整。 ### 1. 文件结构 单个自包含 HTML 文件（无外部依赖，双击即可在浏览器打开）： ``` <head> <script> ← 内嵌 TASKS_DATA（数据层，Copilot 维护） <style> ← 全部 CSS（含深色模式） <body> <div id="toast"> ← 全局 Toast 通知 <header> ← 标题 + 操作按钮 <div id="app"> ← 动态内容区 <p class="generated"> ← 底部说明 <div id="archive-modal"> ← 归档模态框（默认隐藏） <script> ← 全部交互逻辑（IIFE） ``` ### 2. 数据层：TASKS_DATA 内嵌模式 `<head>` 内第一个 `<script>` 块，格式固定不得改变： ```html <script> /* ── TASKS_DATA_START（由 Copilot 直接维护此段）── */ var TASKS_DATA = {"generated":"YYYY-MM-DD HH:mm","tasks":[...]}; /* ── TASKS_DATA_END ── */ </script> ``` - `TASKS_DATA_START` / `TASKS_DATA_END` 是 AI 定位替换的锚点，**不得删除** - `var TASKS_DATA = ...;` 必须为**单行**压缩 JSON - 使用 `var`（不用 `const`/`let`），确保后续 `<script>` 块可访问 ### 3. 渲染逻辑 `render()` 函数将 `state.tasks` 分区渲染到 `#app`： - **待办区**：`status === 'pending'`，按 `category` 分 4 区块，各区块内按 `due` 升序 - 今日任务（`today`）/ 本周任务（`week`）/ 本月任务（`month`）/ 以后 Someday（`someday`） - **已完成区**：`status === 'done'` 或 `'cancelled'`，按 `completed_date` 倒序，最多 30 条 每张卡片 DOM 结构： ``` .card[id="card-{id}"][class="card [done]"] .card-top .card-body .card-title ← 任务标题（done 时加删除线） .card-meta ← 📅 due/完成时间 + priority tag + tags chips .subtasks ← 仅 detail.length > 0 时渲染 .subtask-item ← div（非 label），每项一个 input[checkbox] + span .notes-area span.notes-label ← "备注" textarea.notes-input ← data-taskid 绑定 .card-actions .status-btns button.status-btn[data-status="pending"] ← "▶ 进行中" button.status-btn[data-status="done"] ← "✓ 已完成" button.status-btn[data-status="cancelled"] ← "✕ 已取消" span.save-indicator[id="si-{id}"] ← "✓ 已记录"（短暂闪烁） ``` > 注意：`.subtask-item` 必须是 `<div>`，不能是 `<label>`；CSS 不加 `user-select: none`，保证文本可被鼠标选中复制。 ### 4. 交互事件绑定（`bindEvents()`） 每次 `render()` 后调用： **子任务 checkbox**（`input[data-taskid][data-idx]` change）： - `t._checked[idx] = cb.checked` - 切换 `.subtask-item` 的 `.checked` class - `flashSI(t.id)` **状态按钮**（`.status-btn[data-taskid]` click）： - `t.status = newStatus` - `t.completed_date = (done/cancelled) ? nowStr() : null` - `flashSI(t.id)` - 若任务在 pending ↔ 完成 之间切换，延迟 350ms 后重新 `render()` **备注输入框**（`textarea[data-taskid]` 两个事件）： - `input`：实时更新 `t.notes = ta.value` - `blur`：`flashSI(t.id)` ### 5. 辅助函数 ```javascript function esc(s) { return String(s || "") .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;") .replace(/"/g, "&quot;"); } function nowStr() { var d = new Date(), p = function (n) { return String(n).padStart(2, "0"); }; return ( d.getFullYear() + "-" + p(d.getMonth() + 1) + "-" + p(d.getDate()) + " " + p(d.getHours()) + ":" + p(d.getMinutes()) ); } function showToast(msg, color) { var t = document.getElementById("toast"); t.textContent = msg; t.style.background = color || "#333"; t.classList.add("show"); clearTimeout(t._tmr); t._tmr = setTimeout(function () { t.classList.remove("show"); }, 2000); } function flashSI(id) { var el = document.getElementById("si-" + id); if (!el) return; el.classList.add("show"); clearTimeout(el._t); el._t = setTimeout(function () { el.classList.remove("show"); }, 1800); } ``` ### 6. CSS 设计要点 - CSS 变量分亮/暗两套，`@media (prefers-color-scheme: dark)` 自动切换 - 主要变量：`--bg`、`--surface`、`--surface2`、`--border`、`--text`、`--text2`、`--accent`、`--urgent`、`--high`、`--normal`、`--low`、`--done-bg`、`--done-border`、`--radius: 12px`、`--shadow` - priority tag 颜色：`.tag.urgent`（红）、`.tag.high`（橙）、`.tag.normal`（绿）、`.tag.low`（灰） - 状态按钮高亮：`.act-pending`（蓝）、`.act-done`（绿）、`.act-cancelled`（红） - 已完成卡片：`.card.done`（浅绿背景，0.75 opacity，标题删除线） - 备注框：`width:100%`，`resize:vertical`，focus 时边框变蓝 - Toast：`position:fixed; bottom:24px; right:24px`，默认 `opacity:0`，`.show` 时 `opacity:1` ### 7. 头部操作区 ```html <header> <h1>个人日程看板</h1> <div class="header-actions"> <span class="meta" id="gen-time"></span> <button class="btn btn-ghost" id="btn-reload">↻ 重新加载</button> <button class="btn btn-ghost" id="btn-today">📋 今日完成</button> <button class="btn btn-ghost" id="btn-week">📅 本周完成</button> <button class="btn btn-ghost" id="btn-archive">📚 历史</button> </div> </header> ``` - `btn-reload`：`location.reload()` - `btn-today`：过滤 `state.tasks` 中 `status==='done'` 且 `completed_date` 以今日 `YYYY-MM-DD` 开头的任务，打开报告模态框 - `btn-week`：过滤本周（周一~周日）内完成的任务，打开报告模态框 - `btn-archive`：打开归档模态框，动态注入 `<script src="tasks.js">` 加载 `ARCHIVE_DATA` - **没有任何文件选择按钮** 报告模态框（`#report-modal`）包含只读 `<textarea id="report-text">` 和「📋 复制文本」按钮（`navigator.clipboard.writeText`），方便写日报/周报。 ### 8. 归档模态框 ```html <div id="archive-modal" class="modal-overlay" style="display:none"> <div class="modal-box"> <div class="modal-header"> <span>📚 历史归档</span> <button class="modal-close" id="btn-archive-close">✕</button> </div> <div class="modal-body" id="archive-content"></div> </div> </div> ``` - 点击「📚 历史」→ 显示模态框，动态加载 `tasks.js`，读取 `window.ARCHIVE_DATA` - 按 `completed_date` 年月分组，`<details>` 折叠展示，每组显示任务数 - 归档卡片只读（无状态按钮、无备注编辑框） - 点击遮罩或 ✕ 关闭 - 加载失败或无数据时提示"暂无归档数据" - 模态样式：全屏半透明遮罩，居中白框（最大宽800px，最大高度85vh可滚动），亮/暗双模式


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