管课表、管日程(事件+事项)、理账本、追进度、便签备忘、习惯库。一个 skill,五个 JSON,一份习惯文档。
> 前身为「课搭子(Course Buddy)」。从课搭子 v1.x 迁移的用户,将 skills/course-buddy/data/ 下 JSON 移至 skills/college-cabinet-data/ 即可无缝升级(学习计划和便签为后续新增文件,旧版无对应文件则跳过)。数据格式完全兼容。
基础
模块
系统
大学不止上课。课表轮换、实验分组、DDL 追命、通知轰炸、账目乱飞——靠脑袋记早晚崩。
大学生内阁做的事:课表、日程(含事件和事项)、账本、学习计划、便签各一个 JSON,外加一份习惯文档(habits.md)。查课表自动算周次、过假期、理补课;实验轮换自动展开,只显示你那一组。日程(含事件和事项)统一从 schedule.json 读出,event 按时段筛选,longterm 按截止日显示,逾期着重标注。记账自动更新余额,转账双记防漏。学习计划自定义 task,每日量化追踪。习惯库用自然语言记录条件规则,LLM 在简报时自动判断。通知原文粘贴进来,自动提取事项、判断人群归属、写入。
> v2.0.0 日程重构为事件+事项两种类型、新增 alert 按时提醒、学习计划全新 task 量化模型、weekday 0→1 基、课表查询压缩、防幻觉与日期锚定、自定义简报模块。旧数据自动迁移。
所有涉及日期、星期、课程、简报的操作,第一步必须调用 current_status(action=get, key=time) 获取服务器实时时间,以此为唯一基准。后续所有日期计算基于此结果,不得沿用对话历史或上下文中的日期快照。
当天是周几的判断:weekday = isoweekday(日期),1=周一~7=周日。
所有涉及日程查询的操作(简报或对话),读取 schedule.json events 时,必须同时检查所有 status=active 的 repeat 事件,按当前日期展开为具体实例。events 数组中的单次条目不足以代表当日全部日程。
展开规则:
每次本 skill 被加载时,在响应用户任何请求之前,必须先完成以下检查。涉及数据安全的步骤必须完成,不得跳过。
步骤:
_meta.json,获取 skill 当前版本号../college-cabinet-data/ 目录是否存在:以上步骤完成后,再处理用户的正常请求。
禁止行为:跳过版本检查直接响应用户请求。用户说"今天有什么课"不代表可以跳过迁移——先查版本、再回答。
当版本检查发现无数据文件时,按以下顺序逐模块引导用户完成初始化。每完成或跳过一个模块再进入下一个,不要一次抛出所有。用户说「不用了」「先跳过」「以后再说」即终止引导流程。
开场(发现无数据时先说这一句)
> 我是大学生内阁,帮你管课表、日程、记账、学习进度和便签。你现在还没数据,我带你搭起来?
按顺序引导
> 课表是基础,你有课表截图或文字吗?发给我录进去。录好以后自动算周次、跳过假期、实验分组只显示你那一组。
> 试试:「解析我的课表」+ 贴过来
> 你有几个常用支付渠道?校园卡、微信、银行卡,告诉我名字和余额,我帮你建好。
> 试试:「建两个账户:校园卡余额 500,微信余额 300」
> 日程分两种。有时段的活动直接说时间地点,有截止日的作业说哪天前交。到点提醒,逾期了在简报里标 ⚠️。
> 试试:「5月20日 14:00 排练 化一教室」或者「下周三交无机作业」
> 要备考或者刷网课?设个目标,系统每天帮你追进度。
> 试试:「我要备考英语四级,每天背 50 个单词」
> 还有一些零碎的备忘——老师提了篇文献、同学说了个选课技巧——随手记在便签板上就行。
> 试试:「记一下:张珉上课提了篇 JACS 文献」
> 有些规律不是简单的定时重复——考试周跳过组会、下雨提前出门、实验课当天提醒带实验服。这些写在习惯库里,AI 每次简报时自动判断。
> 试试:「记住:实验课当天早上提醒我带实验服和护目镜」
> 课表录好了,要不要每天早上自动推今日课表和日程?也可以午间推下午课、晚上推明天安排。
> 试试:「设置提醒」
终止条件
skills/college-cabinet-data/ 目录(与 skill 目录平级,不在 skill 目录内)schedule.json → {"version":"4.0","reminder":{"briefings":[{"id":"morning","time":"06:40","modules":["courses","schedule","insight"]},{"id":"noon","time":"12:40","modules":[{"id":"courses","range":"remaining"},{"id":"schedule","range":"remaining"},"insight"]},{"id":"evening","time":"19:00","modules":[{"id":"courses","range":"tomorrow"},{"id":"schedule","range":"tomorrow"},"insight"]},{"id":"night","time":"22:00","modules":["schedule","ledger","notes","insight"]}],"advance":{"longterm":{"urgent_days":3}},"custom_modules":[]},"events":[]}ledger.json → {"version":"1.0","accounts":[],"transactions":[]}courses.json → 见下方课表章节study_plans.json → {"version":"2.0","plans":[]}notes.json → {"version":"1.0","notes":[]}habits.md → 见下方习惯库章节(含模板内容,复制到文件中即可)> 从课搭子迁移:直接将原 skills/course-buddy/data/ 下 JSON 复制到 skills/college-cabinet-data/,无需格式转换。学习计划和便签为后续新增,旧版无对应文件则跳过。
| 文件 | 内容 |
|---|---|
| ------ | ------ |
../college-cabinet-data/schedule.json | 日程(事件+事项) |
../college-cabinet-data/ledger.json | 账户与交易 |
../college-cabinet-data/courses.json | 学期课表(含实验轮换和调停课) |
../college-cabinet-data/study_plans.json | 学习计划(自定义 task,量化进度追踪) |
../college-cabinet-data/notes.json | 便签(纯文本碎片备忘) |
../college-cabinet-data/habits.md | 习惯库(条件规则和行为模式,LLM 读取解释) |
五个 JSON 是各自领域的唯一数据源。habits.md 是 LLM 的行动指南,不替代任何数据文件。数据目录位于 skill 目录外,随版本更新不丢失。memory 文件只记对话摘要,不从中提取结构化数据。
课表和学习计划统一管理。课表是学校安排的课程,学习计划是自己定制的备考、网课等。
学期课程,含单双周、实验轮换、调停课。
{
"version": "2.0",
"semester": "2026年春季学期",
"first_week_date": "2026-03-09",
"total_weeks": 16,
"time_slots": [
{"slot": "1-2", "start": "08:00", "end": "09:30"},
{"slot": "3-4", "start": "10:00", "end": "11:30"},
{"slot": "5-6", "start": "13:30", "end": "15:00"},
{"slot": "7-8", "start": "15:30", "end": "17:00"},
{"slot": "9-10", "start": "17:30", "end": "19:00"},
{"slot": "11-12", "start": "19:30", "end": "21:00"}
],
"holidays": [
{"name": "劳动节", "start": "2026-05-01", "end": "2026-05-05"}
],
"makeup_days": [
{"date": "2026-05-09", "replace_weekday": 2}
],
"courses": [
{
"name": "大学物理实验B",
"weekday": 3,
"time_start": "08:00",
"time_end": "11:30",
"weeks": [2,3,4,5,6,7,8,9,10,11,12,13],
"location": "分组实验",
"teacher": "",
"parallel": true,
"group": 1,
"schedule": {
"2": {"topic": "基本力学量测量", "location": "201室", "teacher": "高志华"},
"3": {"topic": "单摆运动", "location": "229室", "teacher": "周亚洲"}
},
"overrides": {
"11": {"status": "cancelled", "note": "临时停课"},
"5": {"linked_notes": ["n-015"]}
},
"linked_notes": ["n-003", "n-007"]
}
]
}
必填:name、weekday(1=周一~7=周日)、time_start、time_end、weeks、location、teacher。
可选:parallel(分组实验)、group(组号)、schedule(每周不同内容,键为周次编号)、overrides(单周例外,键为周次编号)、linked_notes(关联便签 id 列表)。
linked_notes:存放 notes.json 中的便签 id。课程级的 linked_notes 每节实验课都显示,overrides 级的 linked_notes 仅在对应周显示。典型用法:实验课每节都要带实验服 → 课程级;某周代交报告 → overrides 级。字段可选,缺省即无关联便签。
顶层可选:time_slots(课节映射表)。每条含 slot(显示名如 "1-2")、start(起始时间)、end(结束时间)。配置后输出优先用课节名,未匹配时回退到时段时间。
overrides.status 合法值:
cancelled:该周不上课room_change:教室变更(仅覆盖 location)顺延不是 override 打补丁。用户说"本周顺延到下周"时,直接改数据:本周标 cancelled,下周的 schedule 写入顺延过来的内容(若下周已有同名课程则追加为第二条记录)
对周次 W,确定具体内容的优先级:
查询特定周的课时,同时读取关联便签:课程级 linked_notes + overrides[W].linked_notes 合并去重(课程级先、overrides 级后),从 notes.json 读取对应 content。overrides 级的便签在输出中标注「仅本周」以区分常驻便签。
当前周 = floor((今天 - first_week_date) / 7) + 1
核心原则:课程信息仅来自 courses.json,绝不推测或编造。无匹配则输出"这天没有课"。
当用户提供轮换表、分组名单、或每周具体安排时:
同一时段同名课程有多条记录时,标注 parallel: true。查询时合并显示为一行。用户告知分组后只显示对应那组的内容。
新课表来了 → 解析生成新的 courses.json 覆盖。旧文件改名为 courses-上赛季名称.json 备份。
管理备考、网课、阅读等学习计划。字段全部由用户自定义,无预设分类。
{
"version": "2.0",
"plans": [
{
"id": "cet4",
"title": "英语四级",
"deadline": "2026-06-13",
"status": "active",
"tasks": [
{
"id": "word",
"name": "单词速刷",
"goal": 55,
"unit": "组",
"per_day": 3,
"done": 15,
"log": [
{"date": "2026-05-22", "count": 3},
{"date": "2026-05-23", "count": 2}
]
},
{
"id": "essay",
"name": "范文背诵",
"goal": 30,
"unit": "篇",
"per_day": 2,
"done": 0,
"log": []
}
],
"note": ""
}
]
}
plan
| 字段 | 必填 | 说明 |
|---|---|---|
| ------ | ------ | ------ |
| id | 是 | 唯一标识,英文 |
| title | 是 | 计划名称 |
| deadline | 否 | 截止/考试日期,仅作参考 |
| status | 是 | active / paused / done |
| tasks | 是 | 任务数组,每个计划至少一个 task |
| note | 否 | 备注 |
task
| 字段 | 必填 | 说明 |
|---|---|---|
| ------ | ------ | ------ |
| id | 是 | 唯一标识,英文 |
| name | 是 | 任务名称。用户自定,如 单词速刷 每日阅读 刷题 |
| goal | 是 | 目标总量 |
| unit | 是 | 单位。用户自定,如 组 篇 套 个 min |
| per_day | 否 | 每日标准量。不填则无固定节奏 |
| done | 是 | 累计完成量。等于 log[].count 之和,由 AI 自动维护 |
| log | 是 | 每日完成记录数组 |
log 条目
| 字段 | 必填 | 说明 |
|---|---|---|
| ------ | ------ | ------ |
| date | 是 | 日期,YYYY-MM-DD |
| count | 是 | 当天完成量 |
用户描述学习计划时,AI 创建 plan 并定义 tasks:
用户完全自定义 task 的名称、单位、目标量、每日量。无预设模板。
用户报告进度时,AI 追加 log 条目并更新 done:
| 用户说 | 做什么 |
|---|---|
| -------- | -------- |
| "单词背了 3 组" | 追加 log {date:今天, count:3},done+=3 |
| "今天单词只背了 1 组" | 同上,count=1 |
| "单词到 15 组了" | 若累计 done 不符,追加差异条目使 done=15 |
| "四级进度怎么样" | 列出所有 tasks:name done/goal unit |
| "今天单词做了没" | 查 log 中今日是否有记录 |
每日待办量 = per_day − 今日 log 中已记录 count。若正数则为待办,若零或负则今日已完成。done 达 goal 时 AI 主动提示完成。
study 模块分两段:
📚 今日学习
10:00-11:30 无机化学 化一教室
▸ 英语四级:单词×3组 · 听力×1
📊 学习进度
英语四级:单词 15/55组 · 听力 3/24套
今日任务段:课表课程 + 学习计划中 per_day>0 且今日未完成的 tasks。进度段:所有 active 计划的 tasks 进度概览。
已存在的 .md 格式学习计划(如 课程学习计划/英语四级.md),用户说"把这个导入学习计划" → AI 解析 .md 内容并结构化写入。
术语锚定:日程 = schedule.json 的全部内容。日程下分两种子类型:事件(type=event)和事项(type=longterm)。type 字段只有这两个值,禁止其他任何值。
| type | 中文名 | 特征 | 提醒方式 |
|---|---|---|---|
| ------ | ------ | ------ | ------ |
event | 事件 | 时段或时刻 | 简报提醒(按 range 窗口)+ 按时提醒(默认准时,可选提前) |
longterm | 事项 | 日期(可选时刻) | 简报提醒(按 range 窗口),无按时提醒 |
课程不进入 schedule,课表由 courses.json 独立管理。
{
"version": "4.0",
"events": [
{
"id": "s-20260517-001",
"date": "2026-05-17",
"title": "排练",
"type": "event",
"time": "14:00-16:00",
"location": "化一教室",
"status": "active",
"pinned": false,
"alert": {"on_time": true, "advance_minutes": 10},
"note": ""
},
{
"id": "s-20260518-001",
"date": "2026-05-18",
"title": "组会",
"type": "event",
"time": "14:00-16:00",
"location": "化学楼301",
"status": "active",
"pinned": false,
"repeat": {
"type": "weekly",
"weekdays": [3],
"end_date": "2026-07-01"
},
"note": ""
},
{
"id": "s-20260518-042",
"date": "2026-05-18",
"title": "家教",
"type": "event",
"time": "10:00-12:00",
"lead_min": 30,
"trail_min": 30,
"location": "南湖",
"status": "active",
"pinned": false,
"note": ""
},
{
"id": "s-20260613-001",
"date": "2026-06-13",
"title": "英语四级考试",
"type": "longterm",
"time": "09:00",
"location": "",
"status": "active",
"pinned": true,
"note": "带准考证和耳机",
"linked_notes": ["n-010"]
}
]
}
| 字段 | 必填 | 说明 |
|---|---|---|
| ------ | ------ | ------ |
| id | 是 | 唯一标识,格式 s-YYYYMMDD-序号 |
| date | 是 | 日期,YYYY-MM-DD |
| title | 是 | 事件名称 |
| type | 是 | event 或 longterm。禁止其他任何值 |
| time | 看 type | event 用时段 HH:MM-HH:MM 或时刻 HH:MM;longterm 用时刻 HH:MM(选填,不填为全天) |
| location | 否 | 地点 |
| status | 是 | active / cancelled / done |
| pinned | 否 | 是否置顶,默认 false。置顶事件无视 range 窗口和日期过期限制无条件读取,但 cancelled 的仍排除 |
| alert | event 专用 | 按时提醒配置。on_time(准时提醒,默认 true)、advance_minutes(提前 N 分钟,默认 0)。longterm 无此字段 |
| note | 否 | 备注。禁止存储原始自然语言中的相对时间表述(如"这周二""明天""下个月"),所有时间信息必须以绝对日期存在 date 和 time 字段中 |
| lead_min | 否 | 前置机动时间(分钟),event 专用。用于记录出发/准备时间。存分钟而非绝对时间,改 time 自动跟随 |
| trail_min | 否 | 后置机动时间(分钟),event 专用。用于记录返程/收尾时间。存分钟而非绝对时间,改 time 自动跟随 |
| linked_notes | 否 | 关联便签 id 列表。典型用法:作业 deadline 关联「提交前给导师检查」便签、考试事件关联「带准考证」便签。字段可选,缺省即无关联 |
| repeat | 否 | 重复规则。仅 event 支持,longterm 不支持。缺省为单次事件 |
repeat 结构:
| 字段 | 说明 |
|---|---|
| ------ | ------ |
| repeat.type | daily(每天)/ weekly(每周指定几天)/ interval(每隔 N 天) |
| repeat.weekdays | weekly 专用。数字数组,1=周一~7=周日。如 [3,5] 即每周三五 |
| repeat.interval_days | interval 专用。如 3 即每三天 |
| repeat.count | 可选。重复 N 次后停止。与 end_date 二选一 |
| repeat.end_date | 可选。指定日期后不再产生重复(含当天)。与 count 二选一 |
count 和 end_date 都不填即无限重复。创建无限重复时 Agent 反问一句:「这个重复要到什么时候?可以设个截止日期或次数,也可以不设。」用户说不设就过。
重复事件的 overrides:存储在母体的 overrides 字段中,键为日期。与课程侧 overrides 逻辑一致,用于停改单次发生而不动母体:
"overrides": {
"2026-05-28": {"status": "cancelled", "note": "这周放假"},
"2026-06-04": {"time": "15:00-17:00", "location": "化一教室"}
}
overrides 中可改的字段:status、time、location、note。其他字段(title、type、repeat、alert)不从 overrides 读取,以母体为准。
格式自动判断:含 - 为时段,不含为时刻。
"14:00-16:00")或时刻(如 "10:00")。按时提醒以 time_start 或时刻为触发点"09:00"物理移动时间与正式时间的分离。以家教为例:10:00-12:00 上课,但 9:30 出发、12:30 到宿舍。存 lead_min: 30, trail_min: 30,显示时会自动拼出机动段。
存分钟数而非绝对时间的原因:改 time 时自动跟随,不用手动重算两个字段。两个字段独立可选,大部分日程不需要。
显示规则:
| 情况 | 显示 |
|---|---|
| ------ | ------ |
| 无机动 | 10:00-12:00 |
| 只有前 | 9:30~10:00-12:00 |
| 只有后 | 10:00-12:00~12:30 |
| 前后都有 | 9:30~10:00-12:00~12:30 |
机动段用 ~ 与正式时间段区分,暗示"这一段是缓冲"。- 仍只连接正式起止。
event 除了在简报中出现,还有独立的按时提醒:
on_time: true:在 time 时刻(或 time_start)准时推送一次提醒advance_minutes: N:提前 N 分钟再推送一次。设为 0 则不提前创建 event 时,AI 默认 on_time=true, advance_minutes=0。主动询问用户:"需要提前提醒吗?"用户可口头设定("提前 15 分钟叫我"→ advance_minutes=15)。
心跳或短周期定时任务负责触发按时提醒(检查所有 status=active 的 event,当前时间 = time - advance_minutes 或 = time 时推送)。
| 用户说 | 做什么 |
|---|---|
| -------- | -------- |
| "5月17日 14:00 排练 化一" | 添加 type=event,默认准时提醒,询问是否提前 |
| "十点提醒我给张三打电话" | 添加 type=event,time=10:00 |
| "6月13日英语四级考试" | 添加 type=longterm,date=2026-06-13 |
| "下周五前交分析化学报告" | 添加 type=longterm,date=下周五 |
| "明天的排练取消了" | 对应 event status → cancelled |
| "排练搞完了" | 对应 event status → done |
| "置顶英语四级" | 对应 event pinned → true |
| "取消置顶英语四级" | 对应 event pinned → false |
| "今天有什么" | 查今天所有 active 日程(事件+事项) |
| "这周有什么" | 查未来 7 天所有 active 日程 |
| "每周三五下午排练,到期末" | 添加重复事件。type=weekly, weekdays=[3,5], end_date=推算学期结束 |
| "每天打卡,坚持 30 天" | 添加重复事件。type=daily, count=30 |
| "每三天浇一次花" | 添加重复事件。type=interval, interval_days=3。无结束条件则反问 |
| "下周三排练取消" | 找到对应 repeat 事件 → overrides["对应日期"] = {status:cancelled} |
| "这周三排练改到四点" | 找到对应 repeat 事件 → overrides["对应日期"] = {time:"16:00-..."} |
| "取消每周的组会" | 对应 repeat 事件 status → cancelled,后面所有重复停止 |
仅 event 支持重复,longterm 不支持。
展开规则:每次查询或简报生成时,对含 repeat 的事件按 range 窗口展开为具体日期:
date 起始,按 repeat.type 计算出窗口内所有命中日三种 type 的命中小节:
停止条件:count 或 end_date 先到为准。无限重复的事件展开不受窗口大小影响——today 窗口只展开今天,tomorrow 只展开明天,不会失控。
交互原则:创建重复事件时,Agent 尽量推断结束时间。用户说「到期末」→ 推算学期结束日。说「坚持 30 天」→ count=30。什么都不说也不设截止 → 反问一句再创建。
修改单次:用户说「这周排练取消」「下周三排练改到四点」→ Agent 找到对应 repeat 事件,在当前发生日写 overrides。不碰母体 repeat 规则。
取消整条:用户说「取消每周的组会」→ 母体 status → cancelled,所有后续展开停止。
v2.6.0 起,schedule.json 不再包含 automations 字段。定时自动创建日程/交易的需求由习惯库(habits.md)覆盖——用自然语言描述规则,LLM 在简报时判断并执行。
典型映射:
| 旧 automation 用例 | 习惯库写法 |
|---|---|
| --- | --- |
| 每周四自动创建三天后截止的作业 | - [学期中] 每周四自动在 schedule.json 中创建一条 longterm:标题「交英语作业」,截止日=本周日 23:59 |
| 每月 15 号自动记 50 元话费 | - [长期] 每月 15 号自动在 ledger.json 中记一笔:支出 50 元,类别「通讯」,备注「手机话费」 |
| 停止自动创建 | 「忘掉那条习惯」或修改有效期 |
和 JSON automation 相比,习惯库的优势在于:条件可以叠加上下文(「考试周不创建作业」「余额低于 100 时不自动记话费」),不需要改 schema。
多账户收支管理。每次交易自动更新余额,转账强制双记防漏。
{
"version": "1.0",
"accounts": [
{"id": "campus_card", "name": "校园卡", "balance": 579.25},
{"id": "wechat", "name": "微信", "balance": 320.00}
],
"transactions": [
{
"id": "txn-001",
"date": "2026-05-17",
"time": "12:30",
"account": "校园卡",
"amount": -9.50,
"type": "expense",
"category": "餐饮",
"item": "午餐",
"note": ""
}
]
}
accounts
| 字段 | 说明 |
|---|---|
| ------ | ------ |
| id | 账户唯一标识(英文) |
| name | 账户显示名 |
| balance | 当前余额 |
transactions
| 字段 | 必填 | 说明 |
|---|---|---|
| ------ | ------ | ------ |
| id | 是 | 唯一标识,格式 txn-序号。序号基于所有已有交易的编号最大值 +1,禁止用 len+1 |
| date | 是 | 日期,YYYY-MM-DD |
| time | 否 | 时间,HH:MM |
| account | 是 | 账户名(对应 accounts[].name) |
| amount | 是 | 金额。支出为负数,收入为正数 |
| type | 是 | expense / income / transfer |
| category | 否 | 用户自定义分类。建议:餐饮 交通 购物 居住 娱乐 教育 医疗 其他 |
| item | 否 | 项目/商品名 |
| note | 否 | 备注 |
转账(type=transfer)必须双记:一条从源账户出(负数),一条入目标账户(正数)。写入后立即验证两账户余额变动是否匹配,不匹配则回滚。
不修改、不删除原始交易。修正方式:
| 用户说 | 做什么 |
|---|---|
| -------- | -------- |
| "午饭校园卡 9.5" | 添加 expense,校园卡余额 -9.5 |
| "微信收入 32" | 添加 income,微信余额 +32 |
| "校园卡转微信 400" | 两条 transfer,更新双方余额 |
| "查余额" | 列出所有账户及余额 |
| "这周花了多少" | 汇总本周支出 |
| "这个月餐饮花了多少" | 按 category 筛选汇总 |
用户说"处理通知"并粘贴通知原文时,从原文中提取事项并写入对应的数据文件。
event 或 longterm,禁止发明新值):longterm(事项)event(事件)event(事件)longterm,不发明新 typenote: "[来自通知] 人群: xxx"| 通知原文片段 | 提取结果 |
|---|---|
| ------------- | --------- |
| "5月20日前提交实验报告" | type=longterm, date=2026-05-20, title=提交实验报告 |
| "周三下午2:00在化一教室开班会" | type=event, date=下周三, time=14:00-15:00, title=班会, location=化一教室 |
| "明天上午10:00各组负责人到办公室" | 先判断用户是否为负责人,不是则跳过 |
| "本周四物理实验停课一次" | 写入 courses.json → overrides,status=cancelled |
| "提醒大家今晚12点前填表格" | type=longterm, date=今天, title=填表格 |
| "9:20到教室 9:30-11:30考试" | type=event, time=9:30-11:30, lead_min=10, title=考试 |
| "5月15日交实验报告" | type=longterm, date=2026-05-15, title=交实验报告 |
| 用户说 | 做什么 |
|---|---|
| -------- | -------- |
| "处理通知" + 粘贴原文 | 按上述流程解析并确认后写入 |
收纳放不进课表、日程、记账、学习计划任何现有结构的纯文本碎片。物理隐喻:软木板上的便利贴。
与现有 JSON 的分工:
| 用户说 | 进哪个文件 | 理由 |
|---|---|---|
| -------- | ----------- | ------ |
| "下周三交无机作业" | schedule.json | 有明确日期,事项(longterm) |
| "贾老师说可以用另一种方法算" | notes.json | 碎片备忘,无日期无结构 |
| "校园卡又扣了 9.5" | ledger.json | 有金额 |
| "今天背了 3 组单词" | study_plans.json | 有进度量 |
| "张珉提了篇 JACS 文献名忘了搜一下" | notes.json | 提醒自己的待办,但不带日期 |
能进结构的绝不进便签。便签是兜底,不是捷径。
便签的创建必须由用户明确触发。仅当用户话语中出现「记一下」「便签」「贴个」「备忘」等关键词时才创建便签。其他场景——即使语义上像备忘——Agent 不得主动创建便签,否则会和日程、记账等其他模块产生冲突。
唯一例外是 linked_notes:用户在某门课或某个事件的语境下说「物理实验课每节都要带实验服」,Agent 直接在课程的 linked_notes 中写条目,不走「记一下」的创建门槛。
便签可以通过 linked_notes 挂载到课程和日程上。课程侧支持课程级(每节显示)和 overrides 级(仅指定周显示)两层。日程侧只支持条目级。
创建 linked_notes 的交互规则:
| 用户说 | 写入位置 | 判断逻辑 |
|---|---|---|
| -------- | --------- | -------- |
| 「物理实验课每次都要带实验服」 | 课程级 linked_notes | 「每次」「每节」「都要」→ 课程级 |
| 「这周物理实验课帮张三代交报告」 | overrides 当前周 linked_notes | 「这周」→ 判断当前周次 → overrides 级 |
| 「下周实验课别忘带 U 盘」 | overrides 指定周 linked_notes | 「下周」→ 计算目标周次 → overrides 级 |
| 「四级考试记得带准考证」 | 该 event linked_notes | 当前语境中明确指向某个已有日程条目 |
| 「交无机作业前给导师看一下」 | 该 longterm linked_notes | 同上,明确指向已有事项 |
模糊时反问:Agent 无法从语境中判断是课程级还是 overrides 级、或不确定指向哪个已有条目时,反问一句确认。不要猜。「物理实验课带实验服——是每节都要带,还是就这一次?」
删除联动:删除某条便签时,同时从所有模块的 linked_notes 中移除该 id。删除课程或日程条目时,linked_notes 中的 id 不删除(便签本身仍存在于软木板上),仅链接失效不报错。
{
"version": "1.0",
"notes": [
{
"id": "n-001",
"content": "张珉上课提到一篇JACS,记得搜出来看看",
"created_at": "2026-05-28T22:30:00",
"status": "active"
}
]
}
| 字段 | 必填 | 说明 |
|---|---|---|
| ------ | ------ | ------ |
| id | 是 | 唯一标识,格式 n-序号。序号基于所有已有便签(含 archived)的最大编号 +1,禁止用 len+1 |
| content | 是 | 纯文本内容,不做任何结构化解析 |
| created_at | 是 | 创建时间,ISO 8601 |
| status | 是 | active(贴在板上)/ archived(收进抽屉) |
不设 updated_at:便签不修改,想改就摘掉重写一条。
创建 → active(贴在板上)
├─ "摘掉" → 追问 → archived(收进抽屉)或 物理删除
├─ "拿出来" ← archived → active
├─ "删掉/扔掉" → 物理删除(active 和 archived 均可,不追问)
└─ "丢掉" ← archived → 物理删除(不追问)
| 用户说 | 行为 |
|---|---|
| -------- | ------ |
| "记一下:xxx" | 创建,status=active |
| "我的便签" / "便签板" | 列出所有 active,按 created_at 倒序 |
| "搜一下 JACS" | 全文模糊搜索 content,active 和 archived 都搜 |
| "摘掉那条 JACS 便签" | 追问:"收起来还是丢掉?" → archived / 物理删除 |
| "删掉/扔掉/不要了" | 直接物理删除,不追问 |
| "之前收起来的便签" | 列出所有 archived |
| "把那条归档便签拿出来" | archived → active |
删除原则:"摘掉"是犹豫态 → 追问。"删掉/扔掉"是终局态 → 直接执行。archived 便签被说"扔掉"同样直接删除,二次确认没必要。
notes 模块:显示所有 status=active 的便签。格式 📌 {content} [id],ID 格式 [n-NNN]。无便签时整个模块跳过不占位(不用空占位提示)。
便签没有时间维度,不接受 range 参数。在 normalize→resolve→validate 管线中走"不支持 range"分支,与 ledger 同。
条件性的、依赖上下文的行为规则,用自然语言描述,由 LLM 在简报生成时读取并解释。不替代任何 JSON 文件,是 LLM 的行动指南。
JSON 管确定性数据(课表、日程、账本),习惯库管模糊的行为模式。判断标准:需要读取外部上下文才能决定行为?不需要 → JSON repeat;需要 → 习惯库。
../college-cabinet-data/habits.md,纯 Markdown。章节按生活领域分段:
# 习惯库
> 最后更新:YYYY-MM-DD
> 这里是你的生活规律和条件规则。AI 在生成简报时读取。
> 有新的习惯直接说「记住:xxx」,我会加进来。
## 日程习惯
- [学期中] **考试周**自动跳过组会(组会本身用 repeat 录入,这里只写条件例外)
- [学期中] **实验课当天**推送携带清单:实验服、护目镜、实验报告、预习报告
- [长期] 逾期超过 7 天的事项在简报中降级显示,前缀「📦 归档:」
## 记账习惯
- [长期] 每月 1 号在简报中提醒查校园卡余额,低于 50 元提示充值
## 学习习惯
- [学期中] 四级备考:每天至少背 50 个单词,连续 3 天未达标时在简报中语气加重
## 便签习惯
- [长期] 便签中带有「本周」字样的条目,周一简报中自动显示
## 上下文感知
- [长期] 天气预报有雨时,出行事件提醒提前 20 分钟
- [学期中] 当天课表超过 4 节课时,午间简报中追加一句「今天课多,中午休息一下」
-,以 [有效期] 开头。有效期取 学期中 / 假期 / 长期 / 具体日期范围(如 2026-05-01 ~ 2026-05-31)条件),帮助 LLM 快速定位semester 和当前日期习惯检查不建独立的定时任务。它附着在现有简报基础设施上:
有简报时:每次简报生成前,读完数据文件后,读取 habits.md 并逐条匹配。匹配的习惯注入对应模块输出:
| 习惯章节 | 输出归入 |
|---|---|
| --- | --- |
| 日程习惯 | schedule 模块(跳过事件、追加携带提醒、调整显示) |
| 记账习惯 | ledger 模块(追加余额检查) |
| 学习习惯 | study 模块(调整语气) |
| 便签习惯 | notes 模块(筛选显示) |
| 上下文感知 | insight 模块(跨模块建议、环境提醒) |
没有匹配的习惯 → 静默跳过,不输出占位。
无简报时(reminder.briefings 为空数组):自动创建一条 cron 兜底任务:
cron: 0 1 0 * * * (每天 00:01)
prompt: 执行习惯库每日检查
Agent 被唤醒后校准日期 → 读 habits.md → 匹配则推通知 → 无匹配则静默。用户配置了第一档简报后,这条兜底任务自动删除。删光简报后自动重建。
| 用户说 | 行为 |
|---|---|
| --- | --- |
| 「记住:每周三下午去图书馆」 | 追加到习惯库,有效期默认「学期中」 |
| 「忘掉那条浇花」 | 模糊匹配删除对应条目 |
| 「这个习惯只适用于 5 月」 | 更新有效期 |
| 「改一下组会那条,教室改成 302」 | 定位条目并修改 |
| 「关掉习惯检查」 | 简报中跳过习惯库步骤,删除兜底任务(如有) |
创建习惯时 Agent 需判断:这条该放 JSON repeat 还是习惯库?→ 需要外部上下文判断 → 习惯库;纯时间重复 → 反问用户确认。有效期默认:学业相关默认「学期中」,生活相关默认「长期」。
优先级:courses.json / schedule.json 的显式数据 > 习惯库 > LLM 缺省判断。以数据文件为准,冲突时在 insight 中提一句差异,不自动修改数据文件。
Agent 读习惯库时理解语义而非匹配关键词:
拿不准时(条件模糊、两个习惯冲突)在 insight 中提一句,不静默跳过。
提醒分两层:定时简报(cron 触发,推送摘要)和实时提醒(心跳/定时任务触发,即时推送)。
日期锚定规则:每次生成简报前执行日期校准(见顶部→日期校准),以此为唯一基准。同一天内的多次简报对日期/星期的判断必须一致。简报之间不共享上下文,每次独立校准。
简报档位、时间、模块均可自由设定。默认提供四档,用户可增删。
简报生成前:读取各模块数据文件,然后读取 ../college-cabinet-data/habits.md 执行习惯检查(见六、习惯库)。习惯匹配结果注入对应模块。习惯条目中如有要求创建日程/交易的(如「每月15号自动记话费」),当场写入数据文件。最后生成各模块内容 + insight。习惯检查不占用简报输出篇幅,无匹配则静默跳过。
默认简报(仅含内置模块,天气等外部模块需先创建自定义模块后手动加入):
| id | 标签 | 默认时间 | 默认模块 |
|---|---|---|---|
| ------ | ------ | ---------- | ---------- |
morning | 晨间简报 | 06:40 | courses, schedule, insight |
noon | 午间简报 | 12:40 | {"id":"courses","range":"remaining"}, {"id":"schedule","range":"remaining"}, insight |
evening | 晚间简报 | 19:00 | {"id":"courses","range":"tomorrow"}, {"id":"schedule","range":"tomorrow"}, insight |
night | 睡前简报 | 22:00 | schedule, ledger, notes, insight |
预设模块(来自大学生内阁自身数据,无需额外配置):
| 模块 | 内容 | 标题 | 行格式 | 空占位 |
|---|---|---|---|---|
| ------ | ------ | ------ | -------- | -------- |
courses | 课程 | 📚 课表: | {slot_or_time} {name} {location} {teacher},time_slots 存在时优先用课节名(如"第1-2节"),无匹配则用时段时间 | 无课。 |
schedule | 日程 | 📋 日程: + ⏰ 事项: 两段 | 事件段:{time} {title} {location} [id],含机动段时按 lead_min/trail_min 规则展开。事项段:{date} {title}(余{days}天) [id],已逾期标 ⚠️ {date} {title}(逾期{days}天) [id],距日期≤urgent_days 前缀加 ⚠️ 临近:。无事件时事件段显示 无日程。,无活跃事项时事项段跳过不占位。每条事件和事项必须在行尾附带 ID,格式 [s-YYYYMMDD-NNN] | 无日程。 |
ledger | 各账户余额 | 💰 账户余额: | {账户名}:¥{余额}。余额总览不显示交易 ID,但用户要求列出交易明细时每条交易行尾追加 [txn-NNN] | — |
notes | 便签 | 📌 便签: | {content} [id]。每条便签行尾附带 ID,格式 [n-NNN] | 无便签则跳过 |
study | 学习进度 | 两段:📚 今日学习 + 📊 学习进度 | 今日任务:课表 + 待办 / 进度:{name} {done}/{goal} {unit}。任务条目行尾附带 [task-id] | 无活跃学习计划。 |
insight | AI 洞察 | 无标题,在所有模块输出后空一行直接写 | 无固定格式,AI 看完整简报后自由输出 1-2 句。方向约束见下方 insight 模块说明 | 无输出则跳过(不占空位) |
除上述预设模块外,用户可定义自定义模块。自定义模块在 reminder.custom_modules 中配置,可在 briefings[].modules 中像预设模块一样使用。
用自然语言配置即可,无需手动编辑 JSON:
| 效果 | 这样说 |
|---|---|
| ------ | -------- |
| 早起看一眼今天有什么 | 「早上 7:00 推今日课表和今日日程」 |
| 中午只看下午的课 | 「午间只要下午课表」 |
| 下午课程、日程一起看 | 「午间推下午课程和下午日程」 |
| 放学收到明天的安排 | 「下午 5:00 推明日课表和明日日程」 |
| 睡前确认最后事项 | 「睡前推账户余额和近期事项」 |
| 课、日程、余额、进度全要 | 「早上 7:00 推课表、日程、余额、学习进度」 |
| 加个天气 | 「早上加天气模块,调天气 skill」 |
| 某档不想收了 | 「不要午间简报了」 |
不指定模块只调时间也行:「早上改成 7:30 推送」。
各模块默认范围:课程/日程/学习进度 → today,可换成 remaining、tomorrow、week 或自定义窗口。
insight 是 AI 原生模块,不读数据文件。Agent 生成完所有正常模块后,扫一眼整体内容,在末尾空一行直接输出 1-2 句。无固定格式,无标题行,纯由模型能力驱动。
内容方向(Agent 自行判断,不强制):
| 简报里有什么 | 可以说什么 |
|---|---|
| ------ | ------ |
| 课表很满 + 相邻时段不同地点 | 提醒赶路 or 建议提前走 |
| 有未完成的逾期事项 | 轻推一句别拖了 |
| 有考试或重要日程 | 简短鼓励 or 提醒准备 |
| 全天清闲 | 一句轻量日记感的话 |
| 消费较高或余额偏低 | 消费提醒 |
| 没什么特别 | 可跳过不输出 |
约束:
用户说「不要每日小结」→ 从各简报 modules 中移除 insight。
每个模块可以附加一个时间窗口,指定读取数据的时间范围。范围用 from 和 to 描述起止点。
时间点描述:
| 值 | 含义 |
|---|---|
| ------ | ------ |
"now" | 当前时刻 |
"today.start" | 今日 00:00 |
"today.end" | 今日 23:59 |
{"offset": N, "time": "HH:MM"} | 以今天为 0,N 天后某时刻。time 缺省值:作起点时 "00:00",作终点时 "23:59" |
内置别名(等价窗口的快捷方式):
| 别名 | 等价窗口 | 含义 |
|---|---|---|
| ------ | ---------- | ------ |
"today" | {from: "today.start", to: "today.end"} | 今日全天 |
"remaining" | {from: "now", to: "today.end"} | 从此刻到今日结束 |
"tomorrow" | {from: {offset:1}, to: {offset:1}} | 明日全天 |
"week" | {from: "today.start", to: {offset:7}} | 未来七天 |
各模块默认范围:
| 模块 | 默认 |
|---|---|
| ------ | ------ |
courses | "today" |
schedule | "today" |
study | "today" |
ledger | —(无时间窗口概念) |
notes | —(无时间窗口概念) |
模块语法:modules 数组中每个元素可以是字符串(走默认范围)或对象(自定义范围):
"modules": [
"courses",
{"id": "schedule", "range": "remaining"},
{"id": "schedule", "from": "now", "to": {"offset": 1, "time": "12:00"}}
]
解释:课表全天,日程(事件+事项)今天剩余,另外加一个自定义窗口(从现在到明天中午前)。
解析管线:模块配置依次经过三段处理:
"courses" → {"id":"courses","range":"default"}"default" → 模块对应的具体范围值。from/to 描述解析为绝对时间戳。接收可选 now?: number 参数用于测试时钟注入{"id":"ledger","range":"week"} 应忽略 range三段均为纯函数,独立导出,方便单测。
remaining 语义:{from:"now", to:"today.end"} 指示滤条件为 startTime > now(严格大于)。即只保留尚未开始的条目。
输出规则:模块按 briefings[].modules 顺序依次输出。每个模块与其标题不空行,模块之间空一行。列表按时间升序排列。无数据时用空占位,不跳过。时间比较:只比较结束时间是否已过。
用户自然语言 → 模块名对应:
| 用户说 | 模块+范围 |
|---|---|
| ------ | ------ |
| 今日课表 | courses + today |
| 下午课表 | courses + remaining |
| 明日课表 | courses + tomorrow |
| 今日日程 | schedule + today |
| 下午日程 | schedule + remaining |
| 明日日程 | schedule + tomorrow |
| 余额 / 账户 | ledger |
| 学习进度 | study |
| 便签 / 备忘录 | notes |
schedule 模块统一从 schedule.json 读取,按 type 分两段输出。每条事件和事项行尾必须附带 ID(格式 [s-YYYYMMDD-NNN])。如有 linked_notes,在对应条目下缩进显示关联便签内容,前缀 📌,ID 放在事件/事项行尾,linked_notes 不重复显示 ID。
事件(type=event):按 range 窗口时间筛选。若事件含 repeat 字段,先按窗口展开为具体日期列表(跳过 overrides 中 status=cancelled 的日期),每命中日读取 overrides 中的 time/location/note 覆盖。无 repeat 的事件直接按 date 筛选。today 窗口显示今日全部事件(含已过期),remaining 窗口只显示尚未开始的事件(startTime > now)。过期事件标 ⚠️ 已过期。
事项(type=longterm):按 range 窗口截止日筛选。事项没有"开始时间",只有截止日期。
today / remaining:显示 date ≤ today 的所有未完成事项(过去逾期 + 今天到期)。已逾期标 ⚠️(逾期{days}天)tomorrow / week 等未来窗口:只显示 date 在窗口内的事项(不含逾期)心跳或短周期定时任务(建议每 5 分钟)检查所有 status=active 且 type=event 的事件。pinned 事件不受日期窗口限制,过期仍扫描。
重复事件:先按当前至明日 23:59 的窗口展开所有命中日,每个命中日独立继承母体的 alert 配置(含 overrides 中对该日的时间覆盖)。无 repeat 的事件直接按 date+time 检查。
检查窗口:当前时刻至明日 23:59(不无限扫描所有未来事件)。
longterm 无按时提醒,仅通过简报覆盖。
schedule.json 顶层 reminder 字段覆盖默认值。默认配置见快速上手。
| 参数 | 默认 | 说明 |
|---|---|---|
| ------ | ------ | ------ |
briefings | 四档 | 简报列表,每条含 id、time、label(选填)、modules。可增减档位、调时间、自由组合模块 |
briefings[].modules | 见上表 | 每档简报包含的模块。字符串走默认范围,对象可自定义 |
advance.longterm.urgent_days | 3 | longterm 距日期 ≤ N 天时语气加重,标注"临近" |
custom_modules | [] | 用户自定义模块列表。每条含 id、label、source、format |
用户口头调整("早上改成 7:00 推送"、"晚间只保留近期事项和课表"、"去掉午间简报")→ AI 更新 reminder 配置。
简报标题格式:📅 {日期中文} · 第{周次}周 {星期}。示例:📅 5月20日 · 第11周 周三。
各模块标题和行格式见上方预设模块表。格式为用户口头调整的基准(如"天气改成一句话""近期事项不要倒计时"→ AI 更新对应条目)。
天气、API 余额查询等依赖外部 skill 的功能不作为预设模块,而是通过自定义模块实现。用户可定义任意新模块,关联外部文件或其他 skill。
推荐创建的自定义模块:
| 推荐模块 | 用途 | 创建指令 |
|---|---|---|
| --------- | ------ | --------- |
| 天气 | 在简报中显示今日/明日天气 | "加一个天气模块,调天气 skill" |
| API余额 | 显示各 AI 平台余额 | "加一个 API 余额模块,调 balance-checker" |
定义方式:在 reminder.custom_modules 数组中添加条目,或口头告诉 AI(AI 自动生成配置并加入对应简报)。
字段:
| 字段 | 必填 | 说明 |
|---|---|---|
| ------ | ------ | ------ |
| id | 是 | 唯一标识,用于 modules 数组中引用。如 library |
| label | 是 | 模块标题,在简报中显示。如 📖 图书馆借阅 |
| source | 是 | 数据来源描述。AI 据此获取数据。格式:文件:{路径}、skill:{skill名}、或自然语言描述 |
| format | 是 | 输出格式模板。用 {字段} 占位,每行一条数据。如 {书名} 到期 {日期} |
| empty | 否 | 无数据时的占位文本,默认 无。 |
示例:
用户说:"睡前简报加一个图书馆模块,数据在 ../college-cabinet-data/library.txt,格式跟近期事项类似"
AI 生成:
{
"id": "library",
"label": "📖 图书馆借阅",
"source": "读取 ../college-cabinet-data/library.txt,每行格式:书名 | 到期日期",
"format": "{书名} 到期 {日期}(余{days}天)",
"empty": "无限期借阅。"
}
并在 briefings[].modules 中加入 "library"。生成简报时,AI 按 source 获取数据,按 format 格式化输出。
定时任务由 cron 触发,每条任务对应一次简报生成或按时提醒扫描。
message 必须极简,只写一句话触发指令。 skill 内部是配置驱动的,agent 自己读 JSON 生成内容,不要在 message 里写生成步骤。
正确:
生成晨间简报生成午间简报扫描按时提醒错误:
第一步读 schedule.json,第二步获取天气,第三步筛选今天课程,第四步... ← 硬编码生成流程,skill 一更新就坏超时建议:冷启动 session 需读取多个文件 + 可能调天气/API 等外部 skill,设置不少于 300 秒超时。纯内存操作(如按时提醒扫描)可设 60 秒。
所有输出必须来自数据文件,严禁推测或编造。
查询时
[id]。ID 来自数据文件,不可编造或缩写。课程名和账户名无需 ID数组长度+1。编号空洞时 len+1 会导致 ID 碰撞引用时
s-xxx、n-xxx、txn-xxx)→ 优先按 ID 精确匹配,回读数据文件确认该条目存在质疑时
写入时
../college-cabinet-data/backups/文件名-YYYY-MM-DD.json(保留 7 个)每次大版本变化的迁移路径在此记录。AI 读取数据时自动检测版本并执行迁移,用户无感知。
| schedule.json version | 对应 skill | 主要变化 |
|---|---|---|
| ---------------------- | ----------- | --------- |
| 2.0 | 课搭子 v1.0.0 | 初始版本。type 含 7 种枚举(course/exam/ddl/meeting/activity/lecture/personal),有 weekday 字段 |
| 3.0 | 课搭子 v1.1.0 ~ v1.2.0 / 大学生内阁 v1.0.0 | 课搭子: type 重构为 event/ddl/moment。大学生内阁 v1.0.0: 品牌升级,新增通知处理 |
| 3.0 | 大学生内阁 v2.0.0 ~ v2.4.0 | v2.0.0: type event/longterm、weekday 1 基、alert 按时提醒、学习计划(见上方详细列表)。v2.1.0-v2.4.0: 模块表合并、日期校准、模块范围重构、便签 notes.json、event 机动时间、模块统一命名、事项逾期着重显示 |
| 3.0 | 大学生内阁 v2.5.0 | 新手引导流程、关联便签 linked_notes、便签创建门槛、AI 洞察 insight 模块 |
| 4.0 | 大学生内阁 v2.6.0+ | 重复事件 repeat(daily/weekly/interval + overrides)。习惯库(habits.md)。ID 序号输出。v2.6.1: 展开强制化。v2.6.5: 数据目录外置、type 术语锚定 |
大学生内阁是课搭子的品牌升级版,数据格式完全兼容。迁移只需一步:
原路径: skills/course-buddy/data/*.json
新路径: skills/college-cabinet-data/*.json
将 JSON 文件复制到新路径即可(学习计划为 v1.1.0 新增,旧版无此文件则跳过)。schedule.json、courses.json、ledger.json 无需任何格式转换。
AI 读取数据文件时自动检查版本并执行迁移。
schedule.json 迁移
pinned: false(若字段不存在)reminder.advance.event.briefings → 删除(event 改用模块 range 控制窗口)courses_pm → {"id":"courses","range":"remaining"}courses_tomorrow → {"id":"courses","range":"tomorrow"}events_pm → {"id":"schedule","range":"remaining"}events_tonight → {"id":"schedule","range":"remaining"}events_tomorrow → {"id":"schedule","range":"tomorrow"}courses / schedule / ledger / study → 保持events / focus / balance → schedule / ledger(v2.4.0 自动映射){"version":"1.0","notes":[]}events → schedule,focus → 删除(并入 schedule),balance → ledger,backup → 删除(系统功能不入简报)。旧名出现在 briefings[].modules 时自动替换automations 字段 → 删除(该字段从未在发布版中使用,残留即清理)reminder.briefings 为空)→ 自动创建 00:01 兜底 cron 任务event 或 longtermlongtermeventevent[⚠️ type字段非标准值: 原值],Agent 读到后询问用户data/ 迁移至 ../college-cabinet-data/:旧版数据在 skill 目录内的 data/ 下,更新前需手动将整个 data/ 目录移动到 skill 目录的兄弟路径 skills/college-cabinet-data/。如果 skill 目录内仍有 data/,AI 检测到后应提示用户执行移动,并给出具体命令../college-cabinet-data/ 不存在)→ 自动创建并初始化空模板courses.json 迁移
study_plans.json 迁移
迁移后备份 + 格式验证。失败则回滚并告知用户。
共 10 个版本