
Juanwang Holiday Planner
- 2 installs
- 1 repo stars
- Updated June 15, 2026
- archlizheng/juanwang-holiday-planner
juanwang-holiday-planner is a skill that generates a saveable single-file HTML holiday growth-plan dashboard with daily check-ins, phase reviews, and data sync.
About
This skill creates, reviews, and updates a single-file HTML dashboard for a Chinese-first holiday growth plan. It turns a user's holiday goals into a phased daily plan with daily check-ins, phase reviews, streaks, and completion rates computed from the plan data. It requires a concrete start date, initializes plans with no fake progress, and enforces a health-first, non-shaming tone.
- Turns holiday goals into a phased daily plan delivered as one saveable single-file HTML dashboard
- Supports daily check-ins, phase reviews, streaks, and completion computed from plan data
- Health-first, no-anxiety-marketing design with five emoji mood options
Juanwang Holiday Planner by the numbers
- 2 all-time installs (skills.sh)
- Ranked #2,419 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
juanwang-holiday-planner capabilities & compatibility
- Capabilities
- planning · habit tracking · html export
- Use cases
- planning · project management
What juanwang-holiday-planner says it does
Create, review, and update a saveable single-file HTML holiday growth-plan dashboard.
**Start date is required**: never invent a start date.
npx skills add https://github.com/archlizheng/juanwang-holiday-planner --skill juanwang-holiday-plannerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 1 |
| Last updated | June 15, 2026 |
| Repository | archlizheng/juanwang-holiday-planner ↗ |
What it does
Generate a saveable single-file HTML holiday growth-plan dashboard with daily check-ins and phase reviews.
Who is it for?
Producing a single self-contained HTML holiday plan dashboard with check-ins and phase reviews
Skip if: Anxiety-marketing framing such as extreme weight loss or shame/public-pressure tactics (explicitly prohibited)
When should I use this skill?
when the user wants a holiday growth plan / summer or winter plan / self-discipline check-in HTML dashboard
What you get
One HTML file with a phased plan, daily check-ins, computed streaks and completion, and phase reviews
- single-file HTML holiday growth-plan dashboard
By the numbers
- outputs exactly one .html file
- exactly five emoji mood options
- 7-10 day phase review cadence
Files
<objective> Create, review, and update a saveable single-file HTML holiday growth-plan dashboard. The skill turns a user's holiday goals into a phased daily plan, supports daily check-ins, and can write phase-level AI reviews back into the HTML data bundle.
The product voice is Chinese-first and warm: it helps users stop vague flag-setting without selling anxiety, peer pressure, or unhealthy transformation promises. </objective>
<essential_principles>
- Single HTML is the deliverable: output exactly one
.htmlfile. Use a personalized safe filename by default (juanwang-{nickname}-{goal-keyword}-{startDate}.html) and respect an explicit user filename after sanitizing it and adding.htmlif needed. Do not create zip files, preview images, QR codes,manifest.json, orsw.jsfor normal delivery. - Start date is required: never invent a start date. If the user does not provide a concrete
YYYY-MM-DDstart date, ask for it before generating a plan. - Nickname is optional: ask for a preferred nickname/name at intake when natural, store it in
config.user.name, and fall back to小卷without blocking generation. - No fake progress: new plans initialize with
checkins: {},reviews: {}, selected Day 1, 0 streak, 0% completion, and no placeholder dashboard data. - Data drives UI: dates, calendars, streaks, completion rates, phase review readiness, and report stats must be computed from
APP_DATA.dailyTasksandcheckins. - Health-first workload: protect sleep, recovery, and sustainable effort. If health data declines or the user reports overload, reduce future load before adding tasks.
- Low-friction execution is the core: every generated plan must help the user start when motivation is low through If-Then plans, 2-minute starter tasks, timeboxes, buffer, stage reviews, and small-win feedback.
- No anxiety marketing: avoid “弯道超车”, “卷死同学”, shame, public-pressure tactics, extreme weight loss, and miracle-result promises.
- Mood is emoji data: mood options are exactly
😄,🙁,😝,😭,😡; store the selected emoji string directly incheckins[date].mood.
</essential_principles>
<routing> Route by user intent and then follow the selected workflow exactly:
| User intent | Workflow |
|---|---|
| Create a new holiday plan, growth plan, summer/winter plan, or HTML dashboard | workflows/generate-plan.md |
| Generate or write back a phase review from a synced HTML file | workflows/generate-phase-review.md |
| Modify an existing plan, reduce workload, change future tasks, or recover from falling behind | workflows/adjust-existing-plan.md |
| Audit/check a generated HTML or verify the skill deliverable | workflows/validate-deliverable.md |
If intent is mixed, prioritize in this order: validate existing data → phase review → adjust existing plan → generate new plan. Ask one concise clarification only when the required artifact or intent is missing. </routing>
<reference_index> Load only the references required by the selected workflow:
references/product-principles.md— product scope, users, tone, and delivery modelreferences/app-data-schema.md—APP_DATA, localStorage, review data, and compatibility rulesreferences/failure-mechanisms.md— why holiday plans fail and the product responses that lower execution frictionreferences/planning-methodology.md— phase design, daily task construction, If-Then plans, timeboxing, buffer, AAR/PDCAreferences/safety-and-tone.md— prohibited language, health boundaries, review tonereferences/html-runtime-contract.md— template/runtime requirements and validation invariants
</reference_index>
<workflows_index>
| Workflow | Purpose |
|---|---|
generate-plan.md | Create a new initialized single-file HTML plan |
generate-phase-review.md | Parse synced HTML and write phase review text into APP_DATA.reviews |
adjust-existing-plan.md | Preserve history and adjust only future tasks |
validate-deliverable.md | Verify structure, data, runtime behavior, and market-ready constraints |
</workflows_index>
<success_criteria> A task handled by this skill succeeds when:
- Generated plans deliver one HTML file with no PWA dependency and no fake progress.
- Generated plans include the P0 low-friction system: scenario-bound If-Then, daily 2-minute starter, realistic timebox with buffer, 7-10 day review cadence, and small-win feedback language.
- Validation confirms Day 1 initialization, dynamic dates, dynamic phase review readiness, and five emoji mood options.
- Phase reviews use only real synced data, preserve existing checkins/reviews, and keep a supportive, non-shaming tone.
</success_criteria>
.DS_Store
*.log
node_modules/
MIT License
Copyright (c) 2026 archlizheng
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
juanwang-holiday-planner
卷王·假期成长规划 — 用 AI 编程助手把模糊的假期目标,变成一份可打卡、可复盘、可保存的 单文件 HTML 假期成长仪表盘。
不靠意志力,靠可执行的规划。产品语气温暖、反焦虑:不贩卖「弯道超车」「卷死同学」式压力,优先睡眠、恢复与可持续节奏。
---
在线预览
想先看生成效果,可以打开这份示例网页:卷王·假期成长规划示例。
这是一份 60 天计划的实际生成页面,用来预览「规划 / 打卡 / 仪表盘 / 复盘」等核心交互;正式使用时,Skill 会根据你的开始日期、目标和可用时间生成新的个性化单文件 HTML。
<p> <img src="./assets/preview-plan.png" alt="规划页预览:阶段路线图和每日任务" width="220"> <img src="./assets/preview-checkin.png" alt="打卡页预览:今日任务、健康打卡和心情记录" width="220"> <img src="./assets/preview-dashboard.png" alt="仪表盘预览:关键指标和 60 天打卡日历" width="220"> <img src="./assets/preview-report.png" alt="复盘页预览:勋章墙、数据汇总和 AI 复盘总结" width="220"> </p>
---
快速开始
你 不需要会写代码。装好 Skill 后,在对话里说明假期目标并 必须提供开始日期;AI 会按本仓库规则生成一个个性化命名的单文件 HTML,双击即可在浏览器打开(手机也可传文件后用浏览器打开)。
路径 A:已安装 Skill
1. 新开一次 AI 对话(让 Skill 生效)。 2. 复制下面模板,改成你的信息,发送给 AI。 3. 等待生成 单个 HTML 文件,默认文件名类似 juanwang-小卷-英语运动-2026-07-01.html。
/juanwang-holiday-planner 帮我做一份暑假成长规划
开始日期: 2026-07-01
假期长度: 45 天
昵称: 小周(可选)
主要方向: 英语 + 运动恢复节奏
具体目标: 每天背 20 个四级词,每周跑 2 次
副目标: 读完一本非虚构书(可选)
每天可用时间: 约 2 小时
期望文件名: 小周暑假英语运动计划(可选)触发方式因环境而异:优先用 `/juanwang-holiday-planner`;若未注册斜杠命令,可在对话里说明「请严格按 juanwang-holiday-planner skill 生成单文件 HTML 计划」,或用 `@` 引用已安装目录下的 SKILL.md。
重要:若未提供 YYYY-MM-DD 格式的开始日期,Skill 会要求你补充,不会擅自编造日期。
路径 B:尚未安装 Skill
1. 先安装(推荐一条命令,见下文 安装方式)。 2. 新开一次 AI 对话。 3. 复制 [路径 A](#路径-a已安装-skill) 中的对话模板,改成你的信息后发送。
---
安装方式
思路:让 AI 能读到本仓库的 `SKILL.md` 及 `workflows/`、`references/`、`templates/` 等配套文件。
npx skills install 与 npx skills add 等价(install 是 add 的别名)。CLI 会从 GitHub 拉取仓库并写入各 Agent 约定的 skills 目录,不必先手动 clone。
以下命令假设仓库已发布为archlizheng/juanwang-holiday-planner。若你 fork 了仓库,把owner/repo换成自己的即可。
推荐:一条命令安装
安装到当前项目(便于团队共享):
npx skills install archlizheng/juanwang-holiday-planner -y安装到本机所有项目(个人常用):
npx skills install archlizheng/juanwang-holiday-planner -g -y仅安装到 Cursor:
npx skills install archlizheng/juanwang-holiday-planner -a cursor -y仅安装到 Codex:
npx skills install archlizheng/juanwang-holiday-planner -a codex -y安装后请 新开对话。Skill 名称为 `juanwang-holiday-planner`(与 SKILL.md 的 name 字段一致)。
更新与查看
更新已安装的 skill:
npx skills update juanwang-holiday-planner仅查看本仓库里有哪些 skill、不实际安装:
npx skills install archlizheng/juanwang-holiday-planner --list备选:手动克隆或复制
若环境不支持 skills CLI,可将本仓库克隆或复制到 Agent 的 skills 目录:
| Agent | 常见路径 |
|---|---|
| Cursor(项目级) | .cursor/skills/juanwang-holiday-planner/ |
| Cursor(用户级) | ~/.cursor/skills/juanwang-holiday-planner/ |
| Codex(项目级) | .codex/skills/juanwang-holiday-planner/ |
| Claude Code | ~/.claude/skills/ 或项目内 .claude/skills/ |
目录内须包含 `SKILL.md` 及完整的 workflows/、references/、templates/、scripts/。
---
你会得到什么
生成物是 一个 HTML 文件,默认会按昵称、目标和开始日期生成安全文件名;如果你提供「期望文件名」,Skill 会清理危险字符并自动补上 .html 后缀。文件内含:
| 模块 | 说明 |
|---|---|
| 规划 | 阶段路线图、每日任务、If-Then 预案、2 分钟启动版 |
| 打卡 | 主任务 / 弹性任务、健康数据、心情(5 个 emoji)、备注 |
| 仪表盘 | 连续打卡、完成率、日历、趋势(均由真实打卡数据计算) |
| 复盘 | 阶段复盘卡片(可让 AI 根据同步后的 HTML 写回复盘) |
新计划 不会预填假进度:checkins 与 reviews 初始为空,Day 1 选中,连续天数与完成率为 0。
---
还能做什么
在已安装 Skill 的前提下,你可以继续对话:
| 意图 | 示例说法 |
|---|---|
| 调整未来任务 / 减负 | 「我落后了,帮我把后面 7 天的任务减载,保留已打卡记录」 |
| 阶段复盘 | 「根据我同步出来的 HTML,写第 1 阶段复盘并写回文件」 |
| 校验产物 | 「帮我检查这份 HTML 是否符合 skill 规范」 |
---
字段说明(照抄格式最稳)
| 信息 | 怎么填 |
|---|---|
| 开始日期 | 必填,YYYY-MM-DD,例如 2026-07-01 |
| 假期长度 | 天数,例如 45 |
| 昵称 / 称呼 | 可选;用于页面称呼和默认文件名,未提供时用「小卷」 |
| 主要方向 | 一句话概括,例如「英语 + 运动」 |
| 具体目标 | 可衡量的主目标 |
| 副目标 | 最多 2 个,可选 |
| 期望文件名 | 可选;不需要写 .html,Skill 会自动清理危险字符 |
| 每天可用时间 | 可选;Skill 会按 70%–80% 留 buffer |
| 身份锚点 / 高风险场景 | 可选;用于生成 If-Then 预案 |
---
验证(开发者 / 维护者)
在 skill 目录下执行:
node scripts/validate.mjs templates/juanwang-skill.html
node scripts/smoke-test.mjs templates/juanwang-skill.html---
仓库结构
SKILL.md— 路由与核心原则workflows/— 生成、调整、复盘、校验流程references/— 产品原则、数据 schema、规划方法论、安全语气、运行时契约scripts/—validate.mjs(静态不变量)、smoke-test.mjs(运行时逻辑测试)templates/juanwang-skill.html— 生成时唯一使用的 HTML 模板
请勿将 legacy PWA 文件、二维码、预览图、zip 包或 .DS_Store 纳入发布包。
---
Author release checklist
发布新版本前确认:
- YAML frontmatter 中
name为小写且与目录名一致;description为第三人称并含中文触发词 SKILL.md使用 XML 结构,正文无 markdown 标题,且少于 500 行- 所有 workflow、reference、template、script 文件存在
- 上述两条 validate / smoke-test 命令均通过
- 发布包仅包含上文「仓库结构」所列资源
---
许可证
MIT
<app_data_schema> APP_DATA must be valid JSON injected into the HTML template as:
/*__APP_DATA_START__*/
const APP_DATA = { ... };
/*__APP_DATA_END__*/
const STATE_KEY = computeStateKey(); // juanwang_state_v1_{startDate}_{goalHash}Required top-level fields:
{
"config": {
"output": {
"fileNameBase": "juanwang-小卷-英语运动-2026-07-01"
},
"user": {
"name": "小卷",
"duration": 30,
"mainGoal": {
"category": "卷健康 | 卷学习 | 卷实习 | 卷兴趣 | 卷玩",
"subGoal": "具体子目标",
"specific": "用户可读的具体目标"
},
"sideGoals": [
{"category": "卷学习", "subGoal": "英语四级"}
],
"startDate": "YYYY-MM-DD",
"identityAnchor": "optional gentle identity sentence",
"availableDailyMinutes": 120
}
},
"phases": [],
"dailyTasks": [],
"checkins": {},
"reviews": {}
}config.output.fileNameBase is optional for backward compatibility and must not include .html. For new plans, set it from the safe filename base used for delivery. Prefer user-requested filenames when provided; otherwise use juanwang-{nickname}-{goal-keyword}-{startDate}. If omitted in older files, runtime falls back to config.user.name, config.user.mainGoal, and config.user.startDate.
Required phase fields:
id: number, stable phase id starting at 1title: Chinese phase nameicon: current icon id such assprout,leaf,tree,rocket,trophyrange: e.g.Day 1-7theme: phase themetip: warm phase-level prompt
Optional config.user methodology fields such as identityAnchor, availableDailyMinutes, realisticDailyMinutes, or riskScenarios may be included when useful. config.user.name should use the user's preferred nickname/name when provided, otherwise 小卷; nickname is not a hard gate. The runtime must not depend on methodology fields.
Required dailyTasks[] fields:
day: 1-based day numberdate: concreteYYYY-MM-DD, computed fromstartDatephaseId: matching phase idmainTask: 25-60 minute concrete next physical action with a timeboxstarterTask: 2-minute starter actionelasticTask: optional ~10-25 minute action, explicitly says允许放弃ifThenPlan: high-risk scenario fallback bound to a specific triggerestimatedMinutes: numbertip: daily warm prompt; may include starter and If-Then summaryhealthTarget:{steps, sleepTarget, waterCups, exerciseMinutes}
Initialization rules:
- New plan:
checkins: {}andreviews: {} - UI selected day defaults to Day 1
- Do not inject sample checkins or fake reviews
localStorage key is computed at runtime as juanwang_state_v1_{startDate}_{goalHash} so multiple plans do not overwrite each other.
localStorage shape:
{
"checkins": {
"YYYY-MM-DD": {
"starterDone": true,
"mainDone": true,
"elasticDone": false,
"mood": "😄",
"note": "...",
"health": {
"steps": 8000,
"sleepTime": "23:30",
"waterCups": 8,
"exerciseMinutes": 20
}
}
},
"selectedDay": 1,
"reviews": {
"1": {
"text": "<p>...</p>",
"generatedAt": "ISO timestamp",
"version": "vYYYYMMDD-HHMM"
}
}
}Compatibility rules:
- Preserve existing
checkinsandreviewswhen modifying a synced HTML. - Use
iconas the phase display field; do not reintroduce legacyemojias the primary field. - Phase end day is
max(dailyTasks[].day where phaseId === phase.id), never a hardcoded formula.
Injection safety:
- The initial
APP_DATAblock written at generation time is NOT auto-escaped (only the in-app "sync to HTML" path escapes via.replace(/<\//g, '<\\/')). When you writeAPP_DATAinto the template, escape every</in string values (goals, notes, tips, If-Then text) to<\/. An unescaped</script>anywhere inAPP_DATAterminates the script block and breaks the page.scripts/validate.mjsenforces<script>/</script>balance as a backstop.
</app_data_schema>
<failure_mechanisms> Core belief: 计划失败不是意志力失败,而是系统设计失败。The skill must lower execution friction instead of asking the user to become more disciplined.
P0 product response stack:
| Failure mechanism | Common holiday symptom | Skill response | Generation rule |
|---|---|---|---|
| Intention-behavior gap | 放假前很想改变,第二天情境一变就不动了 | If-Then execution intention | Every day has a concrete ifThenPlan bound to one user risk scenario. |
| Emotion avoidance / procrastination | 一想到任务就焦虑、无聊,于是刷手机或打游戏 | 2-minute starter + optional elastic task | Every day has a starterTask that can be completed in 2 minutes and is easier than the main task. |
| Goal-action mismatch | 目标宏大,但今天不知道下一步做什么 | Next physical action | mainTask must include verb + object + quantity + timebox; avoid abstract tasks like “提升英语”. |
| Planning fallacy | 高估时间和状态,Day 3-7 崩盘 | 70%-80% load + 20%-30% buffer | Use only 70%-80% of the user’s self-reported available time; leave explicit buffer and recovery. |
| Motivation death valley | Day 7-15 断签后自责并摆烂 | 7-10 day review + load reduction | Phase review defaults to shrinking future tasks before adding catch-up work. |
| Completion-rate shame | 看到 40% 完成率后觉得自己失败 | Small-win feedback | Reviews must identify at least one concrete progress signal before giving advice. |
If-Then quality bar:
- Bad: “如果状态不好,就调整心态继续努力。”
- Good: “如果今晚 22:30 以后才想起雅思任务,就不补完整任务,只背 5 个单词并录 30 秒口语。”
- Good: “如果已经打了 2 局王者还想继续,就先把手机放到客厅充电,做完 2 分钟启动版后再决定是否继续。”
2-minute starter quality bar:
- It is not half of the main task; it is the body-level switch that makes starting painless.
- IELTS reading: 打开题目,只圈标题和 3 个关键词。
- IELTS speaking: 打开录音,读 1 个题目并说 30 秒。
- Guitar: 把吉他拿出来,按 2 次 C 和弦。
- Health: 穿好鞋,走到楼下。
Review quality bar: Phase reviews answer these questions using real checkins only: 1. 哪些任务用户做得动? 2. 哪些任务阻力最大? 3. 崩盘场景是否被 If-Then 接住? 4. 下一阶段要减少什么? 5. 下一阶段保留哪个最小动作?
If completion is low, the default recommendation is smaller tasks, clearer cues, fewer side goals, or more buffer. Do not recommend compensatory overwork. </failure_mechanisms>
<html_runtime_contract> The HTML template must remain a self-contained browser file.
Must not contain:
<link rel="manifest" href="manifest.json">navigator.serviceWorker.register(...)beforeinstallpromptinstall banner logic- hardcoded Day 8 default selection
- fake
checkinsin new-planAPP_DATA - hardcoded phase review end days such as Day 7/15/23/30 in runtime logic
Must contain:
/*__APP_DATA_START__*/…/*__APP_DATA_END__*/boundary markers aroundconst APP_DATA = ...;const STATE_KEY = computeStateKey();with per-plan fingerprint (startDate+ goal hash)- data exchange UI for export JSON, sync to HTML, and import JSON (sync must work on
file://without fetch) getFileNameBase()that usesconfig.output.fileNameBasefirst and falls back to nickname, goal keyword, and start date; JSON export and sync-to-HTML download must use this same base- check-in UI renders
starterTask(storesstarterDone) andifThenPlan - mood buttons for exactly
😄,🙁,😝,😭,😡 getTodayDay()auto-aligns selected day; phase review readiness uses progress day, not manual selected day- dynamic calculations for streak (backwards from today; starter counts), completion rates, dashboard stats, calendar title, phase countdown, and phase review readiness
Validation scenarios:
- A new 60-day plan starting
2026-07-01yields Day 1 =2026-07-01and Day 60 =2026-08-29 - Initial dashboard displays 0 streak, 0 check-in days, 0.0% completion, and “暂无数据” for mood summary
- Saving Day 1 main task changes streak to 1 and completion rate to 100.0% when it is the only checkin
- Setting selectedDay to the last day of a phase makes exactly that phase review ready
</html_runtime_contract>
<planning_methodology> Core planning thesis: 用户不是缺计划,用户缺的是在低状态下依然能启动的系统。Every methodology below must become a generation rule, not a theory label.
Input collection:
- Duration: 30 / 45 / 60 / 90 days or a user-provided number
- Start date: required
YYYY-MM-DD; ask if missing and do not default to today/tomorrow - Main direction: 卷玩 / 卷学习 / 卷实习 / 卷兴趣 / 卷健康
- Specific main goal and measurable outcome
- Optional side goals: maximum 2; side goals must not crowd out the main goal
- Optional available daily time and current baseline; if provided, schedule only 70%-80% of it
- Optional identity anchor: “这个假期结束时,你希望自己变成什么样的人?” If absent, infer one gentle sentence from the goals
- Optional high-risk failure scenarios: e.g. 熬夜, 手机刷太久, 王者太久, 下雨, 家里临时安排, 情绪低落
Five-layer methodology stack and generation rules:
| Layer | Method | Skill generation rule |
|---|---|---|
| Why | HARD Goals / identity anchor | Add a warm identity sentence in config.user or plan copy: “我在练习成为……的人”. Do not use shame or comparison. |
| What | OKR | Translate the main goal into one objective and 2-4 measurable key results for phase themes and review criteria. KRs measure outcomes, not just tasks. |
| How | GTD + timeboxing + Pomodoro + If-Then | Convert every goal into the next physical action; give a fixed timebox; include scenario-specific If-Then. |
| Loop | AAR / PDCA | Split phases so the user can review every 7-10 days; reviews adjust future workload based on real data. |
| Stay | Progress principle + small wins | Tips and reviews highlight visible progress, starter-task continuity, better rhythm, and health improvements before gaps. |
Phase splitting:
- 30 days: default 4 phases: Day 1-7, Day 8-15, Day 16-23, Day 24-30
- 31-60 days: 4-6 phases, usually 7-10 day blocks where possible; avoid one long 15+ day phase without review
- >60 days: 6-9 phases; may include a
trophyclosing phase - First phase is deliberately lighter for rhythm building
- Every 7-10 days, include a low-load review/recovery day or make the phase ending day lighter
- For all durations, compute review readiness from actual task phase membership
Daily task design:
- Main task: concrete action + quantity + timebox, usually 25-60 minutes. Prefer “做 1 个 IELTS Listening Section 1 并错题复盘 10 分钟” over “提升听力”.
- Starter task: a 2-minute action that makes starting easy. It must be easier than the main task and executable even at low motivation.
- Elastic task: optional 10-25 minutes, explicitly marked as
允许放弃; side goals usually live here. - If-Then plan: scenario-specific fallback for the user's risk pattern; bind it to one real trigger and one tiny replacement action.
- Health target: conservative progression for steps, water, exercise, and sleep; never trade sleep for extra tasks.
- Tip: encourage the starter and small win, not “加油坚持”.
Workload and buffer rules:
- Default to realistic planning, not the user’s optimistic estimate.
- If user reports available time, schedule only 70%-80% and leave 20%-30% as buffer. Example: 120 minutes available → 60-75 minutes main task + 15-25 minutes optional elastic task + remaining buffer.
- If no available time is provided, keep the first week light: one main timebox plus one optional task only.
- Make the plan barely challenging: current ability × 1.0-1.1, not “爆改”.
- Main goal owns the main task; side goals should usually appear as elastic or low-frequency tasks.
- If the user is behind or health is down, shrink future tasks before adding catch-up work.
- Never design punitive make-up days or compensation marathons.
If-Then generation rules:
- Use the user’s own collapse scenarios first.
- If no scenarios are provided, include common holiday risks: late-night scrolling, long gaming sessions, family interruptions, low mood, and weather.
- The fallback action should preserve continuity, not complete the full task.
- Good format: “如果 [specific trigger], 那么 [tiny physical action],并允许今天只算启动版。”
2-minute starter examples:
- IELTS reading: 打开题目,只圈标题和 3 个关键词。
- IELTS listening: 播放音频前 30 秒,写下听到的 3 个词。
- IELTS speaking: 打开录音,读 1 个题目并说 30 秒。
- Guitar: 把吉他拿出来,按 2 次 C 和弦。
- Health: 穿好鞋,走到楼下;或倒一杯水并站立拉伸 1 分钟。
Review method: Use AAR/PDCA in warm prose, but prioritize adjustment over summary: 1. Original intent for the phase 2. What actually happened, using specific numbers from checkins 3. At least one concrete progress signal or small win 4. Which task/trigger created the most friction 5. Whether If-Then and starter tasks helped the user restart 6. One or two lower-friction next actions for the next phase
Small-win feedback rules:
- Do not define the user by completion rate.
- Mention “没有完全断掉”, “启动版也算保留节奏”, “作息提前了”, “某个时间锚点有效” when supported by data.
- If there is no data, say “暂无数据,先从 Day 1 的 2 分钟启动版开始” rather than inventing progress.
</planning_methodology>
<product_principles> Product name: 卷王·假期成长规划
Value proposition: 不靠意志力,靠可执行的规划。 The skill turns vague holiday goals into a practical daily dashboard with check-ins and stage reviews.
Primary users:
- High-school graduates, incoming high-school students, and incoming college students
- College students or young adults who repeatedly over-plan and under-execute
- Parents may configure plans, but the plan should serve the learner's autonomy
Core deliverable:
- One
.htmlfile with a personalized safe filename. Default format:juanwang-{nickname}-{goal-keyword}-{startDate}.html; if the user explicitly requests a filename, sanitize it and add.htmlif needed. - The file contains CSS, JS,
APP_DATA, check-in UI, dashboard, and data exchange UI - Users can double-click it on desktop or send it to a phone and open it with a browser
- Do not generate zip files, preview screenshots, QR codes,
manifest.json, orsw.js
Main tabs in the HTML: 1. 规划: phase roadmap, daily task list, date labels, task click-to-check-in 2. 打卡: day switcher, main/elastic task checks, health data, mood, note 3. 仪表盘: real streak, completion rates, check-in days, health stats, calendar, recent trend 4. 复盘: medals, data summary, phase-level review cards </product_principles>
<safety_and_tone> Voice:
- Chinese-first, warm, direct, lightly self-aware
- Use second person “你”
- Prefer “先开始”, “允许慢一点”, “每一步都算数”, “启动版也算保留节奏” over pressure language
Do not use:
- “弯道超车”, “卷死同学”, “同龄人正在抛弃你”
- “变态计划”, “邪修逆袭”, “爆改人生”
- “震惊”, “绝绝子”, “刷爆”
- Public commitment or shame as motivation
- Completion-rate shaming in reviews
Health boundaries:
- Do not create extreme weight-loss targets such as “30 天瘦 10 斤”
- Sleep, hydration, and recovery outrank extra tasks
- For minors, avoid body-anxiety framing; use health, energy, routine, and strength language
Phase review tone:
- Start with what the user completed or learned
- Use concrete numbers, not moral judgments
- Explain gaps as rhythm/environment/task-size problems
- Suggest reducing friction or lowering load before asking for more discipline
- Always identify at least one specific small win when data supports it; if data does not support a win, say “暂无数据,先从 2 分钟启动版开始”
</safety_and_tone>
#!/usr/bin/env node
/**
* Logic-layer smoke tests. Extracts the REAL function source from the template
* HTML and runs it in a vm with a mocked clock, so tests fail if template code breaks.
*
* Test data is SYNTHETIC (fixed 7-day plan starting 2026-06-09) and independent
* of the template's embedded sample APP_DATA — the sample plan can be replaced
* freely without breaking these tests.
*/
import { readFileSync } from 'node:fs';
import { createContext, runInContext } from 'node:vm';
import { resolve } from 'node:path';
const file = process.argv[2] || resolve('templates/juanwang-skill.html');
const html = readFileSync(file, 'utf8');
const errors = [];
function fail(msg) {
errors.push(msg);
}
// 仅校验边界块存在;测试数据用下方合成计划,不依赖模板内嵌示例
const appMatch = html.match(/\/\*__APP_DATA_START__\*\/[\s\S]*?const APP_DATA = (\{[\s\S]*?\});[\s\S]*?\/\*__APP_DATA_END__\*\//);
if (!appMatch) {
console.error('❌ 找不到 APP_DATA 边界块');
process.exit(1);
}
try {
JSON.parse(appMatch[1]);
} catch (err) {
console.error('❌ 模板内嵌 APP_DATA 不是合法 JSON: ' + err.message);
process.exit(1);
}
function addDays(iso, offset) {
const [y, m, d] = iso.split('-').map(Number);
const dt = new Date(y, m - 1, d);
dt.setDate(dt.getDate() + offset);
return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')}`;
}
const START = '2026-06-09';
const APP_DATA = {
config: {
output: { fileNameBase: 'juanwang-测试-测试目标-2026-06-09' },
user: { name: '测试', duration: 7, mainGoal: { subGoal: '测试目标', specific: '测试目标' }, startDate: START }
},
phases: [{ id: 1, title: '适应期', icon: 'sprout', range: 'Day 1-7', theme: '测试', tip: '测试' }],
dailyTasks: Array.from({ length: 7 }, function (_, i) {
return {
day: i + 1,
date: addDays(START, i),
phaseId: 1,
mainTask: '主线任务',
starterTask: '启动版',
elasticTask: '弹性任务(允许放弃)',
ifThenPlan: '如果状态差,就只做启动版。',
estimatedMinutes: 30,
tip: '提示',
healthTarget: { steps: 6000, sleepTarget: '23:30 前入睡', waterCups: 8, exerciseMinutes: 20 }
};
}),
checkins: {},
reviews: {}
};
// 从模板中提取真实函数源码(函数体以顶格 "}" 结束)
const FUNC_NAMES = [
'parseLocalDate', 'getPlanDays', 'getTodayDay', 'getLatestCheckinDay',
'getProgressDay', 'getSelectedDay', 'getTaskByDay', 'getPhaseEndDay',
'sanitizeFileNameBase', 'getDisplayName', 'getGoalFileKeyword', 'getFileNameBase',
'isCheckinActive', 'mergeReviews', 'initSelectedDay',
'calcCurrentStreak', 'calcLongestStreak', 'countConsecutiveMissedBefore'
];
const extracted = [];
for (const name of FUNC_NAMES) {
const re = new RegExp('function ' + name + '\\([^)]*\\) \\{[\\s\\S]*?\\n\\}', 'm');
const m = html.match(re);
if (!m) {
fail(`模板缺少函数: ${name}`);
continue;
}
extracted.push(m[0]);
}
if (errors.length) {
console.error('❌ 冒烟测试失败');
errors.forEach((e) => console.error(' -', e));
process.exit(1);
}
function makeContext(fixedNowISO, checkins, appData = APP_DATA) {
const ctx = createContext({});
const setup = `
const __FIXED_NOW__ = ${JSON.stringify(fixedNowISO)};
const RealDate = Date;
Date = class extends RealDate {
constructor(...args) {
if (args.length === 0) { super(__FIXED_NOW__); } else { super(...args); }
}
};
const APP_DATA = ${JSON.stringify(appData)};
let state = { checkins: ${JSON.stringify(checkins)}, selectedDay: 1, reviews: {} };
${extracted.join('\n')}
`;
runInContext(setup, ctx);
return ctx;
}
// Test 1: 开始当天 = Day 1
{
const ctx = makeContext('2026-06-09T10:00:00', {});
const d = runInContext('getTodayDay()', ctx);
if (d !== 1) fail(`startDate 当天应为 Day 1,实际 Day ${d}`);
}
// Test 2: 第 4 天日期映射
{
const ctx = makeContext('2026-06-12T10:00:00', {});
const d = runInContext('getTodayDay()', ctx);
if (d !== 4) fail(`2026-06-12 应为 Day 4,实际 Day ${d}`);
}
// Test 3: 漏打 Day1,打 Day2-3 → 连签 2(从今天往回数)
{
const ctx = makeContext('2026-06-11T20:00:00', {
'2026-06-10': { starterDone: true },
'2026-06-11': { mainDone: true }
});
const s = runInContext('calcCurrentStreak()', ctx);
if (s !== 2) fail(`漏 Day1 后 Day2-3 连签应为 2,实际 ${s}`);
}
// Test 4: 仅 starterDone 也算连签
{
const ctx = makeContext('2026-06-09T20:00:00', { '2026-06-09': { starterDone: true } });
const s = runInContext('calcCurrentStreak()', ctx);
if (s !== 1) fail(`仅 starterDone 连签应为 1,实际 ${s}`);
}
// Test 5: mergeReviews — localStorage 空 reviews 不覆盖 Agent 写回
{
const ctx = makeContext('2026-06-09T10:00:00', {});
const merged = runInContext(
`mergeReviews({ '1': { text: '<p>agent</p>', generatedAt: '2026-06-15T12:00:00' } }, { '1': null })`,
ctx
);
if (!merged['1'] || merged['1'].text !== '<p>agent</p>') fail('Agent 写回的 reviews 被空 localStorage 覆盖');
}
// Test 6: mergeReviews — 较新的本地复盘优先
{
const ctx = makeContext('2026-06-09T10:00:00', {});
const merged = runInContext(
`mergeReviews({ '1': { text: '<p>old</p>', generatedAt: '2026-06-10T12:00:00' } }, { '1': { text: '<p>newer</p>', generatedAt: '2026-06-16T12:00:00' } })`,
ctx
);
if (merged['1'].text !== '<p>newer</p>') fail('mergeReviews 未按 generatedAt 取较新者');
}
// Test 7: 阶段结束日动态计算
{
const ctx = makeContext('2026-06-09T10:00:00', {});
const endDay = runInContext('getPhaseEndDay(1)', ctx);
if (endDay !== APP_DATA.dailyTasks.length) fail(`阶段 1 结束日应为 ${APP_DATA.dailyTasks.length},实际 ${endDay}`);
}
// Test 8: progress day 由今天与最新打卡共同驱动(手动切日不影响)
{
const ctx = makeContext('2026-06-10T10:00:00', { '2026-06-12': { mainDone: true } });
const p = runInContext('state.selectedDay = 7; getProgressDay()', ctx);
if (p !== 4) fail(`progressDay 应为 max(today=2, checkin=4)=4,实际 ${p}`);
}
// Test 9: 过期 selectedDay 自动跳到今天
{
const ctx = makeContext('2026-06-12T10:00:00', {});
const d = runInContext('initSelectedDay(1)', ctx);
if (d !== 4) fail(`stored Day 1 < today Day 4 应跳到 4,实际 ${d}`);
}
// Test 10: 连续 3 天未打卡检测
{
const ctx = makeContext('2026-06-12T10:00:00', {});
const missed = runInContext('countConsecutiveMissedBefore(4)', ctx);
if (missed !== 3) fail(`Day 4 前应有 3 天未打卡,实际 ${missed}`);
}
// Test 11: config.output.fileNameBase 优先,且去掉危险字符和重复后缀
{
const appData = structuredClone(APP_DATA);
appData.config.output.fileNameBase = '暑假/英语:计划.html';
const ctx = makeContext('2026-06-09T10:00:00', {}, appData);
const name = runInContext('getFileNameBase()', ctx);
if (name !== '暑假-英语-计划') fail(`fileNameBase 安全化失败,实际 ${name}`);
}
// Test 12: 旧 HTML 没有 config.output 时自动 fallback
{
const appData = structuredClone(APP_DATA);
delete appData.config.output;
const ctx = makeContext('2026-06-09T10:00:00', {}, appData);
const name = runInContext('getFileNameBase()', ctx);
if (name !== 'juanwang-测试-测试目标-2026-06-09') fail(`旧数据 fallback 文件名错误,实际 ${name}`);
}
if (errors.length) {
console.error('❌ 冒烟测试失败');
errors.forEach((e) => console.error(' -', e));
process.exit(1);
}
console.log('✅ 冒烟测试通过(12 项,运行模板真实函数源码 + 合成测试数据)');
#!/usr/bin/env node
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
const file = process.argv[2] || resolve('templates/juanwang-skill.html');
const html = readFileSync(file, 'utf8');
const errors = [];
const warnings = [];
function fail(msg) {
errors.push(msg);
}
function warn(msg) {
warnings.push(msg);
}
function extractAppData(source) {
const markerMatch = source.match(/\/\*__APP_DATA_START__\*\/[\s\S]*?const APP_DATA = (\{[\s\S]*?\});[\s\S]*?\/\*__APP_DATA_END__\*\//);
if (markerMatch) return JSON.parse(markerMatch[1]);
const legacy = source.match(/const APP_DATA = (\{[\s\S]*?\});\s*(?:const STATE_KEY|\/\*__APP_DATA_END__\*\/)/);
if (legacy) return JSON.parse(legacy[1]);
throw new Error('无法解析 APP_DATA');
}
let data;
try {
data = extractAppData(html);
} catch (err) {
fail(`APP_DATA 解析失败: ${err.message}`);
report();
process.exit(1);
}
// 与 references/safety-and-tone.md 的禁用语保持同步
const banned = [
'弯道超车', '卷死同学', '同龄人正在抛弃你',
'变态计划', '邪修', '爆改',
'震惊', '绝绝子', '刷爆',
'manifest.json', 'serviceWorker', 'beforeinstallprompt'
];
for (const word of banned) {
if (html.includes(word)) fail(`包含禁词/禁依赖: ${word}`);
}
const requiredRuntime = [
'function getTodayDay',
'function computeStateKey',
'function sanitizeFileNameBase',
'function getFileNameBase',
'starterTask',
'ifThenPlan',
'buildSyncedHtml',
'mergeReviews',
'checkin-nudge',
'phase-countdown',
'/*__APP_DATA_START__*/',
'/*__APP_DATA_END__*/'
];
for (const token of requiredRuntime) {
if (!html.includes(token)) fail(`运行时缺少: ${token}`);
}
const moods = ['😄', '🙁', '😝', '😭', '😡'];
for (const mood of moods) {
if (!html.includes(`data-mood="${mood}"`)) fail(`缺少心情按钮: ${mood}`);
}
const moodMatches = html.match(/data-mood="([^"]+)"/g) || [];
if (moodMatches.length !== 5) fail(`心情按钮数量应为 5,实际 ${moodMatches.length}`);
// HTML 解析层:<script> 与 </script> 必须成对。任何字面 </script>(即便在 JS 注释或字符串里)
// 都会被浏览器解析器当作脚本结束,使后续 JS 暴露成页面文本、页面崩坏。
// 正则提取式的 smoke-test 跑的是 node、不经过 HTML 解析器,发现不了这一点,故在此把守。
const openScripts = (html.match(/<script\b/gi) || []).length;
const closeScripts = (html.match(/<\/script>/gi) || []).length;
if (openScripts !== closeScripts) {
fail(`<script> 与 </script> 数量不匹配(${openScripts} vs ${closeScripts}):可能有字面 </script> 提前闭合脚本块,会导致页面渲染崩坏`);
}
if (!data.config?.user?.startDate) fail('缺少 config.user.startDate');
if (!Array.isArray(data.phases) || !data.phases.length) fail('phases 为空');
if (!Array.isArray(data.dailyTasks) || !data.dailyTasks.length) fail('dailyTasks 为空');
const startDate = data.config.user.startDate;
const duration = data.config.user.duration;
if (data.config.output?.fileNameBase != null) {
validateFileNameBase(data.config.output.fileNameBase);
}
if (data.dailyTasks.length !== duration) {
warn(`dailyTasks 数量 (${data.dailyTasks.length}) 与 duration (${duration}) 不一致`);
}
for (let i = 0; i < data.dailyTasks.length; i++) {
const t = data.dailyTasks[i];
const expectedDay = i + 1;
if (t.day !== expectedDay) fail(`Day ${expectedDay} 的 day 字段错误: ${t.day}`);
const expectedDate = addDays(startDate, i);
if (t.date !== expectedDate) fail(`Day ${expectedDay} 日期应为 ${expectedDate},实际 ${t.date}`);
for (const field of ['mainTask', 'starterTask', 'elasticTask', 'ifThenPlan', 'estimatedMinutes', 'tip', 'healthTarget']) {
if (t[field] == null || t[field] === '') fail(`Day ${expectedDay} 缺少 ${field}`);
}
if (!String(t.elasticTask).includes('允许放弃')) fail(`Day ${expectedDay} elasticTask 未标注允许放弃`);
if (!String(t.ifThenPlan).includes('如果')) fail(`Day ${expectedDay} ifThenPlan 缺少场景绑定`);
}
// 自动区分新计划与同步文件:checkins 非空视为同步文件,校验形状而非报错
const checkinDates = Object.keys(data.checkins || {});
const isSyncedFile = checkinDates.length > 0;
if (isSyncedFile) {
const validDates = new Set(data.dailyTasks.map((t) => t.date));
const validMoods = new Set(['😄', '🙁', '😝', '😭', '😡', '']);
for (const date of checkinDates) {
const c = data.checkins[date];
if (!validDates.has(date)) fail(`checkin 日期 ${date} 不在计划范围内`);
if (c.mood != null && !validMoods.has(c.mood)) fail(`checkin ${date} 的 mood 非法: ${c.mood}`);
for (const flag of ['mainDone', 'elasticDone', 'starterDone']) {
if (c[flag] != null && typeof c[flag] !== 'boolean') fail(`checkin ${date} 的 ${flag} 应为布尔值`);
}
}
console.log(` ℹ 同步文件模式:${checkinDates.length} 天打卡数据`);
}
for (const [id, r] of Object.entries(data.reviews || {})) {
if (r == null) continue;
if (!r.text || !r.generatedAt) fail(`review ${id} 缺少 text 或 generatedAt`);
if (!isSyncedFile) fail(`新计划不应包含 review ${id}`);
}
if (!isSyncedFile && checkinDates.length) fail('新计划模板 checkins 应为空');
for (const phase of data.phases) {
const days = data.dailyTasks.filter((t) => t.phaseId === phase.id).map((t) => t.day);
if (!days.length) fail(`阶段 ${phase.id} 没有对应 dailyTasks`);
}
function addDays(iso, offset) {
const [y, m, d] = iso.split('-').map(Number);
const dt = new Date(y, m - 1, d);
dt.setDate(dt.getDate() + offset);
return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}-${String(dt.getDate()).padStart(2, '0')}`;
}
function validateFileNameBase(value) {
if (typeof value !== 'string' || !value.trim()) {
fail('config.output.fileNameBase 应为非空字符串');
return;
}
if (/\.html$/i.test(value)) fail('config.output.fileNameBase 不应包含 .html 后缀');
if (/[\/\\:*?"<>|]/.test(value)) fail(`config.output.fileNameBase 含危险文件名字符: ${value}`);
if (value.length > 80) warn(`config.output.fileNameBase 超过 80 字符: ${value.length}`);
}
function report() {
if (errors.length) {
console.error(`❌ 验证失败 (${file})`);
errors.forEach((e) => console.error(` - ${e}`));
} else {
console.log(`✅ 验证通过 (${file})`);
console.log(` - ${data.dailyTasks.length} 天任务,${data.phases.length} 个阶段`);
}
if (warnings.length) warnings.forEach((w) => console.warn(` ⚠ ${w}`));
}
report();
process.exit(errors.length ? 1 : 0);
<required_reading> Read these before adjusting a plan: 1. references/app-data-schema.md 2. references/failure-mechanisms.md 3. references/planning-methodology.md 4. references/safety-and-tone.md 5. references/html-runtime-contract.md </required_reading>
<process> 1. Parse the synced HTML: extract APP_DATA, checkins, selected day, and reviews. If localStorage data is missing, ask the user to sync to HTML first. 2. Identify history boundary: treat days with checkins as history. Do not alter those daily tasks unless the user explicitly asks for a historical correction. 3. Diagnose load and friction: use completion, health, notes, and the user's stated issue to identify failed mechanisms: intention-behavior gap, emotion avoidance, goal-action mismatch, planning fallacy, or completion shame. 4. Choose the lowest-friction fix: prefer smaller main tasks, clearer 2-minute starters, stronger scenario-bound If-Then plans, more buffer, fewer side goals, or lighter review days before changing the goal itself. 5. Adjust only future tasks: preserve day numbers, dates, phase ids where possible, checkins, and reviews. Keep the main goal prioritized. 6. Write back HTML: replace APP_DATA with the adjusted future tasks and preserved history. 7. Validate: confirm no historical checkins/reviews were dropped and the HTML still passes deliverable validation. </process>
<success_criteria>
- Past completed/check-in days remain unchanged.
- Future tasks are more executable, lower-friction, and not punitive.
- Adjustments preserve or improve daily 2-minute starters, If-Then plans, timeboxes, and buffer.
- Health downturns produce lower load, not higher pressure.
- The adjusted HTML remains a single file with preserved data.
</success_criteria>
<required_reading> Read these before writing a phase review: 1. references/app-data-schema.md 2. references/failure-mechanisms.md 3. references/planning-methodology.md 4. references/safety-and-tone.md 5. references/html-runtime-contract.md </required_reading>
<process> 1. Require synced data: if the user provides an original HTML without APP_DATA.checkins, ask them to open the file and use 📦 数据交换 → 同步到 HTML first. 2. Parse APP_DATA: extract JSON with /const APP_DATA = (\{[\s\S]*?\});/ (between /*__APP_DATA_START__*/ and /*__APP_DATA_END__*/ when present) and parse it. Do not invent missing checkins. 3. Find progress: use the latest checkin date when checkins exist; otherwise use selectedDay if present, or Day 1. 4. Find reviewable phases: for each phase, compute end day from dailyTasks; skip phases with existing reviews[phaseId] unless the user explicitly asks to rewrite. 5. Analyze friction and small wins: compute completion, starter/continuity signals when available, health trends, mood/notes, and repeated collapse scenarios. If no checkins exist, state that there is no data and do not generate a fake review. 6. Write review text: 200-300 Chinese characters per phase, 4-5 <p> paragraphs (literal <p>...</p> HTML tags, not escaped entities), with AAR/PDCA content and specific checkin numbers. Each review must name at least one concrete progress signal, one friction source, whether If-Then/starter tasks helped, and 1-2 lower-friction next actions. If completion is low, recommend smaller tasks or fewer side goals before extra discipline. 7. Write back HTML: merge new reviews into APP_DATA.reviews, preserve checkins and existing reviews, update generated metadata if present, and output a new timestamped HTML. </process>
<success_criteria>
- Review uses only parsed user data.
- Existing checkins and reviews are preserved.
- No phase is reviewed before its dynamically computed end day.
- Review tone is supportive and non-shaming.
- Review reinforces small wins and gives load-reduction suggestions when friction is high.
</success_criteria>
<required_reading> Read these before generating a new plan: 1. references/product-principles.md 2. references/app-data-schema.md 3. references/failure-mechanisms.md 4. references/planning-methodology.md 5. references/safety-and-tone.md 6. references/html-runtime-contract.md </required_reading>
<process> 1. Collect required inputs: duration, start date, main direction, specific main goal, up to 2 side goals, optional nickname/name, optional desired filename, optional available daily time/baseline, optional identity anchor, and optional high-risk scenarios. If start date is missing, ask for it and stop. Do not block generation for missing optional fields; infer gentle defaults. If nickname is missing, use 小卷. 2. Create the low-friction strategy: convert the user’s goals and collapse scenarios into a P0 execution system: identity anchor, realistic workload, 2-minute starters, If-Then fallbacks, timeboxes, buffer, and review cadence. 3. Create phases: split the duration into phase ranges, choose phase icons, themes, and warm tips. Prefer 7-10 day review loops and make the first phase/light review days deliberately easier. Keep phase ids stable and sequential. 4. Create daily tasks: for every day, generate date, phaseId, mainTask, starterTask, elasticTask, ifThenPlan, estimatedMinutes, tip, and healthTarget. Every mainTask must be a next physical action with a timebox; every starterTask must be completable in 2 minutes; every ifThenPlan must bind to a real or default risk scenario. 5. Apply workload buffer: if the user reports available time, use only 70%-80% of it; otherwise keep the first week to one main timebox plus one optional elastic task. Mark elastic tasks as optional/允许放弃. 6. Build APP_DATA: include config, phases, dailyTasks, checkins: {}, and reviews: {}. Do not include sample progress. Store the nickname in config.user.name (小卷 fallback). Store config.output.fileNameBase as the sanitized filename base without .html: use the user's desired filename if provided, otherwise juanwang-{nickname}-{goal-keyword}-{startDate}. Preserve compatible fields; optional methodology notes may live under config.user or copy text, not required schema fields. 7. Create the HTML: copy templates/juanwang-skill.html, replace only the content between /*__APP_DATA_START__*/ and /*__APP_DATA_END__*/ (keep the markers), and write exactly one personalized .html file using config.output.fileNameBase + ".html". Filename safety rules: remove path separators and dangerous characters (/ \ : * ? " < > |), collapse spaces/punctuation into -, keep Chinese/English/numbers/-/_, trim edge -, cap near 80 characters, and avoid duplicate .html. When injecting APP_DATA, escape every </ inside string values as <\/ (a goal or note containing </script> MUST be written as <\/script>) — the initial block is not auto-escaped, and an unescaped </script> will close the script block early and break the whole page. 8. Validate (mandatory gate, do not skip): run node scripts/validate.mjs <path> and node scripts/smoke-test.mjs <path>; both must pass before delivery. If validate reports a <script>/</script> mismatch, locate the literal </ in your APP_DATA, escape it to <\/, and re-run. Then do the browser render check in workflows/validate-deliverable.md: open the file and confirm no raw source text leaks onto the page. 9. Deliver: provide the absolute path to the generated personalized .html file and tell the user they can double-click it or send it to a phone and open it in a browser. </process>
<success_criteria>
- User provided a concrete start date.
- Generated HTML contains exactly one initialized plan and no fake checkins.
- The plan includes scenario-bound If-Then plans, 2-minute starters, realistic timeboxes, buffer, and 7-10 day review loops.
- Day 1 and final day dates match duration and start date.
- Exactly one personalized
.htmlfile is delivered.
</success_criteria>
<required_reading> Read these before validation: 1. references/app-data-schema.md 2. references/failure-mechanisms.md 3. references/planning-methodology.md 4. references/html-runtime-contract.md </required_reading>
<process> 1. Run validation scripts: node scripts/validate.mjs <html> (static invariants) and node scripts/smoke-test.mjs <html> (runs real template logic: today mapping, streak, review merge, progress day). Run both on the template and on any generated personalized .html file. Fix all reported errors before delivery. 2. Check package shape: market package should have SKILL.md, workflows/, references/, scripts/ (validate.mjs + smoke-test.mjs), and templates/juanwang-skill.html. Do not include zip, preview image, QR code, manifest, or service worker as delivery assets. 3. Check SKILL.md: frontmatter name is lowercase and matches the skill folder name; description says when to use it; body uses XML tags and no markdown headings; routed files exist; SKILL.md stays under 500 lines. 4. Browser smoke test (required before delivery): actually open the generated HTML in a browser (or headless Chrome with --virtual-time-budget so the fadeIn animation settles), and confirm:
- the page renders fully with NO raw JS/source text leaking onto the page — a literal
</script>anywhere in the script block (even inside a JS comment or string) closes the script early and breaks the whole page;validate.mjsguards this by checking<script>/</script>stay balanced - new plan opens on today (not a stale selected day), 0 streak, 0% completion
- starter-only check-in counts toward streak
- manual jump to Day 30 does not mark all phases ready for review
- sync-to-HTML download works without fetch (including
file://) and uses{fileNameBase}-v{timestamp}.html - embedded
APP_DATA.reviewssurvives when localStorage reviews are empty
</process>
<success_criteria>
- All package, schema, runtime, behavior, and low-friction methodology checks pass.
- Any failure includes the exact file and invariant to fix.
</success_criteria>
Related skills
FAQ
Is a start date required?
Yes, the skill never invents a start date; if the user does not provide a concrete YYYY-MM-DD start date it asks for it before generating a plan.
What is the deliverable?
Exactly one .html file with no PWA dependency and no fake progress; new plans initialize with empty check-ins, Day 1 selected, 0 streak, and 0% completion.