@yorz/cli 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cli/index.js +520 -227
  2. package/dist/gui/assets/{abnfDiagram-VRR7QNED-D-y0QKuD.js → abnfDiagram-VRR7QNED-gKm9Q_A3.js} +1 -1
  3. package/dist/gui/assets/{arc-B49ieWDx.js → arc-CrUCh7Vt.js} +1 -1
  4. package/dist/gui/assets/{architectureDiagram-ZJ3FMSHR-CcOou6CY.js → architectureDiagram-ZJ3FMSHR-CMSfijC7.js} +1 -1
  5. package/dist/gui/assets/{blockDiagram-677ZJIJ3-DY8EHGpO.js → blockDiagram-677ZJIJ3-p05lGQvB.js} +1 -1
  6. package/dist/gui/assets/{c4Diagram-LMCZKHZV-CV8mRhow.js → c4Diagram-LMCZKHZV-Bv-YyQUO.js} +1 -1
  7. package/dist/gui/assets/channel-DjP6myfL.js +1 -0
  8. package/dist/gui/assets/{chunk-2Q5K7J3B-BjplUf2p.js → chunk-2Q5K7J3B-k4bjrCUS.js} +1 -1
  9. package/dist/gui/assets/{chunk-32BRIVSS-Dua0UKp_.js → chunk-32BRIVSS-kWhk22-t.js} +1 -1
  10. package/dist/gui/assets/{chunk-5VM5RSS4-Br-SORjV.js → chunk-5VM5RSS4-CQ-OF-jm.js} +1 -1
  11. package/dist/gui/assets/{chunk-EX3LRPZG-Dgb6iahE.js → chunk-EX3LRPZG-BCGCkTIr.js} +1 -1
  12. package/dist/gui/assets/{chunk-JWPE2WC7-BkOQaZRq.js → chunk-JWPE2WC7-BxQu3HdL.js} +1 -1
  13. package/dist/gui/assets/{chunk-MOJQB5TN-D1qUO9Uh.js → chunk-MOJQB5TN-DkB3CKU9.js} +1 -1
  14. package/dist/gui/assets/{chunk-RYQCIY6F-CwiUC3Pd.js → chunk-RYQCIY6F-62-xl5oz.js} +1 -1
  15. package/dist/gui/assets/{chunk-V7JOEXUC-O0VQnEjo.js → chunk-V7JOEXUC-DLidRBp5.js} +1 -1
  16. package/dist/gui/assets/{chunk-VR4S4FIN-C-iULcHx.js → chunk-VR4S4FIN-CelmZEAj.js} +1 -1
  17. package/dist/gui/assets/{chunk-XXDRQBXY-buihollE.js → chunk-XXDRQBXY-CZXSrEO5.js} +1 -1
  18. package/dist/gui/assets/classDiagram-OUVF2IWQ-CqIEE3-0.js +1 -0
  19. package/dist/gui/assets/classDiagram-v2-EOCWNBFH-CqIEE3-0.js +1 -0
  20. package/dist/gui/assets/{cose-bilkent-JH36ORCC-DJT3GZ0Y.js → cose-bilkent-JH36ORCC-BJZADzHX.js} +1 -1
  21. package/dist/gui/assets/{cynefin-VYW2F7L2-CUB4SFmD.js → cynefin-VYW2F7L2-2kv3gQiJ.js} +1 -1
  22. package/dist/gui/assets/{cynefinDiagram-TSTJHNR4-Dt0_fxzu.js → cynefinDiagram-TSTJHNR4-BJrGGRZT.js} +1 -1
  23. package/dist/gui/assets/{dagre-VKFMJZFB-BU2VgIkF.js → dagre-VKFMJZFB-C2Eq7ks1.js} +1 -1
  24. package/dist/gui/assets/{diagram-FQU43EPY-CpRyd1t3.js → diagram-FQU43EPY-DrIQap5j.js} +1 -1
  25. package/dist/gui/assets/{diagram-G47NLZAW-CcJkOrvN.js → diagram-G47NLZAW-D3qrZmEa.js} +1 -1
  26. package/dist/gui/assets/{diagram-NH7WQ7WH-BcWa9fg4.js → diagram-NH7WQ7WH-BiV6-mMS.js} +1 -1
  27. package/dist/gui/assets/{diagram-OA4YK3LP-Clu9LYE6.js → diagram-OA4YK3LP-zJhDVFfZ.js} +1 -1
  28. package/dist/gui/assets/{diagram-WEI45ONY-BKt73qmr.js → diagram-WEI45ONY-CDimAYs_.js} +1 -1
  29. package/dist/gui/assets/{ebnfDiagram-CCIWWBDH-ycI3xDTZ.js → ebnfDiagram-CCIWWBDH-CCcf1QsU.js} +1 -1
  30. package/dist/gui/assets/{erDiagram-Q63AITRT-CJf0jIvL.js → erDiagram-Q63AITRT-Q8tGhvrZ.js} +1 -1
  31. package/dist/gui/assets/{flowDiagram-23GEKE2U-8X7ZYojT.js → flowDiagram-23GEKE2U-3alKs_Q2.js} +1 -1
  32. package/dist/gui/assets/{ganttDiagram-NO4QXBWP-DsGJIvEc.js → ganttDiagram-NO4QXBWP-UwtLj5mp.js} +1 -1
  33. package/dist/gui/assets/{gitGraphDiagram-IHSO6WYX-Dg-PnhMR.js → gitGraphDiagram-IHSO6WYX-BkdZNrvw.js} +1 -1
  34. package/dist/gui/assets/index-DakqmUCD.js +40 -0
  35. package/dist/gui/assets/index-bzRYB8_7.css +1 -0
  36. package/dist/gui/assets/{infoDiagram-FWYZ7A6U-CX-PGRjw.js → infoDiagram-FWYZ7A6U-AtNZOkv_.js} +1 -1
  37. package/dist/gui/assets/{ishikawaDiagram-FXEZZL3T-BGq-1WGF.js → ishikawaDiagram-FXEZZL3T-fzZ3wFB-.js} +1 -1
  38. package/dist/gui/assets/{journeyDiagram-5HDEW3XC-CX7J6QLx.js → journeyDiagram-5HDEW3XC-CaInQL3L.js} +1 -1
  39. package/dist/gui/assets/{kanban-definition-HUTT4EX6-XcUXgRy6.js → kanban-definition-HUTT4EX6-BzazGxe7.js} +1 -1
  40. package/dist/gui/assets/{linear-D-w3YyOR.js → linear-FkI6GUdc.js} +1 -1
  41. package/dist/gui/assets/{mermaid.core-BjB4Bulu.js → mermaid.core-Dy3NGXqh.js} +4 -4
  42. package/dist/gui/assets/{mindmap-definition-LN4V7U3C-D2kDYOXu.js → mindmap-definition-LN4V7U3C-BkD5gUUF.js} +1 -1
  43. package/dist/gui/assets/{pegDiagram-2B236MQR-C79JhnNB.js → pegDiagram-2B236MQR-DrzL8aNW.js} +1 -1
  44. package/dist/gui/assets/{pieDiagram-ENE6RG2P-CbLYFwp4.js → pieDiagram-ENE6RG2P-BrrJjIcV.js} +1 -1
  45. package/dist/gui/assets/{quadrantDiagram-ABIIQ3AL-BXNa6Xix.js → quadrantDiagram-ABIIQ3AL-BvfIaaXh.js} +1 -1
  46. package/dist/gui/assets/{railroadDiagram-RFXS5EU6-CG8ojk-9.js → railroadDiagram-RFXS5EU6-uDh6Eejs.js} +1 -1
  47. package/dist/gui/assets/{requirementDiagram-TGXJPOKE-Ddn0mejD.js → requirementDiagram-TGXJPOKE-mq71nhz4.js} +1 -1
  48. package/dist/gui/assets/{sankeyDiagram-HTMAVEWB-DXVSB34u.js → sankeyDiagram-HTMAVEWB-hXTichtO.js} +1 -1
  49. package/dist/gui/assets/{sequenceDiagram-DBY2YBRQ-B00FxwKQ.js → sequenceDiagram-DBY2YBRQ-CgITKOd_.js} +1 -1
  50. package/dist/gui/assets/{sizeCapture-X5ZJPWSS-BUiEsqdO.js → sizeCapture-X5ZJPWSS-Bg4phjBx.js} +1 -1
  51. package/dist/gui/assets/{stateDiagram-2N3HPSRC-DflPz4MT.js → stateDiagram-2N3HPSRC-DKBi1ceK.js} +1 -1
  52. package/dist/gui/assets/stateDiagram-v2-6OUMAXLB-DSkELjnp.js +1 -0
  53. package/dist/gui/assets/{swimlanes-5IMT3BWC-yL7Pms6k.js → swimlanes-5IMT3BWC-BotNlXJq.js} +2 -2
  54. package/dist/gui/assets/swimlanesDiagram-G3AALYLV-_zGHZ8dn.js +8 -0
  55. package/dist/gui/assets/{timeline-definition-FHXFAJF6--V8N1aYE.js → timeline-definition-FHXFAJF6-xPORA673.js} +1 -1
  56. package/dist/gui/assets/{vennDiagram-L72KCM5P-CjlyPLPm.js → vennDiagram-L72KCM5P-9lcR-L8Q.js} +1 -1
  57. package/dist/gui/assets/{wardleyDiagram-EHGQE667-C99qPu9U.js → wardleyDiagram-EHGQE667-CLRUHnek.js} +1 -1
  58. package/dist/gui/assets/{xychartDiagram-FW5EYKEG-DeUNfALY.js → xychartDiagram-FW5EYKEG-C-oJxiEj.js} +1 -1
  59. package/dist/gui/index.html +5 -5
  60. package/package.json +1 -1
  61. package/dist/gui/assets/channel-BakWbuVQ.js +0 -1
  62. package/dist/gui/assets/classDiagram-OUVF2IWQ-DgfCpbC5.js +0 -1
  63. package/dist/gui/assets/classDiagram-v2-EOCWNBFH-DgfCpbC5.js +0 -1
  64. package/dist/gui/assets/index-DGSN0uvJ.css +0 -1
  65. package/dist/gui/assets/index-DIKCFt79.js +0 -39
  66. package/dist/gui/assets/stateDiagram-v2-6OUMAXLB-DQohEYjS.js +0 -1
  67. package/dist/gui/assets/swimlanesDiagram-G3AALYLV-CUZb2EKw.js +0 -8
package/dist/cli/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { homedir, platform } from "node:os";
3
3
  import { Command, Option } from "commander";
4
- import { stat, mkdir, writeFile, rename, readFile, rm, access, readdir, appendFile, copyFile } from "node:fs/promises";
4
+ import { stat, mkdir, writeFile, rename, readFile, rm, access, cp, readdir, appendFile, copyFile } from "node:fs/promises";
5
5
  import { join, isAbsolute, resolve, basename as basename$1, dirname, extname, relative, sep, normalize } from "node:path";
6
6
  import { createHash, randomUUID, randomBytes } from "node:crypto";
7
7
  import { existsSync, openSync, closeSync, statSync, readSync, mkdirSync, watch, createReadStream } from "node:fs";
@@ -23,7 +23,7 @@ import { createOpencode } from "@opencode-ai/sdk";
23
23
  import { createInterface as createInterface$1 } from "node:readline/promises";
24
24
  import MarkdownIt from "markdown-it";
25
25
  const name = "@yorz/cli";
26
- const version = "0.8.1";
26
+ const version = "0.8.2";
27
27
  const type = "module";
28
28
  const description = "";
29
29
  const bin = { "yorz": "./dist/cli/index.js" };
@@ -151,7 +151,7 @@ const DEFAULT_POWER = {
151
151
  };
152
152
  const DEFAULT_APPEARANCE = {
153
153
  themeMode: "system",
154
- themeName: "terminal",
154
+ themeName: "paper",
155
155
  language: "zh-CN"
156
156
  };
157
157
  const SHORTCUT_ACTION_IDS = [
@@ -438,11 +438,11 @@ async function prepareProjectDir(input, cwd) {
438
438
  await mkdir(join(abs, ".yorz", "specs"), { recursive: true });
439
439
  return abs;
440
440
  }
441
- const __vite_glob_0_0 = '---\nname: yorz-debug\ndescription: 以资深工程师的调试纪律定位并修复疑难 bug——假设→取证→验证的证据闭环,靠 debug.md 活文档与 git 快照兜住污染。\n---\n\n# YorZ Debug 深度调试 Skill\n\n指导 Agent 以**资深工程师的调试纪律**逐步逼近疑难 bug 的根因:不凭猜测改代码,坚持「假设 → 取证 → 验证 → 缩小范围 → 再假设」的证据闭环。调试过程赋予更大的自由度(临时改代码、Mock 数据、加日志、建临时验证入口),同时用 `debug.md` 活文档 + `git stash create` 快照兜住"污染性改动不进入最终提交"。\n\n> 本 skill **不复用** yorz-spec 的 plan/tasks/execute 状态机。真相以 `debug.md` 为单一载体;Agent 持续推进直至需要用户回传证据(人在环路),或根因确认并收尾。\n\n## 何时进入本 skill\n\n- **独立触发(无需 spec / UI)**:用户在 Agent 对话中直接点名「用 yorz-debug 调这个 bug」。此时可能没有任何 spec 上下文——按下方「无 spec 场景」处理 `debug.md` 落点。\n- **追加任务勾选 Debug**:用户在 SpecDetail 追加 `fix` 任务时勾选「Debug 模式」,后端 prompt 指向本 skill。\n- **重入**:spec 目录已存在 `status: debugging` 的 `debug.md`(有未收尾记录块),run/append 路由自动切到本 skill。此时**不新建记录块**,定位活跃记录块(frontmatter `active: NNN`)续跑。\n\n## 输入约定\n\n- `spec_dir`(**可选**):spec 目录路径(含 `spec.md`)。给出时 `debug.md` 建在此目录,与 `spec.md` 同级。\n- 待调试 bug 的描述:来自追加任务行 / 用户 prompt。\n- 运行服务上下文(**可选**):由 YorZ Service 注入的当前项目运行中命令服务列表,包含 `runId`、`name`、`cli`、`pid`、`startedAt`、`logFile` 等字段。\n\n### debug.md 落点\n\n- **有 spec 上下文**:`debug.md` 落 `spec_dir`(与 `spec.md` 同级)。\n- **无 spec 场景(独立触发)**:没有 `spec_dir` 时,Debug 活文档必须落在项目临时目录 `.yorz/tmp/debug/`,文件名必须携带本地秒级时间信息,格式为 `debug-YYYYMMDD-HHmmss.md`;若 YorZ Service prompt 已给出具体文件路径,则以该路径为准。其余多记录模型、快照、脚手架、收尾流程与有 spec 时**完全一致**。\n\n## debug.md:多记录活文档\n\n**一个 `debug.md` 承载同一 spec 生命周期内的多次调试**(某次收尾后又发现新 bug,就追加下一条记录,不覆盖历史)。它同时是:调试活文档、**Debug 模式的持久化标记**(存在未收尾记录 = 处于 Debug 模式)、脚手架是否清理干净的守卫。\n\n### 文件级 frontmatter\n\n```yaml\n---\nstatus: debugging # debugging(存在未收尾记录)| resolved(全部记录已收尾)\nactive: 2 # 当前活跃记录编号;resolved 时留空\nupdated_at: \'2026-07-19 15:54:37\' # 本地秒级字符串\n---\n```\n\n### 正文按「记录块」组织\n\n每次进入 Debug **在文末追加**一个记录块,编号 `NNN` 从 1 递增、**不复用**、不覆盖既有块:\n\n```markdown\n## Debug 1 · <bug 一句话简述>\n\n- 状态:debugging # debugging | resolved\n- 快照:<git stash create 得到的 SHA>\n- 进入时间:\'2026-07-19 15:54:37\'\n\n### 1.1 Bug 现象与复现\n\n...\n\n### 1.2 关联链路分析\n\n...\n\n### 1.3 Debug 基线\n\n快照 SHA + 进入时间;`git diff <SHA>` 为退出闸门基准。\n\n### 1.4 假设看板\n\n...\n\n### 1.5 证据\n\n...\n\n### 1.6 脚手架清单\n\n...\n\n### 1.7 收尾核对\n\n...\n```\n\n### 生命周期\n\n- **创建 / 进入**:`debug.md` 不存在则创建(写文件 frontmatter + 第 1 个记录块);已存在则追加 `## Debug <max+1>`,置文件 `status: debugging`、`active: <新编号>`。进入后**立刻**打快照(见「污染防线」)。\n- **单块收尾**:把该记录块块头 `状态` 置 `resolved`,脚手架逐条核销,`git diff <SNAP>` 校验只剩合法修复。\n- **文件 status 收敛**:单块收尾后,若**所有**记录块均 `resolved`,置文件 `status: resolved`、清空 `active`;否则保持 `debugging` 且 `active` 指向仍在调试的块。\n- **归档**:收尾**保留** `debug.md` 供复盘,绝不删除。\n\n## 核心调试循环\n\n**分析 → 规划 → 假设 → 实施 → 验证 → 缩小范围 → 再假设**,直至根因被硬证据锁定。\n\n```mermaid\nflowchart TD\n Start["接手 bug"] --> Repro{"能稳定复现?"}\n Repro -->|否| GetRepro["补齐复现步骤<br/>(问用户 / 自行构造)"]\n GetRepro --> Repro\n Repro -->|是| Analyze["分析关联链路<br/>写入 debug.md"]\n Analyze --> Hypo["提出可证伪假设<br/>成立看到 X / 不成立看到 Y"]\n Hypo --> Probe["写验证性代码 / 加日志<br/>登记脚手架清单"]\n Probe --> Eval{"证据指向?"}\n Eval -->|证伪| Exclude["记入已排除假设 + 依据"]\n Exclude --> Narrow["缩小范围(二分/控制变量)"]\n Narrow --> Hypo\n Eval -->|坐实根因| Fix["写最终修复"]\n Fix --> Verify{"重跑复现步骤<br/>问题消失?"}\n Verify -->|否| Hypo\n Verify -->|是| Close["进入收尾"]\n```\n\n### 方法层\n\n- **二分法**:在可疑区间(提交历史 / 代码路径 / 数据范围)二分定位,每次砍掉一半。\n- **控制变量法**:一次只改一个变量,隔离单一因素的影响。\n- **遍历可能分支**:对有限的分支/枚举/状态逐一验证,排除法收敛。\n\n## 硬约束(资深工程师容易做、Agent 容易漏)\n\n1. **先复现,再调试**:无法稳定复现,**不得进入修改阶段**——否则无从验证修没修好。复现不了就先构造复现路径或请用户提供。\n2. **假设必须可证伪**:提出假设时,同时写出"若成立会看到 X,若不成立会看到 Y",再去取证。写进「假设看板」。\n3. **记录已排除假设 + 依据**:每否定一个假设,连同证据写入看板,**避免兜圈子反复验证同一已否定项**。\n4. **禁止无证据修复**:拿到指向根因的**硬证据**(日志 / 截图 / 可复现的最小用例)前,**只许写验证性代码,不许写最终修复**。\n5. **人机协作**:优先主动复现 Bug、获取日志进行分析判断,如果无途径复现 Bug、获取日志,也可暂停执行,让用户配合复现 Bug、回传日志。\n6. **退出双条件**:根因有硬证据 **且** 修复后重跑复现步骤、证据显示问题消失——**缺一不可**。\n\n### 日志纪律\n\n- **可读可复制**:打印结构化字符串而非整个 object(如 拼接关键字段 k1=v1&k2=v2 或 `JSON.stringify(x)`),避免直接打印对象或复杂结构变量的引用。\n- **避免刷屏**:不要在循环/高频路径里打印大量日志;聚焦怀疑环节,必要时带唯一标记便于检索。\n- **可定位**:日志带上下文(哪个函数 / 哪个分支 / 关键变量值),便于判定假设成立与否。\n\n## 更大的自由度(Debug 模式内允许)\n\n- 编写临时代码(条件短路)或注释有意义代码,控制程序进入 / 屏蔽特定分支;\n- 临时篡改变量值、Mock API 返回,让程序走到特定分支;\n- 编写临时独立测试页面 / 入口,简化复现路径、直接验证怀疑环节;\n- 联网查询第三方 SDK 文档、向用户询问必要信息;\n- 允许 lint / typecheck / build / 单测出现错误,允许**临时**跳过这些检查。\n\n> 每一处临时改动都**必须**在「脚手架清单」登记一行(文件 + 位置 + 类型:短路 / Mock / 注释 / 临时页面 / 临时日志),收尾逐条核销。**未登记 = 会漏还原**。\n\n## 污染防线(强制流程)\n\n在**当前项目与分支就地调试**,不开 worktree;靠快照 + 脚手架清单 + diff 兜住污染。\n\n```mermaid\nflowchart TD\n Enter["进入 Debug(新记录块)"] --> Snap["SNAP = git stash create<br/>零副作用快照当前脏工作区"]\n Snap --> Record["把 SNAP 写入记录块 · Debug 基线"]\n Record --> Loop["调试循环<br/>照常改代码 + 登记脚手架清单"]\n Loop --> Fixed{"根因确认 + 硬证据?"}\n Fixed -->|否| Loop\n Fixed -->|是| Revert["按脚手架清单逐条还原"]\n Revert --> Diff["git diff SNAP"]\n Diff --> Gate{"仅剩合法修复?"}\n Gate -->|否| Revert\n Gate -->|是| Done["置本块 status: resolved<br/>跑变更文件单测/build/typecheck"]\n```\n\n- **进入即打快照**:`git stash create`(**不是** `git stash` / 临时 commit)生成一个记录当前脏工作区的 commit 对象,但**不动工作区 / index / HEAD**。它天然区分"进入前的既有未提交改动"与"调试引入的改动",避免临时 commit 的还原风险。把返回的 SHA 写入记录块「Debug 基线」。\n - 若 `git stash create` 无输出(工作区干净),以当前 `HEAD`(`git rev-parse HEAD`)作为快照基准并注明。\n- **git diff 是退出闸门**:宣告完成前,`git diff <SNAP>` 必须**只剩合法修复**——所有短路 / Mock / 注释 / 临时页面 / 临时日志都已还原。diff 里还有脚手架残留 = 不许收尾。\n\n## 收尾(退出 Debug 模式)\n\n根因确认且修复通过复现验证后:\n\n1. **还原脚手架**:按「脚手架清单」逐条还原临时代码与被注释代码,确保原有逻辑正确;清理不需要的 debug 日志。\n2. **退出闸门**:`git diff <SNAP>` 只剩合法修复(脚手架清单全部核销)。\n3. **常规完整性检查**:跑变更文件关联单测、构建、类型检查。\n4. **落状态**:把该记录块块头 `状态` 置 `resolved`;按「文件 status 收敛」更新文件 frontmatter(全部 resolved → 文件 `status: resolved`、清空 `active`)。\n5. **保留归档**:`debug.md` 保留,供复盘。\n\n> 收尾后如又发现**新** bug,回到本 skill 追加下一条 `## Debug NNN` 记录块(不覆盖历史),重新走一遍循环。\n\n## 写回纪律\n\n- 每次写回 `debug.md` 都更新文件 frontmatter 的 `updated_at`。\n- `updated_at` 用本地秒级 `YYYY-MM-DD HH:mm:ss` 字符串。\n- 记录块编号连续递增、不复用、不重排既有块。\n';
441
+ const __vite_glob_0_0 = '---\nname: yorz-debug\ndescription: 以资深工程师的调试纪律定位并修复疑难 bug——假设→取证→验证的证据闭环,靠 debug.md 活文档与 git 快照兜住污染。\n---\n\n# YorZ Debug 深度调试 Skill\n\n指导 Agent 以**资深工程师的调试纪律**逐步逼近疑难 bug 的根因:不凭猜测改代码,坚持「假设 → 取证 → 验证 → 缩小范围 → 再假设」的证据闭环。调试过程赋予更大的自由度(临时改代码、Mock 数据、加日志、建临时验证入口),同时用 `debug.md` 活文档 + `git stash create` 快照兜住"污染性改动不进入最终提交"。\n\n> 本 skill **不复用** yorz-spec 的 plan/tasks/execute 状态机。真相以 `debug.md` 为单一载体;Agent 持续推进直至需要用户回传证据(人在环路),或根因确认并收尾。\n\n## 何时进入本 skill\n\n- **独立触发(无需 spec / UI)**:用户在 Agent 对话中直接点名「用 yorz-debug 调这个 bug」。此时可能没有任何 spec 上下文——按下方「无 spec 场景」处理 `debug.md` 落点。\n- **追加 `fix` 类型任务**:用户在 SpecDetail 追加 `fix` 任务时,后端直接以 `/yorz-debug @<spec path> <内容>` 指向本 skill——`fix` 即深度调试,无需额外开关。\n- **重入**:spec 目录已存在 `status: debugging` 的 `debug.md`(有未收尾记录块),run/append 路由自动切到本 skill。此时**不新建记录块**,定位活跃记录块(frontmatter `active: NNN`)续跑。\n\n## 输入约定\n\n- `spec_dir`(**可选**):spec 目录路径(含 `spec.md`)。给出时 `debug.md` 建在此目录,与 `spec.md` 同级。\n - 指令形式为 `/yorz-debug @<spec path> <内容>` 时,`<spec path>` 指向 `spec.md`,其所在目录即 `spec_dir`。\n - 其中的 `@` 只是 YorZ 的文件引用标记,**不属于路径本身**;去掉 `@` 后是**项目根目录**(即当前工作目录)下的相对路径,不要相对本 skill 文件所在目录或用户主目录解析。\n- 待调试 bug 的描述:来自指令正文 / 追加任务行 / 用户 prompt。\n- 运行服务上下文(**可选**):由 YorZ Service 注入的当前项目运行中命令服务列表,包含 `runId`、`name`、`cli`、`pid`、`startedAt`、`logFile` 等字段。\n\n### debug.md 落点\n\n- **有 spec 上下文**:`debug.md` 落 `spec_dir`(与 `spec.md` 同级)。\n- **无 spec 场景(独立触发)**:没有 `spec_dir` 时,Debug 活文档必须落在项目临时目录 `.yorz/tmp/debug/`,文件名必须携带本地秒级时间信息,格式为 `debug-YYYYMMDD-HHmmss.md`;若 YorZ Service prompt 已给出具体文件路径,则以该路径为准。其余多记录模型、快照、脚手架、收尾流程与有 spec 时**完全一致**。\n\n## debug.md:多记录活文档\n\n**一个 `debug.md` 承载同一 spec 生命周期内的多次调试**(某次收尾后又发现新 bug,就追加下一条记录,不覆盖历史)。它同时是:调试活文档、**Debug 模式的持久化标记**(存在未收尾记录 = 处于 Debug 模式)、脚手架是否清理干净的守卫。\n\n### 文件级 frontmatter\n\n```yaml\n---\nstatus: debugging # debugging(存在未收尾记录)| resolved(全部记录已收尾)\nactive: 2 # 当前活跃记录编号;resolved 时留空\nupdated_at: \'2026-07-19 15:54:37\' # 本地秒级字符串\n---\n```\n\n### 正文按「记录块」组织\n\n每次进入 Debug **在文末追加**一个记录块,编号 `NNN` 从 1 递增、**不复用**、不覆盖既有块:\n\n```markdown\n## Debug 1 · <bug 一句话简述>\n\n- 状态:debugging # debugging | resolved\n- 快照:<git stash create 得到的 SHA>\n- 进入时间:\'2026-07-19 15:54:37\'\n\n### 1.1 Bug 现象与复现\n\n...\n\n### 1.2 关联链路分析\n\n...\n\n### 1.3 Debug 基线\n\n快照 SHA + 进入时间;`git diff <SHA>` 为退出闸门基准。\n\n### 1.4 假设看板\n\n...\n\n### 1.5 证据\n\n...\n\n### 1.6 脚手架清单\n\n...\n\n### 1.7 收尾核对\n\n...\n```\n\n### 生命周期\n\n- **创建 / 进入**:`debug.md` 不存在则创建(写文件 frontmatter + 第 1 个记录块);已存在则追加 `## Debug <max+1>`,置文件 `status: debugging`、`active: <新编号>`。进入后**立刻**打快照(见「污染防线」)。\n- **单块收尾**:把该记录块块头 `状态` 置 `resolved`,脚手架逐条核销,`git diff <SNAP>` 校验只剩合法修复。\n- **文件 status 收敛**:单块收尾后,若**所有**记录块均 `resolved`,置文件 `status: resolved`、清空 `active`;否则保持 `debugging` 且 `active` 指向仍在调试的块。\n- **归档**:收尾**保留** `debug.md` 供复盘,绝不删除。\n\n## 核心调试循环\n\n**分析 → 规划 → 假设 → 实施 → 验证 → 缩小范围 → 再假设**,直至根因被硬证据锁定。\n\n```mermaid\nflowchart TD\n Start["接手 bug"] --> Repro{"能稳定复现?"}\n Repro -->|否| GetRepro["补齐复现步骤<br/>(问用户 / 自行构造)"]\n GetRepro --> Repro\n Repro -->|是| Analyze["分析关联链路<br/>写入 debug.md"]\n Analyze --> Hypo["提出可证伪假设<br/>成立看到 X / 不成立看到 Y"]\n Hypo --> Probe["写验证性代码 / 加日志<br/>登记脚手架清单"]\n Probe --> Eval{"证据指向?"}\n Eval -->|证伪| Exclude["记入已排除假设 + 依据"]\n Exclude --> Narrow["缩小范围(二分/控制变量)"]\n Narrow --> Hypo\n Eval -->|坐实根因| Fix["写最终修复"]\n Fix --> Verify{"重跑复现步骤<br/>问题消失?"}\n Verify -->|否| Hypo\n Verify -->|是| Close["进入收尾"]\n```\n\n### 方法层\n\n- **二分法**:在可疑区间(提交历史 / 代码路径 / 数据范围)二分定位,每次砍掉一半。\n- **控制变量法**:一次只改一个变量,隔离单一因素的影响。\n- **遍历可能分支**:对有限的分支/枚举/状态逐一验证,排除法收敛。\n\n## 硬约束(资深工程师容易做、Agent 容易漏)\n\n1. **先复现,再调试**:无法稳定复现,**不得进入修改阶段**——否则无从验证修没修好。复现不了就先构造复现路径或请用户提供。\n2. **假设必须可证伪**:提出假设时,同时写出"若成立会看到 X,若不成立会看到 Y",再去取证。写进「假设看板」。\n3. **记录已排除假设 + 依据**:每否定一个假设,连同证据写入看板,**避免兜圈子反复验证同一已否定项**。\n4. **禁止无证据修复**:拿到指向根因的**硬证据**(日志 / 截图 / 可复现的最小用例)前,**只许写验证性代码,不许写最终修复**。\n5. **人机协作**:优先主动复现 Bug、获取日志进行分析判断,如果无途径复现 Bug、获取日志,也可暂停执行,让用户配合复现 Bug、回传日志。\n6. **退出双条件**:根因有硬证据 **且** 修复后重跑复现步骤、证据显示问题消失——**缺一不可**。\n\n### 日志纪律\n\n- **可读可复制**:打印结构化字符串而非整个 object(如 拼接关键字段 k1=v1&k2=v2 或 `JSON.stringify(x)`),避免直接打印对象或复杂结构变量的引用。\n- **避免刷屏**:不要在循环/高频路径里打印大量日志;聚焦怀疑环节,必要时带唯一标记便于检索。\n- **可定位**:日志带上下文(哪个函数 / 哪个分支 / 关键变量值),便于判定假设成立与否。\n\n## 更大的自由度(Debug 模式内允许)\n\n- 编写临时代码(条件短路)或注释有意义代码,控制程序进入 / 屏蔽特定分支;\n- 临时篡改变量值、Mock API 返回,让程序走到特定分支;\n- 编写临时独立测试页面 / 入口,简化复现路径、直接验证怀疑环节;\n- 联网查询第三方 SDK 文档、向用户询问必要信息;\n- 允许 lint / typecheck / build / 单测出现错误,允许**临时**跳过这些检查。\n\n> 每一处临时改动都**必须**在「脚手架清单」登记一行(文件 + 位置 + 类型:短路 / Mock / 注释 / 临时页面 / 临时日志),收尾逐条核销。**未登记 = 会漏还原**。\n\n## 污染防线(强制流程)\n\n在**当前项目与分支就地调试**,不开 worktree;靠快照 + 脚手架清单 + diff 兜住污染。\n\n```mermaid\nflowchart TD\n Enter["进入 Debug(新记录块)"] --> Snap["SNAP = git stash create<br/>零副作用快照当前脏工作区"]\n Snap --> Record["把 SNAP 写入记录块 · Debug 基线"]\n Record --> Loop["调试循环<br/>照常改代码 + 登记脚手架清单"]\n Loop --> Fixed{"根因确认 + 硬证据?"}\n Fixed -->|否| Loop\n Fixed -->|是| Revert["按脚手架清单逐条还原"]\n Revert --> Diff["git diff SNAP"]\n Diff --> Gate{"仅剩合法修复?"}\n Gate -->|否| Revert\n Gate -->|是| Done["置本块 status: resolved<br/>跑变更文件单测/build/typecheck"]\n```\n\n- **进入即打快照**:`git stash create`(**不是** `git stash` / 临时 commit)生成一个记录当前脏工作区的 commit 对象,但**不动工作区 / index / HEAD**。它天然区分"进入前的既有未提交改动"与"调试引入的改动",避免临时 commit 的还原风险。把返回的 SHA 写入记录块「Debug 基线」。\n - 若 `git stash create` 无输出(工作区干净),以当前 `HEAD`(`git rev-parse HEAD`)作为快照基准并注明。\n- **git diff 是退出闸门**:宣告完成前,`git diff <SNAP>` 必须**只剩合法修复**——所有短路 / Mock / 注释 / 临时页面 / 临时日志都已还原。diff 里还有脚手架残留 = 不许收尾。\n\n## 收尾(退出 Debug 模式)\n\n根因确认且修复通过复现验证后:\n\n1. **还原脚手架**:按「脚手架清单」逐条还原临时代码与被注释代码,确保原有逻辑正确;清理不需要的 debug 日志。\n2. **退出闸门**:`git diff <SNAP>` 只剩合法修复(脚手架清单全部核销)。\n3. **常规完整性检查**:跑变更文件关联单测、构建、类型检查。\n4. **落状态**:把该记录块块头 `状态` 置 `resolved`;按「文件 status 收敛」更新文件 frontmatter(全部 resolved → 文件 `status: resolved`、清空 `active`)。\n5. **保留归档**:`debug.md` 保留,供复盘。\n\n> 收尾后如又发现**新** bug,回到本 skill 追加下一条 `## Debug NNN` 记录块(不覆盖历史),重新走一遍循环。\n\n## 写回纪律\n\n- 每次写回 `debug.md` 都更新文件 frontmatter 的 `updated_at`。\n- `updated_at` 用本地秒级 `YYYY-MM-DD HH:mm:ss` 字符串。\n- 记录块编号连续递增、不复用、不重排既有块。\n';
442
442
  const __vite_glob_0_1 = '{\n "$schema": "https://json-schema.org/draft-07/schema#",\n "version": 2,\n "modules": [\n {\n "module": "SKILL",\n "file": "SKILL.md",\n "relatedCases": []\n }\n ]\n}\n';
443
443
  const __vite_glob_0_2 = '---\nname: yorz-git-ops\ndescription: Execute YorZ Review page git operations with focused safety rules.\n---\n\n# YorZ Git Ops Skill\n\n本 skill 描述 service 端拉起 git 操作(带 `action`)时,Agent 应遵守的输入解析与 git 安全约束。**这条路径不进入 yorz-spec 的 plan/tasks/execute 状态机**,亦不修改 spec.md 的 frontmatter `stage`。\n\n## 输入\n\n- spec 文档路径(`<spec_path>`)。\n- spec-id。\n- 当前 git 仓库的未提交变更(`git status -sb` / `git diff` / `git diff --staged`)。\n- action:`commit` / `discard` / `stash`。\n\n## action=commit\n\n- 由 Agent 基于 spec 文档与当前 git 状态,自主决定本次要提交的 spec 相关变更文件清单。\n- commit message 由 Agent 基于本次变更语义自行生成;不附加 `[spec:<id>]` 锚点,不带 scope(遵循 `260630.refct.review-commit-msg-remove-scope`)。\n- 仅允许使用 `git add <paths>` + `git commit -m <message>`;**禁止** `git push` / `git reset --hard` / `git rebase` / 修改任何已提交历史。\n\n## action=discard\n\n- 使用 `git restore --staged --worktree -- <paths>` 丢弃已 tracked 的修改;untracked 新文件使用 `git clean -fd -- <paths>` 清理。\n- 对 untracked 文件先在终端输出待清理列表,再执行 clean,便于日志回溯。\n- **不**预先 `git stash` 作为“保险备份”(遵循用户显式意图,保持轻量)。\n- **禁止** `git reset --hard`、`git push`。\n\n## action=stash\n\n- 使用 `git stash push -m "yorz:<spec-id>" -- <paths>`,仅暂存 spec 相关变更文件。\n- 不要使用 `git stash --all` 之类会影响 untracked / ignored 文件的形式,除非明确需要。\n- **禁止** `git push`。\n\n## 通用硬约束\n\n- 仅操作 `<spec_path>` 与 git 工作区文件;不修改无关 spec、不修改本 skill 文件。\n- 任一 git 子命令失败时,把错误信息写入终端日志(service 端会落盘到 agent-logs),不要静默吞掉。\n- 元确认禁令:不向用户回问“是否继续 / 是否执行”等问题(GUI 已在按钮触发前做过必要的二次确认)。若无法安全判断文件范围或命令失败且无法自行恢复,应停止并把原因写入终端日志。\n';
444
444
  const __vite_glob_0_3 = '{\n "$schema": "https://json-schema.org/draft-07/schema#",\n "version": 2,\n "modules": [\n {\n "module": "SKILL",\n "file": "SKILL.md",\n "relatedCases": []\n }\n ]\n}\n';
445
- const __vite_glob_0_4 = '---\nname: yorz-spec\ndescription: Drive YorZ spec docs through plan / tasks / execute stages with deterministic state updates.\n---\n\n# YorZ Spec Drive Skill\n\n将单个 YorZ spec 文档作为状态机执行,围绕 `plan` / `tasks` / `execute` 三阶段推进,全部任务完成后进入终止态 `done`,把状态持续写回文档。md 是单一真相;Agent 持续推进直至阻塞(待确认项、决策或执行失败)或收尾为 `done` 才退出。\n\n## 如何使用本 skill\n\n本 skill 文档结构精简为 2 个核心文件;每次接到 spec 任务时,**按以下顺序**按需 Read:\n\n1. **本文档(SKILL.md)**:输入约定、frontmatter 规范、格式约定、自动模式判定、全局硬约束、lint 硬约束、持续推进约束。\n2. **[stages.md](./stages.md)**:plan / tasks / execute / new-spec 四阶段流程与正面示例。\n\n[mermaid.md](./mermaid.md) 与 `references/` 目录**仅按需 Read**:只有在判断当前阶段需要输出 mermaid 图表「升维」时才加载,纯状态推进任务不必读取,以节省 context。**plan 阶段的「图形化补充」收尾子步骤**(见 stages.md)会针对 `现状分析`/`技术方案` 两节强制加载 mermaid.md 补图;补图须遵循其核心原则**「图优先、精确信息折叠」**——表层给图,精确细节折叠进 `<details>` 精确层。\n\n## 输入约定\n\n- `spec_path`:**可选**,目标 spec 文档路径。\n - 显式给出时:典型位置 `docs/specs/*.md` 或 `.yorz/specs/<id>/spec.md`,直接更新该 spec。\n - 缺省时按以下顺序解析:\n 1. **从 session 上下文恢复**:扫描当前会话历史中已出现/已读写过的 spec 文档路径;存在多个时优先选取最近一次被读写的;若仍有歧义,向用户确认。\n 2. **从当前 prompt 创建**:若上下文中没有任何 spec 文档,按 [新建 spec 流程](./stages.md#new-spec) 生成路径与骨架。\n- spec 目录默认为 `.yorz/specs`,可由 `<ProjectRoot>/.yorz/config.json` 的 `specsDir` 字段覆盖。\n- `mode`:可选,`plan|tasks|execute|auto`,默认 `auto`。\n- 批注前缀:`!!!`\n\n## frontmatter 规范\n\n每个 spec md 顶部必须有 YAML frontmatter:\n\n```yaml\n---\nstage: plan # plan | tasks | execute | done\nlast_action: 简述上一次动作\nupdated_at: \'2026-06-14 15:42:07\' # 本地秒级字符串\nsummary: 一句话概要,≤ 200 字符\n---\n```\n\n- 字段顺序固定为 `stage` → `last_action` → `updated_at` → `summary`,每个字段独占一行,禁止嵌套与额外字段。\n- `updated_at` 使用本地秒级 `YYYY-MM-DD HH:mm:ss` 字符串。\n- 缺失任意字段时,先补齐再继续。\n\n## Markdown 格式化约定\n\n- 每次写回 spec md 后,**应在仓库支持的条件下运行 Markdown formatter**(优先 `npx prettier --write <spec_path>`);未配置则跳过。\n- formatter 必须保留 YAML frontmatter 不变。\n- 任务清单仅使用单层 `- [ ]` / `- [x]`,避免缩进与嵌套。\n- 二、三级标题必须带层级编号,便于外部按章节号定位:\n - 每次写回前按 body 中 `## ` / `### ` 出现顺序重新编号。\n - 二级标题 `## N. 标题`(N 从 1 起);三级标题 `### N.M 标题`(M 在所属二级下从 1 起)。\n - 该规则不影响 frontmatter 与一级标题(`# `)。\n\n## 自动模式判定顺序(严格按顺序)\n\n`mode=auto` 时,**严格按顺序**判定,命中即停:\n\n1. 若 `spec_path` 缺省:从 session 上下文恢复 spec 路径;恢复失败进入 [新建 spec 流程](./stages.md#new-spec) 并以 `plan` 起步。\n2. **优先扫描 `## 追加任务` 中是否存在 `[open]` 条目**:存在即视为"新输入"信号,进入 `plan`(重开流程)。\n3. 若识别到新增/扩展需求或新增 bug,进入 `plan`(重开流程)。\n4. 若文档存在任意 `!!!` 批注,进入 [tasks](./stages.md#tasks)。\n5. 若 `## 待确认项` 下存在有效条目(非空态 `_暂无_`),停止推进并等待人工批注。\n6. 若 `stage` 已是 `done` 且上述 1–5 均未命中(无新输入/批注/待确认项),直接停止:终止态不再自动推进。\n7. 若存在未完成的**非 `[manual]`** 任务(`- [ ]` 且非 `- [ ] [manual]`),进入 [execute](./stages.md#execute)。\n8. 若任务清单已就绪且不存在未完成的非 manual 任务(走到此处已排除 `[open]`/新需求/批注/待确认项):将 `stage` 置为 `done` 并停止推进(终止态)。\n9. 若 `## 技术实现方案` 为空或明显不完整,进入 [plan](./stages.md#plan)。\n10. 进入 [tasks](./stages.md#tasks) 生成或刷新详细任务清单。\n\n> 章节名 `## 待确认项` 为新名,parser/lint 兼容旧名 `## 待确认问题`;存量 spec 保留旧名仍可识别,新建/重写一律用 `待确认项`。\n>\n> 待确认项判定:章节内存在任一 `### ` 三级标题即视为未决;仅有 `_暂无_` 或整章为空即视为无未决条目;存在未决条目时禁止进入 execute。\n>\n> `done` 判定忽略 `- [ ] [manual]` 人工确认项:即使 manual 项仍未勾选,只要其余任务全部完成、且无待确认项/批注/`[open]`,即可收尾为 `done`。\n\n## 全局硬约束\n\n### 通用\n\n- 仅更新目标 spec 文档,不修改无关文档。\n- 每次写回文档都必须更新 frontmatter 元信息。\n- 执行任务时若涉及代码改动,只改动与任务相关的最小范围。\n\n### 章节建议\n\n初始化 spec 时**建议**创建以下章节(`## 追加任务` `## 用户批注` 为可选,由用户触发追加时懒插入):\n\n- `## 现状分析` / `## 技术实现方案` / `## 待确认项` / `## 任务清单` / `## 执行记录`\n\n> lint 不再强制校验章节齐全;但 routing 按章节名查找时,章节不存在视为空态。\n\n### 变更重开流程\n\n**触发条件:**\n\n- 在已有 spec 中识别到"新增/扩展需求"或"新增 bug",将 `stage` 切回 `plan`,`last_action` 记录"变更重开流程"。\n- `## 追加任务` 存在 `- [open] [feat|refct|fix] ...` 条目时,将 `stage` 切回 `plan`。\n\n**重开后必须做的事:**\n\n- 仅针对新增内容重新走完 `plan → tasks → execute`。\n- 未受影响的既有结论/任务可保留,受影响任务必须重新评估。\n- plan 阶段消费追加任务条目时,**不修改** `[open]` 状态标记。\n\n## 输出优先级\n\n1. 先保证状态正确(frontmatter 元信息更新)。\n2. 再保证结构正确(列表格式、任务格式、候选项)。\n3. 最后保证内容质量(可执行、可验收、可追踪)。\n\n## 写回后的 lint 硬约束\n\n任何阶段完成对 `spec.md` 的写入后,Agent **必须**通过 Bash 运行 `yorz lint <path> --format json`,并 parse stdout。\n\n- `errorCount === 0` 时视为通过。\n- 存在 `severity: error` 时按 `ruleId` + `message` + `line` 定位并修改,然后重新运行 lint。\n- 同一文件 lint 连续失败达到 **3 次**,将偏差作为 `## 待确认项` 条目写入 spec.md 后退出,等待人工干预。\n- `warn` 级 finding 不阻断推进,但应尽力消除。\n\n## 持续推进硬约束\n\nAgent 在 plan / tasks / execute 任意阶段,**只允许**因以下"合法阻塞"退出当轮:\n\n1. `## 待确认项` 章节存在非 `暂无` 条目;\n2. spec md 内存在任何 `!!!` 批注;\n3. 识别到新增/扩展需求或新增 bug,按变更重开流程切回 `plan`;\n4. 执行外部命令 / 工具调用失败,需要用户做不可推断的决断。\n\n**显式禁止**以下"元确认"行为:\n\n- 以"任务很多 / 改动较大 / 范围广 / 时间长 / 风险高"为由询问"是否继续 / 是否执行 / 是否同意此方案"。\n- 在 execute 阶段中段汇报进度并要求用户确认下一步。\n- 反问类如"要不要我开始 / 是否继续下一项"等问句。\n\n替代约定:tasks/execute 阶段若产生新疑问,**必须**作为新条目写回 `## 待确认项` 并按变更重开流程退出。\n\n兜底:对外部世界有副作用、不可逆的命令(如 `git push`、`git reset --hard`、`rm -rf`、修改 CI 配置等破坏性操作)仍按系统默认安全准则处理。\n\n## 与 YorZ 工作流的关联\n\n- 本 skill 不依赖 YorZ Service / GUI 也能独立工作:Agent 直接读写 `spec_path` 即可。\n- 当 YorZ Service 在线时,Agent 写回 md 会被 Service 的 FS Watcher 感知并推送给 GUI。\n- 如需用户介入,Agent 把信息写回文档对应章节后立即退出,由用户人工补齐 `!!!` 批注,再由 CLI / Service 重新拉起 Agent 继续执行。\n';
445
+ const __vite_glob_0_4 = '---\nname: yorz-spec\ndescription: Drive YorZ spec docs through plan / tasks / execute stages with deterministic state updates.\n---\n\n# YorZ Spec Drive Skill\n\n将单个 YorZ spec 文档作为状态机执行,围绕 `plan` / `tasks` / `execute` 三阶段推进,全部任务完成后进入终止态 `done`,把状态持续写回文档。md 是单一真相;Agent 持续推进直至阻塞(待确认项、决策或执行失败)或收尾为 `done` 才退出。\n\n## 如何使用本 skill\n\n本 skill 文档结构精简为 2 个核心文件;每次接到 spec 任务时,**按以下顺序**按需 Read:\n\n1. **本文档(SKILL.md)**:输入约定、frontmatter 规范、格式约定、自动模式判定、全局硬约束、lint 硬约束、持续推进约束。\n2. **[stages.md](./stages.md)**:plan / tasks / execute / new-spec 四阶段流程与正面示例。\n\n[mermaid.md](./mermaid.md) 与 `references/` 目录**仅按需 Read**:只有在判断当前阶段需要输出 mermaid 图表「升维」时才加载,纯状态推进任务不必读取,以节省 context。**plan 阶段的「图形化补充」收尾子步骤**(见 stages.md)会针对 `现状分析`/`技术方案` 两节强制加载 mermaid.md 补图;补图须遵循其核心原则**「图优先、精确信息折叠」**——表层给图,精确细节折叠进 `<details>` 精确层。\n\n## 输入约定\n\n- `spec_path`:**可选**,目标 spec 文档路径。\n - 显式给出时:典型位置 `docs/specs/*.md` 或 `.yorz/specs/<id>/spec.md`,直接更新该 spec。\n - 指令形式 `/yorz-spec @<spec path> <内容>` 中的 `@` 只是 YorZ 的文件引用标记,**不属于路径本身**;去掉 `@` 后是**项目根目录**(即当前工作目录)下的相对路径,不要相对本 skill 文件所在目录或用户主目录解析。\n - 缺省时按以下顺序解析:\n 1. **从 session 上下文恢复**:扫描当前会话历史中已出现/已读写过的 spec 文档路径;存在多个时优先选取最近一次被读写的;若仍有歧义,向用户确认。\n 2. **从当前 prompt 创建**:若上下文中没有任何 spec 文档,按 [新建 spec 流程](./stages.md#new-spec) 生成路径与骨架。\n- spec 目录默认为 `.yorz/specs`,可由 `<ProjectRoot>/.yorz/config.json` 的 `specsDir` 字段覆盖。\n- `mode`:可选,`plan|tasks|execute|auto`,默认 `auto`。\n- 批注前缀:`!!!`\n\n## frontmatter 规范\n\n每个 spec md 顶部必须有 YAML frontmatter:\n\n```yaml\n---\nstage: plan # plan | tasks | execute | done\nlast_action: \'简述上一次动作\'\nupdated_at: \'2026-06-14 15:42:07\' # 本地秒级字符串\nsummary: \'一句话概要,≤ 200 字符\'\n---\n```\n\n- 字段顺序固定为 `stage` → `last_action` → `updated_at` → `summary`,每个字段独占一行,禁止嵌套与额外字段。\n- `updated_at` 使用本地秒级 `YYYY-MM-DD HH:mm:ss` 字符串。\n- 缺失任意字段时,先补齐再继续。\n\n## Markdown 格式化约定\n\n- 每次写回 spec md 后,**应在仓库支持的条件下运行 Markdown formatter**(优先 `npx prettier --write <spec_path>`);未配置则跳过。\n- formatter 必须保留 YAML frontmatter 不变。\n- 任务清单仅使用单层 `- [ ]` / `- [x]`,避免缩进与嵌套。\n- 二、三级标题必须带层级编号,便于外部按章节号定位:\n - 每次写回前按 body 中 `## ` / `### ` 出现顺序重新编号。\n - 二级标题 `## N. 标题`(N 从 1 起);三级标题 `### N.M 标题`(M 在所属二级下从 1 起)。\n - 该规则不影响 frontmatter 与一级标题(`# `)。\n\n## 自动模式判定顺序(严格按顺序)\n\n`mode=auto` 时,**严格按顺序**判定,命中即停:\n\n1. 若 `spec_path` 缺省:从 session 上下文恢复 spec 路径;恢复失败进入 [新建 spec 流程](./stages.md#new-spec) 并以 `plan` 起步。\n2. **优先扫描 `## 追加任务` 中是否存在 `[open]` 条目**:存在即视为"新输入"信号,进入 `plan`(重开流程)。\n3. 若识别到新增/扩展需求或新增 bug,进入 `plan`(重开流程)。\n4. 若文档存在任意 `!!!` 批注,进入 [tasks](./stages.md#tasks)。\n5. 若 `## 待确认项` 下存在有效条目(非空态 `_暂无_`),停止推进并等待人工批注。\n6. 若 `stage` 已是 `done` 且上述 1–5 均未命中(无新输入/批注/待确认项),直接停止:终止态不再自动推进。\n7. 若存在未完成的**非 `[manual]`** 任务(`- [ ]` 且非 `- [ ] [manual]`),进入 [execute](./stages.md#execute)。\n8. 若任务清单已就绪且不存在未完成的非 manual 任务(走到此处已排除 `[open]`/新需求/批注/待确认项):将 `stage` 置为 `done` 并停止推进(终止态)。\n9. 若 `## 技术实现方案` 为空或明显不完整,进入 [plan](./stages.md#plan)。\n10. 进入 [tasks](./stages.md#tasks) 生成或刷新详细任务清单。\n\n> 章节名 `## 待确认项` 为新名,parser/lint 兼容旧名 `## 待确认问题`;存量 spec 保留旧名仍可识别,新建/重写一律用 `待确认项`。\n>\n> 待确认项判定:章节内存在任一 `### ` 三级标题即视为未决;仅有 `_暂无_` 或整章为空即视为无未决条目;存在未决条目时禁止进入 execute。\n>\n> `done` 判定忽略 `- [ ] [manual]` 人工确认项:即使 manual 项仍未勾选,只要其余任务全部完成、且无待确认项/批注/`[open]`,即可收尾为 `done`。\n\n## 全局硬约束\n\n### 通用\n\n- 仅更新目标 spec 文档,不修改无关文档。\n- 每次写回文档都必须更新 frontmatter 元信息。\n- 执行任务时若涉及代码改动,只改动与任务相关的最小范围。\n\n### 章节建议\n\n初始化 spec 时**建议**创建以下章节(`## 追加任务` `## 用户批注` 为可选,由用户触发追加时懒插入):\n\n- `## 现状分析` / `## 技术实现方案` / `## 待确认项` / `## 任务清单` / `## 执行记录`\n\n> lint 不再强制校验章节齐全;但 routing 按章节名查找时,章节不存在视为空态。\n\n### 变更重开流程\n\n**触发条件:**\n\n- 在已有 spec 中识别到"新增/扩展需求"或"新增 bug",将 `stage` 切回 `plan`,`last_action` 记录"变更重开流程"。\n- `## 追加任务` 存在 `- [open] [feat|refct|fix] ...` 条目时,将 `stage` 切回 `plan`。\n\n**重开后必须做的事:**\n\n- 仅针对新增内容重新走完 `plan → tasks → execute`。\n- 未受影响的既有结论/任务可保留,受影响任务必须重新评估。\n- plan 阶段消费追加任务条目时,**不修改** `[open]` 状态标记。\n\n## 输出优先级\n\n1. 先保证状态正确(frontmatter 元信息更新)。\n2. 再保证结构正确(列表格式、任务格式、候选项)。\n3. 最后保证内容质量(可执行、可验收、可追踪)。\n\n## 写回后的 lint 硬约束\n\n任何阶段完成对 `spec.md` 的写入后,Agent **必须**通过 Bash 运行 `yorz lint <path> --format json`,并 parse stdout。\n\n- `errorCount === 0` 时视为通过。\n- 存在 `severity: error` 时按 `ruleId` + `message` + `line` 定位并修改,然后重新运行 lint。\n- 同一文件 lint 连续失败达到 **3 次**,将偏差作为 `## 待确认项` 条目写入 spec.md 后退出,等待人工干预。\n- `warn` 级 finding 不阻断推进,但应尽力消除。\n\n## 持续推进硬约束\n\nAgent 在 plan / tasks / execute 任意阶段,**只允许**因以下"合法阻塞"退出当轮:\n\n1. `## 待确认项` 章节存在非 `暂无` 条目;\n2. spec md 内存在任何 `!!!` 批注;\n3. 识别到新增/扩展需求或新增 bug,按变更重开流程切回 `plan`;\n4. 执行外部命令 / 工具调用失败,需要用户做不可推断的决断。\n\n**显式禁止**以下"元确认"行为:\n\n- 以"任务很多 / 改动较大 / 范围广 / 时间长 / 风险高"为由询问"是否继续 / 是否执行 / 是否同意此方案"。\n- 在 execute 阶段中段汇报进度并要求用户确认下一步。\n- 反问类如"要不要我开始 / 是否继续下一项"等问句。\n\n替代约定:tasks/execute 阶段若产生新疑问,**必须**作为新条目写回 `## 待确认项` 并按变更重开流程退出。\n\n兜底:对外部世界有副作用、不可逆的命令(如 `git push`、`git reset --hard`、`rm -rf`、修改 CI 配置等破坏性操作)仍按系统默认安全准则处理。\n\n## 与 YorZ 工作流的关联\n\n- 本 skill 不依赖 YorZ Service / GUI 也能独立工作:Agent 直接读写 `spec_path` 即可。\n- 当 YorZ Service 在线时,Agent 写回 md 会被 Service 的 FS Watcher 感知并推送给 GUI。\n- 如需用户介入,Agent 把信息写回文档对应章节后立即退出,由用户人工补齐 `!!!` 批注,再由 CLI / Service 重新拉起 Agent 继续执行。\n';
446
446
  const __vite_glob_0_5 = "---\nstage: execute\nlast_action: 主任务完成,追加 1 项 [open] 待处理\nupdated_at: 2026-06-20\nsummary: HELLO.txt 已经创建;追加任务:在 HELLO.txt 末尾追加一行 `goodbye world\\n`。\n---\n\n# HELLO 占位\n\n## 1. 背景\n\n希望仓库根有占位文件,便于演示。\n\n## 2. 需求\n\n- 在仓库根创建 `HELLO.txt`,内容 `hello world\\n`\n\n## 3. 现状分析\n\n`HELLO.txt` 已经存在,内容 `hello world\\n`。\n\n## 4. 技术实现方案\n\n- 直接写入 `HELLO.txt`,内容 `hello world\\n`。\n\n## 5. 待确认问题\n\n- 暂无\n\n## 6. 任务清单\n\n- [x] 在仓库根新建 `HELLO.txt`,内容 `hello world\\n`,验收点:文件存在且字节数 = 12。\n\n## 7. 追加任务\n\n- [open] 在 `HELLO.txt` 末尾追加一行 `goodbye world\\n`,验收点:最终文件含 2 行、共 26 字节。\n\n## 8. 执行记录\n\n- 2026-06-20 创建 `HELLO.txt`,内容 `hello world\\n`。\n";
447
447
  const __vite_glob_0_6 = '{\n "module": "execute"\n}\n';
448
448
  const __vite_glob_0_7 = "---\nstage: execute\nlast_action: 任务已就绪,等待执行\nupdated_at: 2026-06-20\nsummary: 在仓库根新增 HELLO.txt 占位文件,内容为 hello world。\n---\n\n# HELLO 占位\n\n## 1. 背景\n\n希望在仓库根有一个 HELLO.txt 占位文件,用于演示 execute 阶段的勾选行为。\n\n## 2. 需求\n\n- 在仓库根创建 `HELLO.txt`,内容固定为 `hello world\\n`\n\n## 3. 现状分析\n\n仓库根目前没有 `HELLO.txt`,需新建。\n\n## 4. 技术实现方案\n\n- 直接写入 `HELLO.txt`,内容 `hello world\\n`,无 BOM、无尾随空行。\n\n## 5. 待确认问题\n\n- 暂无\n\n## 6. 任务清单\n\n- [ ] 在仓库根新建 `HELLO.txt`,内容为 `hello world\\n`,验收点:文件存在且内容字节数 = 12。\n\n## 7. 追加任务\n\n- 暂无\n\n## 8. 执行记录\n\n- 暂无\n";
@@ -455,7 +455,7 @@ const __vite_glob_0_13 = '{\n "module": "routing"\n}\n';
455
455
  const __vite_glob_0_14 = "---\nstage: plan\nlast_action: 已生成 plan,等待用户批注待确认问题\nupdated_at: 2026-06-20\nsummary: 给项目新增「最近访问」入口,挂在 Specs 列表上方;数据本地存储;上限 10 条;提供清空按钮。\n---\n\n# 最近访问入口\n\n## 1. 背景\n\n用户反馈每次打开应用都要在 spec 列表里翻找昨天碰过的几个 spec。希望首屏看到一个「最近访问」入口直接跳转。\n\n## 2. 需求\n\n- 新增「最近访问」入口,列出用户近期访问过的 spec\n- 决策范围:挂载位置、数据来源、上限条数、清空策略\n\n## 3. 现状分析\n\nGUI 主导航当前只有 `Specs`、`Settings`;Spec 列表按 mtime 排序,没有访问记录。\n\n## 4. 技术实现方案\n\n- 在 `Specs` 列表上方插入「最近访问」区域,最多展示 10 条,按访问时间倒序。\n- 访问记录写入浏览器 `localStorage`,key 为 `yorz:recent-specs`,存 spec id + 最后访问时间。\n- 列表右侧提供「清空」按钮,弹确认对话框后清空 key。\n\n## 5. 待确认问题\n\n- 「最近访问」入口挂在哪里?\n - 挂在 Specs 列表上方\n - 挂在主导航单独 tab\n - 挂在 Settings 页面\n !!!选「挂在 Specs 列表上方」\n- 访问记录的存储位置?\n - localStorage\n - 服务端数据库\n !!!选 localStorage,本地够用\n- 最多保留多少条?\n - 10 条\n - 20 条\n - 50 条\n !!!选 10 条\n- 是否提供「清空」按钮?\n - 提供,弹确认对话框\n - 不提供\n !!!提供,弹确认对话框\n\n## 6. 任务清单\n\n- 暂无\n\n## 7. 追加任务\n\n- 暂无\n\n## 8. 执行记录\n\n- 暂无\n";
456
456
  const __vite_glob_0_15 = '{\n "module": "tasks"\n}\n';
457
457
  const __vite_glob_0_16 = '{\n "$schema": "https://json-schema.org/draft-07/schema#",\n "version": 2,\n "modules": [\n {\n "module": "SKILL",\n "file": "SKILL.md",\n "relatedCases": ["reopen-on-new-requirement", "append-task-state"]\n },\n {\n "module": "stages",\n "file": "stages.md",\n "relatedCases": [\n "plan-candidates",\n "tasks-consume-annotations",\n "execute-checkbox-flip",\n "append-task-state",\n "new-spec-skeleton"\n ]\n },\n {\n "module": "mermaid",\n "file": "mermaid.md",\n "relatedCases": []\n }\n ]\n}\n';
458
- const __vite_glob_0_17 = '# Mermaid 图表输出指南\n\n本模块指导 Agent 在 spec 文档的各个阶段(plan / tasks / execute)按需输出 mermaid 图表,对信息进行"升维",提升可读性与决策效率。\n\n## 核心原则:图优先、精确信息折叠\n\n一切图形化决策先服从这条贯穿性原则:**spec 表层图形优先,精确信息折叠收纳**。\n\n- **图形目标**:让读者快速建立三类认知——核心逻辑流程、模块结构、模块间交互。图是「认知层」。\n- **图形边界**:图中**不写**文件路径、代码行号、源码/伪代码全文、精确函数签名;这些属于「精确层」,不该塞进节点标签。\n- **分层表达**:章节表层先给图(认知层),再把 Agent 实施所需的精确信息折叠进「精确层」承载物(见[精确信息折叠](#精确信息折叠));图与折叠块**互补而非重复**。\n\n```mermaid\nflowchart TD\n A[章节含复杂信息] --> B{信息属性}\n B -->|流程 分支 循环| C[flowchart]\n B -->|类型 接口 关系| D[classDiagram]\n B -->|模块交互 时序| E[sequenceDiagram]\n B -->|模块结构 影响面| F[结构图 + 语义配色]\n C --> G[表层放图 认知层]\n D --> G\n E --> G\n F --> G\n G --> H[路径 行号 源码 类型全文 折叠进精确层]\n```\n\n## 图表选型表\n\n根据信息特征选择最合适的图表类型。**优先选择最能清晰传达信息的类型**,而非最复杂的类型。每行"替代什么"列提示:该场景下应**用图替代**的低效文本形式。\n\n| 信息特征 | 推荐图表 | 替代什么 | 典型 spec 落点 |\n| -------------------------- | -------------------------------------------------------- | ---------------------- | ---------------------------- |\n| 流程、步骤、决策分支 | [**flowchart**](references/flowchart.md) | md 列表 + 文字描述 | 核心代码逻辑、执行流程 |\n| 组件间交互、消息传递 | [**sequenceDiagram**](references/sequenceDiagram.md) | 编号文字描述交互过程 | 现状分析链路、技术方案交互 |\n| 状态机、阶段流转 | [**stateDiagram**](references/stateDiagram.md) | 文字列举状态变更条件 | 关键状态关系、spec 阶段流转 |\n| 类结构、类型定义、继承关系 | [**classDiagram**](references/classDiagram.md) | TS/代码片段 | 数据模型、模块结构、类型设计 |\n| 数据库表、实体关系 | [**erDiagram**](references/entityRelationshipDiagram.md) | 建表 SQL 或文字描述 | 数据模型设计 |\n| 系统架构、组件依赖 | [**architecture**](references/architecture.md) | 文字描述系统组成 | 现状分析架构、总体架构 |\n| 层级逻辑、数据结构 | [**treeView-beta**](references/treeView.md) | ASCII 字符树、嵌套列表 | 模块层级、文件结构、AST 结构 |\n| 层级结构 + 占比分布 | [**treemap-beta**](references/treemap.md) | ASCII 字符树、嵌套列表 | 模块层级量级、文件结构占比 |\n| 层级关系、知识结构 | [**mindmap**](references/mindmap.md) | 缩进列表 | 需求拆解、影响面分析 |\n| 历史事件、变更时间线 | [**timeline**](references/timeline.md) | 文字按日期罗列 | 执行记录、变更历史 |\n| Git 分支、合并策略 | [**gitgraph**](references/gitgraph.md) | 文字描述分支操作 | Git 工作流设计 |\n| 事件建模、系统信息流 | [**eventmodeling**](references/eventmodeling.md) | 文字描述事件流 | 事件驱动架构设计、CQRS 模型 |\n| 二进制协议、数据包结构 | [**packet**](references/packet.md) | 文字描述协议字段 | 二进制协议分析、网络包结构 |\n| 方案候选决策、风险象限 | [**quadrantChart**](references/quadrantChart.md) | 文字描述优劣对比 | 候选方案决策、风险评估 |\n| 多方案能力对比、权衡 | [**radar**](references/radar.md) | 文字描述能力对比 | 架构方案对比、技术选型权衡 |\n| 多模块职责在复杂流程中 | [**swimlane**](references/swimlanes.md) | 普通流程图 | 复杂业务流程、跨团队协作 |\n| 统计数据、趋势分析 | [**xyChart**](references/xyChart.md) | 文字描述数据 | 变更影响分析、日志统计 |\n\n## 场景优先级\n\n### 必须用图(高优先级)\n\n以下场景**必须**使用对应图表替代纯文字描述:\n\n1. **类型定义与关系** → `classDiagram`:展示接口/类型的属性、方法、继承与组合关系。不要用 TS 代码块替代——类图能直观展现关系拓扑。\n - **可判定触发词**:见到「接口/类型/entry 结构的定义」「一段 TS interface / type / class 全文」「数据模型字段罗列」——必须出 classDiagram。\n2. **核心业务流程/算法逻辑** → `flowchart`:包含分支判断、循环、并行路径的逻辑。不要用 md 列表 + 文字逐步描述——flowchart 的分支视觉远比缩进列表清晰。\n - **可判定触发词**:见到「分步/编号叙述 + 分支策略」「命令/函数的实现流程散文」「先…再…否则…」这类流程散文——必须出 flowchart,别退回有序列表。\n3. **状态机与生命周期** → `stateDiagram`:实体在不同状态间的流转与触发条件。不要用文字列举状态——状态图能展现并发状态与守卫条件。\n4. **层级逻辑与数据结构** → `treeView-beta`:模块/文件/目录的层级关系、AST 结构等。不要用 ASCII 字符树——treeView 更清晰且可渲染(mermaid fence 首行须写 `treeView-beta`,与 lint 白名单一致)。\n\n> **代码/类型全文折叠配套(强约束)**:类型定义、实现源码、伪代码在图中**只表达结构与关系**;其**全文**移入[精确信息折叠](#精确信息折叠)承载,**严禁** classDiagram 与等价 TS 代码块并列重复贴出。\n\n### 推荐用图(中优先级)\n\n5. **组件交互** → `sequenceDiagram`:多组件间的消息传递、API 调用时序。\n6. **数据模型** → `erDiagram`:数据库表结构与外键关系。\n7. **系统架构** → `architecture`:组件依赖与分组、部署拓扑。\n8. **需求拆解** → `mindmap`:影响面分析、需求分解为子项。\n9. **多模块职责流程** → `swimlane`:复杂业务流程中跨模块/团队的分工与职责边界。\n\n### 按需用图(低优先级)\n\n10. **变更历史** → `timeline`:执行记录中按时间排列关键事件。\n11. **Git 工作流** → `gitgraph`:描述分支策略与合并流程。\n12. **层级占比** → `treemap-beta`:需要同时展示层级结构与各部分量级/占比时(mermaid fence 首行须写 `treemap-beta`)。\n13. **方案候选决策** → `quadrantChart`:Review/方案选择时做象限分析。\n14. **多方案能力对比** → `radar`:架构选型时做多维度能力雷达对比。\n15. **统计数据** → `xyChart`:日志/变更影响/性能数据等统计可视化。\n16. **事件建模** → `eventmodeling`:事件驱动系统设计时描述信息流。\n17. **二进制协议** → `packet`:协议分析时展示数据包字段结构。\n\n## 各阶段落点指导\n\n### plan 阶段(收尾「图形化补充」子步骤)\n\n> 出图作为 plan 的**独立收尾子步骤**执行(见 [stages.md](./stages.md) 的「图形化补充」):方案文字定稿后,专门回看下列两节补图,**仅覆盖 `现状分析` / `技术方案` 两节**,不扩展到其它章节。\n\n- **## 现状分析**:用 **architecture** 图描述现有系统结构与组件依赖;用 **flowchart** 描述当前业务流程或数据流。\n- **## 技术实现方案**:用 **flowchart** 描述方案逻辑与决策分支;用 **sequenceDiagram** 描述模块间交互与时序;用 **architecture** 图描述改造后的总体架构。\n- **## 技术实现方案 · 兼容性/影响范围**:凡方案含「被变更模块的现有组成结构 + 影响面」,必须出**结构图**(classDiagram 或 flowchart)展示现有模块组成,并按下方[影响面语义配色](#影响面语义配色强约定)标注 breaking / affected 区域。此落点属 plan 技术方案(非 review),plan 收尾补图工序必须覆盖。\n\n#### 影响面语义配色(强约定)\n\n统一语义色,用 `classDef` 承载——**红 = breaking change 区域,黄 = 受影响区域**;classDiagram 与 flowchart 均按下列范式上色。\n\nflowchart 范式:\n\n```mermaid\nflowchart TB\n subgraph 现有模块结构\n A[模块 A 不变]\n B[模块 B breaking]\n C[模块 C 受影响]\n end\n classDef breaking fill:#ffdddd,stroke:#e03131,color:#c92a2a\n classDef affected fill:#fff3bf,stroke:#f08c00,color:#e67700\n class B breaking\n class C affected\n```\n\nclassDiagram 范式:\n\n```mermaid\nclassDiagram\n class ModuleA { +stable() }\n class ModuleB { +changed() }\n class ModuleC { +touched() }\n ModuleA --> ModuleB\n ModuleB --> ModuleC\n classDef breaking fill:#ffdddd,stroke:#e03131,color:#c92a2a\n classDef affected fill:#fff3bf,stroke:#f08c00,color:#e67700\n class ModuleB:::breaking\n class ModuleC:::affected\n```\n\n### review 阶段\n\n- **风险提示**:用 **quadrantChart** 按风险概率 × 影响程度做象限分布,直观标识高优风险项;用 **mindmap** 将风险按类别(安全 / 性能 / 兼容性 / 数据一致性等)层级展开,快速纵览全貌。\n- **变更范围**:用 **mindmap** 以变更源为根节点展开影响面(受影响模块、文件、接口、配置等);当变更沿调用链传播时,用 **flowchart** 绘制波及路径,标明直接与间接影响层级。\n- **变更前后差异**:用 **flowchart** 并排呈现 Before / After 流程,关键变更点用样式高亮(如红色边框标记新增节点、删除线标记移除节点);涉及数据模型变更时用 **classDiagram** 或 **erDiagram** 对比结构差异;涉及配置/枚举等层级数据变化时用 **treeView** 做树形 Diff。\n\n## 输出规范\n\n1. **代码块包裹**:mermaid 代码必须用 ` ```mermaid ` 代码块包裹。\n2. **语法正确**:确保代码可直接渲染,无语法错误。不确定时参考 `references/` 对应文档。\n3. **语义化命名**:节点、变量使用有意义的名称(如 `Skill输出` 而非 `A`),便于理解。\n4. **适度样式**:需要时包含样式(颜色、形状区分)以提升可读性,但不要过度装饰。\n5. **中文友好**:节点文本可使用中文;ID 使用英文或拼音以避免 mermaid 解析问题。\n\n### 示例\n\n```mermaid\nflowchart LR\n A[Skill 输出 mermaid code] --> B[spec.md]\n B -->|SSE 推送| C[GUI SpecDetail]\n C --> D[fence rule 识别]\n D --> E[异步渲染图形]\n```\n\n## 精确信息折叠\n\n「图优先、精确信息折叠」的落地承载。GUI 渲染器已开启**受控 HTML**:仅放行 `<details>` / `<summary>`,故用原生 `<details>` 折叠精确层。\n\n**该折叠的信息(精确层)**:文件路径与行号、接口/类型/类的全文定义、实现源码或伪代码、精确函数签名、逐字段配置清单、冗长的命令样例。这些是 Agent 实施所需、但会干扰认知的细节。\n\n**不该折叠的信息(认知层)**:章节表层的图、方案要点、决策结论——保持直接可见。\n\n写法(`<details>` 前后留空行,内部 markdown 正常解析):\n\n```markdown\n<details>\n<summary>add.ts 类型定义与实现细节</summary>\n\n- `src/cli/add.ts:42` 新增 `interface AddOptions { path: string; force?: boolean }`\n- 具体源码 / 逐字段说明 …\n\n</details>\n```\n\n> 注意:`<details>`/`<summary>` 之外的原始 HTML(如 `<div>`、`<script>`)会被 GUI 转义为文本,不要依赖其它标签。\n\n## 节制原则\n\n- **只在信息确实复杂、图形能显著提升可读性时输出**,避免为出图而出图。\n- 简单的线性列表、两三步操作无需图表。\n- 单个章节不宜超过 2 张图,过多图表反而降低可读性。\n- 图表应**补充**文字描述而非替代——文字说明图表无法表达的细节。\n- 如果不确定是否需要图表,优先不画,在文字分析足够清晰时省略。\n';
458
+ const __vite_glob_0_17 = '# Mermaid 图表输出指南\n\n本模块指导 Agent 在 spec 文档的各个阶段(plan / tasks / execute)按需输出 mermaid 图表,对信息进行"升维",提升可读性与决策效率。\n\n## 核心原则:图优先、精确信息折叠\n\n一切图形化决策先服从这条贯穿性原则:**spec 表层图形优先,精确信息折叠收纳**。\n\n- **图形目标**:让读者快速建立三类认知——核心逻辑流程、模块结构、模块间交互。图是「认知层」。\n- **图形边界**:图中**不写**文件路径、代码行号、源码/伪代码全文、精确函数签名;这些属于「精确层」,不该塞进节点标签。\n- **分层表达**:章节表层先给图(认知层),再把 Agent 实施所需的精确信息折叠进「精确层」承载物(见[精确信息折叠](#精确信息折叠));图与折叠块**互补而非重复**。\n\n```mermaid\nflowchart TD\n A[章节含复杂信息] --> B{信息属性}\n B -->|流程 分支 循环| C[flowchart]\n B -->|类型 接口 关系| D[classDiagram]\n B -->|模块交互 时序| E[sequenceDiagram]\n B -->|模块结构 影响面| F[结构图 + 语义配色]\n C --> G[表层放图 认知层]\n D --> G\n E --> G\n F --> G\n G --> H[路径 行号 源码 类型全文 折叠进精确层]\n```\n\n## 图表选型表\n\n根据信息特征选择最合适的图表类型。**优先选择最能清晰传达信息的类型**,而非最复杂的类型。每行"替代什么"列提示:该场景下应**用图替代**的低效文本形式。\n\n| 信息特征 | 推荐图表 | 替代什么 | 典型 spec 落点 |\n| -------------------------- | -------------------------------------------------------- | ------------------------------ | ------------------------------------------------ |\n| 流程、步骤、决策分支 | [**flowchart**](references/flowchart.md) | md 列表 + 文字描述 | 核心代码逻辑、执行流程 |\n| 组件间交互、消息传递 | [**sequenceDiagram**](references/sequenceDiagram.md) | 编号文字描述交互过程 | 现状分析链路、技术方案交互 |\n| 状态机、阶段流转 | [**stateDiagram**](references/stateDiagram.md) | 文字列举状态变更条件 | 关键状态关系、spec 阶段流转 |\n| 类结构、类型定义、继承关系 | [**classDiagram**](references/classDiagram.md) | TS/代码片段 | 数据模型、模块结构、类型设计 |\n| 数据库表、实体关系 | [**erDiagram**](references/entityRelationshipDiagram.md) | 建表 SQL 或文字描述 | 数据模型设计 |\n| 系统架构、组件依赖 | [**architecture**](references/architecture.md) | 文字描述系统组成 | 现状分析架构、总体架构 |\n| 多层级关系、多层级信息结构 | [**flowchart**](references/flowchart.md) | 多层级缩进列表,ASCII 思维导图 | 表达多层级结构信息、需求拆解、用例图、影响面分析 |\n| 层级结构 + 占比分布 | [**treemap-beta**](references/treemap.md) | ASCII 字符树、嵌套列表 | 模块层级量级、文件结构占比 |\n| 历史事件、变更时间线 | [**timeline**](references/timeline.md) | 文字按日期罗列 | 执行记录、变更历史 |\n| 事件建模、系统信息流 | [**eventmodeling**](references/eventmodeling.md) | 文字描述事件流 | 事件驱动架构设计、CQRS 模型 |\n| 二进制协议、数据包结构 | [**packet**](references/packet.md) | 文字描述协议字段 | 二进制协议分析、网络包结构 |\n| 方案候选决策、风险象限 | [**quadrantChart**](references/quadrantChart.md) | 文字描述优劣对比 | 候选方案决策、风险评估 |\n| 多方案能力对比、权衡 | [**radar**](references/radar.md) | 文字描述能力对比 | 架构方案对比、技术选型权衡 |\n| 多模块职责在复杂流程中 | [**swimlane**](references/swimlanes.md) | 普通流程图 | 复杂业务流程、跨团队协作 |\n| 统计数据、趋势分析 | [**xyChart**](references/xyChart.md) | 文字描述数据 | 变更影响分析、日志统计 |\n\n## 场景优先级\n\n### 必须用图(高优先级)\n\n以下场景**必须**使用对应图表替代纯文字描述:\n\n1. **类型定义与关系** → `classDiagram`:展示接口/类型的属性、方法、继承与组合关系。不要用 TS 代码块替代——类图能直观展现关系拓扑。\n - **可判定触发词**:见到「接口/类型/entry 结构的定义」「一段 TS interface / type / class 全文」「数据模型字段罗列」——必须出 classDiagram。\n2. **核心业务流程/算法逻辑** → `flowchart`:包含分支判断、循环、并行路径的逻辑。不要用 md 列表 + 文字逐步描述——flowchart 的分支视觉远比缩进列表清晰。\n - **可判定触发词**:见到「分步/编号叙述 + 分支策略」「命令/函数的实现流程散文」「先…再…否则…」这类流程散文——必须出 flowchart,别退回有序列表。\n3. **状态机与生命周期** → `stateDiagram`:实体在不同状态间的流转与触发条件。不要用文字列举状态——状态图能展现并发状态与守卫条件。\n\n> **代码/类型全文折叠配套(强约束)**:类型定义、实现源码、伪代码在图中**只表达结构与关系**;其**全文**移入[精确信息折叠](#精确信息折叠)承载,**严禁** classDiagram 与等价 TS 代码块并列重复贴出。\n\n### 推荐用图(中优先级)\n\n5. **组件交互** → `sequenceDiagram`:多组件间的消息传递、API 调用时序。\n6. **数据模型** → `erDiagram`:数据库表结构与外键关系。\n7. **系统架构** → `architecture`:组件依赖与分组、部署拓扑。\n8. **需求拆解** → `flowchart`:影响面分析、需求分解为子项。\n9. **多模块职责流程** → `swimlane`:复杂业务流程中跨模块/团队的分工与职责边界。\n\n### 按需用图(低优先级)\n\n10. **变更历史** → `timeline`:执行记录中按时间排列关键事件。\n11. **层级占比** → `treemap-beta`:需要同时展示层级结构与各部分量级/占比时(mermaid fence 首行须写 `treemap-beta`)。\n12. **方案候选决策** → `quadrantChart`:Review/方案选择时做象限分析。\n13. **多方案能力对比** → `radar`:架构选型时做多维度能力雷达对比。\n14. **统计数据** → `xyChart`:日志/变更影响/性能数据等统计可视化。\n15. **事件建模** → `eventmodeling`:事件驱动系统设计时描述信息流。\n16. **二进制协议** → `packet`:协议分析时展示数据包字段结构。\n\n## 各阶段落点指导\n\n### plan 阶段(收尾「图形化补充」子步骤)\n\n> 出图作为 plan 的**独立收尾子步骤**执行(见 [stages.md](./stages.md) 的「图形化补充」):方案文字定稿后,专门回看下列两节补图,**仅覆盖 `现状分析` / `技术方案` 两节**,不扩展到其它章节。\n\n- **## 现状分析**:用 **architecture** 图描述现有系统结构与组件依赖;用 **flowchart** 描述当前业务流程或数据流。\n- **## 技术实现方案**:用 **flowchart** 描述方案逻辑与决策分支;用 **sequenceDiagram** 描述模块间交互与时序;用 **architecture** 图描述改造后的总体架构。\n- **## 技术实现方案 · 兼容性/影响范围**:凡方案含「被变更模块的现有组成结构 + 影响面」,必须出**结构图**(classDiagram 或 flowchart)展示现有模块组成,并按下方[影响面语义配色](#影响面语义配色强约定)标注 breaking / affected 区域。此落点属 plan 技术方案(非 review),plan 收尾补图工序必须覆盖。\n\n#### 影响面语义配色(强约定)\n\n统一语义色,用 `classDef` 承载——**红 = breaking change 区域,黄 = 受影响区域**;classDiagram 与 flowchart 均按下列范式上色。\n\nflowchart 范式:\n\n```mermaid\nflowchart TB\n subgraph 现有模块结构\n A[模块 A 不变]\n B[模块 B breaking]\n C[模块 C 受影响]\n end\n classDef breaking fill:#ffdddd,stroke:#e03131,color:#c92a2a\n classDef affected fill:#fff3bf,stroke:#f08c00,color:#e67700\n class B breaking\n class C affected\n```\n\nclassDiagram 范式:\n\n```mermaid\nclassDiagram\n class ModuleA { +stable() }\n class ModuleB { +changed() }\n class ModuleC { +touched() }\n ModuleA --> ModuleB\n ModuleB --> ModuleC\n classDef breaking fill:#ffdddd,stroke:#e03131,color:#c92a2a\n classDef affected fill:#fff3bf,stroke:#f08c00,color:#e67700\n class ModuleB:::breaking\n class ModuleC:::affected\n```\n\n### review 阶段\n\n- **风险提示**:用 **quadrantChart** 按风险概率 × 影响程度做象限分布,直观标识高优风险项;用 **mindmap** 将风险按类别(安全 / 性能 / 兼容性 / 数据一致性等)层级展开,快速纵览全貌。\n- **变更范围**:用 **mindmap** 以变更源为根节点展开影响面(受影响模块、文件、接口、配置等);当变更沿调用链传播时,用 **flowchart** 绘制波及路径,标明直接与间接影响层级。\n- **变更前后差异**:用 **flowchart** 并排呈现 Before / After 流程,关键变更点用样式高亮(如红色边框标记新增节点、删除线标记移除节点);涉及数据模型变更时用 **classDiagram** 或 **erDiagram** 对比结构差异;涉及配置/枚举等层级数据变化时用 **treeView** 做树形 Diff。\n\n## 输出规范\n\n1. **代码块包裹**:mermaid 代码必须用 ` ```mermaid ` 代码块包裹。\n2. **语法正确**:确保代码可直接渲染,无语法错误。不确定时参考 `references/` 对应文档。\n3. **语义化命名**:节点、变量使用有意义的名称(如 `Skill输出` 而非 `A`),便于理解。\n4. **适度样式**:需要时包含样式(颜色、形状区分)以提升可读性,但不要过度装饰。\n5. **中文友好**:节点文本可使用中文;ID 使用英文或拼音以避免 mermaid 解析问题。\n\n### 示例\n\n```mermaid\nflowchart LR\n A[Skill 输出 mermaid code] --> B[spec.md]\n B -->|SSE 推送| C[GUI SpecDetail]\n C --> D[fence rule 识别]\n D --> E[异步渲染图形]\n```\n\n## 精确信息折叠\n\n「图优先、精确信息折叠」的落地承载。GUI 渲染器已开启**受控 HTML**:仅放行 `<details>` / `<summary>`,故用原生 `<details>` 折叠精确层。\n\n**该折叠的信息(精确层)**:文件路径与行号、接口/类型/类的全文定义、实现源码或伪代码、精确函数签名、逐字段配置清单、冗长的命令样例。这些是 Agent 实施所需、但会干扰认知的细节。\n\n**不该折叠的信息(认知层)**:章节表层的图、方案要点、决策结论——保持直接可见。\n\n写法(`<details>` 前后留空行,内部 markdown 正常解析):\n\n```markdown\n<details>\n<summary>add.ts 类型定义与实现细节</summary>\n\n- `src/cli/add.ts:42` 新增 `interface AddOptions { path: string; force?: boolean }`\n- 具体源码 / 逐字段说明 …\n\n</details>\n```\n\n> 注意:`<details>`/`<summary>` 之外的原始 HTML(如 `<div>`、`<script>`)会被 GUI 转义为文本,不要依赖其它标签。\n\n## 节制原则\n\n- **只在信息确实复杂、图形能显著提升可读性时输出**,避免为出图而出图。\n- 简单的线性列表、两三步操作无需图表。\n- 单个章节不宜超过 2 张图,过多图表反而降低可读性。\n- 图表应**补充**文字描述而非替代——文字说明图表无法表达的细节。\n- 如果不确定是否需要图表,优先不画,在文字分析足够清晰时省略。\n';
459
459
  const __vite_glob_0_18 = '> **Warning**\n>\n> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.\n>\n> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/architecture.md](../../packages/mermaid/src/docs/syntax/architecture.md).\n\n# Architecture Diagrams Documentation (v11.1.0+)\n\n> In the context of mermaid-js, the architecture diagram is used to show the relationship between services and resources commonly found within the Cloud or CI/CD deployments. In an architecture diagram, services (nodes) are connected by edges. Related services can be placed within groups to better illustrate how they are organized.\n\n## Example\n\n```mermaid-example\narchitecture-beta\n group api(cloud)[API]\n\n service db(database)[Database] in api\n service disk1(disk)[Storage] in api\n service disk2(disk)[Storage] in api\n service server(server)[Server] in api\n\n db:L -- R:server\n disk1:T -- B:server\n disk2:T -- B:db\n```\n\n```mermaid\narchitecture-beta\n group api(cloud)[API]\n\n service db(database)[Database] in api\n service disk1(disk)[Storage] in api\n service disk2(disk)[Storage] in api\n service server(server)[Server] in api\n\n db:L -- R:server\n disk1:T -- B:server\n disk2:T -- B:db\n```\n\n## Syntax\n\nThe building blocks of an architecture are `groups`, `services`, `edges`, and `junctions`.\n\nFor supporting components, icons are declared by surrounding the icon name with `()`, while labels are declared by surrounding the text with `[]`.\n\nTo begin an architecture diagram, use the keyword `architecture-beta`, followed by your groups, services, edges, and junctions. While each of the 3 building blocks can be declared in any order, care must be taken to ensure the identifier was previously declared by another component.\n\n### Groups\n\nThe syntax for declaring a group is:\n\n```\ngroup {group id}({icon name})[{title}] (in {parent id})?\n```\n\nPut together:\n\n```\ngroup public_api(cloud)[Public API]\n```\n\ncreates a group identified as `public_api`, uses the icon `cloud`, and has the label `Public API`.\n\nAdditionally, groups can be placed within a group using the optional `in` keyword\n\n```\ngroup private_api(cloud)[Private API] in public_api\n```\n\n### Services\n\nThe syntax for declaring a service is:\n\n```\nservice {service id}({icon name})[{title}] (in {parent id})?\n```\n\nPut together:\n\n```\nservice database1(database)[My Database]\n```\n\ncreates the service identified as `database1`, using the icon `database`, with the label `My Database`.\n\nIf the service belongs to a group, it can be placed inside it through the optional `in` keyword\n\n```\nservice database1(database)[My Database] in private_api\n```\n\n### Edges\n\nThe syntax for declaring an edge is:\n\n```\n{serviceId}{{group}}?:{T|B|L|R} {<}?--{>}? {T|B|L|R}:{serviceId}{{group}}?\n```\n\n#### Edge Direction\n\nThe side of the service the edge comes out of is specified by adding a colon (`:`) to the side of the service connecting to the arrow and adding `L|R|T|B`\n\nFor example:\n\n```\ndb:R -- L:server\n```\n\ncreates an edge between the services `db` and `server`, with the edge coming out of the right of `db` and the left of `server`.\n\n```\ndb:T -- L:server\n```\n\ncreates a 90 degree edge between the services `db` and `server`, with the edge coming out of the top of `db` and the left of `server`.\n\n#### Arrows\n\nArrows can be added to each side of an edge by adding `<` before the direction on the left, and/or `>` after the direction on the right.\n\nFor example:\n\n```\nsubnet:R --> L:gateway\n```\n\ncreates an edge with the arrow going into the `gateway` service\n\n#### Edges out of Groups\n\nTo have an edge go from a group to another group or service within another group, the `{group}` modifier can be added after the `serviceId`.\n\nFor example:\n\n```\nservice server[Server] in groupOne\nservice subnet[Subnet] in groupTwo\n\nserver{group}:B --> T:subnet{group}\n```\n\ncreates an edge going out of `groupOne`, adjacent to `server`, and into `groupTwo`, adjacent to `subnet`.\n\nIt\'s important to note that `groupId`s cannot be used for specifying edges and the `{group}` modifier can only be used for services within a group.\n\n### Aligning siblings (v\\<MERMAID_RELEASE_VERSION>+)\n\nWhen several services share similar edge topology (for example, three databases all connecting `R --> L:mcp`), the layout heuristic may collapse them onto the same coordinate so that two render on top of each other. The `align` directive declares that a set of services share a row (same y) or a column (same x), and forces them to spread along that axis.\n\n```\nalign row {idA} {idB} {idC} ...\nalign column {idA} {idB} ...\n```\n\nMembers must already be declared as services or junctions, and at least two members are required. Each `align` directive lives on its own line.\n\nPick the axis based on how the listed members are connected:\n\n- Use **`align column`** when the members connect to a common downstream node _via the same horizontal port pair_ (e.g. all use `R --> L:mcp`). They naturally form a vertical stack to one side, with parallel arrows reaching the downstream node.\n- Use **`align row`** when the members connect to a common downstream node _via the same vertical port pair_ (e.g. all use `B --> T:proc`). They naturally form a horizontal row above the downstream node.\n\nThree databases all feeding `mcp` via right-to-left edges → stack them in a column:\n\n```mermaid-example\narchitecture-beta\n group api(cloud)[API]\n service db1(database)[DB1] in api\n service db2(database)[DB2] in api\n service db3(database)[DB3] in api\n service mcp(server)[MCP] in api\n db1:R --> L:mcp\n db2:R --> L:mcp\n db3:R --> L:mcp\n align column db1 db2 db3\n```\n\n```mermaid\narchitecture-beta\n group api(cloud)[API]\n service db1(database)[DB1] in api\n service db2(database)[DB2] in api\n service db3(database)[DB3] in api\n service mcp(server)[MCP] in api\n db1:R --> L:mcp\n db2:R --> L:mcp\n db3:R --> L:mcp\n align column db1 db2 db3\n```\n\nThree sources all feeding `proc` via top-to-bottom edges → arrange them in a row:\n\n```mermaid-example\narchitecture-beta\n service src1(server)[Source 1]\n service src2(server)[Source 2]\n service src3(server)[Source 3]\n service proc(server)[Processor]\n src1:B --> T:proc\n src2:B --> T:proc\n src3:B --> T:proc\n align row src1 src2 src3\n```\n\n```mermaid\narchitecture-beta\n service src1(server)[Source 1]\n service src2(server)[Source 2]\n service src3(server)[Source 3]\n service proc(server)[Processor]\n src1:B --> T:proc\n src2:B --> T:proc\n src3:B --> T:proc\n align row src1 src2 src3\n```\n\nThe order of members in the `align` directive determines their order along the axis. The gap between aligned members is controlled by `idealEdgeLengthMultiplier`.\n\n> **Note:** the declared order must not contradict the directions of edges between the listed members. For example, if the diagram contains `a:L --> R:b` (which places `a` to the right of `b`), then `align row a b` will conflict with that edge direction and the layout engine will fail to render. Use `align row b a` instead, or remove the conflicting edge.\n\n#### Grid layouts (combining `row` and `column`)\n\n`align row` only pins the y-coordinate of its members. To produce a clean grid where columns also align across tiers, pair each `align row` with one or more `align column` directives. The columns can span as many rows as you like — chain every node that should share an x-coordinate, even across groups.\n\n```mermaid-example\narchitecture-beta\n group sources(cloud)[Sources]\n service src_a(server)[Source A] in sources\n service src_b(server)[Source B] in sources\n service src_c(server)[Source C] in sources\n\n group storage(database)[Storage]\n service db_one(database)[DB One] in storage\n service db_two(database)[DB Two] in storage\n service db_three(database)[DB Three] in storage\n\n group output(disk)[Output]\n service brief(disk)[Brief] in output\n service analyst(server)[Analyst] in output\n service delivery(cloud)[Delivery] in output\n\n src_a:B --> T:db_one\n src_b:B --> T:db_two\n src_c:B --> T:db_three\n db_two:B --> T:brief\n brief:R --> L:analyst\n analyst:R --> L:delivery\n\n align row src_a src_b src_c\n align row db_one db_two db_three\n align row brief analyst delivery\n\n align column src_a db_one\n align column src_b db_two brief\n align column src_c db_three\n```\n\n```mermaid\narchitecture-beta\n group sources(cloud)[Sources]\n service src_a(server)[Source A] in sources\n service src_b(server)[Source B] in sources\n service src_c(server)[Source C] in sources\n\n group storage(database)[Storage]\n service db_one(database)[DB One] in storage\n service db_two(database)[DB Two] in storage\n service db_three(database)[DB Three] in storage\n\n group output(disk)[Output]\n service brief(disk)[Brief] in output\n service analyst(server)[Analyst] in output\n service delivery(cloud)[Delivery] in output\n\n src_a:B --> T:db_one\n src_b:B --> T:db_two\n src_c:B --> T:db_three\n db_two:B --> T:brief\n brief:R --> L:analyst\n analyst:R --> L:delivery\n\n align row src_a src_b src_c\n align row db_one db_two db_three\n align row brief analyst delivery\n\n align column src_a db_one\n align column src_b db_two brief\n align column src_c db_three\n```\n\nThe result is three left-to-right tiers stacked vertically with a straight spine through the middle column. Edges between aligned nodes render as straight horizontal or vertical lines; cross-axis edges (e.g. `db_one:R --> T:hub`) get a single 90° elbow.\n\n> **Tip:** if a long single-word label is too wide to fit on one line at small `iconSize` values, increase `iconSize` (or use a shorter title) to keep it on one line.\n\n### Junctions\n\nJunctions are a special type of node which acts as a potential 4-way split between edges.\n\nThe syntax for declaring a junction is:\n\n```\njunction {junction id} (in {parent id})?\n```\n\n```mermaid-example\narchitecture-beta\n service left_disk(disk)[Disk]\n service top_disk(disk)[Disk]\n service bottom_disk(disk)[Disk]\n service top_gateway(internet)[Gateway]\n service bottom_gateway(internet)[Gateway]\n junction junctionCenter\n junction junctionRight\n\n left_disk:R -- L:junctionCenter\n top_disk:B -- T:junctionCenter\n bottom_disk:T -- B:junctionCenter\n junctionCenter:R -- L:junctionRight\n top_gateway:B -- T:junctionRight\n bottom_gateway:T -- B:junctionRight\n```\n\n```mermaid\narchitecture-beta\n service left_disk(disk)[Disk]\n service top_disk(disk)[Disk]\n service bottom_disk(disk)[Disk]\n service top_gateway(internet)[Gateway]\n service bottom_gateway(internet)[Gateway]\n junction junctionCenter\n junction junctionRight\n\n left_disk:R -- L:junctionCenter\n top_disk:B -- T:junctionCenter\n bottom_disk:T -- B:junctionCenter\n junctionCenter:R -- L:junctionRight\n top_gateway:B -- T:junctionRight\n bottom_gateway:T -- B:junctionRight\n```\n\n## Configuration\n\n### `randomize` (v11.14.0+)\n\nBy default, architecture diagrams start nodes at deterministic seed positions (`randomize: false`). Setting `randomize` to `true` randomizes initial node positions before running the layout algorithm, which may produce varied but potentially better-spaced layouts on each render.\n\n> Note: `randomize: false` alone is not enough to guarantee identical renders — the underlying fcose layout still calls `Math.random()` during its constraint solver. Use the `seed` option (described below) to lock the layout fully.\n\nVia frontmatter:\n\n```\n%%{init: {"architecture": {"randomize": true}}}%%\narchitecture-beta\n group api(cloud)[API]\n service db(database)[Database] in api\n service server(server)[Server] in api\n db:R --> L:server\n```\n\nVia `mermaid.initialize()`:\n\n```javascript\nmermaid.initialize({\n architecture: {\n randomize: true,\n },\n});\n```\n\n| Option | Type | Default | Description |\n| ----------- | ------- | ------- | ---------------------------------------------------------------------- |\n| `randomize` | boolean | `false` | Whether to randomize initial node positions before running the layout. |\n\n### Layout tuning (v11.15.0+)\n\nThe following options pass through to the underlying [fcose](https://github.com/iVis-at-Bilkent/cytoscape.js-fcose) layout so you can adjust spacing and density without changing your diagram source:\n\n| Option | Type | Default | Description |\n| --------------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `nodeSeparation` | number | `75` | Minimum separation, in pixels, between sibling nodes in the same group. Pass-through to fcose. |\n| `idealEdgeLengthMultiplier` | number | `1.5` | Multiplier applied to `iconSize` to compute the ideal length of edges between nodes within the same group. Increase for breathing room, decrease to pack tighter. Cross-group edges are not affected. |\n| `edgeElasticity` | number | `0.45` | Spring elasticity (0–1) on same-group edges. Higher pulls connected nodes closer; lower lets them spread out. Cross-group edges are not affected. |\n| `numIter` | number | `2500` | Maximum fcose iterations. Increase for higher-quality layouts on large diagrams at the cost of render time. |\n| `seed` | number | `1` | Deterministic seed for fcose. Defaults to `1` so the same diagram always renders with the same layout. Set to `0` to opt out and use native (non-deterministic) `Math.random`. Any other number selects a different reproducible layout variant. |\n\nExample — bumping `idealEdgeLengthMultiplier` stretches the spacing between connected nodes in a chain:\n\n```\n%%{init: {"architecture": {"idealEdgeLengthMultiplier": 3}}}%%\narchitecture-beta\n service a(server)[A]\n service b(server)[B]\n service c(server)[C]\n a:R --> L:b\n b:R --> L:c\n```\n\n> **Note:** these knobs tune fcose\'s force-directed layout; they do not change which nodes the layout heuristic considers adjacent. If two siblings render on top of each other because they share the same logical position in the spatial map (a known limitation tracked in [#6120](https://github.com/mermaid-js/mermaid/issues/6120)), no combination of these knobs will move them apart — see the upcoming `align row|column` directive instead.\n\n## Icons\n\nBy default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.\nUsers can use any of the 200,000+ icons available in iconify.design, or add other custom icons, by [registering an icon pack](../config/icons.md).\n\nAfter the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.\n\n```mermaid-example\narchitecture-beta\n group api(logos:aws-lambda)[API]\n\n service db(logos:aws-aurora)[Database] in api\n service disk1(logos:aws-glacier)[Storage] in api\n service disk2(logos:aws-s3)[Storage] in api\n service server(logos:aws-ec2)[Server] in api\n\n db:L -- R:server\n disk1:T -- B:server\n disk2:T -- B:db\n```\n\n```mermaid\narchitecture-beta\n group api(logos:aws-lambda)[API]\n\n service db(logos:aws-aurora)[Database] in api\n service disk1(logos:aws-glacier)[Storage] in api\n service disk2(logos:aws-s3)[Storage] in api\n service server(logos:aws-ec2)[Server] in api\n\n db:L -- R:server\n disk1:T -- B:server\n disk2:T -- B:db\n```\n';
460
460
  const __vite_glob_0_19 = '> **Warning**\n>\n> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.\n>\n> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/classDiagram.md](../../packages/mermaid/src/docs/syntax/classDiagram.md).\n\n# Class diagrams\n\n> "In software engineering, a class diagram in the Unified Modeling Language (UML) is a type of static structure diagram that describes the structure of a system by showing the system\'s classes, their attributes, operations (or methods), and the relationships among objects."\n>\n> -Wikipedia\n\nThe class diagram is the main building block of object-oriented modeling. It is used for general conceptual modeling of the structure of the application, and for detailed modeling to translate the models into programming code. Class diagrams can also be used for data modeling. The classes in a class diagram represent both the main elements, interactions in the application, and the classes to be programmed.\n\nMermaid can render class diagrams.\n\n```mermaid-example\n---\ntitle: Animal example\n---\nclassDiagram\n note "From Duck till Zebra"\n Animal <|-- Duck\n note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging"\n Animal <|-- Fish\n Animal <|-- Zebra\n Animal : +int age\n Animal : +String gender\n Animal: +isMammal()\n Animal: +mate()\n class Duck{\n +String beakColor\n +swim()\n +quack()\n }\n class Fish{\n -int sizeInFeet\n -canEat()\n }\n class Zebra{\n +bool is_wild\n +run()\n }\n```\n\n```mermaid\n---\ntitle: Animal example\n---\nclassDiagram\n note "From Duck till Zebra"\n Animal <|-- Duck\n note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging"\n Animal <|-- Fish\n Animal <|-- Zebra\n Animal : +int age\n Animal : +String gender\n Animal: +isMammal()\n Animal: +mate()\n class Duck{\n +String beakColor\n +swim()\n +quack()\n }\n class Fish{\n -int sizeInFeet\n -canEat()\n }\n class Zebra{\n +bool is_wild\n +run()\n }\n```\n\n## Syntax\n\n### Class\n\nUML provides mechanisms to represent class members, such as attributes and methods, and additional information about them.\nA single instance of a class in the diagram contains three compartments:\n\n- The top compartment contains the name of the class. It is printed in bold and centered, and the first letter is capitalized. It may also contain optional annotation text describing the nature of the class.\n- The middle compartment contains the attributes of the class. They are left-aligned and the first letter is lowercase.\n- The bottom compartment contains the operations the class can execute. They are also left-aligned and the first letter is lowercase.\n\n```mermaid-example\n---\ntitle: Bank example\n---\nclassDiagram\n class BankAccount\n BankAccount : +String owner\n BankAccount : +Bigdecimal balance\n BankAccount : +deposit(amount)\n BankAccount : +withdrawal(amount)\n\n```\n\n```mermaid\n---\ntitle: Bank example\n---\nclassDiagram\n class BankAccount\n BankAccount : +String owner\n BankAccount : +Bigdecimal balance\n BankAccount : +deposit(amount)\n BankAccount : +withdrawal(amount)\n\n```\n\n## Define a class\n\nThere are two ways to define a class:\n\n- Explicitly using keyword **class** like `class Animal` which would define the Animal class.\n- Via a **relationship** which defines two classes at a time along with their relationship. For instance, `Vehicle <|-- Car`.\n\n```mermaid-example\nclassDiagram\n class Animal\n Vehicle <|-- Car\n```\n\n```mermaid\nclassDiagram\n class Animal\n Vehicle <|-- Car\n```\n\nNaming convention: a class name should be composed only of alphanumeric characters (including unicode), underscores, and dashes (-).\n\n### Class labels\n\nIn case you need to provide a label for a class, you can use the following syntax:\n\n```mermaid-example\nclassDiagram\n class Animal["Animal with a label"]\n class Car["Car with *! symbols"]\n Animal --> Car\n```\n\n```mermaid\nclassDiagram\n class Animal["Animal with a label"]\n class Car["Car with *! symbols"]\n Animal --> Car\n```\n\nYou can also use backticks to escape special characters in the label:\n\n```mermaid-example\nclassDiagram\n class `Animal Class!`\n class `Car Class`\n `Animal Class!` --> `Car Class`\n```\n\n```mermaid\nclassDiagram\n class `Animal Class!`\n class `Car Class`\n `Animal Class!` --> `Car Class`\n```\n\n## Defining Members of a class\n\nUML provides mechanisms to represent class members such as attributes and methods, as well as additional information about them.\n\nMermaid distinguishes between attributes and functions/methods based on if the **parenthesis** `()` are present or not. The ones with `()` are treated as functions/methods, and all others as attributes.\n\nThere are two ways to define the members of a class, and regardless of whichever syntax is used to define the members, the output will still be same. The two different ways are :\n\n- Associate a member of a class using **:** (colon) followed by member name, useful to define one member at a time. For example:\n\n```mermaid-example\nclassDiagram\nclass BankAccount\nBankAccount : +String owner\nBankAccount : +BigDecimal balance\nBankAccount : +deposit(amount)\nBankAccount : +withdrawal(amount)\n```\n\n```mermaid\nclassDiagram\nclass BankAccount\nBankAccount : +String owner\nBankAccount : +BigDecimal balance\nBankAccount : +deposit(amount)\nBankAccount : +withdrawal(amount)\n```\n\n- Associate members of a class using **{}** brackets, where members are grouped within curly brackets. Suitable for defining multiple members at once. For example:\n\n```mermaid-example\nclassDiagram\nclass BankAccount{\n +String owner\n +BigDecimal balance\n +deposit(amount)\n +withdrawal(amount)\n}\n```\n\n```mermaid\nclassDiagram\nclass BankAccount{\n +String owner\n +BigDecimal balance\n +deposit(amount)\n +withdrawal(amount)\n}\n```\n\n#### Return Type\n\nOptionally you can end a method/function definition with the data type that will be returned (note: there must be a space between the final `)` and the return type). An example:\n\n```mermaid-example\nclassDiagram\nclass BankAccount{\n +String owner\n +BigDecimal balance\n +deposit(amount) bool\n +withdrawal(amount) int\n}\n```\n\n```mermaid\nclassDiagram\nclass BankAccount{\n +String owner\n +BigDecimal balance\n +deposit(amount) bool\n +withdrawal(amount) int\n}\n```\n\n#### Generic Types\n\nGenerics can be represented as part of a class definition, and for class members/return types. In order to denote an item as generic, you enclose that type within `~` (**tilde**). **Nested** type declarations such as `List<List<int>>` are supported, though generics that include a comma are currently not supported. (such as `List<List<K, V>>`)\n\n> _note_ when a generic is used within a class definition, the generic type is NOT considered part of the class name. i.e.: for any syntax which required you to reference the class name, you need to drop the type part of the definition. This also means that mermaid does not currently support having two classes with the same name, but different generic types.\n\n```mermaid-example\nclassDiagram\nclass Square~Shape~{\n int id\n List~int~ position\n setPoints(List~int~ points)\n getPoints() List~int~\n}\n\nSquare : -List~string~ messages\nSquare : +setMessages(List~string~ messages)\nSquare : +getMessages() List~string~\nSquare : +getDistanceMatrix() List~List~int~~\n```\n\n```mermaid\nclassDiagram\nclass Square~Shape~{\n int id\n List~int~ position\n setPoints(List~int~ points)\n getPoints() List~int~\n}\n\nSquare : -List~string~ messages\nSquare : +setMessages(List~string~ messages)\nSquare : +getMessages() List~string~\nSquare : +getDistanceMatrix() List~List~int~~\n```\n\n#### Visibility\n\nTo describe the visibility (or encapsulation) of an attribute or method/function that is a part of a class (i.e. a class member), optional notation may be placed before that members\' name:\n\n- `+` Public\n- `-` Private\n- `#` Protected\n- `~` Package/Internal\n\n> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type:\n>\n> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*`\n> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$`\n\n> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end:\n>\n> - `$` Static e.g.: `String someField$`\n\n## Defining Relationship\n\nA relationship is a general term covering the specific types of logical connections found on class and object diagrams.\n\n```\n[classA][Arrow][ClassB]\n```\n\nThere are eight different types of relations defined for classes under UML which are currently supported:\n\n| Type | Description |\n| ------- | ------------- |\n| `<\\|--` | Inheritance |\n| `*--` | Composition |\n| `o--` | Aggregation |\n| `-->` | Association |\n| `--` | Link (Solid) |\n| `..>` | Dependency |\n| `..\\|>` | Realization |\n| `..` | Link (Dashed) |\n\n```mermaid-example\nclassDiagram\nclassA <|-- classB\nclassC *-- classD\nclassE o-- classF\nclassG <-- classH\nclassI -- classJ\nclassK <.. classL\nclassM <|.. classN\nclassO .. classP\n\n```\n\n```mermaid\nclassDiagram\nclassA <|-- classB\nclassC *-- classD\nclassE o-- classF\nclassG <-- classH\nclassI -- classJ\nclassK <.. classL\nclassM <|.. classN\nclassO .. classP\n\n```\n\nWe can use the labels to describe the nature of the relation between two classes. Also, arrowheads can be used in the opposite direction as well:\n\n```mermaid-example\nclassDiagram\nclassA --|> classB : Inheritance\nclassC --* classD : Composition\nclassE --o classF : Aggregation\nclassG --> classH : Association\nclassI -- classJ : Link(Solid)\nclassK ..> classL : Dependency\nclassM ..|> classN : Realization\nclassO .. classP : Link(Dashed)\n\n```\n\n```mermaid\nclassDiagram\nclassA --|> classB : Inheritance\nclassC --* classD : Composition\nclassE --o classF : Aggregation\nclassG --> classH : Association\nclassI -- classJ : Link(Solid)\nclassK ..> classL : Dependency\nclassM ..|> classN : Realization\nclassO .. classP : Link(Dashed)\n\n```\n\n### Labels on Relations\n\nIt is possible to add label text to a relation:\n\n```\n[classA][Arrow][ClassB]:LabelText\n```\n\n```mermaid-example\nclassDiagram\nclassA <|-- classB : implements\nclassC *-- classD : composition\nclassE o-- classF : aggregation\n```\n\n```mermaid\nclassDiagram\nclassA <|-- classB : implements\nclassC *-- classD : composition\nclassE o-- classF : aggregation\n```\n\n### Two-way relations\n\nRelations can logically represent an N:M association:\n\n```mermaid-example\nclassDiagram\n Animal <|--|> Zebra\n```\n\n```mermaid\nclassDiagram\n Animal <|--|> Zebra\n```\n\nHere is the syntax:\n\n```\n[Relation Type][Link][Relation Type]\n```\n\nWhere `Relation Type` can be one of:\n\n| Type | Description |\n| ----- | ----------- |\n| `<\\|` | Inheritance |\n| `\\*` | Composition |\n| `o` | Aggregation |\n| `>` | Association |\n| `<` | Association |\n| `\\|>` | Realization |\n\nAnd `Link` can be one of:\n\n| Type | Description |\n| ---- | ----------- |\n| -- | Solid |\n| .. | Dashed |\n\n### Lollipop Interfaces\n\nClasses can also be given a special relation type that defines a lollipop interface on the class. A lollipop interface is defined using the following syntax:\n\n- `bar ()-- foo`\n- `foo --() bar`\n\nThe interface (bar) with the lollipop connects to the class (foo).\n\nNote: Each interface that is defined is unique and is meant to not be shared between classes / have multiple edges connecting to it.\n\n```mermaid-example\nclassDiagram\n bar ()-- foo\n```\n\n```mermaid\nclassDiagram\n bar ()-- foo\n```\n\n```mermaid-example\nclassDiagram\n class Class01 {\n int amount\n draw()\n }\n Class01 --() bar\n Class02 --() bar\n\n foo ()-- Class01\n```\n\n```mermaid\nclassDiagram\n class Class01 {\n int amount\n draw()\n }\n Class01 --() bar\n Class02 --() bar\n\n foo ()-- Class01\n```\n\n## Define Namespace\n\nA namespace groups classes.\n\n```mermaid-example\nclassDiagram\nnamespace BaseShapes {\n class Triangle\n class Rectangle {\n double width\n double height\n }\n}\n```\n\n```mermaid\nclassDiagram\nnamespace BaseShapes {\n class Triangle\n class Rectangle {\n double width\n double height\n }\n}\n```\n\n### Namespace Labels (v11.15.0+)\n\nA namespace can be given a display label using square bracket syntax, similar to class labels:\n\n```mermaid-example\nclassDiagram\n namespace Auth["Authentication Service"] {\n class UserService {\n +login()\n +logout()\n }\n }\n```\n\n```mermaid\nclassDiagram\n namespace Auth["Authentication Service"] {\n class UserService {\n +login()\n +logout()\n }\n }\n```\n\nThe label replaces the namespace name in the rendered diagram, while the name is still used internally for relationships and nesting.\n\n### Nested Namespaces (v11.15.0+)\n\nNamespaces can be nested to represent hierarchical groupings. There are two ways to define nested namespaces:\n\n**Dot notation** creates intermediate namespaces automatically:\n\n```mermaid-example\nclassDiagram\n namespace Company.Engineering.Backend {\n class Developer {\n +writeCode()\n }\n }\n namespace Company.Engineering.Frontend {\n class Designer {\n +createMockup()\n }\n }\n namespace Company.Engineering {\n class TechLead {\n +planSprint()\n }\n }\n TechLead --> Developer : leads\n TechLead --> Designer : leads\n```\n\n```mermaid\nclassDiagram\n namespace Company.Engineering.Backend {\n class Developer {\n +writeCode()\n }\n }\n namespace Company.Engineering.Frontend {\n class Designer {\n +createMockup()\n }\n }\n namespace Company.Engineering {\n class TechLead {\n +planSprint()\n }\n }\n TechLead --> Developer : leads\n TechLead --> Designer : leads\n```\n\n**Syntactic nesting** places namespace blocks inside other namespace blocks:\n\n```mermaid-example\nclassDiagram\n namespace Platform {\n namespace Auth {\n class UserService {\n +login()\n +logout()\n }\n }\n namespace Data {\n class Repository {\n +find()\n +save()\n }\n }\n class Gateway {\n +route()\n }\n }\n Gateway --> UserService : delegates\n Gateway --> Repository : delegates\n```\n\n```mermaid\nclassDiagram\n namespace Platform {\n namespace Auth {\n class UserService {\n +login()\n +logout()\n }\n }\n namespace Data {\n class Repository {\n +find()\n +save()\n }\n }\n class Gateway {\n +route()\n }\n }\n Gateway --> UserService : delegates\n Gateway --> Repository : delegates\n```\n\nBoth approaches can be combined. Dot notation like `namespace A.B.C` will automatically create namespaces `A` and `A.B` as parents if they don\'t already exist.\n\n#### Compact rendering (`hierarchicalNamespaces: false`)\n\nBy default (`hierarchicalNamespaces: true`), each segment of a dotted or syntactically-nested namespace name renders as its own cluster, producing a nested layout.\n\nSetting `hierarchicalNamespaces: false` in the class diagram config switches to **compact mode**: only namespaces the user explicitly declares are drawn — each as a single flat box labelled with its fully-qualified name. Auto-created intermediate ancestors are skipped, and classes inside them are moved to their nearest declared namespace.\n\n```mermaid-example\n---\nconfig:\n class:\n hierarchicalNamespaces: false\n---\nclassDiagram\n namespace Company.Engineering.Backend {\n class Developer {\n +writeCode()\n }\n }\n namespace Company.Engineering.Frontend {\n class Designer {\n +createMockup()\n }\n }\n namespace Company {\n class CEO {\n +makeDecisions()\n }\n }\n CEO --> Developer : oversees\n CEO --> Designer : oversees\n```\n\n```mermaid\n---\nconfig:\n class:\n hierarchicalNamespaces: false\n---\nclassDiagram\n namespace Company.Engineering.Backend {\n class Developer {\n +writeCode()\n }\n }\n namespace Company.Engineering.Frontend {\n class Designer {\n +createMockup()\n }\n }\n namespace Company {\n class CEO {\n +makeDecisions()\n }\n }\n CEO --> Developer : oversees\n CEO --> Designer : oversees\n```\n\n## Cardinality / Multiplicity on relations\n\nMultiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies.\n\nMultiplicity notations are placed near the end of an association.\n\nThe different cardinality options are :\n\n- `1` Only 1\n- `0..1` Zero or One\n- `1..*` One or more\n- `*` Many\n- `n` n (where n>1)\n- `0..n` zero to n (where n>1)\n- `1..n` one to n (where n>1)\n\nCardinality can be easily defined by placing the text option within quotes `"` before or after a given arrow. For example:\n\n```\n[classA] "cardinality1" [Arrow] "cardinality2" [ClassB]:LabelText\n```\n\n```mermaid-example\nclassDiagram\n Customer "1" --> "*" Ticket\n Student "1" --> "1..*" Course\n Galaxy --> "many" Star : Contains\n```\n\n```mermaid\nclassDiagram\n Customer "1" --> "*" Ticket\n Student "1" --> "1..*" Course\n Galaxy --> "many" Star : Contains\n```\n\n## Annotations on classes\n\nIt is possible to annotate classes with markers to provide additional metadata about the class. This can give a clearer indication about its nature. Some common annotations include:\n\n- `<<Interface>>` To represent an Interface class\n- `<<Abstract>>` To represent an abstract class\n- `<<Service>>` To represent a service class\n- `<<Enumeration>>` To represent an enum\n\nAnnotations are defined within the opening `<<` and closing `>>`. There are three ways to add an annotation to a class, and in all cases the output will be the same:\n\n- **Inline** with the class definition:\n\n```mermaid-example\nclassDiagram\n class Shape <<interface>>\n```\n\n```mermaid\nclassDiagram\n class Shape <<interface>>\n```\n\n- In a **_separate line_** after a class is defined:\n\n```mermaid-example\nclassDiagram\nclass Shape\n<<interface>> Shape\nShape : noOfVertices\nShape : draw()\n```\n\n```mermaid\nclassDiagram\nclass Shape\n<<interface>> Shape\nShape : noOfVertices\nShape : draw()\n```\n\n- In a **_nested structure_** along with the class definition:\n\n```mermaid-example\nclassDiagram\nclass Shape{\n <<interface>>\n noOfVertices\n draw()\n}\nclass Color{\n <<enumeration>>\n RED\n BLUE\n GREEN\n WHITE\n BLACK\n}\n\n```\n\n```mermaid\nclassDiagram\nclass Shape{\n <<interface>>\n noOfVertices\n draw()\n}\nclass Color{\n <<enumeration>>\n RED\n BLUE\n GREEN\n WHITE\n BLACK\n}\n\n```\n\n## Comments\n\nComments can be entered within a class diagram, which will be ignored by the parser. Comments need to be on their own line, and must be prefaced with `%%` (double percent signs). Any text until the next newline will be treated as a comment, including any class diagram syntax.\n\n```mermaid-example\nclassDiagram\n%% This whole line is a comment classDiagram class Shape <<interface>>\nclass Shape{\n <<interface>>\n noOfVertices\n draw()\n}\n```\n\n```mermaid\nclassDiagram\n%% This whole line is a comment classDiagram class Shape <<interface>>\nclass Shape{\n <<interface>>\n noOfVertices\n draw()\n}\n```\n\n## Setting the direction of the diagram\n\nWith class diagrams you can use the direction statement to set the direction in which the diagram will render:\n\n```mermaid-example\nclassDiagram\n direction RL\n class Student {\n -idCard : IdCard\n }\n class IdCard{\n -id : int\n -name : string\n }\n class Bike{\n -id : int\n -name : string\n }\n Student "1" --o "1" IdCard : carries\n Student "1" --o "1" Bike : rides\n```\n\n```mermaid\nclassDiagram\n direction RL\n class Student {\n -idCard : IdCard\n }\n class IdCard{\n -id : int\n -name : string\n }\n class Bike{\n -id : int\n -name : string\n }\n Student "1" --o "1" IdCard : carries\n Student "1" --o "1" Bike : rides\n```\n\n## Interaction\n\nIt is possible to bind a click event to a node. The click can lead to either a javascript callback or to a link which will be opened in a new browser tab. **Note**: This functionality is disabled when using `securityLevel=\'strict\'` and enabled when using `securityLevel=\'loose\'`.\n\nYou would define these actions on a separate line after all classes have been declared.\n\n```\naction className "reference" "tooltip"\nclick className call callback() "tooltip"\nclick className href "url" "tooltip"\n```\n\n- _action_ is either `link` or `callback`, depending on which type of interaction you want to have called\n- _className_ is the id of the node that the action will be associated with\n- _reference_ is either the url link, or the function name for callback.\n- (_optional_) tooltip is a string to be displayed when hovering over element (note: The styles of the tooltip are set by the class .mermaidTooltip.)\n- note: callback function will be called with the nodeId as parameter.\n\n## Notes\n\nIt is possible to add notes on the diagram using `note "line1\\nline2"`. A note can be added for a specific class using `note for <CLASS NAME> "line1\\nline2"`.\n\n### Examples\n\n```mermaid-example\nclassDiagram\n note "This is a general note"\n note for MyClass "This is a note for a class"\n class MyClass{\n }\n```\n\n```mermaid\nclassDiagram\n note "This is a general note"\n note for MyClass "This is a note for a class"\n class MyClass{\n }\n```\n\n_URL Link:_\n\n```mermaid-example\nclassDiagram\nclass Shape\nlink Shape "https://www.github.com" "This is a tooltip for a link"\nclass Shape2\nclick Shape2 href "https://www.github.com" "This is a tooltip for a link"\n```\n\n```mermaid\nclassDiagram\nclass Shape\nlink Shape "https://www.github.com" "This is a tooltip for a link"\nclass Shape2\nclick Shape2 href "https://www.github.com" "This is a tooltip for a link"\n```\n\n_Callback:_\n\n```mermaid-example\nclassDiagram\nclass Shape\ncallback Shape "callbackFunction" "This is a tooltip for a callback"\nclass Shape2\nclick Shape2 call callbackFunction() "This is a tooltip for a callback"\n```\n\n```mermaid\nclassDiagram\nclass Shape\ncallback Shape "callbackFunction" "This is a tooltip for a callback"\nclass Shape2\nclick Shape2 call callbackFunction() "This is a tooltip for a callback"\n```\n\n```html\n<script>\n const callbackFunction = function () {\n alert(\'A callback was triggered\');\n };\n<\/script>\n```\n\n```mermaid-example\nclassDiagram\n class Class01\n class Class02\n callback Class01 "callbackFunction" "Callback tooltip"\n link Class02 "https://www.github.com" "This is a link"\n class Class03\n class Class04\n click Class03 call callbackFunction() "Callback tooltip"\n click Class04 href "https://www.github.com" "This is a link"\n```\n\n```mermaid\nclassDiagram\n class Class01\n class Class02\n callback Class01 "callbackFunction" "Callback tooltip"\n link Class02 "https://www.github.com" "This is a link"\n class Class03\n class Class04\n click Class03 call callbackFunction() "Callback tooltip"\n click Class04 href "https://www.github.com" "This is a link"\n```\n\n> **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2.\n\nBeginner\'s tip—a full example using interactive links in an HTML page:\n\n```html\n<body>\n <pre class="mermaid">\n classDiagram\n Animal <|-- Duck\n Animal <|-- Fish\n Animal <|-- Zebra\n Animal : +int age\n Animal : +String gender\n Animal: +isMammal()\n Animal: +mate()\n class Duck{\n +String beakColor\n +swim()\n +quack()\n }\n class Fish{\n -int sizeInFeet\n -canEat()\n }\n class Zebra{\n +bool is_wild\n +run()\n }\n\n callback Duck "callback" "Tooltip"\n link Zebra "https://www.github.com" "This is a link"\n </pre>\n\n <script>\n const callback = function () {\n alert(\'A callback was triggered\');\n };\n const config = {\n startOnLoad: true,\n securityLevel: \'loose\',\n };\n mermaid.initialize(config);\n <\/script>\n</body>\n```\n\n## Styling\n\n### Styling a node\n\nIt is possible to apply specific styles such as a thicker border or a different background color to an individual node using the `style` keyword.\n\nNote that notes and namespaces cannot be styled individually but do support themes.\n\n```mermaid-example\nclassDiagram\n class Animal\n class Mineral\n style Animal fill:#f9f,stroke:#333,stroke-width:4px\n style Mineral fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n```\n\n```mermaid\nclassDiagram\n class Animal\n class Mineral\n style Animal fill:#f9f,stroke:#333,stroke-width:4px\n style Mineral fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5\n```\n\n#### Classes\n\nMore convenient than defining the style every time is to define a class of styles and attach this class to the nodes that\nshould have a different look.\n\nA class definition looks like the example below:\n\n```\nclassDef className fill:#f9f,stroke:#333,stroke-width:4px;\n```\n\nAlso, it is possible to define style to multiple classes in one statement:\n\n```\nclassDef firstClassName,secondClassName font-size:12pt;\n```\n\nAttachment of a class to a node is done as per below:\n\n```\ncssClass "nodeId1" className;\n```\n\nIt is also possible to attach a class to a list of nodes in one statement:\n\n```\ncssClass "nodeId1,nodeId2" className;\n```\n\nA shorter form of adding a class is to attach the classname to the node using the `:::` operator:\n\n```mermaid-example\nclassDiagram\n class Animal:::someclass\n classDef someclass fill:#f96\n```\n\n```mermaid\nclassDiagram\n class Animal:::someclass\n classDef someclass fill:#f96\n```\n\nOr:\n\n```mermaid-example\nclassDiagram\n class Animal:::someclass {\n -int sizeInFeet\n -canEat()\n }\n classDef someclass fill:#f96\n```\n\n```mermaid\nclassDiagram\n class Animal:::someclass {\n -int sizeInFeet\n -canEat()\n }\n classDef someclass fill:#f96\n```\n\n### Default class\n\nIf a class is named default it will be applied to all nodes. Specific styles and classes should be defined afterwards to override the applied default styling.\n\n```\nclassDef default fill:#f9f,stroke:#333,stroke-width:4px;\n```\n\n```mermaid-example\nclassDiagram\n class Animal:::pink\n class Mineral\n\n classDef default fill:#f96,color:red\n classDef pink color:#f9f\n```\n\n```mermaid\nclassDiagram\n class Animal:::pink\n class Mineral\n\n classDef default fill:#f96,color:red\n classDef pink color:#f9f\n```\n\n### CSS Classes\n\nIt is also possible to predefine classes in CSS styles that can be applied from the graph definition as in the example\nbelow:\n\n**Example style**\n\n```html\n<style>\n .styleClass > * > g {\n fill: #ff0000;\n stroke: #ffff00;\n stroke-width: 4px;\n }\n</style>\n```\n\n**Example definition**\n\n```mermaid-example\nclassDiagram\n class Animal:::styleClass\n```\n\n```mermaid\nclassDiagram\n class Animal:::styleClass\n```\n\n> cssClasses cannot be added using this shorthand method at the same time as a relation statement.\n\n## Configuration\n\n### Members Box\n\nIt is possible to hide the empty members box of a class node.\n\nThis is done by changing the **hideEmptyMembersBox** value of the class diagram configuration. For more information on how to edit the Mermaid configuration see the [configuration page.](https://mermaid.js.org/config/configuration.html)\n\n### Possible configuration parameters:\n\n| Parameter | Description | Default value |\n| ------------------- | ------------------------------------------------------------ | ------------- |\n| hideEmptyMembersBox | Hides the empty members box of a class node when set to true | false |\n\n```mermaid-example\n---\n config:\n class:\n hideEmptyMembersBox: true\n---\nclassDiagram\n class Duck\n```\n\n```mermaid\n---\n config:\n class:\n hideEmptyMembersBox: true\n---\nclassDiagram\n class Duck\n```\n';
461
461
  const __vite_glob_0_20 = '> **Warning**\n>\n> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.\n>\n> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/configuration.md](../../packages/mermaid/src/docs/config/configuration.md).\n\n# Configuration\n\nWhen mermaid starts, configuration is extracted to determine a configuration to be used for a diagram. There are 3 sources for configuration:\n\n- The default configuration\n- Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.\n- Frontmatter (v10.5.0+) - diagram authors can update selected configuration parameters in the frontmatter of the diagram. These are applied to the render config.\n- Directives (Deprecated by Frontmatter) - diagram authors can update selected configuration parameters directly in the diagram code via directives. These are applied to the render config.\n\n**The render config** is configuration that is used when rendering by applying these configurations.\n\n## Frontmatter config\n\nThe entire mermaid configuration (except the secure configs) can be overridden by the diagram author in the frontmatter of the diagram. The frontmatter is a YAML block at the top of the diagram.\n\n```mermaid-example\n---\ntitle: Hello Title\nconfig:\n theme: base\n themeVariables:\n primaryColor: "#00ff00"\n---\nflowchart\n Hello --> World\n\n```\n\n```mermaid\n---\ntitle: Hello Title\nconfig:\n theme: base\n themeVariables:\n primaryColor: "#00ff00"\n---\nflowchart\n Hello --> World\n\n```\n\n## Theme configuration\n\n## Starting mermaid\n\n```mermaid-example\nsequenceDiagram\n Site->>mermaid: initialize\n Site->>mermaid: content loaded\n mermaid->>mermaidAPI: init\n```\n\n```mermaid\nsequenceDiagram\n Site->>mermaid: initialize\n Site->>mermaid: content loaded\n mermaid->>mermaidAPI: init\n```\n\n## Initialize\n\nThe initialize call is applied **only once**. It is called by the site integrator in order to override the default configuration at a site level.\n\n## configApi.reset\n\nThis method resets the configuration for a diagram to the overall site configuration, which is the configuration provided by the site integrator. Before each rendering of a diagram, reset is called at the very beginning.\n';
@@ -481,7 +481,7 @@ const __vite_glob_0_39 = "> **Warning**\n>\n> ## THIS IS AN AUTOGENERATED FILE.
481
481
  const __vite_glob_0_40 = '> **Warning**\n>\n> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.\n>\n> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/treeView.md](../../packages/mermaid/src/docs/syntax/treeView.md).\n\n# TreeView Diagram (v11.14.0+)\n\n## Introduction\n\nA TreeView diagram is used to represent hierarchical data in the form of a directory-like structure, with file-type icons, connector lines, and optional annotations.\n\n## Syntax\n\nThe structure of the tree depends only on indentation. Labels can be **bare** (unquoted) or **quoted** (for names containing spaces).\n\n- Directories are indicated by a trailing `/` on the label — they get a folder icon and bold text.\n- Files are auto-detected by extension and assigned a matching icon.\n- Quoted labels (`"my file"`) support spaces in names.\n\n```\ntreeView-beta\n my-project/\n src/\n index.js\n package.json\n README.md\n```\n\nQuoted labels (backward compatible):\n\n```\ntreeView-beta\n "my project"\n "folder with spaces"\n "file.js"\n```\n\n## Box-Drawing Input\n\nAs an alternative to indentation, you can use box-drawing characters to define the tree structure. The parser auto-detects the format — no extra keyword or config is needed. This is how most file tree diagrams are drawn already, so you can turn those into Mermaid diagrams with very little effort.\n\nBoth standard (`├──`, `└──`, `│`) and heavy (`┣━━`, `┗━━`, `┃`) Unicode variants are supported.\n\n```mermaid-example\ntreeView-beta\n├── src/\n│ ├── index.ts\n│ └── utils.ts\n├── package.json\n└── README.md\n```\n\n```mermaid\ntreeView-beta\n├── src/\n│ ├── index.ts\n│ └── utils.ts\n├── package.json\n└── README.md\n```\n\nAll annotations work the same way — just append them after the label:\n\n```mermaid-example\ntreeView-beta\n├── src/\n│ ├── App.tsx :::highlight icon(react) ## main component\n│ └── index.ts ## entry point\n├── .env ## environment variables\n├── Dockerfile\n└── package.json\n```\n\n```mermaid\ntreeView-beta\n├── src/\n│ ├── App.tsx :::highlight icon(react) ## main component\n│ └── index.ts ## entry point\n├── .env ## environment variables\n├── Dockerfile\n└── package.json\n```\n\nDepth is inferred from the column position of the branch character, so deeper nesting works naturally:\n\n```mermaid-example\ntreeView-beta\n├── packages/\n│ ├── mermaid/\n│ │ ├── src/\n│ │ │ ├── parser.ts\n│ │ │ └── renderer.ts\n│ │ └── package.json\n│ └── parser/\n│ └── src/\n└── README.md\n```\n\n```mermaid\ntreeView-beta\n├── packages/\n│ ├── mermaid/\n│ │ ├── src/\n│ │ │ ├── parser.ts\n│ │ │ └── renderer.ts\n│ │ └── package.json\n│ └── parser/\n│ └── src/\n└── README.md\n```\n\n> **Note:** If a parse error occurs, line numbers in the error message refer to your original input. Tab characters are automatically expanded to spaces.\n\n## Annotations\n\n### Highlighting with :::class\n\nAnnotate a node with `:::className` to apply a CSS class. A built-in `highlight` class is provided:\n\n```mermaid-example\ntreeView-beta\n src/\n App.tsx :::highlight\n index.js\n package.json\n```\n\n```mermaid\ntreeView-beta\n src/\n App.tsx :::highlight\n index.js\n package.json\n```\n\n### Inline descriptions with `##`\n\nAdd a visible description after `##` — rendered next to the label in italic:\n\n```mermaid-example\ntreeView-beta\n src/\n index.js ## app entry point\n config.ts ## runtime configuration\n package.json ## project manifest\n```\n\n```mermaid\ntreeView-beta\n src/\n index.js ## app entry point\n config.ts ## runtime configuration\n package.json ## project manifest\n```\n\n### Icon overrides with icon()\n\nOverride the auto-detected icon with `icon(name)`:\n\n```mermaid-example\ntreeView-beta\n data/\n model.bin icon(database)\n weights.h5 icon(database)\n src/\n index.js\n```\n\n```mermaid\ntreeView-beta\n data/\n model.bin icon(database)\n weights.h5 icon(database)\n src/\n index.js\n```\n\n### Combined annotations\n\nAnnotations can be combined in any order:\n\n```mermaid-example\ntreeView-beta\n my-project/\n src/\n App.tsx :::highlight icon(react) ## main component\n index.js ## entry point\n .env ## environment variables\n Dockerfile\n package.json\n```\n\n```mermaid\ntreeView-beta\n my-project/\n src/\n App.tsx :::highlight icon(react) ## main component\n index.js ## entry point\n .env ## environment variables\n Dockerfile\n package.json\n```\n\n## Comments\n\nUse `%%` for invisible comments (standard Mermaid convention):\n\n```\ntreeView-beta\n %% Generated files — do not edit\n src/\n generated/\n index.js\n```\n\n## Examples\n\nBasic with quoted labels:\n\n```mermaid-example\ntreeView-beta\n "packages"\n "mermaid"\n "src"\n "parser"\n```\n\n```mermaid\ntreeView-beta\n "packages"\n "mermaid"\n "src"\n "parser"\n```\n\nWith custom config:\n\n```mermaid-example\n---\nconfig:\n treeView:\n rowIndent: 80\n lineThickness: 3\n themeVariables:\n treeView:\n labelFontSize: \'20px\'\n labelColor: \'#FF0000\'\n lineColor: \'#00FF00\'\n---\ntreeView-beta\n "packages"\n "mermaid"\n "src"\n "parser"\n```\n\n```mermaid\n---\nconfig:\n treeView:\n rowIndent: 80\n lineThickness: 3\n themeVariables:\n treeView:\n labelFontSize: \'20px\'\n labelColor: \'#FF0000\'\n lineColor: \'#00FF00\'\n---\ntreeView-beta\n "packages"\n "mermaid"\n "src"\n "parser"\n```\n\n## Config Variables\n\n| Property | Description | Default Value |\n| ------------- | --------------------------------- | ------------- |\n| rowIndent | Indentation for each row | 10 |\n| paddingX | Horizontal padding of row | 5 |\n| paddingY | Vertical padding of row | 5 |\n| lineThickness | Thickness of the line | 1 |\n| showIcons | Whether to show file/folder icons | true |\n\n### Theme Variables\n\n| Property | Description | Default Value |\n| ---------------- | ------------------------------ | -------------------- |\n| labelFontSize | Font size of the label | \'16px\' |\n| labelColor | Color of the label | \'black\' |\n| lineColor | Color of the line | \'black\' |\n| iconColor | Color of file-type icons | \'#546e7a\' |\n| descriptionColor | Color of `##` description text | \'#6a9955\' |\n| highlightBg | Highlight background fill | rgba(255,193,7,0.15) |\n| highlightStroke | Highlight border stroke | #ffc107 |\n\n## Supported Icons\n\nIcons are auto-detected from file extensions and known filenames:\n\n| Extension / Filename | Icon |\n| --------------------- | ---------- |\n| `.js`, `.mjs`, `.cjs` | javascript |\n| `.ts` | typescript |\n| `.jsx`, `.tsx` | react |\n| `.py` | python |\n| `.json` | json |\n| `.md`, `.mdx` | markdown |\n| `.html`, `.htm` | html |\n| `.css`, `.scss` | css |\n| `.yaml`, `.yml` | yaml |\n| `.sh`, `.bash` | terminal |\n| `.sql`, `.db` | database |\n| `.lock` | lock |\n| `.gitignore` | git |\n| `Dockerfile` | docker |\n| `Makefile` | terminal |\n| Directories (`/`) | folder |\n| Unknown extension | file |\n';
482
482
  const __vite_glob_0_41 = '> **Warning**\n>\n> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.\n>\n> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/treemap.md](../../packages/mermaid/src/docs/syntax/treemap.md).\n\n# Treemap Diagram\n\n> A treemap diagram displays hierarchical data as a set of nested rectangles. Each branch of the tree is represented by a rectangle, which is then tiled with smaller rectangles representing sub-branches.\n\n> **Warning**\n> This is a new diagram type in Mermaid. Its syntax may evolve in future versions.\n\n## Introduction\n\nTreemap diagrams are an effective way to visualize hierarchical data and show proportions between categories and subcategories. The size of each rectangle is proportional to the value it represents, making it easy to compare different parts of a hierarchy.\n\nTreemap diagrams are particularly useful for:\n\n- Visualizing hierarchical data structures\n- Comparing proportions between categories\n- Displaying large amounts of hierarchical data in a limited space\n- Identifying patterns and outliers in hierarchical data\n\n## Syntax\n\n```\ntreemap-beta\n"Section 1"\n "Leaf 1.1": 12\n "Section 1.2"\n "Leaf 1.2.1": 12\n"Section 2"\n "Leaf 2.1": 20\n "Leaf 2.2": 25\n```\n\n### Node Definition\n\nNodes in a treemap are defined using the following syntax:\n\n- **Section/Parent nodes**: Defined with quoted text `"Section Name"`\n- **Leaf nodes with values**: Defined with quoted text followed by a colon and value `"Leaf Name": value`\n- **Hierarchy**: Created using indentation (spaces or tabs)\n- **Styling**: Nodes can be styled using the `:::class` syntax\n\n## Examples\n\n### Basic Treemap\n\n```mermaid-example\ntreemap-beta\n"Category A"\n "Item A1": 10\n "Item A2": 20\n"Category B"\n "Item B1": 15\n "Item B2": 25\n```\n\n```mermaid\ntreemap-beta\n"Category A"\n "Item A1": 10\n "Item A2": 20\n"Category B"\n "Item B1": 15\n "Item B2": 25\n```\n\n### Hierarchical Treemap\n\n```mermaid-example\ntreemap-beta\n"Products"\n "Electronics"\n "Phones": 50\n "Computers": 30\n "Accessories": 20\n "Clothing"\n "Men\'s": 40\n "Women\'s": 40\n```\n\n```mermaid\ntreemap-beta\n"Products"\n "Electronics"\n "Phones": 50\n "Computers": 30\n "Accessories": 20\n "Clothing"\n "Men\'s": 40\n "Women\'s": 40\n```\n\n### Treemap with Styling\n\n```mermaid-example\ntreemap-beta\n"Section 1"\n "Leaf 1.1": 12\n "Section 1.2":::class1\n "Leaf 1.2.1": 12\n"Section 2"\n "Leaf 2.1": 20:::class1\n "Leaf 2.2": 25\n "Leaf 2.3": 12\n\nclassDef class1 fill:red,color:blue,stroke:#FFD600;\n```\n\n```mermaid\ntreemap-beta\n"Section 1"\n "Leaf 1.1": 12\n "Section 1.2":::class1\n "Leaf 1.2.1": 12\n"Section 2"\n "Leaf 2.1": 20:::class1\n "Leaf 2.2": 25\n "Leaf 2.3": 12\n\nclassDef class1 fill:red,color:blue,stroke:#FFD600;\n```\n\n## Styling and Configuration\n\nTreemap diagrams can be customized using Mermaid\'s styling and configuration options.\n\n### Using classDef for Styling\n\nYou can define custom styles for nodes using the `classDef` syntax, which is a standard feature across many Mermaid diagram types:\n\n```mermaid-example\ntreemap-beta\n"Main"\n "A": 20\n "B":::important\n "B1": 10\n "B2": 15\n "C": 5\n\nclassDef important fill:#f96,stroke:#333,stroke-width:2px;\n```\n\n```mermaid\ntreemap-beta\n"Main"\n "A": 20\n "B":::important\n "B1": 10\n "B2": 15\n "C": 5\n\nclassDef important fill:#f96,stroke:#333,stroke-width:2px;\n```\n\n### Theme Configuration\n\nYou can customize the colors of your treemap using the theme configuration:\n\n```mermaid-example\n---\nconfig:\n theme: \'forest\'\n---\ntreemap-beta\n"Category A"\n "Item A1": 10\n "Item A2": 20\n"Category B"\n "Item B1": 15\n "Item B2": 25\n```\n\n```mermaid\n---\nconfig:\n theme: \'forest\'\n---\ntreemap-beta\n"Category A"\n "Item A1": 10\n "Item A2": 20\n"Category B"\n "Item B1": 15\n "Item B2": 25\n```\n\n### Diagram Padding\n\nYou can adjust the padding around the treemap diagram using the `diagramPadding` configuration option:\n\n```mermaid-example\n---\nconfig:\n treemap:\n diagramPadding: 200\n---\ntreemap-beta\n"Category A"\n "Item A1": 10\n "Item A2": 20\n"Category B"\n "Item B1": 15\n "Item B2": 25\n```\n\n```mermaid\n---\nconfig:\n treemap:\n diagramPadding: 200\n---\ntreemap-beta\n"Category A"\n "Item A1": 10\n "Item A2": 20\n"Category B"\n "Item B1": 15\n "Item B2": 25\n```\n\n## Configuration Options\n\nThe treemap diagram supports the following configuration options:\n\n| Option | Description | Default |\n| -------------- | --------------------------------------------------------------------------- | ------- |\n| useMaxWidth | When true, the diagram width is set to 100% and scales with available space | true |\n| padding | Internal padding between nodes | 10 |\n| diagramPadding | Padding around the entire diagram | 8 |\n| showValues | Whether to show values in the treemap | true |\n| nodeWidth | Width of nodes | 100 |\n| nodeHeight | Height of nodes | 40 |\n| borderWidth | Width of borders | 1 |\n| valueFontSize | Font size for values | 12 |\n| labelFontSize | Font size for labels | 14 |\n| valueFormat | Format for values (see Value Formatting section) | \',\' |\n\n## Advanced Features\n\n### Value Formatting\n\nValues in treemap diagrams can be formatted to display in different ways using the `valueFormat` configuration option. This option primarily uses [D3\'s format specifiers](https://github.com/d3/d3-format#locale_format) to control how numbers are displayed, with some additional special cases for common formats.\n\nSome common format patterns:\n\n- `,` - Thousands separator (default)\n- `$` - Add dollar sign\n- `.1f` - Show one decimal place\n- `.1%` - Show as percentage with one decimal place\n- `$0,0` - Dollar sign with thousands separator\n- `$.2f` - Dollar sign with 2 decimal places\n- `$,.2f` - Dollar sign with thousands separator and 2 decimal places\n\nThe treemap diagram supports both standard D3 format specifiers and some common currency formats that combine the dollar sign with other formatting options.\n\nExample with currency formatting:\n\n```mermaid-example\n---\nconfig:\n treemap:\n valueFormat: \'$0,0\'\n---\ntreemap-beta\n"Budget"\n "Operations"\n "Salaries": 700000\n "Equipment": 200000\n "Supplies": 100000\n "Marketing"\n "Advertising": 400000\n "Events": 100000\n```\n\n```mermaid\n---\nconfig:\n treemap:\n valueFormat: \'$0,0\'\n---\ntreemap-beta\n"Budget"\n "Operations"\n "Salaries": 700000\n "Equipment": 200000\n "Supplies": 100000\n "Marketing"\n "Advertising": 400000\n "Events": 100000\n```\n\nExample with percentage formatting:\n\n```mermaid-example\n---\nconfig:\n treemap:\n valueFormat: \'$.1%\'\n---\ntreemap-beta\n"Market Share"\n "Company A": 0.35\n "Company B": 0.25\n "Company C": 0.15\n "Others": 0.25\n```\n\n```mermaid\n---\nconfig:\n treemap:\n valueFormat: \'$.1%\'\n---\ntreemap-beta\n"Market Share"\n "Company A": 0.35\n "Company B": 0.25\n "Company C": 0.15\n "Others": 0.25\n```\n\n## Common Use Cases\n\nTreemap diagrams are commonly used for:\n\n1. **Financial Data**: Visualizing budget allocations, market shares, or portfolio compositions\n2. **File System Analysis**: Showing disk space usage by folders and files\n3. **Population Demographics**: Displaying population distribution across regions and subregions\n4. **Product Hierarchies**: Visualizing product categories and their sales volumes\n5. **Organizational Structures**: Representing departments and team sizes in a company\n\n## Limitations\n\n- Treemap diagrams work best when the data has a natural hierarchy\n- Very small values may be difficult to see or label in a treemap diagram\n- Deep hierarchies (many levels) can be challenging to represent clearly\n- Treemap diagrams are not well suited for representing data with negative values\n\n## Related Diagrams\n\nIf treemap diagrams don\'t suit your needs, consider these alternatives:\n\n- [**Pie Charts**](./pie.md): For simple proportion comparisons without hierarchy\n- **Sunburst Diagrams**: For hierarchical data with a radial layout (yet to be released in Mermaid).\n- [**Sankey Diagrams**](./sankey.md): For flow-based hierarchical data\n\n## Notes\n\nThe treemap diagram implementation in Mermaid is designed to be simple to use while providing powerful visualization capabilities. As this is a newer diagram type, feedback and feature requests are welcome through the Mermaid GitHub repository.\n';
483
483
  const __vite_glob_0_42 = '> **Warning**\n>\n> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.\n>\n> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/xyChart.md](../../packages/mermaid/src/docs/syntax/xyChart.md).\n\n# XY Chart\n\n> In the context of mermaid-js, the XY chart is a comprehensive charting module that encompasses various types of charts that utilize both x-axis and y-axis for data representation. Presently, it includes two fundamental chart types: the bar chart and the line chart. These charts are designed to visually display and analyze data that involve two numerical variables.\n\n> It\'s important to note that while the current implementation of mermaid-js includes these two chart types, the framework is designed to be dynamic and adaptable. Therefore, it has the capacity for expansion and the inclusion of additional chart types in the future. This means that users can expect an evolving suite of charting options within the XY chart module, catering to various data visualization needs as new chart types are introduced over time.\n\n## Example\n\n```mermaid-example\nxychart\n title "Sales Revenue"\n x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]\n y-axis "Revenue (in $)" 4000 --> 11000\n bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n```\n\n```mermaid\nxychart\n title "Sales Revenue"\n x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]\n y-axis "Revenue (in $)" 4000 --> 11000\n bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n```\n\n## Syntax\n\n> **Note**\n> All text values that contain only one word can be written without `"`. If a text value has many words in it, specifically if it contains spaces, enclose the value in `"`\n\n### Orientations\n\nThe chart can be drawn horizontal or vertical, default value is vertical.\n\n```\nxychart horizontal\n...\n```\n\n### Title\n\nThe title is a short description of the chart and it will always render on top of the chart.\n\n#### Example\n\n```\nxychart\n title "This is a simple example"\n ...\n```\n\n> **Note**\n> If the title is a single word one no need to use `"`, but if it has space `"` is needed\n\n### x-axis\n\nThe x-axis primarily serves as a categorical value, although it can also function as a numeric range value when needed.\n\n#### Example\n\n1. `x-axis title min --> max` x-axis will function as numeric with the given range\n2. `x-axis "title with space" [cat1, "cat2 with space", cat3]` x-axis if categorical, categories are text type\n\n### y-axis\n\nThe y-axis is employed to represent numerical range values, it cannot have categorical values.\n\n#### Example\n\n1. `y-axis title min --> max`\n2. `y-axis title` it will only add the title, the range will be auto generated from data.\n\n> **Note**\n> Both x and y axis are optional if not provided we will try to create the range\n\n### Line chart\n\nA line chart offers the capability to graphically depict lines.\n\n#### Example\n\n1. `line [2.3, 45, .98, -3.4]` it can have all valid numeric values.\n2. `line "series name" [2.3, 45, .98, -3.4]` adds the line to the legend.\n\n### Bar chart\n\nA bar chart offers the capability to graphically depict bars.\n\n#### Example\n\n1. `bar [2.3, 45, .98, -3.4]` it can have all valid numeric values.\n2. `bar "series name" [2.3, 45, .98, -3.4]` adds the bar to the legend.\n\n### Legend (v\\<MERMAID_RELEASE_VERSION>+)\n\nNamed line and bar plots are automatically shown in a legend. Unnamed plots are omitted from the legend.\n\n```mermaid-example\nxychart-beta\n title "An Example Chart"\n x-axis ["90d", "60d", "30d", "7d", "1d", "Current"]\n y-axis "Seconds" 0 --> 198.2\n line "avg" [48.1, 41.5, 45.7, 72.8, 67.7, 59.9]\n line "p50" [38.2, 36.8, 39.7, 54.5, 49.0, 38.4]\n line "p95" [112.2, 75.3, 103.0, 177.0, 180.2, 109.4]\n```\n\n```mermaid\nxychart-beta\n title "An Example Chart"\n x-axis ["90d", "60d", "30d", "7d", "1d", "Current"]\n y-axis "Seconds" 0 --> 198.2\n line "avg" [48.1, 41.5, 45.7, 72.8, 67.7, 59.9]\n line "p50" [38.2, 36.8, 39.7, 54.5, 49.0, 38.4]\n line "p95" [112.2, 75.3, 103.0, 177.0, 180.2, 109.4]\n```\n\n#### Simplest example\n\nThe only two things required are the chart name (`xychart`) and one data set. So you will be able to draw a chart with a simple config like\n\n```\nxychart\n line [+1.3, .6, 2.4, -.34]\n```\n\n## Chart Configurations\n\n| Parameter | Description | Default value |\n| ------------------------ | ------------------------------------------------------------- | :-----------: |\n| width | Width of the chart | 700 |\n| height | Height of the chart | 500 |\n| titlePadding | Top and Bottom padding of the title | 10 |\n| titleFontSize | Title font size | 20 |\n| showTitle | Title to be shown or not | true |\n| showLegend | Legend to be shown for named plots or not | true |\n| legendFontSize | Legend font size | 14 |\n| legendPadding | Padding around the legend | 10 |\n| xAxis | xAxis configuration | AxisConfig |\n| yAxis | yAxis configuration | AxisConfig |\n| chartOrientation | \'vertical\' or \'horizontal\' | \'vertical\' |\n| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 |\n| showDataLabel | Should show the value corresponding to the bar within the bar | false |\n| showDataLabelOutsideBar | If showing data label then show it outside the bar. | false |\n\n### AxisConfig\n\n| Parameter | Description | Default value |\n| ------------- | ------------------------------------------------------------ | :-----------: |\n| showLabel | Show axis labels or tick values | true |\n| labelFontSize | Font size of the label to be drawn | 14 |\n| labelPadding | Top and Bottom padding of the label | 5 |\n| showTitle | Axis title to be shown or not | true |\n| titleFontSize | Axis title font size | 16 |\n| titlePadding | Top and Bottom padding of Axis title | 5 |\n| showTick | Tick to be shown or not | true |\n| tickLength | How long the tick will be | 5 |\n| tickWidth | How width the tick will be | 2 |\n| showAxisLine | Axis line to be shown or not | true |\n| axisLineWidth | Thickness of the axis line | 2 |\n| labelRotation | Label rotation in degrees (only applicable on bottom X axis) | 0 |\n\n## Chart Theme Variables\n\nThemes for xychart reside inside the `xychart` attribute, allowing customization through the following syntax:\n\n```yaml\n---\nconfig:\n themeVariables:\n xyChart:\n titleColor: \'#ff0000\'\n---\n```\n\n| Parameter | Description |\n| ---------------- | --------------------------------------------------------- |\n| backgroundColor | Background color of the whole chart |\n| titleColor | Color of the Title text |\n| dataLabelColor | Color of the Data labels (if shown) |\n| legendTextColor | Color of the legend text |\n| xAxisLabelColor | Color of the x-axis labels |\n| xAxisTitleColor | Color of the x-axis title |\n| xAxisTickColor | Color of the x-axis tick |\n| xAxisLineColor | Color of the x-axis line |\n| yAxisLabelColor | Color of the y-axis labels |\n| yAxisTitleColor | Color of the y-axis title |\n| yAxisTickColor | Color of the y-axis tick |\n| yAxisLineColor | Color of the y-axis line |\n| plotColorPalette | String of colors separated by comma e.g. "#f3456, #43445" |\n\n### Setting Colors for Lines and Bars\n\nTo set the color for lines and bars, use the `plotColorPalette` parameter. Colors in the palette will correspond sequentially to the elements in your chart (e.g., first bar/line will use the first color specified in the palette).\n\n```mermaid-example\n---\nconfig:\n themeVariables:\n xyChart:\n plotColorPalette: \'#000000, #0000FF, #00FF00, #FF0000\'\n---\nxychart\ntitle "Different Colors in xyChart"\nx-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]\ny-axis "valuesY" 0 --> 50\n%% Black line\nline [10,20,30,40]\n%% Blue bar\nbar [20,30,25,35]\n%% Green bar\nbar [15,25,20,30]\n%% Red line\nline [5,15,25,35]\n```\n\n```mermaid\n---\nconfig:\n themeVariables:\n xyChart:\n plotColorPalette: \'#000000, #0000FF, #00FF00, #FF0000\'\n---\nxychart\ntitle "Different Colors in xyChart"\nx-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]\ny-axis "valuesY" 0 --> 50\n%% Black line\nline [10,20,30,40]\n%% Blue bar\nbar [20,30,25,35]\n%% Green bar\nbar [15,25,20,30]\n%% Red line\nline [5,15,25,35]\n```\n\n## Displaying individual values on a bar chart (v11.14.0+)\n\nTo show the value corresponding to a bar specify `showDataLabel: true`.\n\n```mermaid-example\n---\nconfig:\n xyChart:\n showDataLabel: true\n---\nxychart\n title "Genres in top 100 book survey of 2025"\n x-axis [comedy, romance, mystery, crime, "non fiction", other]\n y-axis "Number of Books" 0 --> 30\n bar [12,2,20,25,17,24]\n```\n\n```mermaid\n---\nconfig:\n xyChart:\n showDataLabel: true\n---\nxychart\n title "Genres in top 100 book survey of 2025"\n x-axis [comedy, romance, mystery, crime, "non fiction", other]\n y-axis "Number of Books" 0 --> 30\n bar [12,2,20,25,17,24]\n```\n\nLabels are shown within the bar by default. To show the labels outside the bar, specify `showDataLabelOutsideBar: true`.\n\n```mermaid-example\n---\nconfig:\n xyChart:\n showDataLabel: true\n showDataLabelOutsideBar: true\n---\nxychart\n title "Genres in top 100 book survey of 2025"\n x-axis [comedy, romance, mystery, crime, "non fiction", other]\n y-axis "Number of Books" 0 --> 30\n bar [12,2,20,25,17,24]\n```\n\n```mermaid\n---\nconfig:\n xyChart:\n showDataLabel: true\n showDataLabelOutsideBar: true\n---\nxychart\n title "Genres in top 100 book survey of 2025"\n x-axis [comedy, romance, mystery, crime, "non fiction", other]\n y-axis "Number of Books" 0 --> 30\n bar [12,2,20,25,17,24]\n```\n\n## Per-point text labels for line charts (v\\<MERMAID_RELEASE_VERSION>+)\n\nEach data point in a `line` can optionally include a quoted string label after the numeric value. Labels render above points in vertical orientation, or to the right in horizontal orientation, using the line\'s stroke color.\n\n```mermaid-example\nxychart\n title "Smallest AI models scoring above 60% on MMLU"\n x-axis "Date" ["Apr 2022", "Feb 2023", "Jul 2023", "Sep 2023", "Apr 2024"]\n y-axis "Parameters (B)" 0 --> 600\n line [540 "PaLM", 65 "LLaMA-65B", 34 "Llama 2 34B", 7 "Mistral 7B", 3.8 "Phi-3-mini"]\n```\n\n```mermaid\nxychart\n title "Smallest AI models scoring above 60% on MMLU"\n x-axis "Date" ["Apr 2022", "Feb 2023", "Jul 2023", "Sep 2023", "Apr 2024"]\n y-axis "Parameters (B)" 0 --> 600\n line [540 "PaLM", 65 "LLaMA-65B", 34 "Llama 2 34B", 7 "Mistral 7B", 3.8 "Phi-3-mini"]\n```\n\nLabels are optional per point — you can mix labeled and unlabeled values:\n\n```mermaid-example\nxychart\n title "Quarterly Performance"\n x-axis [Q1, Q2, Q3, Q4]\n y-axis "Revenue ($M)" 0 --> 100\n line [25 "Launch", 45, 72, 90 "Target Hit"]\n```\n\n```mermaid\nxychart\n title "Quarterly Performance"\n x-axis [Q1, Q2, Q3, Q4]\n y-axis "Revenue ($M)" 0 --> 100\n line [25 "Launch", 45, 72, 90 "Target Hit"]\n```\n\nExisting syntax without labels continues to work unchanged.\n\n> **Note**\n> Point labels use a fixed font size of 12px. In vertical charts, labels appear above each point. In horizontal charts, labels appear to the right. Labels are currently supported on `line` plots only; the syntax is accepted on `bar` plots but labels are ignored.\n\n## Example on config and theme\n\n```mermaid-example\n---\nconfig:\n xyChart:\n width: 900\n height: 600\n showDataLabel: true\n themeVariables:\n xyChart:\n titleColor: "#ff0000"\n---\nxychart\n title "Sales Revenue"\n x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]\n y-axis "Revenue (in $)" 4000 --> 11000\n bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n```\n\n```mermaid\n---\nconfig:\n xyChart:\n width: 900\n height: 600\n showDataLabel: true\n themeVariables:\n xyChart:\n titleColor: "#ff0000"\n---\nxychart\n title "Sales Revenue"\n x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]\n y-axis "Revenue (in $)" 4000 --> 11000\n bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]\n```\n';
484
- const __vite_glob_0_43 = '# 阶段流程(plan / tasks / execute / done / new-spec)\n\n## plan\n\n理解文档,分析现有代码,编写技术实现方案,更新到当前文档中;需要用户介入的事项以三级标题写入 `## 待确认项`,待用户后续审阅批注;暂时只需更新文档,不要实施代码。\n\n**目标:** 输出 `## 现状分析`、`## 技术实现方案`、`## 待确认项` 三节;禁止输出任务清单格式 `- [ ]`。\n\n若 `## 追加任务` 存在 `- [open] [feat|refct|fix] ...` 条目,必须将其纳入分析范围;plan 阶段不修改 `[open]` 状态标记。\n\n### 待确认项的三种类型\n\n一个待确认项,要么让用户**抉择**,要么让用户**确认**,要么请用户**自由补充**:\n\n| 类型 | 标记 | 语义 | 何时用 |\n| -------- | ------------------- | --------------------------------------------------------------------------------------- | --------------------- |\n| 抉择型 | `[choice]` | 给用户**选择权**:存在多个都可行、优劣取决于你拿不到的信息(业务优先级/外部约束)的方案 | 多方案差异大、难取舍 |\n| 确认型 | `[confirm]` | 给用户**否决权**:只有一个可行方案,但它有明显代价(技术债/影响面/不可逆) | 单方案但影响重大 |\n| 自由文本 | `(自由文本)` 后缀 | 请用户补一段开放说明 | 需要主观文案/开放意见 |\n\n> 无标记且带有序候选,向后兼容视为 `[choice]`。\n\n### 准入门槛(写入待确认项前必过的自检)\n\n**第一道过滤器先于类型判断**:这个答案,你能否通过读代码、跑测试、查项目约定自己确定?\n\n- **能** → 禁止写成待确认项,自己去查、去定,把决策+理由写进 `## 技术实现方案`(决策说明),不要打断用户。\n- **不能**(决策依赖用户独有信息,或选错的返工不可逆)→ 才写成待确认项,并归入 choice/confirm。\n\n> 待确认项是压力释放阀,不是免责声明。被你砍掉的判断不该凭空消失,要落成 `## 技术实现方案` 里的决策说明(决策 + 理由 + 被否决的备选)。\n\n### 抉择型 [choice] 正确示例\n\n```\n### 5.1 [choice] 候选答案的展现形式应采用哪种?\n1. 嵌套子列表\n2. 表格 (推荐)\n```\n\n- 候选写一级有序列表;恰 1 个候选以 ` (推荐)` / ` (推荐)` 结尾。\n- 空态与格式由 lint 的 `pending-questions/*` 规则保证。\n\n### 确认型 [confirm] 正确示例\n\n```\n### 5.2 [confirm] 将 UserID 从 int 迁移为 uuid\n\n**方案**:一次性迁移,双写过渡 2 周。\n**影响**:🔴 需停机窗口;对外 /api/user 字段类型变更,客户端需同步。\n```\n\n- 只有 `**方案**` 与 `**影响**`(或 `**代价**`)两字段,**不列候选**。\n- 影响行用红/黄语义标注严重度:🔴 breaking(破坏性/不可逆)、🟡 affected(有影响但可控)。\n- 确认型 = "我要这么干了,除非你喊停" 的知会 + 急停按钮:放行是常态,用户否决才是例外。\n\n### 确认型的否决语义(用户在 GUI 上如何回应)\n\n用户对确认型只有两种回应:**确认放行** 或 **否决**。否决必须携带理由,并细分意图(GUI 渲染为嵌套单选):\n\n| 用户决策 | 回写 `!!!选择:…` | 含义 |\n| ------------------------- | ------------------------------------ | ---------------------- |\n| 确认,按此推进 | `确认,按此推进` | 接受代价,继续 |\n| 否决·换方案 | `否决·换方案` | 原目标下另寻实现 |\n| 否决·补约束 | `否决·补约束` | 用户补充你漏掉的硬约束 |\n| 否决·弃目标·废弃当前目标 | `否决·弃目标·废弃当前目标,继续其余` | 该目标不做,其余照旧 |\n| 否决·弃目标·放弃整个 spec | `否决·弃目标·放弃整个 spec` | 整条 spec 作废 |\n\n> 决策不改动 API:编码进批注的 `选择:…`(结构化意图)+ `备注:…`(理由)。tasks 阶段按下表消费。\n\n### 图形化补充(plan 收尾子步骤)\n\n方案文字定稿后、结束 plan 前,**必须**专门回看 `## 现状分析`、`## 技术实现方案` 两节做一次 mermaid 图形化补充(仅此两节,其它章节不动):\n\n- 加载 [mermaid.md](./mermaid.md),先服从其**核心原则「图优先、精确信息折叠」**:表层给图(认知层),精确细节(路径、行号、类型/源码全文)折叠进 `<details>` 精确层,图与折叠互补不重复。\n- 按选型表与「必须用图」触发词补图:分步/分支散文→flowchart,接口/类型定义→classDiagram;含「兼容性/影响范围」时出结构图并按红=breaking、黄=affected 语义配色。\n- 这是一道独立聚焦的工序(不新增 stage):把「想清楚方案 / 写成文字」与「回看补图」分开,避免图形化被跳过。补图完成后再结束 plan。\n\n### 待确认项自检(plan 收尾子步骤)\n\n方案与图形化定稿后、结束 plan 前,**必须**专门回看 `## 待确认项` 做一次自检(与「图形化补充」同构:把"写方案"与"回看校验"分开,避免自检被跳过):\n\n1. **准入过滤**:逐条问"能否自己查证?"——能则删除该条、把决策落进 `## 技术实现方案`(决策说明)。\n2. **类型归类**:留下的每条明确标 `[choice]` 或 `[confirm]`(或 `(自由文本)`);单一类型,不兼具。\n3. **确认型补影响**:每个 `[confirm]` 必带 `**方案**` 与 `**影响**`(红/黄语义);影响写不出来,说明它其实够不上确认型,降级或删除。\n\n自检完成后再结束 plan。\n\n空态整章仅一行斜体占位:\n\n```\n## 5. 待确认项\n\n_暂无_\n```\n\n> 格式由 lint 的 `pending-questions/*` 规则保证;不确定时运行 `yorz lint` 查看报错。\n\n## tasks\n\n根据批注内容(`!!!` 前缀)更新技术方案;审查批注是否正确解答了待确认项;若引入新增/扩展需求或新增 bug,切回 plan;若存在冲突或歧义,回退 plan 并写入待确认项后退出;否则将技术方案拆解为可执行任务,写入文档,然后进入 execute。\n\n### 批注消费规则\n\n- 读取并消费所有 `!!!` 批注,将批注意图合并进 `## 技术实现方案` 与 `## 任务清单`。\n- 处理完成后**删除对应 `!!!` 文本**,然后**整段删除** `## 用户批注` 章节。\n\n### 确认型答复的消费\n\n当批注含 `!!!选择:<决策>;备注:<理由>`(引用行的前缀 `待确认项`/`待确认问题` 皆可),按 `选择:` 分派下游行为,并**无论确认或否决都留痕**——在 `## 技术实现方案` 追加一行决策记录:`> 决策记录:<条目> —— 用户<决策>,理由:<理由>。`(决策记录不随 `## 用户批注` 删除而蒸发):\n\n| `选择:` | 下游行为 |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------- |\n| 确认,按此推进 | 接受代价,落决策记录,继续拆任务 |\n| 否决·换方案 | 切回 plan:原目标下另寻实现(新方案可能又产生 choice/confirm) |\n| 否决·补约束 | 切回 plan:把理由当硬约束重算方案 |\n| 否决·弃目标·废弃当前目标,继续其余 | 从 `## 需求` 剔除该目标,落决策记录,继续其余目标 |\n| 否决·弃目标·放弃整个 spec | 落决策记录(作废),将 `stage` 置 `done` 终止,`last_action` 记「用户放弃整个 spec,标记终止」 |\n\n### 任务清单正确示例\n\n```\n## 6. 任务清单\n\n- [ ] 删除 src/foo.ts 中的 deprecatedFunc(验收:grep 无残留引用)\n- [ ] 更新 index.ts 导出(验收:tsc --noEmit 通过)\n```\n\n> 每项任务包含"动作 + 对象 + 验收点";仅使用单层 `- [ ]`;禁止泛化任务。\n\n### `[manual]` 人工确认任务标记\n\n对「需人工确认、Agent 不能自动完成」的事项,用 `- [ ] [manual] ...` 标注:\n\n```\n- [ ] [manual] 联系运维确认灰度窗口(验收:人工回复确认)\n```\n\n`done` 收尾判定**忽略**未勾选的 `[manual]` 项:只要其余非 manual 任务全部完成,即可收尾为 `done`,无需等待人工项被勾选(详见 execute 的「收尾为 done」)。\n\n### 自动衔接\n\n> 任务量不构成暂停理由:以下衔接规则与任务清单长度、改动半径无关。\n\n若待确认项为 `_暂无_` 且无新冲突,同一轮继续进入 execute,**同一轮衔接 execute 前后不得**向用户追加任何"是否执行/是否继续"询问。\n\n## execute\n\n顺序执行 `## 任务清单` 中未完成项(`- [ ]`)。执行中若发现新增/扩展需求或新增 bug,立即停止,切回 plan。\n\n> **执行过程中不得对用户做整批/中段元确认**:除"新需求/新 bug → 切回 plan"这一合法阻塞外,任务清单内部的任何疑问都必须以 `## 待确认项` 形式写回触发重开,禁止追加"是否继续 / 我是否应该执行下一项"等问句停顿。\n\n### 每完成一项后立刻\n\n- 将该项从 `- [ ]` 改为 `- [x]`。\n- 在 `## 执行记录` 增加一条结果(改动点、验证方式、阻塞项若有)。\n\n### 默认执行验证\n\n若仓库存在可用测试/构建命令,默认执行并记录结果;若当前环境无法执行,记录原因。\n\n### 追加任务 `[open] → [fixed]` 状态机\n\n若本轮起因是 `## 追加任务` 中的 `[open]` 条目:每完成一个追加项,把状态标记从 `[open]` 改为 `[fixed]`(位置和正文保留不变),在 `## 执行记录` 追加一条结果。已 `[fixed]` 的条目保留作为历史,不删除。\n\n### 收尾为 done\n\n当 `## 任务清单` 不存在未完成的**非 `[manual]`** 任务(`- [ ] [manual]` 人工确认项被忽略),且 `## 待确认项` 为 `_暂无_`、无任何 `!!!` 批注、无 `## 追加任务` 的 `[open]` 条目时,在 execute 收尾同一轮:\n\n- 将 `stage` 置为 `done`,`last_action` 记「任务全部完成,标记 done」。\n- 在 `## 执行记录` 追加一条收尾条目。\n\n`done` 是终止态:机器 / GUI 可据 `stage=done` 直接判定完成。此后不再自动推进;收到新增/扩展需求、新 bug,或 `## 追加任务` 出现 `[open]` 时,按「变更重开流程」切回 `plan`。\n\n## new-spec\n\n当 `spec_path` 缺省且无法从 session 上下文恢复时,自动生成路径与初始骨架。\n\n### 步骤\n\n1. **提炼摘要**:从用户输入提炼 kebab-case `summary-name`(≤ 40 字符,不以数字开头,必须语义化)。中文为主难以提炼时使用 `untitled-<NNN>` 占位。\n2. **判定 type**:`feat` / `refct` / `fix`;prompt 中已显式给出时直接使用;不确定时询问用户,兜底取 `feat`。\n3. **生成 id**:`YYMMDD.<type>.<summary-name>`(如 `260614.feat.spec-frontmatter`)。\n4. **生成路径**:`<specsDir>/<id>/spec.md`。id 冲突时追加 `-2`、`-3` 后缀。\n5. **初始化文档**:写入 frontmatter(`stage: plan`、`last_action: 新建 spec`、`updated_at` 本地秒级字符串、`summary` ≤ 200 字符);建议创建全部章节(背景 / 需求 / 现状分析 / 技术实现方案 / 待确认项 / 任务清单 / 执行记录),将用户原始需求写入背景或需求。\n6. **进入 plan**:初始化后在同一轮继续推进 plan,不要因为"文档已创建"就退出。\n\n### CLI 触发模式\n\nCLI/Service 同时给出 `类型:<type>` 与需求但未提供 `spec_path` 时,必须自动进入新建流程,不询问类型/路径;初始化后同一轮继续推进 plan,直到遇到真正阻塞才退出。\n';
484
+ const __vite_glob_0_43 = '# 阶段流程(plan / tasks / execute / done / new-spec)\n\n## plan\n\n理解文档,分析现有代码,编写技术实现方案,更新到当前文档中;需要用户介入的事项以三级标题写入 `## 待确认项`,待用户后续审阅批注;暂时只需更新文档,不要实施代码。\n\n**目标:** 输出 `## 现状分析`、`## 技术实现方案`、`## 待确认项` 三节;禁止输出任务清单格式 `- [ ]`。\n\n若 `## 追加任务` 存在 `- [open] [feat|refct|fix] ...` 条目,必须将其纳入分析范围;plan 阶段不修改 `[open]` 状态标记。\n\n### 待确认项的三种类型\n\n一个待确认项,要么让用户**抉择**,要么让用户**确认**,要么请用户**自由补充**:\n\n| 类型 | 标记 | 语义 | 何时用 |\n| -------- | ------------------- | --------------------------------------------------------------------------------------- | --------------------- |\n| 抉择型 | `[choice]` | 给用户**选择权**:存在多个都可行、优劣取决于你拿不到的信息(业务优先级/外部约束)的方案 | 多方案差异大、难取舍 |\n| 确认型 | `[confirm]` | 给用户**否决权**:只有一个可行方案,但它有明显代价(技术债/影响面/不可逆) | 单方案但影响重大 |\n| 自由文本 | `(自由文本)` 后缀 | 请用户补一段开放说明 | 需要主观文案/开放意见 |\n\n> 无标记且带有序候选,向后兼容视为 `[choice]`。\n\n### 准入门槛(写入待确认项前必过的自检)\n\n**第一道过滤器先于类型判断**:这个答案,你能否通过读代码、跑测试、查项目约定自己确定?\n\n- **能** → 禁止写成待确认项,自己去查、去定,把决策+理由写进 `## 技术实现方案`(决策说明),不要打断用户。\n- **不能**(决策依赖用户独有信息,或选错的返工不可逆)→ 才写成待确认项,并归入 choice/confirm。\n\n> 待确认项是压力释放阀,不是免责声明。被你砍掉的判断不该凭空消失,要落成 `## 技术实现方案` 里的决策说明(决策 + 理由 + 被否决的备选)。\n\n### 抉择型 [choice] 正确示例\n\n```\n### 5.1 [choice] 候选答案的展现形式应采用哪种?\n1. 嵌套子列表\n2. 表格 (推荐)\n```\n\n- 候选写一级有序列表;恰 1 个候选以 ` (推荐)` / ` (推荐)` 结尾。\n- 空态与格式由 lint 的 `pending-questions/*` 规则保证。\n\n### 确认型 [confirm] 正确示例\n\n```\n### 5.2 [confirm] 将 UserID 从 int 迁移为 uuid\n\n**方案**:一次性迁移,双写过渡 2 周。\n**影响**:🔴 需停机窗口;对外 /api/user 字段类型变更,客户端需同步。\n```\n\n- 只有 `**方案**` 与 `**影响**`(或 `**代价**`)两字段,**不列候选**。\n- 影响行用红/黄语义标注严重度:🔴 breaking(破坏性/不可逆)、🟡 affected(有影响但可控)。\n- 确认型 = "我要这么干了,除非你喊停" 的知会 + 急停按钮:放行是常态,用户否决才是例外。\n\n### 确认型的否决语义(用户在 GUI 上如何回应)\n\n用户对确认型只有两种回应:**确认放行** 或 **否决**。否决必须携带理由,并细分意图(GUI 渲染为嵌套单选):\n\n| 用户决策 | 回写 `!!!选择:…` | 含义 |\n| ------------------------- | ------------------------------------ | ---------------------- |\n| 确认,按此推进 | `确认,按此推进` | 接受代价,继续 |\n| 否决·换方案 | `否决·换方案` | 原目标下另寻实现 |\n| 否决·补约束 | `否决·补约束` | 用户补充你漏掉的硬约束 |\n| 否决·弃目标·废弃当前目标 | `否决·弃目标·废弃当前目标,继续其余` | 该目标不做,其余照旧 |\n| 否决·弃目标·放弃整个 spec | `否决·弃目标·放弃整个 spec` | 整条 spec 作废 |\n\n> 决策不改动 API:编码进批注的 `选择:…`(结构化意图)+ `备注:…`(理由)。tasks 阶段按下表消费。\n\n### 图形化补充(plan 收尾子步骤)\n\n方案文字定稿后、结束 plan 前,**必须**专门回看 `## 现状分析`、`## 技术实现方案` 两节做一次 mermaid 图形化补充(仅此两节,其它章节不动):\n\n- 加载 [mermaid.md](./mermaid.md),先服从其**核心原则「图优先、精确信息折叠」**:表层给图(认知层),精确细节(路径、行号、类型/源码全文)折叠进 `<details>` 精确层,图与折叠互补不重复。\n- 按选型表与「必须用图」触发词补图:分步/分支散文→flowchart,接口/类型定义→classDiagram;含「兼容性/影响范围」时出结构图并按红=breaking、黄=affected 语义配色。\n- 这是一道独立聚焦的工序(不新增 stage):把「想清楚方案 / 写成文字」与「回看补图」分开,避免图形化被跳过。补图完成后再结束 plan。\n\n### 待确认项自检(plan 收尾子步骤)\n\n方案与图形化定稿后、结束 plan 前,**必须**专门回看 `## 待确认项` 做一次自检(与「图形化补充」同构:把"写方案"与"回看校验"分开,避免自检被跳过):\n\n1. **准入过滤**:逐条问"能否自己查证?"——能则删除该条、把决策落进 `## 技术实现方案`(决策说明)。\n2. **类型归类**:留下的每条明确标 `[choice]` 或 `[confirm]`(或 `(自由文本)`);单一类型,不兼具。\n3. **确认型补影响**:每个 `[confirm]` 必带 `**方案**` 与 `**影响**`(红/黄语义);影响写不出来,说明它其实够不上确认型,降级或删除。\n\n自检完成后再结束 plan。\n\n空态整章仅一行斜体占位:\n\n```\n## 5. 待确认项\n\n_暂无_\n```\n\n> 格式由 lint 的 `pending-questions/*` 规则保证;不确定时运行 `yorz lint` 查看报错。\n\n## tasks\n\n根据批注内容(`!!!` 前缀)更新技术方案;审查批注是否正确解答了待确认项;若引入新增/扩展需求或新增 bug,切回 plan;若存在冲突或歧义,回退 plan 并写入待确认项后退出;否则将技术方案拆解为可执行任务,写入文档,然后进入 execute。\n\n### 批注消费规则\n\n- 读取并消费所有 `!!!` 批注,将批注意图合并进 `## 技术实现方案` 与 `## 任务清单`。\n- 处理完成后**删除对应 `!!!` 文本**,然后**整段删除** `## 用户批注` 章节。\n\n### 确认型答复的消费\n\n当批注含 `!!!选择:<决策>;备注:<理由>`(引用行的前缀 `待确认项`/`待确认问题` 皆可),按 `选择:` 分派下游行为,并**无论确认或否决都留痕**——在 `## 技术实现方案` 追加一行决策记录:`> 决策记录:<条目> —— 用户<决策>,理由:<理由>。`(决策记录不随 `## 用户批注` 删除而蒸发):\n\n| `选择:` | 下游行为 |\n| ---------------------------------- | ---------------------------------------------------------------------------------------------- |\n| 确认,按此推进 | 接受代价,落决策记录,继续拆任务 |\n| 否决·换方案 | 切回 plan:原目标下另寻实现(新方案可能又产生 choice/confirm) |\n| 否决·补约束 | 切回 plan:把理由当硬约束重算方案 |\n| 否决·弃目标·废弃当前目标,继续其余 | 从 `## 需求` 剔除该目标,落决策记录,继续其余目标 |\n| 否决·弃目标·放弃整个 spec | 落决策记录(作废),将 `stage` 置 `done` 终止,`last_action` 记「用户放弃整个 spec,标记终止」 |\n\n### 任务清单正确示例\n\n```\n## 6. 任务清单\n\n- [ ] 删除 src/foo.ts 中的 deprecatedFunc(验收:grep 无残留引用)\n- [ ] 更新 index.ts 导出(验收:tsc --noEmit 通过)\n```\n\n> 每项任务包含"动作 + 对象 + 验收点";仅使用单层 `- [ ]`;禁止泛化任务。\n\n### `[manual]` 人工确认任务标记\n\n对「需人工确认、Agent 不能自动完成」的事项,用 `- [ ] [manual] ...` 标注:\n\n```\n- [ ] [manual] 联系运维确认灰度窗口(验收:人工回复确认)\n```\n\n`done` 收尾判定**忽略**未勾选的 `[manual]` 项:只要其余非 manual 任务全部完成,即可收尾为 `done`,无需等待人工项被勾选(详见 execute 的「收尾为 done」)。\n\n### 自动衔接\n\n> 任务量不构成暂停理由:以下衔接规则与任务清单长度、改动半径无关。\n\n若待确认项为 `_暂无_` 且无新冲突,同一轮继续进入 execute,**同一轮衔接 execute 前后不得**向用户追加任何"是否执行/是否继续"询问。\n\n## execute\n\n顺序执行 `## 任务清单` 中未完成项(`- [ ]`)。执行中若发现新增/扩展需求或新增 bug,立即停止,切回 plan。\n\n> **执行过程中不得对用户做整批/中段元确认**:除"新需求/新 bug → 切回 plan"这一合法阻塞外,任务清单内部的任何疑问都必须以 `## 待确认项` 形式写回触发重开,禁止追加"是否继续 / 我是否应该执行下一项"等问句停顿。\n\n### 每完成一项后立刻\n\n- 将该项从 `- [ ]` 改为 `- [x]`。\n- 在 `## 执行记录` 增加一条结果(改动点、验证方式、阻塞项若有)。\n\n### 默认执行验证\n\n若仓库存在可用测试/构建命令,默认执行并记录结果;若当前环境无法执行,记录原因。\n\n### 追加任务 `[open] → [fixed]` 状态机\n\n若本轮起因是 `## 追加任务` 中的 `[open]` 条目:每完成一个追加项,把状态标记从 `[open]` 改为 `[fixed]`(位置和正文保留不变),在 `## 执行记录` 追加一条结果。已 `[fixed]` 的条目保留作为历史,不删除。\n\n### 收尾为 done\n\n当 `## 任务清单` 不存在未完成的**非 `[manual]`** 任务(`- [ ] [manual]` 人工确认项被忽略),且 `## 待确认项` 为 `_暂无_`、无任何 `!!!` 批注、无 `## 追加任务` 的 `[open]` 条目时,在 execute 收尾同一轮:\n\n- 将 `stage` 置为 `done`,`last_action` 记「任务全部完成,标记 done」。\n- 在 `## 执行记录` 追加一条收尾条目。\n\n`done` 是终止态:机器 / GUI 可据 `stage=done` 直接判定完成。此后不再自动推进;收到新增/扩展需求、新 bug,或 `## 追加任务` 出现 `[open]` 时,按「变更重开流程」切回 `plan`。\n\n## new-spec\n\n当 `spec_path` 缺省且无法从 session 上下文恢复时,自动生成路径与初始骨架。\n\n### 步骤\n\n1. **提炼摘要**:从用户输入提炼 kebab-case `summary-name`(≤ 40 字符,不以数字开头,必须语义化):只使用对需求有语义代表性的英文/数字字符;中文为主难以提炼出可读 slug 时直接使用 `untitled-<NNN>`(3 位日期内自增编号)占位,**禁止把中文挤压为零散英文片段**(例如禁止出现 `spec-agent-spec-agent` 之类的拼接)。\n2. **判定 type**:`feat` / `refct` / `fix`;prompt 中已显式给出时直接使用;不确定时询问用户,兜底取 `feat`。\n3. **生成 id**:`YYMMDD.<type>.<summary-name>`(如 `260614.feat.spec-frontmatter`)。\n4. **生成路径**:`<specsDir>/<id>/spec.md`。id 冲突时追加 `-2`、`-3` 后缀。\n5. **初始化文档**:写入 frontmatter(`stage: plan`、`last_action: 新建 spec`、`updated_at` 本地秒级字符串、`summary` 为对需求的**真实概述**且 ≤ 200 字符,不要原样照搬整段需求);建议创建全部章节(背景 / 需求 / 现状分析 / 技术实现方案 / 待确认项 / 任务清单 / 执行记录),将用户原始需求写入背景或需求。\n6. **进入 plan**:初始化后在同一轮继续推进 plan,按 SKILL 规则补齐 `现状分析` / `技术实现方案` / `待确认项`,再视需要进入 tasks/execute;不要因为"文档已创建"就退出。\n\n### CLI 触发模式\n\nCLI/Service 同时给出 `类型:<type>` 与需求但未提供 `spec_path` 时,必须自动进入新建流程,不询问类型/路径;初始化后同一轮继续推进 plan,直到遇到真正阻塞才退出。\n';
485
485
  async function isGitRepo(cwd) {
486
486
  try {
487
487
  await stat(join(cwd, ".git"));
@@ -811,6 +811,27 @@ class AttachmentStore {
811
811
  async draftExists(draftId) {
812
812
  return existsSync(this.draftDir(draftId));
813
813
  }
814
+ /**
815
+ * Copy a draft in from another project's store, keeping its id.
816
+ *
817
+ * Needed by "new spec in a fresh worktree": the user uploads attachments from
818
+ * the main project while typing, but the worktree only exists from the moment
819
+ * they hit create — and `.yorz/tmp` is gitignored, so `git worktree add`
820
+ * cannot carry the draft over. Copying keeps every downstream path relative,
821
+ * so the prompt needs no worktree-specific branch.
822
+ *
823
+ * Returns `false` when the source draft is gone (TTL sweep, manual cleanup):
824
+ * that is "nothing to carry over", not a failure.
825
+ */
826
+ async importDraftFrom(source, draftId) {
827
+ const from = source.draftDir(draftId);
828
+ const to = this.draftDir(draftId);
829
+ if (from === to || !existsSync(from)) return false;
830
+ if (existsSync(to)) return true;
831
+ await this.ensureRoot();
832
+ await cp(from, to, { recursive: true });
833
+ return true;
834
+ }
814
835
  /**
815
836
  * Store a file under the draft.
816
837
  * - Validates MIME / size.
@@ -1202,12 +1223,213 @@ function configureLogger(patch) {
1202
1223
  logger.configure(patch);
1203
1224
  return logger;
1204
1225
  }
1226
+ const COMMAND_RE = /^\/([\w-]+)(?:\s+([\s\S]*))?$/;
1227
+ const SPEC_PATH_RE = /^@?\S+\.md$/;
1228
+ const SPEC_PATH_ANCHOR_NOTE = "该 spec 路径是**项目根目录**(即你的当前工作目录)下的相对路径,不要相对 skill 文件所在目录或用户主目录解析;命令行中的 `@` 只是文件引用标记,不属于路径本身。";
1229
+ const SPEC_TYPE_RE = /^(feat|refct|fix)\s*[::]\s*([\s\S]*)$/;
1230
+ function parseBuiltinCommand(prompt) {
1231
+ const matched = COMMAND_RE.exec(prompt.trim());
1232
+ if (!matched) return null;
1233
+ const rest = (matched[2] ?? "").trim();
1234
+ const head = /^(\S+)(?:\s+([\s\S]*))?$/.exec(rest);
1235
+ if (head && SPEC_PATH_RE.test(head[1])) {
1236
+ return {
1237
+ name: matched[1],
1238
+ specPath: stripMention(head[1]),
1239
+ specType: "",
1240
+ body: (head[2] ?? "").trim()
1241
+ };
1242
+ }
1243
+ return { name: matched[1], specPath: "", ...splitSpecType(rest) };
1244
+ }
1245
+ function splitSpecType(rest) {
1246
+ const typed = SPEC_TYPE_RE.exec(rest);
1247
+ if (!typed) return { specType: "", body: rest };
1248
+ return { specType: typed[1], body: typed[2].trim() };
1249
+ }
1250
+ function specDirOf(specPath) {
1251
+ const cut = specPath.lastIndexOf("/");
1252
+ return cut > 0 ? specPath.slice(0, cut) : ".";
1253
+ }
1254
+ function stripMention(specPath) {
1255
+ return specPath.startsWith("@") ? specPath.slice(1) : specPath;
1256
+ }
1257
+ function formatBuiltinCommand(name2, specPath, body = "") {
1258
+ const mention = specPath ? `@${stripMention(specPath)}` : "";
1259
+ return [`/${name2}`, mention, body.trim()].filter(Boolean).join(" ");
1260
+ }
1261
+ function formatTypedBuiltinCommand(name2, specType, body = "") {
1262
+ return `/${name2} ${specType}: ${body.trim()}`.trimEnd();
1263
+ }
1205
1264
  function skillEntryPath(skillName, env = process.env) {
1206
1265
  return resolveSkillEntry(skillName, env);
1207
1266
  }
1208
1267
  function skillRef(skillName, env = process.env) {
1209
1268
  return `请先完整阅读并严格遵循 ${skillEntryPath(skillName, env)}(YorZ 内置 ${skillName} skill 规则)`;
1210
1269
  }
1270
+ const CHAT_DEBUG_DIR_REL = ".yorz/tmp/debug";
1271
+ const CHAT_DEBUG_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
1272
+ const CHAT_DEBUG_CLEANUP_INTERVAL_MS = 60 * 60 * 1e3;
1273
+ function isYorzDebugCommand(prompt) {
1274
+ return /^\/yorz-debug(?:\s|$)/.test(prompt.trim());
1275
+ }
1276
+ function buildDebugPrompt(prompt, opts = {}) {
1277
+ const parsed = parseBuiltinCommand(prompt);
1278
+ const original = prompt.trim();
1279
+ const body = parsed?.body ?? "";
1280
+ const specPath = parsed?.specPath ?? "";
1281
+ const runtimeContext = opts.runtimeContext?.trim() ? `
1282
+ ${opts.runtimeContext.trim()}` : "";
1283
+ const head = specPath ? [
1284
+ `${skillRef("yorz-debug")},然后进入 Debug 模式:spec 目录 \`${specDirOf(specPath)}\`(含 \`${specPath}\`)。`,
1285
+ `Debug 活文档为 \`${specDirOf(specPath)}/debug.md\`:不存在则创建;已存在则按 skill 的「重入」规则处理——frontmatter \`status: debugging\` 时定位 \`active\` 指向的 \`## Debug NNN\` 记录块续跑、勿新建,否则在文末追加新的记录块。`,
1286
+ `新建记录块时立即 \`git stash create\` 打快照写入 Debug 基线;续跑已有记录块则沿用其中已记录的基线。`,
1287
+ SPEC_PATH_ANCHOR_NOTE
1288
+ ] : [
1289
+ `${skillRef("yorz-debug")},然后进入 Debug 模式。`,
1290
+ `本次是普通 chat 独立触发,没有 spec_dir。Debug 活文档必须写入临时文件 \`${CHAT_DEBUG_DIR_REL}/debug-${formatDebugTimestamp(opts.now ?? /* @__PURE__ */ new Date())}.md\`。`,
1291
+ `写入前请确保目录 \`${CHAT_DEBUG_DIR_REL}/\` 存在;该目录属于临时目录,会由 YorZ 定时清理。`,
1292
+ `如果该文件不存在则创建;本文件只承载本次 chat debug 记录,不需要追加复用其他文件。`
1293
+ ];
1294
+ const tail = body ? "待调试问题见下方用户输入(忽略其中的 `/yorz-debug` 指令前缀与 spec 路径参数):" : specPath ? "请结合该 spec 的 `## 追加任务` 与对话上下文确认本次待调试问题;若信息不足,请向用户补齐复现信息。" : "请先根据对话上下文确认待调试问题;若上下文不足,请向用户补齐复现信息。";
1295
+ return wrapHiddenPrompt([...head, runtimeContext, "", tail].filter(Boolean).join("\n"), original);
1296
+ }
1297
+ async function cleanupExpiredChatDebugFiles(projectPath, opts = {}) {
1298
+ const root = join(projectPath, CHAT_DEBUG_DIR_REL);
1299
+ if (!existsSync(root)) return { removed: [] };
1300
+ const removed = [];
1301
+ const cutoff = (opts.now ?? Date.now()) - (opts.ttlMs ?? CHAT_DEBUG_RETENTION_MS);
1302
+ let entries;
1303
+ try {
1304
+ entries = await readdir(root, { withFileTypes: true });
1305
+ } catch {
1306
+ return { removed };
1307
+ }
1308
+ for (const entry of entries) {
1309
+ if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1310
+ const file = join(root, entry.name);
1311
+ try {
1312
+ const s = await stat(file);
1313
+ if (s.mtimeMs <= cutoff) {
1314
+ await rm(file, { force: true });
1315
+ removed.push(entry.name);
1316
+ }
1317
+ } catch {
1318
+ }
1319
+ }
1320
+ return { removed };
1321
+ }
1322
+ function scheduleChatDebugCleanup(projectPath) {
1323
+ void mkdir(join(projectPath, CHAT_DEBUG_DIR_REL), { recursive: true }).then(() => cleanupExpiredChatDebugFiles(projectPath)).catch(() => {
1324
+ });
1325
+ const timer = setInterval(() => {
1326
+ void cleanupExpiredChatDebugFiles(projectPath).catch(() => {
1327
+ });
1328
+ }, CHAT_DEBUG_CLEANUP_INTERVAL_MS);
1329
+ timer.unref?.();
1330
+ return () => clearInterval(timer);
1331
+ }
1332
+ function formatDebugTimestamp(date) {
1333
+ const pad = (n) => String(n).padStart(2, "0");
1334
+ return [
1335
+ date.getFullYear(),
1336
+ pad(date.getMonth() + 1),
1337
+ pad(date.getDate()),
1338
+ "-",
1339
+ pad(date.getHours()),
1340
+ pad(date.getMinutes()),
1341
+ pad(date.getSeconds())
1342
+ ].join("");
1343
+ }
1344
+ const SLASH_NAME_RE = /^\/([\w-]+)(?:\s|$)/;
1345
+ const SPEC_COMMAND_RE = /^\/yorz-spec(?:\s|$)/;
1346
+ function isSlashCommand(prompt) {
1347
+ return SLASH_NAME_RE.test(prompt.trim());
1348
+ }
1349
+ function buildSpecPrompt(prompt, specsDirRelative, opts = {}) {
1350
+ const parsed = parseBuiltinCommand(prompt);
1351
+ const original = prompt.trim();
1352
+ const body = parsed?.body ?? "";
1353
+ const specPath = parsed?.specPath ?? "";
1354
+ const specType = parsed?.specType ?? "";
1355
+ const head = specPath ? [
1356
+ `${skillRef("yorz-spec")},然后按其自动模式判定推进 spec:\`${specPath}\`。`,
1357
+ SPEC_PATH_ANCHOR_NOTE
1358
+ ] : specType ? [
1359
+ `${skillRef("yorz-spec")},然后按其「新建 spec」流程创建新的 spec 文档,并立即按 plan 阶段继续推进直至阻塞。`,
1360
+ `类型:${specType}(已由调用方指定,不要再询问)。`,
1361
+ `spec 目录为 \`${specsDirRelative}/\`。`,
1362
+ ...buildDraftAttachmentGuide(specsDirRelative, opts.draftId)
1363
+ ] : [
1364
+ `${skillRef("yorz-spec")},然后按其自动模式判定推进。`,
1365
+ `本次是普通 chat 独立触发,未指定 spec_path:若当前会话上下文中已出现过 spec 文档,继续推进该 spec;否则按 skill 的「新建 spec」流程创建后立即推进 plan 阶段。`,
1366
+ `spec 目录为 \`${specsDirRelative}/\`。`
1367
+ ];
1368
+ const tail = body ? specType && !specPath ? "本次需求见下方用户输入(忽略其中的 `/yorz-spec` 指令前缀与类型参数),据此创建 spec。" : "本次需求见下方用户输入(忽略其中的 `/yorz-spec` 指令前缀与 spec 路径参数);若该内容已作为 `[open]` 条目写入 `## 追加任务`,按追加任务流程消费。" : specPath ? "本次没有额外输入,按 spec 现状继续推进。" : "请先根据对话上下文确认本次要处理的 spec 或需求;若上下文不足,请向用户补齐后再推进。";
1369
+ return wrapHiddenPrompt([...head, "", tail].join("\n"), original);
1370
+ }
1371
+ function buildDraftAttachmentGuide(specsDirRelative, draftId) {
1372
+ if (!draftId) return [];
1373
+ return [
1374
+ "",
1375
+ `附件迁移:本次新建 spec 关联了草稿附件目录 \`.yorz/tmp/drafts/${draftId}/attachments/\`。`,
1376
+ `- 在创建 \`${specsDirRelative}/<id>/\` 目录并写入 \`spec.md\` 骨架**之后**,立即把该 draft 目录下的所有文件迁移到 \`${specsDirRelative}/<id>/attachments/\`,文件名保持不变。`,
1377
+ "- 迁移完成后,在 `## 背景` 章节末尾追加一段附件列表,每个附件占一行(按文件扩展名判定 `kind`):",
1378
+ " - 图片(`.png` / `.jpg` / `.jpeg` / `.gif` / `.webp` / `.bmp` / `.svg` / `.avif` / `.heic`):使用 `![<文件名>](attachments/<文件名>)`",
1379
+ " - PDF(`.pdf`) / 文本(`.txt` / `.md` / `.markdown`):使用 `[<文件名>](attachments/<文件名>)`",
1380
+ "- 迁移失败(如 draft 目录已被清理、权限不足)时,**不要静默丢弃**:在 `## 待确认项` 章节追加一条记录说明问题,并退出本轮等待用户介入。"
1381
+ ];
1382
+ }
1383
+ function buildSpecDispatch(opts) {
1384
+ const specPath = `${opts.specsDirRelative}/${opts.specId}/spec.md`;
1385
+ const commandLine = formatBuiltinCommand(
1386
+ opts.debug ? "yorz-debug" : "yorz-spec",
1387
+ specPath,
1388
+ opts.body
1389
+ );
1390
+ const prompt = opts.debug ? buildDebugPrompt(commandLine, { runtimeContext: opts.runtimeContext }) : buildSpecPrompt(commandLine, opts.specsDirRelative);
1391
+ return { commandLine, prompt };
1392
+ }
1393
+ function buildDraftDispatch(opts) {
1394
+ const commandLine = formatTypedBuiltinCommand("yorz-spec", opts.type, opts.requirement);
1395
+ const prompt = buildSpecPrompt(commandLine, opts.specsDirRelative, { draftId: opts.draftId });
1396
+ return { commandLine, prompt };
1397
+ }
1398
+ function buildUnknownCommandPrompt(prompt, hit) {
1399
+ const original = prompt.trim();
1400
+ const lines = [
1401
+ "下方用户输入以 `/` 开头,这是 YorZ 输入框的指令语法,**不是**你自身的 slash command,请勿按命令解析、也不要回复 `Unknown command`。"
1402
+ ];
1403
+ if (hit) {
1404
+ lines.push(
1405
+ `\`/${hit.name}\` 是用户在 YorZ 中配置的自定义指令,但未配置隐藏提示词${hit.description ? `;其说明为:${hit.description}` : ""}。请结合该指令名与下方正文理解用户意图。`
1406
+ );
1407
+ } else {
1408
+ lines.push("该指令未在 YorZ 中配置,请忽略指令前缀,按普通文本理解下方正文。");
1409
+ }
1410
+ return wrapHiddenPrompt(lines.join("\n"), original);
1411
+ }
1412
+ function resolveChatPrompt(prompt, instructions, opts = {}) {
1413
+ const original = prompt.trim();
1414
+ if (!isSlashCommand(original)) return { prompt, builtin: null };
1415
+ if (isYorzDebugCommand(original)) {
1416
+ return {
1417
+ prompt: buildDebugPrompt(original, { now: opts.now, runtimeContext: opts.runtimeContext }),
1418
+ builtin: "yorz-debug"
1419
+ };
1420
+ }
1421
+ if (SPEC_COMMAND_RE.test(original)) {
1422
+ return {
1423
+ prompt: buildSpecPrompt(original, opts.specsDirRelative ?? ".yorz/specs"),
1424
+ builtin: "yorz-spec"
1425
+ };
1426
+ }
1427
+ const hit = matchCustomInstruction(original, instructions);
1428
+ if (hit?.hiddenPrompt.trim()) {
1429
+ return { prompt: wrapHiddenPrompt(hit.hiddenPrompt, original), builtin: null };
1430
+ }
1431
+ return { prompt: buildUnknownCommandPrompt(original, hit), builtin: null };
1432
+ }
1211
1433
  const METRICS_DIR_NAME = "metrics";
1212
1434
  const TELEMETRY_FILE_NAME = "telemetry.jsonl";
1213
1435
  const PROJECTS_INDEX_FILE_NAME = "projects.json";
@@ -1538,6 +1760,7 @@ async function writeAtomic(file, content) {
1538
1760
  await writeFile(tmp, content, "utf8");
1539
1761
  await rename(tmp, file);
1540
1762
  }
1763
+ const SPEC_BUSY_ERROR = "该 spec 有正在执行的会话,请等待其结束后再试";
1541
1764
  function createSpecsRoutes(resolveProject) {
1542
1765
  const app = new Hono();
1543
1766
  const need = async (c) => {
@@ -1565,10 +1788,25 @@ function createSpecsRoutes(resolveProject) {
1565
1788
  const input = parseCreateBody(body);
1566
1789
  if ("error" in input) return c.json({ error: input.error }, 400);
1567
1790
  if (!input.title && input.requirement) {
1791
+ if (input.draftId && input.draftProjectId && input.draftProjectId !== p.id) {
1792
+ const source = await resolveProject(input.draftProjectId);
1793
+ if (source) {
1794
+ try {
1795
+ await p.attachments.importDraftFrom(source.attachments, input.draftId);
1796
+ } catch (err) {
1797
+ return c.json({ error: `draft attachments copy failed: ${err.message}` }, 400);
1798
+ }
1799
+ }
1800
+ }
1568
1801
  const beforeIds = new Set((await p.store.list()).map((s) => s.id));
1569
- const prompt = buildDraftPrompt(input.type, input.requirement, input.draftId);
1802
+ const { commandLine, prompt } = buildDraftDispatch({
1803
+ specsDirRelative: p.specsDirRelative,
1804
+ type: input.type,
1805
+ requirement: input.requirement,
1806
+ draftId: input.draftId
1807
+ });
1570
1808
  const { sessionId } = await p.sessions.createSession();
1571
- const handle = await p.sessions.send(sessionId, prompt, void 0, { trigger: "new-spec" });
1809
+ const handle = await p.sessions.send(sessionId, prompt, commandLine, { trigger: "new-spec" });
1572
1810
  handle.onDone((finalSessionId) => {
1573
1811
  void bindDraftSessionToCreatedSpec(p, beforeIds, finalSessionId);
1574
1812
  });
@@ -1695,30 +1933,30 @@ function createSpecsRoutes(resolveProject) {
1695
1933
  } catch (err) {
1696
1934
  return c.json({ error: err.message }, 400);
1697
1935
  }
1698
- if (parsed.autoRun) {
1699
- const { sessionId } = await p.sessions.ensureSessionForSpec(specId);
1700
- const debugActive = await readDebugMdStatus(join(p.specsDir, specId)) === "debugging";
1701
- const prompt = parsed.debug ? buildDebugPrompt(p.specsDirRelative, specId, "new", await buildDebugRuntimeContext(p)) : debugActive ? buildDebugPrompt(
1702
- p.specsDirRelative,
1703
- specId,
1704
- "resume",
1705
- await buildDebugRuntimeContext(p)
1706
- ) : `${skillRef("yorz-spec")},然后处理 spec:${p.specsDirRelative}/${specId}/spec.md`;
1707
- const handle = await p.sessions.send(sessionId, prompt, void 0, {
1708
- trigger: "append",
1709
- specId
1710
- });
1711
- trackSpecStage({
1712
- projectRoot: p.path,
1713
- store: p.store,
1714
- specId,
1715
- handle,
1716
- before: detail,
1717
- trigger: "append"
1718
- });
1719
- return c.json({ ok: true, runId: handle.runId, sessionId });
1720
- }
1721
- return c.json({ ok: true });
1936
+ if (await p.sessions.isSpecRunning(specId)) return c.json({ ok: true, busy: true });
1937
+ const { sessionId } = await p.sessions.createSessionForSpec(specId);
1938
+ const debugActive = await readDebugMdStatus(join(p.specsDir, specId)) === "debugging";
1939
+ const debug = parsed.kind === "fix" || debugActive;
1940
+ const { commandLine, prompt } = buildSpecDispatch({
1941
+ specsDirRelative: p.specsDirRelative,
1942
+ specId,
1943
+ debug,
1944
+ body: parsed.description,
1945
+ runtimeContext: debug ? await buildDebugRuntimeContext(p) : void 0
1946
+ });
1947
+ const handle = await p.sessions.send(sessionId, prompt, commandLine, {
1948
+ trigger: "append",
1949
+ specId
1950
+ });
1951
+ trackSpecStage({
1952
+ projectRoot: p.path,
1953
+ store: p.store,
1954
+ specId,
1955
+ handle,
1956
+ before: detail,
1957
+ trigger: "append"
1958
+ });
1959
+ return c.json({ ok: true, runId: handle.runId, sessionId });
1722
1960
  });
1723
1961
  app.post("/projects/:projectId/specs/:id/run", async (c) => {
1724
1962
  const p = await need(c);
@@ -1726,10 +1964,16 @@ function createSpecsRoutes(resolveProject) {
1726
1964
  const specId = c.req.param("id");
1727
1965
  const detail = await p.store.read(specId);
1728
1966
  if (!detail) return c.json({ error: "spec not found" }, 404);
1729
- const { sessionId } = await p.sessions.ensureSessionForSpec(specId);
1967
+ if (await p.sessions.isSpecRunning(specId)) return c.json({ error: SPEC_BUSY_ERROR }, 409);
1968
+ const { sessionId } = await p.sessions.createSessionForSpec(specId);
1730
1969
  const debugActive = await readDebugMdStatus(join(p.specsDir, specId)) === "debugging";
1731
- const prompt = debugActive ? buildDebugPrompt(p.specsDirRelative, specId, "resume", await buildDebugRuntimeContext(p)) : `${skillRef("yorz-spec")},然后处理 spec:${p.specsDirRelative}/${specId}/spec.md`;
1732
- const handle = await p.sessions.send(sessionId, prompt, void 0, {
1970
+ const { commandLine, prompt } = buildSpecDispatch({
1971
+ specsDirRelative: p.specsDirRelative,
1972
+ specId,
1973
+ debug: debugActive,
1974
+ runtimeContext: debugActive ? await buildDebugRuntimeContext(p) : void 0
1975
+ });
1976
+ const handle = await p.sessions.send(sessionId, prompt, commandLine, {
1733
1977
  trigger: "run",
1734
1978
  specId
1735
1979
  });
@@ -1770,7 +2014,8 @@ function createSpecsRoutes(resolveProject) {
1770
2014
  ${text}
1771
2015
  """
1772
2016
  `;
1773
- const { sessionId } = await p.sessions.ensureSessionForSpec(specId);
2017
+ if (await p.sessions.isSpecRunning(specId)) return c.json({ error: SPEC_BUSY_ERROR }, 409);
2018
+ const { sessionId } = await p.sessions.latestSessionForSpec(specId);
1774
2019
  const handle = await p.sessions.send(sessionId, prompt, void 0, {
1775
2020
  trigger: "explain",
1776
2021
  specId
@@ -1788,34 +2033,6 @@ ${text}
1788
2033
  });
1789
2034
  return app;
1790
2035
  }
1791
- function buildDraftPrompt(type2, requirement, draftId) {
1792
- const lines = [
1793
- `${skillRef("yorz-spec")},然后按其「新建 spec」流程,根据下方信息创建新的 spec 文档,并立即按 plan 阶段继续推进直至阻塞。`,
1794
- "",
1795
- `类型:${type2}(已由调用方指定,不要再询问)`,
1796
- "需求:",
1797
- '"""',
1798
- requirement.trim(),
1799
- '"""',
1800
- "",
1801
- "硬性要求:",
1802
- `- 生成 kebab-case summary-name 时只使用对需求有语义代表性的英文/数字字符;如难以从中文中提炼出可读 slug,请直接使用 \`untitled-\` + 3 位日期内自增编号占位,禁止把中文挤压为零散英文片段(例如禁止出现 \`spec-agent-spec-agent\` 之类的拼接)。`,
1803
- "- frontmatter.summary 必须是对需求的真实概述(≤200 字符),不要原样照搬整段需求。",
1804
- "- 完成 spec 文件初始化后立即进入 plan 阶段,按 SKILL 规则补齐 `现状分析` / `技术实现方案` / `待确认项`,再视需要进入 tasks/execute。"
1805
- ];
1806
- if (draftId) {
1807
- lines.push(
1808
- "",
1809
- `附件迁移:本次新建 spec 关联了草稿附件目录 \`.yorz/tmp/drafts/${draftId}/attachments/\`。`,
1810
- "- 在创建 `.yorz/specs/<id>/` 目录并写入 `spec.md` 骨架**之后**,立即把该 draft 目录下的所有文件迁移到 `.yorz/specs/<id>/attachments/`,文件名保持不变。",
1811
- "- 迁移完成后,在 `## 背景` 章节末尾追加一段附件列表,每个附件占一行(按文件扩展名判定 `kind`):",
1812
- " - 图片(`.png` / `.jpg` / `.jpeg` / `.gif` / `.webp` / `.bmp` / `.svg` / `.avif` / `.heic`):使用 `![<文件名>](attachments/<文件名>)`",
1813
- " - PDF(`.pdf`) / 文本(`.txt` / `.md` / `.markdown`):使用 `[<文件名>](attachments/<文件名>)`",
1814
- "- 迁移失败(如 draft 目录已被清理、权限不足)时,**不要静默丢弃**:在 `## 待确认项` 章节追加一条记录说明问题,并退出本轮等待用户介入。"
1815
- );
1816
- }
1817
- return lines.join("\n");
1818
- }
1819
2036
  async function bindDraftSessionToCreatedSpec(project, beforeIds, sessionId) {
1820
2037
  try {
1821
2038
  const created = (await project.store.list()).filter((s) => !beforeIds.has(s.id));
@@ -1864,14 +2081,6 @@ async function readDebugMdStatus(specDir) {
1864
2081
  const val = statusLine.slice(statusLine.indexOf(":") + 1).trim().replace(/^['"]|['"]$/g, "");
1865
2082
  return val === "debugging" ? "debugging" : val === "resolved" ? "resolved" : null;
1866
2083
  }
1867
- function buildDebugPrompt(specsDirRelative, specId, mode, runtimeContext = "") {
1868
- const specPath = `${specsDirRelative}/${specId}/spec.md`;
1869
- const debugPath = `${specsDirRelative}/${specId}/debug.md`;
1870
- if (mode === "resume") {
1871
- return `${skillRef("yorz-debug")},然后继续调试:spec 目录 ${specsDirRelative}/${specId}(含 ${specPath})。该目录下已存在处于 debugging 状态的 ${debugPath},请定位其活跃记录块(frontmatter active 指向的 ## Debug NNN)继续「假设 → 取证 → 验证」循环,勿新建记录块。` + runtimeContext;
1872
- }
1873
- return `${skillRef("yorz-debug")},然后进入 Debug 模式:spec 目录 ${specsDirRelative}/${specId}(含 ${specPath})。若 ${debugPath} 不存在则创建,否则在文末追加新的 ## Debug 记录块;进入后立即 git stash create 打快照写入 Debug 基线,再开始「假设 → 取证 → 验证」循环。` + runtimeContext;
1874
- }
1875
2084
  async function buildDebugRuntimeContext(project) {
1876
2085
  return formatDebugRuntimeContext(await project.commands.listRuns());
1877
2086
  }
@@ -1919,6 +2128,12 @@ function parseCreateBody(body) {
1919
2128
  }
1920
2129
  out.draftId = obj.draftId;
1921
2130
  }
2131
+ if (obj.draftProjectId !== void 0) {
2132
+ if (typeof obj.draftProjectId !== "string" || !obj.draftProjectId.trim()) {
2133
+ return { error: "draftProjectId must be a non-empty string" };
2134
+ }
2135
+ out.draftProjectId = obj.draftProjectId.trim();
2136
+ }
1922
2137
  return out;
1923
2138
  }
1924
2139
  function isSafeAttachmentName(name2) {
@@ -1991,7 +2206,7 @@ function parseAppendBody(body) {
1991
2206
  if (typeof obj.description !== "string" || !obj.description.trim()) {
1992
2207
  return { error: "description required" };
1993
2208
  }
1994
- const out = { kind, description: obj.description, autoRun: true, debug: false };
2209
+ const out = { kind, description: obj.description };
1995
2210
  if (obj.sectionPath !== void 0) {
1996
2211
  if (typeof obj.sectionPath !== "string") return { error: "sectionPath must be a string" };
1997
2212
  if (obj.sectionPath.length > 200) return { error: "sectionPath too long (max 200)" };
@@ -2002,14 +2217,6 @@ function parseAppendBody(body) {
2002
2217
  if (obj.quote.length > 500) return { error: "quote too long (max 500)" };
2003
2218
  if (obj.quote.trim()) out.quote = obj.quote;
2004
2219
  }
2005
- if (obj.autoRun !== void 0) {
2006
- if (typeof obj.autoRun !== "boolean") return { error: "autoRun must be a boolean" };
2007
- out.autoRun = obj.autoRun;
2008
- }
2009
- if (obj.debug !== void 0) {
2010
- if (typeof obj.debug !== "boolean") return { error: "debug must be a boolean" };
2011
- out.debug = obj.debug && kind === "fix";
2012
- }
2013
2220
  return out;
2014
2221
  }
2015
2222
  function parseAnnotateBody(body) {
@@ -2031,73 +2238,6 @@ function parseAnnotateBody(body) {
2031
2238
  if (note.length > 2e3) return { error: "note too long (max 2000)" };
2032
2239
  return { sectionPath, quote, note };
2033
2240
  }
2034
- const CHAT_DEBUG_DIR_REL = ".yorz/tmp/debug";
2035
- const CHAT_DEBUG_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
2036
- const CHAT_DEBUG_CLEANUP_INTERVAL_MS = 60 * 60 * 1e3;
2037
- function isYorzDebugCommand(prompt) {
2038
- return /^\/yorz-debug(?:\s|$)/.test(prompt.trim());
2039
- }
2040
- function buildChatDebugPrompt(prompt, now = /* @__PURE__ */ new Date()) {
2041
- const original = prompt.trim();
2042
- const body = original.replace(/^\/yorz-debug(?:\s+|$)/, "").trim();
2043
- const debugPath = `${CHAT_DEBUG_DIR_REL}/debug-${formatDebugTimestamp(now)}.md`;
2044
- const guide = [
2045
- `${skillRef("yorz-debug")},然后进入 Debug 模式。`,
2046
- `本次是普通 chat 独立触发,没有 spec_dir。Debug 活文档必须写入临时文件 \`${debugPath}\`。`,
2047
- `写入前请确保目录 \`${CHAT_DEBUG_DIR_REL}/\` 存在;该目录属于临时目录,会由 YorZ 定时清理。`,
2048
- `如果该文件不存在则创建;本文件只承载本次 chat debug 记录,不需要追加复用其他文件。`,
2049
- "",
2050
- body ? "待调试问题见下方用户输入(忽略其中的 `/yorz-debug` 指令前缀):" : "请先根据对话上下文确认待调试问题;若上下文不足,请向用户补齐复现信息。"
2051
- ].join("\n");
2052
- return wrapHiddenPrompt(guide, original);
2053
- }
2054
- async function cleanupExpiredChatDebugFiles(projectPath, opts = {}) {
2055
- const root = join(projectPath, CHAT_DEBUG_DIR_REL);
2056
- if (!existsSync(root)) return { removed: [] };
2057
- const removed = [];
2058
- const cutoff = (opts.now ?? Date.now()) - (opts.ttlMs ?? CHAT_DEBUG_RETENTION_MS);
2059
- let entries;
2060
- try {
2061
- entries = await readdir(root, { withFileTypes: true });
2062
- } catch {
2063
- return { removed };
2064
- }
2065
- for (const entry of entries) {
2066
- if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
2067
- const file = join(root, entry.name);
2068
- try {
2069
- const s = await stat(file);
2070
- if (s.mtimeMs <= cutoff) {
2071
- await rm(file, { force: true });
2072
- removed.push(entry.name);
2073
- }
2074
- } catch {
2075
- }
2076
- }
2077
- return { removed };
2078
- }
2079
- function scheduleChatDebugCleanup(projectPath) {
2080
- void mkdir(join(projectPath, CHAT_DEBUG_DIR_REL), { recursive: true }).then(() => cleanupExpiredChatDebugFiles(projectPath)).catch(() => {
2081
- });
2082
- const timer = setInterval(() => {
2083
- void cleanupExpiredChatDebugFiles(projectPath).catch(() => {
2084
- });
2085
- }, CHAT_DEBUG_CLEANUP_INTERVAL_MS);
2086
- timer.unref?.();
2087
- return () => clearInterval(timer);
2088
- }
2089
- function formatDebugTimestamp(date) {
2090
- const pad = (n) => String(n).padStart(2, "0");
2091
- return [
2092
- date.getFullYear(),
2093
- pad(date.getMonth() + 1),
2094
- pad(date.getDate()),
2095
- "-",
2096
- pad(date.getHours()),
2097
- pad(date.getMinutes()),
2098
- pad(date.getSeconds())
2099
- ].join("");
2100
- }
2101
2241
  const CURRENT_VERSION = 1;
2102
2242
  const DEFAULT_SPECS_DIR = ".yorz/specs";
2103
2243
  function defaultProjectConfig() {
@@ -2205,55 +2345,6 @@ function normalizeSpecsDir(value) {
2205
2345
  if (!trimmed) return DEFAULT_SPECS_DIR;
2206
2346
  return trimmed;
2207
2347
  }
2208
- const SLASH_NAME_RE = /^\/([\w-]+)(?:\s|$)/;
2209
- const SPEC_COMMAND_RE = /^\/yorz-spec(?:\s|$)/;
2210
- function isSlashCommand(prompt) {
2211
- return SLASH_NAME_RE.test(prompt.trim());
2212
- }
2213
- function buildChatSpecPrompt(prompt, specsDirRelative) {
2214
- const original = prompt.trim();
2215
- const body = original.replace(SPEC_COMMAND_RE, "").trim();
2216
- const guide = [
2217
- `${skillRef("yorz-spec")},然后按其自动模式判定推进。`,
2218
- `本次是普通 chat 独立触发,未指定 spec_path:若当前会话上下文中已出现过 spec 文档,继续推进该 spec;否则按 skill 的「新建 spec」流程创建后立即推进 plan 阶段。`,
2219
- `spec 目录为 \`${specsDirRelative}/\`。`,
2220
- "",
2221
- body ? "本次需求见下方用户输入(忽略其中的 `/yorz-spec` 指令前缀):" : "请先根据对话上下文确认本次要处理的 spec 或需求;若上下文不足,请向用户补齐后再推进。"
2222
- ].join("\n");
2223
- return wrapHiddenPrompt(guide, original);
2224
- }
2225
- function buildUnknownCommandPrompt(prompt, hit) {
2226
- const original = prompt.trim();
2227
- const lines = [
2228
- "下方用户输入以 `/` 开头,这是 YorZ 输入框的指令语法,**不是**你自身的 slash command,请勿按命令解析、也不要回复 `Unknown command`。"
2229
- ];
2230
- if (hit) {
2231
- lines.push(
2232
- `\`/${hit.name}\` 是用户在 YorZ 中配置的自定义指令,但未配置隐藏提示词${hit.description ? `;其说明为:${hit.description}` : ""}。请结合该指令名与下方正文理解用户意图。`
2233
- );
2234
- } else {
2235
- lines.push("该指令未在 YorZ 中配置,请忽略指令前缀,按普通文本理解下方正文。");
2236
- }
2237
- return wrapHiddenPrompt(lines.join("\n"), original);
2238
- }
2239
- function resolveChatPrompt(prompt, instructions, opts = {}) {
2240
- const original = prompt.trim();
2241
- if (!isSlashCommand(original)) return { prompt, builtin: null };
2242
- if (isYorzDebugCommand(original)) {
2243
- return { prompt: buildChatDebugPrompt(original, opts.now), builtin: "yorz-debug" };
2244
- }
2245
- if (SPEC_COMMAND_RE.test(original)) {
2246
- return {
2247
- prompt: buildChatSpecPrompt(original, opts.specsDirRelative ?? ".yorz/specs"),
2248
- builtin: "yorz-spec"
2249
- };
2250
- }
2251
- const hit = matchCustomInstruction(original, instructions);
2252
- if (hit?.hiddenPrompt.trim()) {
2253
- return { prompt: wrapHiddenPrompt(hit.hiddenPrompt, original), builtin: null };
2254
- }
2255
- return { prompt: buildUnknownCommandPrompt(original, hit), builtin: null };
2256
- }
2257
2348
  async function loadCustomInstructions(projectPath) {
2258
2349
  const [project, global] = await Promise.all([
2259
2350
  loadProjectConfig(projectPath).then((cfg) => cfg.customInstructions).catch(() => []),
@@ -2329,6 +2420,18 @@ function createSessionsRoutes(resolveProject) {
2329
2420
  return c.json({ error: err.message }, 500);
2330
2421
  }
2331
2422
  });
2423
+ app.get("/projects/:projectId/specs/:id/messages", async (c) => {
2424
+ const p = await need(c);
2425
+ if (p instanceof Response) return p;
2426
+ const specId = c.req.param("id");
2427
+ const detail = await p.store.read(specId);
2428
+ if (!detail) return c.json({ error: "spec not found" }, 404);
2429
+ try {
2430
+ return c.json(await p.sessions.getSpecMessages(specId));
2431
+ } catch (err) {
2432
+ return c.json({ error: err.message }, 500);
2433
+ }
2434
+ });
2332
2435
  app.get("/projects/:projectId/sessions/:sid/messages", async (c) => {
2333
2436
  const p = await need(c);
2334
2437
  if (p instanceof Response) return p;
@@ -2624,18 +2727,30 @@ async function currentBranch(cwd) {
2624
2727
  }
2625
2728
  return branch;
2626
2729
  }
2730
+ function parseRefLines(stdout) {
2731
+ return stdout.split("\n").map((line) => line.trim()).filter(Boolean);
2732
+ }
2627
2733
  async function listBranches(cwd) {
2628
- const [current, branchesResult] = await Promise.all([
2734
+ const [current, branchesResult, remotesResult] = await Promise.all([
2629
2735
  currentBranch(cwd),
2630
2736
  runGit(cwd, [
2631
2737
  "for-each-ref",
2632
2738
  "--sort=-committerdate",
2633
2739
  "--format=%(refname:short)",
2634
2740
  "refs/heads"
2741
+ ]),
2742
+ runGit(cwd, [
2743
+ "for-each-ref",
2744
+ "--sort=-committerdate",
2745
+ "--format=%(refname:short)",
2746
+ "refs/remotes"
2635
2747
  ])
2636
2748
  ]);
2637
- const branches = branchesResult.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
2638
- return { current, branches };
2749
+ const branches = parseRefLines(branchesResult.stdout);
2750
+ const remoteBranches = parseRefLines(remotesResult.stdout).filter(
2751
+ (ref) => !ref.endsWith("/HEAD")
2752
+ );
2753
+ return { current, branches, remoteBranches };
2639
2754
  }
2640
2755
  async function checkoutBranch(cwd, branch) {
2641
2756
  const target = branch?.trim() ?? "";
@@ -2651,6 +2766,34 @@ async function checkoutBranch(cwd, branch) {
2651
2766
  }
2652
2767
  return { current: await currentBranch(cwd) };
2653
2768
  }
2769
+ async function conflictFiles(cwd) {
2770
+ const res = await runGitRaw(cwd, ["diff", "--name-only", "--diff-filter=U"]);
2771
+ if (res.code !== 0) return [];
2772
+ return parseRefLines(res.stdout);
2773
+ }
2774
+ async function mergeBranch(cwd, branch) {
2775
+ const target = branch?.trim() ?? "";
2776
+ if (!target) throw new GitError("invalid_branch", "branch must not be empty");
2777
+ const state = await listBranches(cwd);
2778
+ if (!state.branches.includes(target) && !state.remoteBranches.includes(target)) {
2779
+ throw new GitError("invalid_branch", `unknown branch: ${target}`);
2780
+ }
2781
+ if (state.current === target) {
2782
+ throw new GitError("merge_self", `cannot merge ${target} into itself`);
2783
+ }
2784
+ const before = (await runGit(cwd, ["rev-parse", "HEAD"])).stdout.trim();
2785
+ const res = await runGitRaw(cwd, ["merge", "--no-edit", target]);
2786
+ if (res.code !== 0) {
2787
+ const conflicts = await conflictFiles(cwd);
2788
+ await runGitRaw(cwd, ["merge", "--abort"]);
2789
+ const detail = [res.stdout.trim(), res.stderr.trim()].filter(Boolean).join("\n") || "git merge failed";
2790
+ const message = conflicts.length ? `${detail}
2791
+ conflicting files (merge aborted): ${conflicts.join(", ")}` : detail;
2792
+ throw new GitError(conflicts.length ? "merge_conflict" : "merge_failed", message, res.stderr);
2793
+ }
2794
+ const after = (await runGit(cwd, ["rev-parse", "HEAD"])).stdout.trim();
2795
+ return { current: state.current, merged: target, alreadyUpToDate: before === after };
2796
+ }
2654
2797
  async function push(cwd) {
2655
2798
  const branch = await currentBranch(cwd);
2656
2799
  const upstream = await runGitRaw(cwd, [
@@ -2714,9 +2857,10 @@ function createSpecReviewRoutes(resolveProject) {
2714
2857
  if (!action || typeof action !== "string" || !VALID_ACTIONS.has(action)) {
2715
2858
  return c.json({ error: "action must be one of commit | discard | stash" }, 400);
2716
2859
  }
2860
+ if (await p.sessions.isSpecRunning(specId)) return c.json({ error: SPEC_BUSY_ERROR }, 409);
2717
2861
  const specRel = `${p.specsDirRelative}/${specId}/spec.md`;
2718
2862
  const prompt = buildGitOpsPrompt(action, specId, specRel);
2719
- const { sessionId } = await p.sessions.ensureSessionForSpec(specId);
2863
+ const { sessionId } = await p.sessions.createSessionForSpec(specId);
2720
2864
  const handle = await p.sessions.send(sessionId, prompt, void 0, {
2721
2865
  trigger: "git-ops",
2722
2866
  specId
@@ -2855,6 +2999,21 @@ function createGitRoutes(resolveProject) {
2855
2999
  throw err;
2856
3000
  }
2857
3001
  });
3002
+ app.post("/projects/:projectId/git/merge", async (c) => {
3003
+ const p = await need(c);
3004
+ if (p instanceof Response) return p;
3005
+ const body = await readBody(c);
3006
+ if (!body) return c.json({ error: "invalid JSON body" }, 400);
3007
+ const branch = typeof body.branch === "string" ? body.branch.trim() : "";
3008
+ if (!branch) return c.json({ error: "branch must not be empty" }, 400);
3009
+ try {
3010
+ const result = await mergeBranch(p.path, branch);
3011
+ return c.json({ ok: true, ...result });
3012
+ } catch (err) {
3013
+ if (err instanceof GitError) return c.json({ error: err.message }, 400);
3014
+ throw err;
3015
+ }
3016
+ });
2858
3017
  app.post("/projects/:projectId/git/commit", async (c) => {
2859
3018
  const p = await need(c);
2860
3019
  if (p instanceof Response) return p;
@@ -4937,9 +5096,25 @@ class SessionStore {
4937
5096
  file;
4938
5097
  cache = null;
4939
5098
  writeChain = Promise.resolve();
5099
+ lastStamp = 0;
4940
5100
  constructor(projectPath) {
4941
5101
  this.file = join(projectPath, ".yorz", "tmp", "sessions", "index.json");
4942
5102
  }
5103
+ /**
5104
+ * Wall-clock time, forced to strictly increase within this process.
5105
+ *
5106
+ * `updatedAt` is not just a display value — it is the sort key that decides
5107
+ * which round is "the latest" (`latestBySpec`, and the list order the Chat
5108
+ * panel renders). A spec now owns one session per round, and consecutive
5109
+ * rounds are routinely created inside the same millisecond, where raw
5110
+ * `Date.now()` produces ties that resolve to an arbitrary — in practice the
5111
+ * *oldest* — session. Bumping by 1ms on a tie keeps every ordering total.
5112
+ */
5113
+ stamp() {
5114
+ const now = Math.max(Date.now(), this.lastStamp + 1);
5115
+ this.lastStamp = now;
5116
+ return now;
5117
+ }
4943
5118
  async load() {
4944
5119
  if (this.cache) return this.cache;
4945
5120
  try {
@@ -4967,9 +5142,25 @@ class SessionStore {
4967
5142
  const items = await this.load();
4968
5143
  return items.find((s) => s.id === id);
4969
5144
  }
4970
- async getBySpec(specId) {
5145
+ /**
5146
+ * Every session bound to a spec, oldest first. A spec now owns MANY sessions
5147
+ * (one per system-driven round), so this — not a single lookup — is the
5148
+ * primitive: chronological order is what the Chat panel's aggregated
5149
+ * transcript is stitched from.
5150
+ */
5151
+ async listBySpec(specId) {
4971
5152
  const items = await this.load();
4972
- return items.find((s) => s.specId === specId);
5153
+ return items.filter((s) => s.specId === specId).sort((a, b) => a.createdAt - b.createdAt);
5154
+ }
5155
+ /** The spec's most recently active session — where user-driven turns land. */
5156
+ async latestBySpec(specId) {
5157
+ const items = await this.load();
5158
+ let latest;
5159
+ for (const s of items) {
5160
+ if (s.specId !== specId) continue;
5161
+ if (!latest || s.updatedAt > latest.updatedAt) latest = s;
5162
+ }
5163
+ return latest;
4973
5164
  }
4974
5165
  async upsert(info) {
4975
5166
  const items = await this.load();
@@ -4979,7 +5170,7 @@ class SessionStore {
4979
5170
  await this.persist();
4980
5171
  }
4981
5172
  async create(kind, id, title, specId) {
4982
- const now = Date.now();
5173
+ const now = this.stamp();
4983
5174
  const info = { id, title, kind, createdAt: now, updatedAt: now };
4984
5175
  if (specId) info.specId = specId;
4985
5176
  await this.upsert(info);
@@ -4992,7 +5183,7 @@ class SessionStore {
4992
5183
  const entry = items.find((s) => s.id === oldId);
4993
5184
  if (!entry) return;
4994
5185
  entry.id = newId;
4995
- entry.updatedAt = Date.now();
5186
+ entry.updatedAt = this.stamp();
4996
5187
  await this.persist();
4997
5188
  }
4998
5189
  async updateTitle(id, title) {
@@ -5000,7 +5191,7 @@ class SessionStore {
5000
5191
  const entry = items.find((s) => s.id === id);
5001
5192
  if (!entry) return;
5002
5193
  entry.title = title;
5003
- entry.updatedAt = Date.now();
5194
+ entry.updatedAt = this.stamp();
5004
5195
  await this.persist();
5005
5196
  }
5006
5197
  async bindSpec(id, specId) {
@@ -5008,7 +5199,7 @@ class SessionStore {
5008
5199
  const entry = items.find((s) => s.id === id);
5009
5200
  if (!entry) return false;
5010
5201
  entry.specId = specId;
5011
- entry.updatedAt = Date.now();
5202
+ entry.updatedAt = this.stamp();
5012
5203
  await this.persist();
5013
5204
  return true;
5014
5205
  }
@@ -5016,7 +5207,7 @@ class SessionStore {
5016
5207
  const items = await this.load();
5017
5208
  const entry = items.find((s) => s.id === id);
5018
5209
  if (!entry) return;
5019
- entry.updatedAt = Date.now();
5210
+ entry.updatedAt = this.stamp();
5020
5211
  await this.persist();
5021
5212
  }
5022
5213
  }
@@ -5490,13 +5681,20 @@ function createApp(opts) {
5490
5681
  });
5491
5682
  return;
5492
5683
  }
5493
- const { sessionId } = await main.sessions.ensureSessionForSpec(specId);
5494
- void main.sessions.send(
5495
- sessionId,
5496
- `${skillRef("yorz-spec")},然后处理 spec:${main.specsDirRelative}/${specId}/spec.md`,
5497
- void 0,
5498
- { trigger: "conflict", specId }
5499
- );
5684
+ if (await main.sessions.isSpecRunning(specId)) {
5685
+ worktreeLog2.warn("skip conflict Agent: spec already has a running session", {
5686
+ mainProjectId,
5687
+ specId
5688
+ });
5689
+ return;
5690
+ }
5691
+ const { sessionId } = await main.sessions.createSessionForSpec(specId);
5692
+ const { commandLine, prompt } = buildSpecDispatch({
5693
+ specsDirRelative: main.specsDirRelative,
5694
+ specId,
5695
+ debug: false
5696
+ });
5697
+ void main.sessions.send(sessionId, prompt, commandLine, { trigger: "conflict", specId });
5500
5698
  }
5501
5699
  });
5502
5700
  api.route("/", createProjectRoutes(opts.registry, worktreeManager));
@@ -6171,6 +6369,39 @@ function primaryModel(modelUsage) {
6171
6369
  }
6172
6370
  return best;
6173
6371
  }
6372
+ const SPEC_WRITE_TOOLS = /* @__PURE__ */ new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
6373
+ function isSpecWrite(name2, input) {
6374
+ if (typeof name2 !== "string" || !SPEC_WRITE_TOOLS.has(name2)) return false;
6375
+ const path = isRecord(input) ? input.file_path : void 0;
6376
+ return typeof path === "string" && path.endsWith("spec.md");
6377
+ }
6378
+ function usageOf(message) {
6379
+ return isRecord(message) ? message.usage : void 0;
6380
+ }
6381
+ class PhaseAccumulator {
6382
+ constructor(startedAt) {
6383
+ this.startedAt = startedAt;
6384
+ }
6385
+ usage = {};
6386
+ requests = 0;
6387
+ /** Count one API response, whether or not it reported usage. */
6388
+ add(raw) {
6389
+ this.requests += 1;
6390
+ const snapshot = normalizeUsage("claude", raw);
6391
+ if (!snapshot) return;
6392
+ for (const [key, value] of Object.entries(snapshot)) {
6393
+ const field = key;
6394
+ this.usage[field] = (this.usage[field] ?? 0) + value;
6395
+ }
6396
+ }
6397
+ snapshot() {
6398
+ return {
6399
+ usage: { ...this.usage },
6400
+ requests: this.requests,
6401
+ durationMs: Date.now() - this.startedAt
6402
+ };
6403
+ }
6404
+ }
6174
6405
  class ClaudeSession {
6175
6406
  constructor(id, cwd, isNew) {
6176
6407
  this.id = id;
@@ -6194,6 +6425,13 @@ class ClaudeSession {
6194
6425
  };
6195
6426
  if (this.isNew && !this.started) options.sessionId = this.id;
6196
6427
  else options.resume = this.id;
6428
+ const phase = new PhaseAccumulator(Date.now());
6429
+ let planPhase;
6430
+ const withPhases = (base) => ({
6431
+ ...base ?? {},
6432
+ planPhase,
6433
+ observedTotal: phase.snapshot()
6434
+ });
6197
6435
  try {
6198
6436
  const q = query({ prompt, options });
6199
6437
  let usage;
@@ -6207,10 +6445,12 @@ class ClaudeSession {
6207
6445
  yield { type: "session-started", sessionId: this.id };
6208
6446
  }
6209
6447
  if (m.type === "assistant") {
6448
+ phase.add(usageOf(m.message));
6210
6449
  for (const b of blocksFrom(m.message)) {
6211
6450
  if (b.type === "text" && typeof b.text === "string") {
6212
6451
  yield { type: "text", delta: b.text };
6213
6452
  } else if (b.type === "tool_use") {
6453
+ if (!planPhase && isSpecWrite(b.name, b.input)) planPhase = phase.snapshot();
6214
6454
  yield { type: "tool-use", name: String(b.name ?? "?"), input: b.input ?? {} };
6215
6455
  }
6216
6456
  }
@@ -6244,10 +6484,10 @@ class ClaudeSession {
6244
6484
  };
6245
6485
  } else if (m.type === "system" && m.subtype === "session_state_changed" && m.state === "idle") {
6246
6486
  completed = true;
6247
- yield { type: "turn-completed", usage, metrics };
6487
+ yield { type: "turn-completed", usage, metrics: withPhases(metrics) };
6248
6488
  }
6249
6489
  }
6250
- if (!completed) yield { type: "turn-completed", usage, metrics };
6490
+ if (!completed) yield { type: "turn-completed", usage, metrics: withPhases(metrics) };
6251
6491
  } catch (err) {
6252
6492
  if (ctrl.signal.aborted) return;
6253
6493
  yield { type: "error", message: err instanceof Error ? err.message : String(err) };
@@ -7146,26 +7386,53 @@ class SessionManager {
7146
7386
  return { sessionId, kind: k };
7147
7387
  }
7148
7388
  /**
7149
- * Look up the dedicated session for a spec WITHOUT creating one. Used by the
7389
+ * Look up the spec's most recent session WITHOUT creating one. Used by the
7150
7390
  * read-only `GET /specs/:id/session` probe: merely opening a spec detail page
7151
7391
  * must not mint a session that never runs a turn (those would show up in the
7152
7392
  * list as "ghost" entries with permanently empty history).
7153
7393
  */
7154
7394
  async findSessionForSpec(specId) {
7155
- const existing = await this.store.getBySpec(specId);
7395
+ const existing = await this.store.latestBySpec(specId);
7156
7396
  if (!existing) return null;
7157
7397
  return { sessionId: existing.id, kind: existing.kind };
7158
7398
  }
7159
7399
  /**
7160
- * Get (or lazily create) the dedicated session for a spec. All system-driven
7161
- * rounds (run / explain / review / git-ops) reuse this per-spec session so
7162
- * their output lands in one conversation the Chat panel can switch to.
7163
- * Only call this when a turn is actually about to be sent.
7400
+ * Mint a FRESH session for a spec — one per system-driven round (run / append
7401
+ * / git-ops / conflict).
7402
+ *
7403
+ * This replaces the old `ensureSessionForSpec()` reuse. That reuse existed
7404
+ * purely so the Chat panel had one conversation to switch to, and it made
7405
+ * every later round inherit the whole transcript of the earlier ones: measured
7406
+ * over 11 days, a non-first `run` cost 32% more than a first one for the same
7407
+ * number of turns. Spec state lives in the md (frontmatter / task list /
7408
+ * 执行记录), never in session memory, so a cold session loses nothing. The
7409
+ * one-conversation illusion is now rebuilt in the UI by grouping on `specId`.
7410
+ */
7411
+ async createSessionForSpec(specId) {
7412
+ return this.createSession(void 0, specId, specId);
7413
+ }
7414
+ /**
7415
+ * The spec's most recent session, created on demand. Used by user-driven
7416
+ * rounds (explain, chat) whose value comes from continuing the conversation
7417
+ * the user is looking at rather than starting cold.
7164
7418
  */
7165
- async ensureSessionForSpec(specId) {
7419
+ async latestSessionForSpec(specId) {
7166
7420
  const existing = await this.findSessionForSpec(specId);
7167
7421
  if (existing) return existing;
7168
- return this.createSession(void 0, specId, specId);
7422
+ return this.createSessionForSpec(specId);
7423
+ }
7424
+ /**
7425
+ * Whether ANY session of this spec has a turn in flight.
7426
+ *
7427
+ * Splitting rounds into separate sessions removed an implicit serialization:
7428
+ * they used to share one session, so a second dispatch queued behind the
7429
+ * first. Independent sessions would instead run two agents writing the same
7430
+ * `spec.md` concurrently. Callers use this to refuse the second dispatch.
7431
+ */
7432
+ async isSpecRunning(specId) {
7433
+ if (this.running.size === 0) return false;
7434
+ const sessions = await this.store.listBySpec(specId);
7435
+ return sessions.some((s) => this.running.has(s.id));
7169
7436
  }
7170
7437
  async bindSessionToSpec(sessionId, specId, title) {
7171
7438
  const ok = await this.store.bindSpec(sessionId, specId);
@@ -7258,6 +7525,27 @@ class SessionManager {
7258
7525
  if (!adapter.capabilities().getMessages) return [];
7259
7526
  return adapter.getMessages(sid);
7260
7527
  }
7528
+ /**
7529
+ * The spec's whole conversation, stitched from every session it owns, oldest
7530
+ * first. Ordering is decided here rather than in the GUI: the client would
7531
+ * otherwise have to fan out N requests and re-sort them, and the divider it
7532
+ * draws between sessions needs each session's `kind` / `createdAt` anyway.
7533
+ *
7534
+ * One unreadable session degrades to an empty entry instead of failing the
7535
+ * whole transcript — an aborted round can leave a session with no transcript
7536
+ * on disk, and losing the other rounds' history over that would be worse.
7537
+ */
7538
+ async getSpecMessages(specId) {
7539
+ const sessions = await this.store.listBySpec(specId);
7540
+ return Promise.all(
7541
+ sessions.map(async (s) => ({
7542
+ sessionId: s.id,
7543
+ kind: s.kind,
7544
+ createdAt: s.createdAt,
7545
+ messages: await this.getMessages(s.id).catch(() => [])
7546
+ }))
7547
+ );
7548
+ }
7261
7549
  async getUsageStatus() {
7262
7550
  const adapter = this.adapters.get(this.defaultKind);
7263
7551
  if (!adapter.capabilities().usageStatus || !adapter.getUsageStatus) {
@@ -7339,7 +7627,12 @@ class SessionManager {
7339
7627
  stopReason: m.stopReason,
7340
7628
  model: m.model,
7341
7629
  durMs: m.durationMs,
7342
- apiDurMs: m.apiDurationMs
7630
+ apiDurMs: m.apiDurationMs,
7631
+ // Splits one dispatch at the plan→execute handover. Absent when
7632
+ // the run never wrote a spec (git ops, explain, plain chat), so
7633
+ // existing lines keep their exact shape.
7634
+ planPhase: m.planPhase,
7635
+ observedTotal: m.observedTotal
7343
7636
  });
7344
7637
  } else if (ev.type === "compact") {
7345
7638
  telemetry.record("agent.compact", {