marchen 0.8.0 → 0.8.1

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.
package/dist/index.mjs CHANGED
@@ -60,7 +60,7 @@ tags: [workflow, implementation]
60
60
 
61
61
  **暂停条件:**
62
62
  - 任务不清晰 → 询问用户
63
- - 发现设计问题 → 建议更新 artifact
63
+ - 发现设计问题 → 建议用 \`/marchen:update\` 修订计划
64
64
  - 遇到错误或阻塞 → 报告并等待
65
65
  - 用户中断
66
66
 
@@ -453,7 +453,7 @@ tags: [workflow, lite]
453
453
 
454
454
  **暂停条件:**
455
455
  - 任务不清晰 → 询问用户
456
- - 发现设计问题 → 建议更新 artifact
456
+ - 发现设计问题 → 建议用 \`/marchen:update\` 修订计划
457
457
  - 遇到错误或阻塞 → 报告并等待
458
458
  - 用户中断
459
459
 
@@ -550,7 +550,8 @@ tags: [workflow, preview]
550
550
 
551
551
  \`\`\`
552
552
  /marchen:apply <name> 开始实现
553
- /marchen:propose <name> 修改提案
553
+ /marchen:update <name> 修订细节(方案、需求、任务的局部调整)
554
+ /marchen:propose <name> 重新提案(变更意图或方向改变)
554
555
  \`\`\`
555
556
 
556
557
  ---
@@ -927,6 +928,91 @@ tags: [workflow, review]
927
928
  - 用户提供的凭据/账号等敏感数据:不写入任何 artifact,不在报告里复述明文,截图前对密码/token/邮箱等敏感字段脱敏或回避
928
929
  - 大 diff 先看 \`git diff --stat\`,按需读单个文件,避免灌爆 context
929
930
  - 使用 AskUserQuestion 时,选项不超过 4 个
931
+ `},update:{fileName:`update.md`,content:`---
932
+ name: "Marchen: Update"
933
+ description: 修订变更的已有规划产物并保持彼此一致,绝不修改代码
934
+ category: Workflow
935
+ tags: [workflow, artifacts]
936
+ ---
937
+
938
+ 修订变更的已有规划产物,并保持彼此一致。绝不修改代码。
939
+
940
+ ---
941
+
942
+ **输入**:\`/marchen:update\` 后面跟变更名称(如 \`/marchen:update add-auth\`),可附带修改诉求;省略名称时自动推断。
943
+
944
+ **流程**
945
+
946
+ 1. **选择变更**
947
+
948
+ 有名称就用,没有则:
949
+ - 从对话上下文推断
950
+ - 只有一个 open 变更时自动选择,并明示"使用变更: \`<name>\`"
951
+ - 多个变更时 \`marchen list --json\` + **AskUserQuestion** 让用户选,选项展示名称、schema、任务进度、创建时间(\`createdAt\`),最近创建的标记"(推荐)"
952
+ - open 变更较多、创建时间不足以判断时,可用 \`ls -dt marchen/changes/*/\` 按最近改动排序辅助推荐(该命令不可用时忽略,退回 createdAt)
953
+
954
+ **重要**:多个候选时绝不猜测或自动选定,始终让用户决定。
955
+
956
+ 2. **获取产物清单**
957
+
958
+ \`\`\`bash
959
+ marchen status <name> --json
960
+ \`\`\`
961
+
962
+ 返回 JSON 包含:
963
+ - \`schema\`:该变更使用的工作流 schema
964
+ - \`artifacts[]\`:各产物的 \`id\`、\`status\`(filled / empty / missing / no-content)和 \`path\`(相对变更目录)
965
+ - specs 类型的产物额外带 \`capabilities[]\`,实际文件为 \`specs/<capability>/spec.md\`
966
+ - \`workflow\` 与 \`tasks\`:依赖状态与任务进度
967
+
968
+ 产物的 id 和路径来自当前 schema——**不要假设产物名字,不要基于硬编码的产物名做分支判断**。自定义 schema 必须原样可用。
969
+
970
+ 可编辑对象是 \`status\` 为 \`filled\` 的产物文件,真实路径为 \`marchen/changes/<name>/<path>\`(specs 按 capabilities 展开到各 spec.md)。
971
+
972
+ 3. **理解诉求**
973
+
974
+ - 用户提出了具体修改("design 现在改用 X")→ 以此作为起点编辑。
975
+ - 只说"update"/"让它自洽" → 当作一致性审查:通读已有产物,互相对照,找出矛盾、缺口和重复。
976
+
977
+ 4. **读取并调和**
978
+
979
+ - 读取诉求涉及的产物,以及该变更其余 filled 产物。
980
+ - 先落实用户要求的修改,然后逐个检查其他产物与它是否一致——**任意方向**:改后置产物可能需要回改前置产物,不是只有顺流而下。构建顺序是好用的阅读顺序,不是修订方向的约束。
981
+ - 记录所有因此不一致、缺失或矛盾的地方。
982
+ - 只修订已存在的文件。**不要创建**尚不存在的产物,也不要在 specs 下新建 capability 目录——记录下来,第 6 步告知用户。
983
+ - 变更本来就自洽时,直接说明,不做任何修改。
984
+
985
+ 5. **逐个产物确认后写入**
986
+
987
+ - 展示每处拟修订的内容和理由,用户确认后才写入。
988
+ - 用户拒绝的修订不写,该产物保持原样。
989
+ - 需要大幅重写时,先获取该产物的格式规则和模板:
990
+
991
+ \`\`\`bash
992
+ marchen instructions <name> <artifact-id> --json
993
+ \`\`\`
994
+
995
+ 6. **指出下一步(仅建议,绝不代为执行)**
996
+
997
+ - 还有 empty / missing 的产物 → 告知用户,建议按 \`marchen instructions\` 的指引补全。
998
+ - 变更已实现过(tasks 已勾选)→ 代码可能已和修订后的计划不符,建议 \`/marchen:apply\` 把差异带进代码。
999
+ - 全部完成且已实现 → 建议 \`/marchen:archive\`。
1000
+
1001
+ **输出**
1002
+
1003
+ 每次调用结束时展示:
1004
+ - 修订了哪些产物(以及哪些拟修订被用户拒绝)
1005
+ - 哪些缺失产物被记录但未创建
1006
+ - 变更当前所处状态,和推荐的下一条命令
1007
+
1008
+ **护栏**
1009
+
1010
+ - 只改规划产物——**绝不修改实现代码**(update 不是 apply)。修订后的计划意味着要改代码时,停下来,指向 \`/marchen:apply\`。
1011
+ - 使用 \`marchen status\` 报告的产物 id 和路径;绝不基于硬编码产物名分支。
1012
+ - 只编辑已存在的文件;不推进构建前沿:不创建新产物、不在 specs 下新建 capability——补全是 propose/instructions 的职责。
1013
+ - 每处修改写入前必须经用户确认;update 是专门的修订动作,不是 explore 式的顺手捕获。
1014
+ - 如果诉求改变的是变更的**意图**而非细化,建议用 \`/marchen:propose\` 重开一个新变更("修订 vs 重开"启发式)。
1015
+ - 使用 AskUserQuestion 时,选项不超过 4 个
930
1016
  `}},x={antigravity:{id:`antigravity`,name:`Antigravity`,skillDir:`.agent/skills`},"claude-code":{id:`claude-code`,name:`Claude Code`,skillDir:`.claude/skills`,commandDir:`.claude/commands/marchen`},codex:{id:`codex`,name:`Codex`,skillDir:`.codex/skills`},copilot:{id:`copilot`,name:`GitHub Copilot`,skillDir:`.github/skills`},cursor:{id:`cursor`,name:`Cursor`,skillDir:`.cursor/skills`},"gemini-cli":{id:`gemini-cli`,name:`Gemini CLI`,skillDir:`.gemini/skills`},kilocode:{id:`kilocode`,name:`Kilo Code`,skillDir:`.kilocode/skills`},kiro:{id:`kiro`,name:`Kiro`,skillDir:`.kiro/skills`},opencode:{id:`opencode`,name:`OpenCode`,skillDir:`.opencode/skills`},windsurf:{id:`windsurf`,name:`Windsurf`,skillDir:`.windsurf/skills`}},S=[`claude-code`],C={full:{name:`full`,artifacts:[{id:`proposal`,generates:`proposal.md`,requires:[],template:`## 动机
931
1017
 
932
1018
  <!-- 说明这个变更的动机。解决什么问题?为什么现在做? -->
@@ -1082,7 +1168,7 @@ description: 按变更的 tasks.md 逐个实现任务。适用于用户想按任
1082
1168
 
1083
1169
  **暂停条件:**
1084
1170
  - 任务不清晰 → 询问用户
1085
- - 发现设计问题 → 建议更新 artifact
1171
+ - 发现设计问题 → 建议用 \`/marchen:update\` 修订计划
1086
1172
  - 遇到错误或阻塞 → 报告并等待
1087
1173
  - 用户中断
1088
1174
 
@@ -1468,7 +1554,7 @@ description: 一键式轻量变更流程。创建 lite 变更、实现任务、
1468
1554
 
1469
1555
  **暂停条件:**
1470
1556
  - 任务不清晰 → 询问用户
1471
- - 发现设计问题 → 建议更新 artifact
1557
+ - 发现设计问题 → 建议用 \`/marchen:update\` 修订计划
1472
1558
  - 遇到错误或阻塞 → 报告并等待
1473
1559
  - 用户中断
1474
1560
 
@@ -1565,7 +1651,8 @@ argument-hint: <change-name>
1565
1651
 
1566
1652
  \`\`\`
1567
1653
  /marchen:apply <name> 开始实现
1568
- /marchen:propose <name> 修改提案
1654
+ /marchen:update <name> 修订细节(方案、需求、任务的局部调整)
1655
+ /marchen:propose <name> 重新提案(变更意图或方向改变)
1569
1656
  \`\`\`
1570
1657
 
1571
1658
  ---
@@ -1938,6 +2025,88 @@ description: 对照变更意图检查代码实现的完整性和一致性,支
1938
2025
  - 用户提供的凭据/账号等敏感数据:不写入任何 artifact,不在报告里复述明文,截图前对密码/token/邮箱等敏感字段脱敏或回避
1939
2026
  - 大 diff 先看 \`git diff --stat\`,按需读单个文件,避免灌爆 context
1940
2027
  - 使用 AskUserQuestion 时,选项不超过 4 个
2028
+ `},update:{dirName:`marchen-update`,content:`---
2029
+ name: marchen-update
2030
+ description: 修订变更的已有规划产物并双向调和保持一致。适用于用户想修改变更的计划(proposal/specs/design/tasks)、把新决策合入计划、或在编辑后让各产物重新对齐。只改规划产物,绝不修改实现代码。
2031
+ ---
2032
+
2033
+ 修订变更的已有规划产物,并保持彼此一致。绝不修改代码。
2034
+
2035
+ ---
2036
+
2037
+ **输入**:用户的请求应包含变更名称,或可从上下文推断;通常还带着修改诉求(如"design 改用 X")。
2038
+
2039
+ **流程**
2040
+
2041
+ 1. **选择变更**
2042
+
2043
+ 有名称就用,没有则:
2044
+ - 从对话上下文推断
2045
+ - 只有一个 open 变更时自动选择,并明示"使用变更: \`<name>\`"
2046
+ - 多个变更时 \`marchen list --json\` + **AskUserQuestion** 让用户选,选项展示名称、schema、任务进度、创建时间(\`createdAt\`),最近创建的标记"(推荐)"
2047
+ - open 变更较多、创建时间不足以判断时,可用 \`ls -dt marchen/changes/*/\` 按最近改动排序辅助推荐(该命令不可用时忽略,退回 createdAt)
2048
+
2049
+ **重要**:多个候选时绝不猜测或自动选定,始终让用户决定。
2050
+
2051
+ 2. **获取产物清单**
2052
+
2053
+ \`\`\`bash
2054
+ marchen status <name> --json
2055
+ \`\`\`
2056
+
2057
+ 返回 JSON 包含:
2058
+ - \`schema\`:该变更使用的工作流 schema
2059
+ - \`artifacts[]\`:各产物的 \`id\`、\`status\`(filled / empty / missing / no-content)和 \`path\`(相对变更目录)
2060
+ - specs 类型的产物额外带 \`capabilities[]\`,实际文件为 \`specs/<capability>/spec.md\`
2061
+ - \`workflow\` 与 \`tasks\`:依赖状态与任务进度
2062
+
2063
+ 产物的 id 和路径来自当前 schema——**不要假设产物名字,不要基于硬编码的产物名做分支判断**。自定义 schema 必须原样可用。
2064
+
2065
+ 可编辑对象是 \`status\` 为 \`filled\` 的产物文件,真实路径为 \`marchen/changes/<name>/<path>\`(specs 按 capabilities 展开到各 spec.md)。
2066
+
2067
+ 3. **理解诉求**
2068
+
2069
+ - 用户提出了具体修改("design 现在改用 X")→ 以此作为起点编辑。
2070
+ - 只说"update"/"让它自洽" → 当作一致性审查:通读已有产物,互相对照,找出矛盾、缺口和重复。
2071
+
2072
+ 4. **读取并调和**
2073
+
2074
+ - 读取诉求涉及的产物,以及该变更其余 filled 产物。
2075
+ - 先落实用户要求的修改,然后逐个检查其他产物与它是否一致——**任意方向**:改后置产物可能需要回改前置产物,不是只有顺流而下。构建顺序是好用的阅读顺序,不是修订方向的约束。
2076
+ - 记录所有因此不一致、缺失或矛盾的地方。
2077
+ - 只修订已存在的文件。**不要创建**尚不存在的产物,也不要在 specs 下新建 capability 目录——记录下来,第 6 步告知用户。
2078
+ - 变更本来就自洽时,直接说明,不做任何修改。
2079
+
2080
+ 5. **逐个产物确认后写入**
2081
+
2082
+ - 展示每处拟修订的内容和理由,用户确认后才写入。
2083
+ - 用户拒绝的修订不写,该产物保持原样。
2084
+ - 需要大幅重写时,先获取该产物的格式规则和模板:
2085
+
2086
+ \`\`\`bash
2087
+ marchen instructions <name> <artifact-id> --json
2088
+ \`\`\`
2089
+
2090
+ 6. **指出下一步(仅建议,绝不代为执行)**
2091
+
2092
+ - 还有 empty / missing 的产物 → 告知用户,建议按 \`marchen instructions\` 的指引补全。
2093
+ - 变更已实现过(tasks 已勾选)→ 代码可能已和修订后的计划不符,建议 \`/marchen:apply\` 把差异带进代码。
2094
+ - 全部完成且已实现 → 建议 \`/marchen:archive\`。
2095
+
2096
+ **输出**
2097
+
2098
+ 每次调用结束时展示:
2099
+ - 修订了哪些产物(以及哪些拟修订被用户拒绝)
2100
+ - 哪些缺失产物被记录但未创建
2101
+ - 变更当前所处状态,和推荐的下一条命令
2102
+
2103
+ **护栏**
2104
+
2105
+ - 只改规划产物——**绝不修改实现代码**(update 不是 apply)。修订后的计划意味着要改代码时,停下来,指向 \`/marchen:apply\`。
2106
+ - 使用 \`marchen status\` 报告的产物 id 和路径;绝不基于硬编码产物名分支。
2107
+ - 只编辑已存在的文件;不推进构建前沿:不创建新产物、不在 specs 下新建 capability——补全是 propose/instructions 的职责。
2108
+ - 每处修改写入前必须经用户确认;update 是专门的修订动作,不是 explore 式的顺手捕获。
2109
+ - 如果诉求改变的是变更的**意图**而非细化,建议用 \`/marchen:propose\` 重开一个新变更("修订 vs 重开"启发式)。
1941
2110
  `}};async function E(e){try{await p.mkdir(e,{recursive:!0})}catch(e){if(e.code!==`EEXIST`)throw e}}async function D(e){try{return await p.access(e),!0}catch{return!1}}async function O(e,t){try{await p.rename(e,t)}catch(t){throw t.code===`ENOENT`?new y(`目录不存在`,e):t}}async function k(e){try{return await p.readdir(e)}catch(t){throw t.code===`ENOENT`?new y(`目录不存在`,e):t}}async function A(e){try{return await p.readFile(e,`utf-8`)}catch(t){throw t.code===`ENOENT`?new y(`文件不存在`,e):t}}async function j(e,t){await E(u(e)),await p.writeFile(e,t,`utf-8`)}async function M(e,t){await E(u(e)),await p.appendFile(e,t,`utf-8`)}function N(e=process.cwd()){return f(e)}function P(e=process.cwd()){return f(e,t)}function F(e=process.cwd()){return f(P(e),n)}function I(e=process.cwd()){return f(P(e),i)}async function L(e){let t=await A(e);try{return m.load(t)}catch(t){throw new y(`YAML 解析失败`,e,t instanceof Error?t:void 0)}}async function R(e,t){await j(e,m.dump(t,{indent:2}))}const ee=/^[a-z0-9]+(?:-[a-z0-9]+)*$/;var te=class e{constructor(e){this.workspace=e}static isValidName(e){return ee.test(e)}async create(t,n){if(await this.ensureInitialized(),!e.isValidName(t))throw new _(`变更名称 "${t}" 不合法,请使用 kebab-case 格式(如 add-dark-mode)`);let r=d(this.workspace.changeDir,t);if(await D(r))throw new _(`变更 "${t}" 已存在`);let i=n??`full`,o=w(i);await E(r);for(let e of o.artifacts)e.generates.endsWith(`/`)&&await E(d(r,e.generates));let s={name:t,schema:i,createdAt:new Date().toISOString(),status:`open`};await R(d(r,a),s)}async archive(e,t){await this.ensureInitialized();let n=d(this.workspace.changeDir,e);if(!await D(n))throw new _(`变更 "${e}" 不存在`);let r=d(n,a),i=await L(r),o=new Date().toISOString(),s=o.slice(0,10),c=d(this.workspace.archiveDir,`${s}-${e}`);if(await D(c))throw new _(`归档目标 "${s}-${e}" 已存在`);return await R(r,{...i,status:`archived`,archivedAt:o}),await O(n,c),await this.appendChangelog(e,s,t?.summary),await this.updateSearchIndex(),{name:e,schema:i.schema,archivedTo:c,archivedAt:o}}async appendChangelog(e,t,n){let r=this.workspace.changelogPath;await D(r)||await j(r,`# 变更日志
1942
2111
  `);let i=`[${e}](./archive/${t}-${e}/)`,a=n?.trim().replace(/[\r\n]+/g,` `);await M(r,`${a?`- ${t}: ${i} — ${a}`:`- ${t}: ${i}`}\n`)}async updateSearchIndex(){try{await Promise.race([this.doUpdateSearchIndex(),new Promise(e=>setTimeout(e,3e4))])}catch{}}async doUpdateSearchIndex(){let{SearchManager:e}=await import(`./search-manager-zPFpsFnI.mjs`).then(e=>e.n),t=new e(this.workspace);await t.isAvailable()&&(await t.indexChange(),await t.close())}async list(){await this.ensureInitialized();let e=await k(this.workspace.changeDir),t=[];for(let n of e){let e=d(this.workspace.changeDir,n,a);if(await D(e))try{let n=await L(e);t.push(n)}catch{continue}}return t.sort((e,t)=>new Date(t.createdAt).getTime()-new Date(e.createdAt).getTime()),t}async status(e){await this.ensureInitialized();let t=d(this.workspace.changeDir,e);if(!await D(t))throw new _(`变更 "${e}" 不存在`);let n=await L(d(t,a)),r=w(n.schema),i=[];for(let e of r.artifacts){let n=d(t,e.generates);if(e.id===`specs`){let t=await this.detectSpecsStatus(n);i.push({id:e.id,path:e.generates,...t})}else{let t=await this.detectContentStatus(n);i.push({id:e.id,status:t,path:e.generates})}}let o=new Map(i.map(e=>[e.id,e.status])),s=this.computeWorkflow(r,o),c=i.find(e=>e.id===`tasks`),l=null;if(c&&c.status===`filled`){let e=await A(d(t,`tasks.md`)),n=this.parseTaskItems(e);l={total:n.length,completed:n.filter(e=>e.completed).length,items:n}}return{name:e,schema:n.schema,artifacts:i,workflow:s,tasks:l}}async getInstructions(e,t){await this.ensureInitialized();let n=d(this.workspace.changeDir,e);if(!await D(n))throw new _(`变更 "${e}" 不存在`);let r=await L(d(n,a)),i=w(r.schema),o=i.artifacts.find(e=>e.id===t);if(!o)throw new _(`Artifact "${t}" 不存在,可用的 artifact: ${i.artifacts.map(e=>e.id).join(`, `)}`);let s=o.template??``,c=o.instruction,l=[];for(let e of o.requires){let t=i.artifacts.find(t=>t.id===e);if(!t)continue;let r=d(n,t.generates);if(e===`specs`){let n=await this.detectSpecsStatus(r),i=n.status===`filled`?await this.readSpecsContent(r):null;l.push({id:e,status:n.status,path:t.generates,content:i})}else{let n=await this.detectContentStatus(r),i=n===`filled`?await A(r):null;l.push({id:e,status:n,path:t.generates,content:i})}}let u=i.artifacts.filter(e=>e.requires.includes(t)).map(e=>e.id);return{changeName:e,artifactId:t,schemaName:r.schema,changeDir:n,outputPath:o.generates,template:s,instruction:c,context:l,unlocks:u,state:null,progress:null}}async detectContentStatus(e){return await D(e)?(await A(e)).replace(/<!--[\s\S]*?-->/g,``).split(`
1943
2112
  `).filter(e=>e.trim()!==``).filter(e=>!/^#{1,6}\s/.test(e.trim())).join(`
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["nodeFs","nodeFs"],"sources":["../../../packages/shared/src/errors.ts","../../../packages/config/src/generated/command-templates.ts","../../../packages/config/src/providers.ts","../../../packages/config/src/templates.ts","../../../packages/config/src/schema.ts","../../../packages/config/src/generated/skill-templates.ts","../../../packages/fs/src/directory.ts","../../../packages/fs/src/file.ts","../../../packages/fs/src/paths.ts","../../../packages/fs/src/yaml.ts","../../../packages/core/src/change-manager.ts","../../../packages/core/src/workspace.ts","../src/utils/context.ts","../src/utils/error.ts","../src/commands/archive.ts","../src/utils/model-progress.ts","../src/commands/init.ts","../src/commands/instructions.ts","../src/commands/list.ts","../src/commands/new.ts","../src/commands/search.ts","../src/commands/status.ts","../src/commands/update.ts","../src/program.ts","../src/index.ts"],"sourcesContent":["/**\n * Marchen 统一错误基类\n *\n * 所有业务错误的根类,CLI 层可用 instanceof MarchenError 兜底捕获\n */\nexport class MarchenError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'MarchenError'\n }\n}\n\n/**\n * 用户输入校验错误\n *\n * 名称不合法、变更已存在/不存在等用户操作问题\n */\nexport class ValidationError extends MarchenError {\n constructor(message: string) {\n super(message)\n this.name = 'ValidationError'\n }\n}\n\n/**\n * 状态前置条件错误\n *\n * 工作区未初始化等系统状态不满足的场景,可附带操作建议\n */\nexport class StateError extends MarchenError {\n constructor(\n message: string,\n public readonly hint?: string,\n ) {\n super(message)\n this.name = 'StateError'\n }\n}\n\n/**\n * 文件系统操作错误\n *\n * 文件/目录不存在、YAML 解析失败等 IO 异常\n */\nexport class FileSystemError extends MarchenError {\n constructor(\n message: string,\n public readonly path: string,\n public readonly cause?: Error,\n ) {\n super(message)\n this.name = 'FileSystemError'\n }\n}\n","// 此文件由 scripts/generate-templates.ts 自动生成,请勿手动修改\n\nexport const COMMAND_APPLY = `---\nname: \"Marchen: Apply\"\ndescription: 按变更的 tasks.md 逐个实现任务\ncategory: Workflow\ntags: [workflow, implementation]\n---\n\n按变更的 tasks.md 逐个实现任务,完成后勾选 checkbox。\n\n---\n\n**输入**:\\`/marchen:apply\\` 后面跟变更名称,或省略自动推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"使用变更: \\`<name>\\`\"\n\n2. **获取实现指令**\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的信息数组,每项包含 \\`id\\`、\\`status\\`、\\`path\\`、\\`content\\`\n - \\`instruction\\`:实现指引\n - \\`changeDir\\`:变更目录绝对路径\n\n 根据 \\`state\\` 处理:\n - \\`\"blocked\"\\` → 提示先完成 artifacts(\\`/marchen:propose\\`)\n - \\`\"all_done\"\\` → 提示归档(\\`/marchen:archive\\`)\n - \\`\"ready\"\\` → 继续\n\n3. **显示进度**\n\n 从返回的 JSON 读取并显示:\n \"变更: \\`<name>\\` | 进度: N/M | 下一个: <第一个未完成任务的描述>\"\n\n4. **逐个实现任务**\n\n 从 \\`context\\` 中读取所有 artifact 内容作为上下文。\n\n 对每个未完成任务:\n - 显示 \"任务 N/M: <描述>\"\n - 实现代码改动\n - 在 tasks.md 中勾选:\\`- [ ]\\` → \\`- [x]\\`\n 文件路径:\\`<changeDir>/tasks.md\\`\n - 显示 \"✓ 完成\"\n - 继续下一个\n\n **暂停条件:**\n - 任务不清晰 → 询问用户\n - 发现设计问题 → 建议更新 artifact\n - 遇到错误或阻塞 → 报告并等待\n - 用户中断\n\n5. **显示结果**\n\n 全部完成时:\n \"全部完成 (N/N),可以用 \\`/marchen:review\\` 检查实现,或直接 \\`/marchen:archive\\` 归档。\"\n\n 暂停时:\n \"暂停于任务 N/M: <原因>\"\n\n**护栏**\n\n- 实现前必须读 context 中的 artifact 内容\n- 每完成一个任务立即勾选 checkbox,不要攒着\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- 如果实现过程中遇到不确定的设计决策,可以用 \\`marchen search \"<关键词>\" --json\\` 搜索历史变更中的相关方案\n- 使用 AskUserQuestion 时,选项不超过 4 个\n- \\`instruction\\` 是给你的指引,不要原样复制到代码注释中\n`\n\nexport const COMMAND_ARCHIVE = `---\nname: \"Marchen: Archive\"\ndescription: 归档已完成的变更\ncategory: Workflow\ntags: [workflow, archive]\n---\n\n归档已完成的变更,检查完成度后执行归档。\n\n---\n\n**输入**:用户的请求可包含变更名称(如 \\`/marchen:archive add-auth\\`),也可不带。\n\n**流程**\n\n1. **确定变更名称**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n **重要**:不要猜测或自动选择,必须让用户确认。\n\n2. **检查完成度**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 解析 JSON,检查:\n - \\`artifacts\\`:每个 artifact 的 \\`status\\` 是否为 \\`filled\\`\n - \\`tasks.completed\\` vs \\`tasks.total\\`(\\`tasks\\` 为 null 时视为无任务,跳过检查)\n\n **如果全部完成:** 直接进入下一步。\n\n **如果有未完成的 artifact 或 task:**\n - 显示警告,列出未完成项\n - 用 **AskUserQuestion** 确认是否继续\n - 用户确认后继续,不阻塞\n\n3. **生成摘要**\n\n 读取 \\`marchen/changes/<name>/proposal.md\\`,从中生成一句话中文摘要(≤50字),概括这次变更做了什么。摘要应包含关键语义词,便于后续 AI 检索。\n\n4. **执行归档**\n\n \\`\\`\\`bash\n marchen archive <name> --summary \"<生成的摘要>\" --json\n \\`\\`\\`\n\n 解析返回的 JSON 获取归档结果。\n\n5. **显示结果**\n\n \\`\\`\\`\n 变更 \"<name>\" 已归档\n Schema: <schema>\n 归档到: <archivedTo>\n \\`\\`\\`\n\n 如有警告(未完成项),一并显示。\n\n**护栏**\n\n- 未提供名称时必须用 AskUserQuestion 让用户选择\n- 用 \\`status --json\\` 检查完成度,不要自己读文件判断\n- 警告不阻塞归档,只提醒 + 确认\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const COMMAND_EXPLORE = `---\nname: \"Marchen: Explore\"\ndescription: 进入探索模式 — 思考想法、调查问题、厘清需求\ncategory: Workflow\ntags: [workflow, explore, thinking]\n---\n\n进入探索模式。深入思考,自由可视化,跟随对话走向任何方向。\n\n**重要:探索模式只用于思考,不用于实现。** 可以读文件、搜索代码、调查代码库,但绝不能写代码或实现功能。如果用户要求实现,提醒他们先退出探索模式并用 \\`/marchen:propose\\` 创建变更。可以创建 Marchen artifact(proposal、design、spec)——那是捕获思考,不是实现。\n\n**这是一种姿态,不是工作流。** 没有固定步骤、没有必须的顺序、没有强制输出。你是帮助用户探索的思考伙伴。\n\n**输入**:\\`/marchen:explore\\` 后面可以跟任何内容:\n- 模糊的想法:\"实时协作\"\n- 具体的问题:\"auth 系统越来越难维护了\"\n- 变更名称:\"add-dark-mode\"(在该变更上下文中探索)\n- 方案比较:\"postgres vs sqlite\"\n- 什么都不带也行\n\n---\n\n## 姿态\n\n- **好奇,不武断** — 提出自然涌现的问题,不按脚本走\n- **开放线索,不审问** — 展示多个有趣方向,让用户跟随感兴趣的。不要把他们引导到单一路径上\n- **视觉化** — 大量使用 ASCII 图表来辅助思考\n- **自适应** — 跟随有趣的线索,有新信息时及时转向\n- **耐心** — 不急于下结论,让问题的形状自然浮现\n- **接地气** — 在相关时探索实际代码库,不要空谈理论\n\n---\n\n## 你可以做什么\n\n根据用户带来的内容,你可能会:\n\n**探索问题空间**\n- 提出从用户所说内容中自然涌现的澄清问题\n- 挑战假设\n- 重新框定问题\n- 寻找类比\n\n**调查代码库**\n- 映射与讨论相关的现有架构\n- 找到集成点\n- 识别已有的模式\n- 发现隐藏的复杂性\n\n**比较方案**\n- 头脑风暴多种方案\n- 构建比较表\n- 勾勒权衡\n- 推荐路径(如果被问到)\n\n**可视化**\n\\`\\`\\`\n┌─────────────────────────────────────────┐\n│ Use ASCII diagrams liberally │\n├─────────────────────────────────────────┤\n│ │\n│ ┌────────┐ ┌────────┐ │\n│ │ State │────────▶│ State │ │\n│ │ A │ │ B │ │\n│ └────────┘ └────────┘ │\n│ │\n│ System diagrams, state machines, │\n│ data flows, architecture sketches, │\n│ dependency graphs, comparison tables │\n│ │\n└─────────────────────────────────────────┘\n\\`\\`\\`\n\n**发现风险和未知**\n- 识别可能出错的地方\n- 找到理解上的空白\n- 建议 spike 或调查\n\n---\n\n## Marchen 感知\n\n你了解 Marchen 系统。自然地使用它,不要强制。\n\n### 检查上下文\n\n开始时快速检查现有状态和历史:\n\n1. 当前变更:\n\\`\\`\\`bash\nmarchen list --json\n\\`\\`\\`\n\n2. 变更历史概览:\n\\`\\`\\`bash\ncat marchen/changelog.md\n\\`\\`\\`\n这是所有已归档变更的索引,每条包含日期、变更名和一句话摘要。先扫一遍找到与用户话题相关的条目。如果找到,直接读对应 archive 目录下的 proposal.md 或 design.md 了解详情。\n\n3. 语义搜索:\n\n这是 RAG 搜索,不是 grep——构造语义完整的短语,不要用单个泛词。\n\n\\`\\`\\`bash\nmarchen search \"<语义完整的查询短语>\" --json\n\\`\\`\\`\n\n**查询构造指引:**\n- 用描述性短语,不用单个词:\n \"初始化适配多个 agent 客户端\" → \\`\"multi-agent provider 初始化\"\\`\n \"之前怎么处理错误的\" → \\`\"错误处理 error handling 重构\"\\`\n \"暗色模式的设计决策\" → \\`\"dark mode 设计方案\"\\`\n- 中英文混合效果更好(归档内容里中英都有)\n- 如果结果不理想,换个角度重新构造查询\n\n如果有匹配结果(score >= 0.4),读取对应 archive 目录下的 design.md 或 proposal.md 了解详细决策。\n\n如果 \\`marchen search\\` 不可用(命令报错),回退到 changelog.md + 手动读 archive 目录。\n\n这告诉你:\n- 是否有进行中的变更\n- 它们的名称、schema 和状态\n- 项目过去做过哪些变更\n- 用户可能在做什么\n\n如果用户提到了特定变更名称,读取它的 artifact 作为上下文。\n\n### 没有变更时\n\n自由思考。当洞察结晶时,根据复杂度推荐下一步:\n\n**判断标准:**\n- \\`/marchen:lite\\` — bug 修复、小改动、单一任务组、不需要设计文档\n- \\`/marchen:propose\\` — 新功能、多步骤、需要 design/specs、涉及多模块\n\n**推荐方式:** 直接在回复中输出推荐,说明理由,让用户自行输入命令。示例:\n\n> 想法差不多成型了。这个改动比较简单(只涉及一个文件的小调整),建议用 \\`/marchen:lite\\` 直接走轻量流程。\n>\n> 如果你觉得需要更完整的设计文档,也可以用 \\`/marchen:propose\\`。\n\n根据讨论内容给出你的推荐和理由,但让用户自己决定输入哪个命令。\n\n### 有变更时\n\n如果用户提到了变更或你发现某个变更相关:\n\n1. **读取已有 artifact 作为上下文**\n - \\`marchen/changes/<name>/proposal.md\\`\n - \\`marchen/changes/<name>/design.md\\`\n - \\`marchen/changes/<name>/tasks.md\\`\n - 等\n\n2. **在对话中自然引用**\n - \"你的 design 提到用 Redis,但我们刚发现 SQLite 更合适……\"\n - \"proposal 把范围限定在付费用户,但我们现在觉得应该面向所有人……\"\n\n3. **在做出决策时提议捕获**\n\n | 洞察类型 | 捕获到哪里 |\n |---------|-----------|\n | 发现新需求 | \\`specs/<capability>/spec.md\\` |\n | 需求变更 | \\`specs/<capability>/spec.md\\` |\n | 做出设计决策 | \\`design.md\\` |\n | 范围变更 | \\`proposal.md\\` |\n | 发现新工作 | \\`tasks.md\\` |\n | 假设被推翻 | 相关 artifact |\n\n 示例:\n - \"这是一个设计决策。要记录到 design.md 吗?\"\n - \"这是新需求。要加到 specs 里吗?\"\n - \"这改变了范围。要更新 proposal 吗?\"\n\n4. **用户决定** — 提议后继续。不施压,不自动捕获。\n\n---\n\n## 不必做的事\n\n- 按脚本走\n- 每次问同样的问题\n- 产出特定 artifact\n- 得出结论\n- 如果有价值的岔路就不必守住话题\n- 简短(这是思考时间)\n\n---\n\n## 结束探索\n\n没有固定的结束方式。探索可能:\n\n- **流向下一阶段**:用 **AskUserQuestion** 提供 \\`/marchen:lite\\` 和 \\`/marchen:propose\\` 选项,附带推荐理由\n- **更新 artifact**:\"已将这些决策更新到 design.md\"\n- **只是提供清晰度**:用户得到了需要的,继续前进\n- **稍后继续**:\"随时可以继续\"\n\n当想法结晶时,你可以提供总结——但不是必须的。有时候思考过程本身就是价值。\n\n---\n\n## 护栏\n\n- **不实现** — 绝不写代码或实现功能。创建 Marchen artifact 可以,写应用代码不行\n- **不伪装理解** — 不清楚就深挖\n- **不催促** — 探索是思考时间,不是任务时间\n- **不强制结构** — 让模式自然浮现\n- **不自动捕获** — 提议保存洞察,不要直接做\n- **要可视化** — 一张好图胜过千言万语\n- **要探索代码库** — 让讨论扎根于现实\n- **要质疑假设** — 包括用户的和你自己的\n`\n\nexport const COMMAND_LITE = `---\nname: \"Marchen: Lite\"\ndescription: 一键式轻量变更流程。创建 lite 变更、实现任务、询问归档,一气呵成\ncategory: Workflow\ntags: [workflow, lite]\n---\n\n一键式轻量变更 — 使用 lite schema 创建变更,自动实现任务,完成后询问归档。\n适合 bug 修复、小改动、explore 之后的快速执行。\n\n---\n\n**输入**:\\`/marchen:lite\\` 后面跟变更名称(kebab-case)或变更描述。\n\n**流程**\n\n1. **确定变更名称**\n\n 如果提供了输入,直接使用或从描述中提取 kebab-case 名称(如\"修复登录 bug\" → \\`fix-login-bug\\`)。\n\n 如果没有输入,用 **AskUserQuestion** 工具询问:\n > \"你想做什么变更?描述一下你要构建或修复的内容。\"\n\n 从回答中提取 kebab-case 名称。\n\n **重要**:必须理解用户想做什么才能继续。\n\n2. **创建变更目录**\n\n \\`\\`\\`bash\n marchen new <name> --schema lite\n \\`\\`\\`\n\n 创建 \\`marchen/changes/<name>/\\` 目录,包含 \\`.metadata.yaml\\` 和 \\`tasks.md\\` 骨架。\n\n 如果同名变更已存在,用 **AskUserQuestion** 询问用户是继续已有变更还是换个名称。\n\n3. **获取 tasks 指令**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 确认变更创建成功,然后获取 tasks 的创建指令:\n\n \\`\\`\\`bash\n marchen instructions <name> tasks --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`template\\`:tasks.md 的骨架结构(含 \\`## 背景\\` 章节)\n - \\`instruction\\`:如何填充 tasks 的指导文本\n - \\`outputPath\\`:写入路径(\\`tasks.md\\`)\n - \\`context\\`:上下文信息(lite schema 下为空数组)\n\n4. **填充 tasks.md**\n\n 根据用户描述 + \\`instruction\\` 指引 + \\`template\\` 结构,填充 tasks.md:\n - \\`## 背景\\`:简要说明变更目的和方案\n - 任务列表:按组分类,checkbox 格式\n\n 写入 \\`marchen/changes/<name>/tasks.md\\`。\n\n 如果用户描述太模糊,用 **AskUserQuestion** 澄清关键信息。\n\n5. **开始实现**\n\n 获取实现指令:\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的信息数组\n - \\`instruction\\`:实现指引\n - \\`changeDir\\`:变更目录绝对路径\n\n 显示:\"变更: \\`<name>\\` | 任务: 0/N | 开始实现...\"\n\n 对每个未完成任务:\n - 显示 \"任务 N/M: <描述>\"\n - 实现代码改动\n - 在 tasks.md 中勾选:\\`- [ ]\\` → \\`- [x]\\`\n 文件路径:\\`<changeDir>/tasks.md\\`\n - 显示 \"✓ 完成\"\n - 继续下一个\n\n **暂停条件:**\n - 任务不清晰 → 询问用户\n - 发现设计问题 → 建议更新 artifact\n - 遇到错误或阻塞 → 报告并等待\n - 用户中断\n\n 暂停时显示:\"暂停于任务 N/M: <原因>\",流程结束。\n\n6. **全部完成 → 询问归档**\n\n 所有任务完成后,用 **AskUserQuestion** 询问:\n\n > \"全部任务已完成 (N/N),是否归档这个变更?\"\n > - 归档\n > - 暂不归档\n\n **如果用户选择归档:**\n\n 读取 \\`marchen/changes/<name>/tasks.md\\` 的背景段,生成一句话中文摘要(≤50字)。\n\n \\`\\`\\`bash\n marchen archive <name> --summary \"<摘要>\" --json\n \\`\\`\\`\n\n 显示:\n \\`\\`\\`\n 变更 \"<name>\" 已归档\n 归档到: <archivedTo>\n \\`\\`\\`\n\n **如果用户选择暂不归档:**\n\n 显示:\"好的,后续可以用 \\`/marchen:archive <name>\\` 归档。\"\n\n**护栏**\n\n- 必须使用 \\`--schema lite\\` 创建变更\n- tasks.md 的 \\`## 背景\\` 章节必须填写,不能留空\n- 任务粒度要小到一个会话内能完成\n- 如果上下文关键信息不清楚,询问用户;但小疑问优先做合理判断,保持节奏\n- 已存在同名变更时必须询问用户,不要覆盖\n- 实现前必须读 context 中的 artifact 内容\n- 每完成一个任务立即勾选 checkbox,不要攒着\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- \\`instruction\\` 是给你的指引,不要把它原样复制到代码注释或 tasks.md 中\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const COMMAND_PROPOSE_PREVIEW = `---\nname: \"Marchen: Propose Preview\"\ndescription: 预览 propose 生成的变更摘要\ncategory: Workflow\ntags: [workflow, preview]\n---\n\n预览一个变更的浓缩摘要 — 纯终端输出,不写文件。\n\n适用于 propose 完成后人快速 review,避免来回翻 4~7 个 artifact 文件。\n\n---\n\n**输入**:\\`/marchen:propose-preview\\` 后面跟变更名称,或省略自动推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"Preview 变更: \\`<name>\\`\"\n\n2. **获取 artifact 内容**\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`schemaName\\`:\\`\"full\"\\` / \\`\"lite\"\\`\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的内容数组(\\`id\\` / \\`status\\` / \\`content\\`)\n\n **如果 state 为 \\`blocked\\`**:打印 \"变更未填完,先用 /marchen:propose 补齐 artifact 再预览\",结束。不要强行摘要半成品。\n\n3. **生成卡片并直接打印**\n\n 根据 \\`schemaName\\` 选模板:\n - \\`full\\` → 四段:改了什么 / 关键决策 / 影响范围 / 风险\n - \\`lite\\` → 两段:改了什么 / 任务概览\n\n 严格按下面的\"摘要规则\"生成,输出为单个卡片,禁止加任何解释段落。\n\n4. **末尾追加一行下一步提示**\n\n \\`\\`\\`\n /marchen:apply <name> 开始实现\n /marchen:propose <name> 修改提案\n \\`\\`\\`\n\n---\n\n## 摘要规则(必须严格遵守)\n\n**通用约束:**\n\n- 卡片框宽 70 字符(含 \\`│\\` 边框),便于主流终端整齐显示\n- 每行内容(去掉边框后)≤ 60 字符;超出必须截断/合并/重写,不许折行\n- 禁止粘贴 artifact 原文片段,所有内容必须重新组织、压缩\n- 中文为主,技术名词保留英文(如 \\`SearchManager\\`、\\`SDK\\`)\n- 宁可少写,不要堆。摘不下就合并或截断,在框底加 \\`更多详情见 marchen/changes/<name>/\\`\n\n**full schema 段落上限:**\n\n| 段落 | 上限 | 来源 |\n|---------|------------------|---------------------------------|\n| 改了什么 | 6 条 bullet | proposal 的\"能力\"小节 |\n| 关键决策 | 5 条 bullet | design 的\"决策\"小节 |\n| 影响范围 | 8 节点 ASCII 图 | proposal 的\"影响范围\" + design |\n| 风险 | 3 条 bullet | design 的\"风险与权衡\" |\n\n**lite schema 段落上限:**\n\n| 段落 | 上限 | 来源 |\n|---------|------------------|-------------------------------|\n| 改了什么 | 6 条 bullet | tasks.md 任务组标题 + 推断 |\n| 任务概览 | 1 行/任务组 | tasks.md 一级标题 + 完成进度 |\n\n**影响范围图退化策略:**\n\n如果节点数超 8 个 / 关系不清晰 / 没有明显依赖结构 → 改用 bullet 列模块名,不要硬画歪斜的 ASCII。\n\n**任务进度条规则(lite):**\n\n进度条固定 10 格宽,按 \\`completed/total\\` 比例画。例如 \\`8/8\\` → \\`██████████\\`,\\`3/5\\` → \\`██████░░░░\\`。\n\n---\n\n## 输出样例\n\n**full schema:**\n\n\\`\\`\\`\n╭─ <name> ────────────────────────────── full · <N> 任务 ─╮\n│ │\n│ <一句话动机,从 proposal \"动机\" 段提炼,≤ 55 字> │\n│ │\n│ ━━ 改了什么 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ ✚ <capability-name> <一句话能力描述> │\n│ ... │\n│ │\n│ ━━ 关键决策 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ 1. <决策内容> │\n│ ... │\n│ │\n│ ━━ 影响范围 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ <ASCII 图 或 bullet 模块列表> │\n│ │\n│ ━━ 风险 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ • <风险> │\n│ │\n╰──────────────────────────────────────────────────────────╯\n\\`\\`\\`\n\n**lite schema:**\n\n\\`\\`\\`\n╭─ <name> ──────────────────────────── lite · <N> 任务 ─╮\n│ │\n│ <一句话动机,从 tasks.md \"背景\" 段提炼> │\n│ │\n│ ━━ 改了什么 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ • <推断的核心变更> │\n│ │\n│ ━━ 任务概览 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ 1. <任务组标题> ██████████ N/M ✓ │\n│ │\n╰───────────────────────────────────────────────────────╯\n\\`\\`\\`\n\n**护栏**\n\n- 不写文件,纯 stdout 输出\n- 不调用 AskUserQuestion 询问\"要打印什么\",遵守摘要规则即可\n- 不展开补全 artifact 信息——artifact 里没有的,摘要里就不该有\n- state 为 \\`blocked\\` 时拒绝生成,不强行摘要半成品\n- 不要在卡片外加解释段落,只打印卡片 + 下一步提示\n- \\`instruction\\` 字段(apply JSON 里有)是给 apply 用的,不是给 preview 的,忽略它\n`\n\nexport const COMMAND_PROPOSE = `---\nname: \"Marchen: Propose\"\ndescription: 提出新变更,创建并填充所有 artifact\ncategory: Workflow\ntags: [workflow, artifacts]\n---\n\n提出新变更 — 创建变更目录并按依赖顺序生成所有 artifact。\n\n将创建以下 artifact:\n- proposal.md(动机和变更内容)\n- specs/(每个能力的需求规格)\n- design.md(技术方案)\n- tasks.md(实现任务清单)\n\n完成后可用 /marchen:apply 开始实现。\n\n---\n\n**输入**:\\`/marchen:propose\\` 后面跟变更名称(kebab-case)或变更描述。\n\n**流程**\n\n1. **确定变更名称**\n\n 如果提供了输入,直接使用或从描述中提取 kebab-case 名称(如\"添加用户认证\" → \\`add-user-auth\\`)。\n\n 如果没有输入,用 **AskUserQuestion** 工具询问:\n > \"你想做什么变更?描述一下你要构建或修复的内容。\"\n\n 从回答中提取 kebab-case 名称。\n\n **重要**:必须理解用户想做什么才能继续。\n\n2. **创建变更目录**\n\n \\`\\`\\`bash\n marchen new <name>\n \\`\\`\\`\n\n 创建 \\`marchen/changes/<name>/\\` 目录和 \\`.metadata.yaml\\`。\n\n 如果同名变更已存在,用 **AskUserQuestion** 询问用户是继续已有变更还是换个名称。\n\n3. **循环创建 artifact**\n\n 用 **TaskCreate** 工具创建任务列表追踪进度。\n\n 循环执行以下步骤:\n\n a. **查询当前状态**\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n 返回 JSON 包含:\n - \\`workflow.next\\`:下一个应该创建的 artifact ID,全部完成时为 \\`null\\`\n - \\`workflow.ready\\`:当前可以创建的 artifact 列表\n - \\`workflow.blocked\\`:被阻塞的 artifact 列表\n - \\`artifacts\\`:每个 artifact 的状态详情(\\`id\\`、\\`status\\`、\\`path\\`)\n\n 如果 \\`workflow.next\\` 为 \\`null\\` → 全部完成,跳到第 4 步。\n\n b. **获取创建指令**\n \\`\\`\\`bash\n marchen instructions <name> <workflow.next> --json\n \\`\\`\\`\n 返回 JSON 包含:\n - \\`template\\`:artifact 的 markdown 骨架结构,用它作为输出文件的框架\n - \\`instruction\\`:如何填充该 artifact 的指导文本\n - \\`outputPath\\`:写入路径(相对于变更目录)\n - \\`context\\`:上下文 artifact 的信息数组,每项包含 \\`id\\`、\\`status\\`、\\`content\\`(已填充的内容直接在这里,不需要额外读文件)\n - \\`unlocks\\`:完成此 artifact 后解锁的 artifact 列表\n\n c. **创建 artifact**\n\n 根据 artifact 类型处理:\n\n **普通 artifact(proposal / design / tasks):**\n - 读取 \\`context\\` 中 \\`status\\` 为 \\`filled\\` 的 \\`content\\` 作为上下文\n - 按 \\`instruction\\` 指引 + \\`template\\` 结构生成内容\n - 写入 \\`marchen/changes/<name>/<outputPath>\\`\n - 写入后验证文件存在\n\n **specs(目录型 artifact,outputPath 为 \\`specs/\\`):**\n - 读取 proposal 内容(在 \\`context\\` 中,\\`id\\` 为 \\`proposal\\` 的 \\`content\\`)\n - 从 proposal 的\"能力\"章节提取能力列表(kebab-case 名称)\n - 为每个能力:\n - 创建目录 \\`marchen/changes/<name>/specs/<capability>/\\`\n - 按 \\`template\\` 结构 + \\`instruction\\` 指引生成 spec 内容\n - 写入 \\`specs/<capability>/spec.md\\`\n - 写入后验证每个 spec 文件存在\n\n **如果 proposal 的上下文不够清晰**(用户描述太模糊):\n - 用 **AskUserQuestion** 澄清关键信息\n - 然后继续创建\n\n d. 显示进度:\"已创建 \\`<artifact-id>\\`\",标记任务完成,回到步骤 a。\n\n4. **显示最终状态**\n\n \\`\\`\\`bash\n marchen status <name>\n \\`\\`\\`\n\n**输出**\n\n完成后显示:\n- 变更名称和目录位置\n- 已创建的 artifact 列表及简要说明\n- 用纯文字(不调用 AskUserQuestion,不自动执行)提示下一步两个并列选项,由用户自行决定:\n\n \\`\\`\\`\n 下一步:\n /marchen:apply <name> 直接开始实现\n /marchen:propose-preview <name> 先看一眼浓缩摘要再决定\n \\`\\`\\`\n\n**护栏**\n\n- 按依赖顺序创建,不跳过 artifact\n- 每次循环创建一个 artifact(specs 算一个,但包含多个文件)\n- 写入后验证文件存在再继续下一个\n- 如果上下文关键信息不清楚,询问用户;但小疑问优先做合理判断,保持节奏\n- 已存在同名变更时必须询问用户,不要覆盖\n- \\`instruction\\` 是给你的指引,不要把它原样复制到 artifact 文件中\n- 使用 AskUserQuestion 时,选项不超过 4 个;需要更多选项时合并或分步询问\n`\n\nexport const COMMAND_REVIEW = `---\nname: \"Marchen: Review\"\ndescription: 对照变更意图检查代码实现,支持 chrome-devtools MCP 的 UI 场景验证\ncategory: Workflow\ntags: [workflow, review]\n---\n\n对照变更的 artifact 检查代码改动,必要时驱动浏览器验证 UI 行为,报告遗漏、偏差和阻塞。\n\n---\n\n**输入**:\\`/marchen:review\\` 后面跟变更名称,或省略自动推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"Review 变更: \\`<name>\\`\"\n\n2. **嗅探 diff 中的 UI 改动**\n\n 执行 \\`git diff --name-only HEAD\\`,判断改动是否涉及前端 UI(组件、样式、模板、静态资源等)。命中则记下命中文件,用于下一步的提示。\n\n3. **AskUserQuestion 选择 review 模式**\n\n 用 **AskUserQuestion** 让用户三选一:\n\n - **代码 review**(默认):对照 artifact 检查 git diff,不跑代码\n - **UI 验证**:用 chrome-devtools MCP 实际打开页面验证 specs 中的场景\n - **两者都做**:先代码、再 UI\n\n 若上一步命中 UI 文件,在问题描述里附一句:\"检测到 diff 涉及 UI 文件(<列出命中文件>),建议选 UI 验证或两者都做。\"\n 未命中时不附加提示,默认推荐\"代码 review\"。\n\n 保存用户选择为 \\`<mode>\\`。\n\n4. **获取意图与改动(公共前置)**\n\n - 执行 \\`marchen instructions <name> apply --json\\`,从返回 JSON 的 \\`context\\` 数组里读取 \\`status\\` 为 \"filled\" 的 artifact(proposal/specs/design/tasks)作为变更意图\n - 执行 \\`git diff HEAD\\`;为空则 \\`git diff HEAD~1\\`;仍为空则报告 \"未检测到代码改动\" 并结束\n\n diff 大到可能挤占 context 时,先 \\`git diff --stat HEAD\\` 看摘要,再按需读单个文件 diff。\n\n5. **代码 review**(mode 为 \"代码 review\" 或 \"两者都做\" 时执行)\n\n 逐条对照变更意图和代码改动,输出报告:\n\n **任务完成度** — tasks 中每个任务是否有对应改动:\n - ✅ 任务: <描述> — 已实现\n - ❌ 任务: <描述> — 未找到对应改动\n\n **一致性检查** — 实现是否符合 design 决策:\n - ✅ <决策> — 已遵守\n - ⚠️ <决策> — 实现有偏差:<说明>\n\n **需求覆盖** — specs 中需求是否被覆盖:\n - ✅ <需求> — 已覆盖\n - ❌ <需求> — 未覆盖\n\n **发现的问题(如有)**\n - <文件:行号> <问题描述>\n\n 全部通过:输出 \"✅ 代码 review 通过。\"\n\n 遇到无法判断的情况(tasks 描述与 diff 对不上但可能是改名了、spec 表述含糊等),直接用 **AskUserQuestion** 就地问用户,不要积攒到最后。\n\n6. **UI 验证**(mode 为 \"UI 验证\" 或 \"两者都做\" 时执行)\n\n ### a. 检测 chrome-devtools MCP 可用性\n\n 尝试调用 chrome-devtools MCP 的只读探测工具(例如列出已打开的页面)。工具不存在或调用失败 → 在报告里写:\n\n > ⏭ chrome-devtools MCP 不可用,跳过 UI 验证。\n > 安装方法:\\`npx chrome-devtools-mcp@latest\\`,并参考所用 AI 工具的 MCP 配置文档将其注册。\n\n 然后跳过本节剩余步骤。\n\n ### b. 提取待验证场景\n\n 优先从变更的 specs 文件中提取所有 \\`#### 场景:\\` 块(按 spec 文件分组)。\n 若 specs 不存在或不含场景 → 退化到从 tasks/proposal 推断 UI 相关验证点,并在报告中标注 \"基于 tasks 推断,覆盖度可能不完整\"。\n 两者都没有可提取场景 → 报告 \"未找到可验证的 UI 场景\" 并跳过本节。\n\n ### c. 推断 dev server URL\n\n 从项目文件(脚本、框架配置、环境变量、README 等)推断 dev server 地址和启动命令,不要硬猜端口。\n\n 推出候选 URL 后用 chrome-devtools MCP 的导航工具打开,再用快照工具确认页面像被测应用(合理 title、含框架 root 节点或变更描述涉及的标志性内容)。看着不像被测应用 → 当作未能推断。\n\n 推不出 URL → 全部场景 ⏭ \"URL unknown\",在报告里说明推断依据,提示用户确认地址后重新运行 review,跳过本节执行步骤。\n\n ### d. 推断到 URL 但 dev server 未启动 → 询问是否帮忙起\n\n 推出来 URL 但导航失败(连接拒绝)→ 主会话 **AskUserQuestion**,附上推断依据和启动命令:\n\n - **帮我起 dev server 然后继续**\n - **我手动起,起完告诉你**\n - **跳过 UI 验证**\n\n 选\"帮我起\":\n 1. 用 Bash 的 \\`run_in_background\\` 执行推断到的启动命令(如 \\`pnpm dev\\`、\\`npm run dev\\`)\n 2. 轮询端口直到可访问;出现明显异常(进程退出 / 长时间无任何输出 / 报错日志)才判定启动失败,让用户手动检查\n 3. 启动成功后继续执行后续步骤,并在 review 结束时显式告知用户后台进程 PID 和端口,提示 \"用完请自行 kill <PID>\"——MUST NOT 自动 kill\n\n 选\"我手动起\":等用户告知启动完成后继续;用户可以新会话执行 \\`/marchen:review\\` 重跑。\n\n 选\"跳过 UI\":所有场景 ⏭ \"dev server 未启动\",跳过后续步骤。\n\n ### e. 乐观执行场景\n\n 对每个待验证场景:\n\n 1. 用 chrome-devtools MCP 的导航工具打开对应路径\n 2. 用快照工具观察页面状态\n 3. 把场景的 GIVEN/WHEN/THEN 翻译成具体交互(点击、填写、读取文本)并执行\n 4. 比对页面状态与场景预期\n\n 遇到阻塞(登录墙、权限不足、缺数据、表单需要真实输入等):**主会话可以就地 AskUserQuestion**——例如\"需要登录账号才能继续,提供测试账号?/ 跳过这个场景 / 暂停 review\",而不必积攒到最后。\n\n 仍无法继续的场景 → 记录 ⏭ + 阻塞原因,跳到下一个。\n 场景翻译不出具体操作(例如纯后端行为描述)→ ⏭ \"不适合 UI 验证\"。\n\n ### f. UI 报告格式\n\n \\`\\`\\`\n ## UI 验证(chrome-devtools MCP)\n\n 测试目标:<URL 或 \"未确定\">\n\n ✅ <场景标题> — 通过\n 说明:<可选简述>\n ❌ <场景标题> — 失败\n 证据:<console error 摘要 / 页面文案 / 关键截图路径>\n ⏭ <场景标题> — 跳过:<阻塞原因>\n \\`\\`\\`\n\n7. **展示报告并按结果分支**\n\n - **全部通过且无 ⏭**:提示 \"可以用 \\`/marchen:archive\\` 归档。\"\n - **有 ❌ 失败**:提示用户修复后可以再次 \\`/marchen:review\\`。\n - **有 ⏭ 跳过**:用 **AskUserQuestion** 让用户选择:\n - **补信息后继续**:收集所需信息(账号、数据等),就地再跑被跳过的场景\n - **跳过这些场景,继续归档**:保留报告,提示 \\`/marchen:archive\\`\n - **暂停去修复**:什么都不做\n\n 如本次 review 启动了后台 dev server,再次提醒用户进程信息(PID/端口)。\n\n**护栏**\n\n- 不要修改任何代码,只报告(review 不是 apply)\n- UI 验证阻塞即停,不要硬闯(不登录、不猜数据、不绕权限);要继续就向用户索取信息\n- 起 dev server 必须先获得用户授权(AskUserQuestion);起完不自动 kill,告知 PID 让用户自行管理\n- 用户提供的凭据/账号等敏感数据:不写入任何 artifact,不在报告里复述明文,截图前对密码/token/邮箱等敏感字段脱敏或回避\n- 大 diff 先看 \\`git diff --stat\\`,按需读单个文件,避免灌爆 context\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\n/** Command 模板定义 */\nexport interface CommandTemplate {\n readonly fileName: string\n readonly content: string\n}\n\n/** 所有 command 模板 */\nexport const COMMAND_TEMPLATES: Record<string, CommandTemplate> = {\n apply: { fileName: 'apply.md', content: COMMAND_APPLY },\n archive: { fileName: 'archive.md', content: COMMAND_ARCHIVE },\n explore: { fileName: 'explore.md', content: COMMAND_EXPLORE },\n lite: { fileName: 'lite.md', content: COMMAND_LITE },\n 'propose-preview': { fileName: 'propose-preview.md', content: COMMAND_PROPOSE_PREVIEW },\n propose: { fileName: 'propose.md', content: COMMAND_PROPOSE },\n review: { fileName: 'review.md', content: COMMAND_REVIEW },\n}\n","import type { AgentProvider } from '@marchen/shared'\n\nexport const AGENT_PROVIDERS: Record<string, AgentProvider> = {\n antigravity: {\n id: 'antigravity',\n name: 'Antigravity',\n skillDir: '.agent/skills',\n },\n 'claude-code': {\n id: 'claude-code',\n name: 'Claude Code',\n skillDir: '.claude/skills',\n commandDir: '.claude/commands/marchen',\n },\n codex: {\n id: 'codex',\n name: 'Codex',\n skillDir: '.codex/skills',\n },\n copilot: {\n id: 'copilot',\n name: 'GitHub Copilot',\n skillDir: '.github/skills',\n },\n cursor: {\n id: 'cursor',\n name: 'Cursor',\n skillDir: '.cursor/skills',\n },\n 'gemini-cli': {\n id: 'gemini-cli',\n name: 'Gemini CLI',\n skillDir: '.gemini/skills',\n },\n kilocode: {\n id: 'kilocode',\n name: 'Kilo Code',\n skillDir: '.kilocode/skills',\n },\n kiro: {\n id: 'kiro',\n name: 'Kiro',\n skillDir: '.kiro/skills',\n },\n opencode: {\n id: 'opencode',\n name: 'OpenCode',\n skillDir: '.opencode/skills',\n },\n windsurf: {\n id: 'windsurf',\n name: 'Windsurf',\n skillDir: '.windsurf/skills',\n },\n}\n\nexport const DEFAULT_PROVIDER_IDS: readonly string[] = ['claude-code']\n","/**\n * Artifact 模板内容\n *\n * 每个 artifact 的初始 markdown 骨架,创建变更时写入对应文件\n */\n\n/** proposal.md 模板 */\nexport const PROPOSAL_TEMPLATE = `## 动机\n\n<!-- 说明这个变更的动机。解决什么问题?为什么现在做? -->\n\n## 变更内容\n\n<!-- 描述具体变更内容。列出新增的能力、修改或移除的部分。 -->\n\n## 能力\n\n### 新增能力\n<!-- 新增的能力。使用 kebab-case 命名,每个会生成 specs/<name>/spec.md -->\n\n### 修改能力\n<!-- 需要修改的现有能力。仅当规范级别的行为发生变化时才列出。 -->\n\n## 影响范围\n\n<!-- 受影响的代码、API、依赖或系统 -->\n`\n\n/** design.md 模板 */\nexport const DESIGN_TEMPLATE = `## 背景\n\n<!-- 背景和当前状态 -->\n\n## 目标与非目标\n\n**目标:**\n<!-- 这个设计要达成什么 -->\n\n**非目标:**\n<!-- 明确排除在范围之外的内容 -->\n\n## 决策\n\n<!-- 关键技术决策和理由 -->\n\n## 风险与权衡\n\n<!-- 已知风险和权衡 -->\n`\n\n/** spec.md 模板 */\nexport const SPEC_TEMPLATE = `## 目的\n\n<!-- 这个能力的领域职责,一两句话 -->\n\n### 需求: <!-- 需求名称 -->\n\n<!-- 系统 SHALL/MUST 做什么(必须有描述文本) -->\n\n#### 场景: <!-- 场景名称 -->\n\n- **GIVEN** <!-- 前置条件/上下文 -->\n- **WHEN** <!-- 触发动作 -->\n- **THEN** <!-- 期望结果 -->\n- **AND** <!-- 追加条件或结果 -->\n`\n\n/** tasks.md 模板 */\nexport const TASKS_TEMPLATE = `## 1. <!-- 任务组名称 -->\n\n- [ ] 1.1 <!-- 任务描述 -->\n- [ ] 1.2 <!-- 任务描述 -->\n`\n","import type { SchemaDefinition } from '@marchen/shared'\nimport { ValidationError } from '@marchen/shared'\nimport {\n DESIGN_TEMPLATE,\n PROPOSAL_TEMPLATE,\n SPEC_TEMPLATE,\n TASKS_TEMPLATE,\n} from './templates.js'\n\n/**\n * lite schema 的 tasks 模板(带背景章节)\n */\nconst LITE_TASKS_TEMPLATE = `## 背景\n\n<!-- 简要说明这个变更的目的和方案 -->\n\n## 1. <!-- 任务组名称 -->\n\n- [ ] 1.1 <!-- 任务描述 -->\n- [ ] 1.2 <!-- 任务描述 -->\n`\n\n/**\n * apply 阶段的指导文本(所有 schema 共用)\n */\nexport const APPLY_INSTRUCTION = `按 tasks.md 逐个实现任务,完成后勾选 checkbox。\n- 每完成一个任务立即将 - [ ] 改为 - [x]\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- 如果发现设计问题,暂停并建议更新 artifact`\n\n/**\n * 内置 schema 定义\n *\n * full: 完整工作流 proposal → specs → design → tasks\n * lite: 轻量工作流,只有 tasks(适合 explore 之后快速执行)\n */\nexport const SCHEMAS: Record<string, SchemaDefinition> = {\n full: {\n name: 'full',\n artifacts: [\n {\n id: 'proposal',\n generates: 'proposal.md',\n requires: [],\n template: PROPOSAL_TEMPLATE,\n instruction: `根据用户的描述,填写 proposal 的各个部分。\n- 动机:说明为什么要做这个变更,解决什么问题\n- 变更内容:列出具体改动\n- 能力:列出需要创建 spec 的 capability(kebab-case 命名),每个会生成 specs/<name>/spec.md\n- 影响范围:说明涉及的代码和系统\n保持简洁,1-2 页。聚焦\"为什么\"和\"做什么\",不要写实现细节。`,\n },\n {\n id: 'specs',\n generates: 'specs/',\n requires: ['proposal'],\n template: SPEC_TEMPLATE,\n instruction: `根据 proposal 中列出的能力,为每个能力创建 specs/<name>/spec.md。\n\n格式规则:\n- 以 '## 目的' 开头,一两句话说明这个能力的领域职责\n- 每个需求用 '### 需求:' 开头,标题后必须有描述文本(用 SHALL/MUST 表述行为)\n- 每个场景用 '#### 场景:' 开头(必须 4 个 #)\n- 场景内容用列表格式,关键字加粗:- **GIVEN** / - **WHEN** / - **THEN** / - **AND**\n- GIVEN 描述前置条件(可省略,但推荐写明)\n- 每个需求至少一个场景\n- 场景应该是可测试的(每个场景对应一个潜在的 test case)\n\n内容约束:\n- 只描述可观察行为,不描述实现方式\n- 不包含内部类名、函数名、框架/库选择等实现细节(这些属于 design.md)\n- 使用 RFC 2119 关键字表达需求强度:SHALL/MUST(必须)、SHOULD(推荐)、MAY(可选)`,\n },\n {\n id: 'design',\n generates: 'design.md',\n requires: ['proposal'],\n template: DESIGN_TEMPLATE,\n instruction: `根据 proposal 的动机和 specs 的需求,设计技术实现方案。\n- 背景:当前状态和约束\n- 目标与非目标:明确范围\n- 决策:关键技术选择和理由(为什么选 X 而不是 Y)\n- 风险与权衡:已知限制和可能出错的地方\n聚焦架构和方案,不要写逐行实现细节。`,\n },\n {\n id: 'tasks',\n generates: 'tasks.md',\n requires: ['specs', 'design'],\n template: TASKS_TEMPLATE,\n instruction: `根据 specs 的需求和 design 的技术方案,拆分实现任务。\n- 按任务组分组,每组用 ## 标题\n- 每个任务用 checkbox 格式:- [ ] X.Y 描述\n- 任务粒度要小到一个会话内能完成\n- 按依赖顺序排列(先做的在前)`,\n },\n ],\n },\n lite: {\n name: 'lite',\n artifacts: [\n {\n id: 'tasks',\n generates: 'tasks.md',\n requires: [],\n template: LITE_TASKS_TEMPLATE,\n instruction: `根据背景描述,拆分实现任务。\n- 按任务组分组,每组用 ## 标题\n- 每个任务用 checkbox 格式:- [ ] X.Y 描述\n- 任务粒度要小到一个会话内能完成\n- 按依赖顺序排列(先做的在前)`,\n },\n ],\n },\n}\n\n/** 默认 schema 名称 */\nexport const DEFAULT_SCHEMA_NAME = 'full'\n\n/**\n * 按名称查找 schema\n *\n * @param name - schema 名称\n * @returns SchemaDefinition\n * @throws {ValidationError} schema 不存在时抛出,附带可用列表\n */\nexport function getSchema(name: string): SchemaDefinition {\n const schema = SCHEMAS[name]\n if (!schema) {\n const available = Object.keys(SCHEMAS).join(', ')\n throw new ValidationError(\n `Schema \"${name}\" 不存在,可用的 schema: ${available}`,\n )\n }\n return schema\n}\n","// 此文件由 scripts/generate-templates.ts 自动生成,请勿手动修改\n\nexport const SKILL_APPLY = `---\nname: marchen-apply\ndescription: 按变更的 tasks.md 逐个实现任务。适用于用户想按任务清单逐步实现代码。\n---\n\n按变更的 tasks.md 逐个实现任务,完成后勾选 checkbox。\n\n---\n\n**输入**:用户的请求应包含变更名称,或可从上下文推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"使用变更: \\`<name>\\`\"\n\n2. **获取实现指令**\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的信息数组,每项包含 \\`id\\`、\\`status\\`、\\`path\\`、\\`content\\`\n - \\`instruction\\`:实现指引\n - \\`changeDir\\`:变更目录绝对路径\n\n 根据 \\`state\\` 处理:\n - \\`\"blocked\"\\` → 提示先完成 artifacts\n - \\`\"all_done\"\\` → 提示归档\n - \\`\"ready\"\\` → 继续\n\n3. **显示进度**\n\n 从返回的 JSON 读取并显示:\n \"变更: \\`<name>\\` | 进度: N/M | 下一个: <第一个未完成任务的描述>\"\n\n4. **逐个实现任务**\n\n 从 \\`context\\` 中读取所有 artifact 内容作为上下文。\n\n 对每个未完成任务:\n - 显示 \"任务 N/M: <描述>\"\n - 实现代码改动\n - 在 tasks.md 中勾选:\\`- [ ]\\` → \\`- [x]\\`\n 文件路径:\\`<changeDir>/tasks.md\\`\n - 显示 \"✓ 完成\"\n - 继续下一个\n\n **暂停条件:**\n - 任务不清晰 → 询问用户\n - 发现设计问题 → 建议更新 artifact\n - 遇到错误或阻塞 → 报告并等待\n - 用户中断\n\n5. **显示结果**\n\n 全部完成时:\n \"全部完成 (N/N),可以用 \\`marchen review <name>\\` 检查实现,或直接 \\`marchen archive <name>\\` 归档。\"\n\n 暂停时:\n \"暂停于任务 N/M: <原因>\"\n\n**护栏**\n\n- 实现前必须读 context 中的 artifact 内容\n- 每完成一个任务立即勾选 checkbox,不要攒着\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- 如果实现过程中遇到不确定的设计决策,可以用 \\`marchen search \"<关键词>\" --json\\` 搜索历史变更中的相关方案\n- \\`instruction\\` 是给你的指引,不要原样复制到代码注释中\n`\n\nexport const SKILL_ARCHIVE = `---\nname: marchen-archive\ndescription: 归档已完成的变更。检查完成度后执行归档,适用于实现完毕后收尾。\n---\n\n归档已完成的变更,检查完成度后执行归档。\n\n---\n\n**输入**:用户的请求可包含变更名称(如 \\`/marchen:archive add-auth\\`),也可不带。\n\n**流程**\n\n1. **确定变更名称**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n **重要**:不要猜测或自动选择,必须让用户确认。\n\n2. **检查完成度**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 解析 JSON,检查:\n - \\`artifacts\\`:每个 artifact 的 \\`status\\` 是否为 \\`filled\\`\n - \\`tasks.completed\\` vs \\`tasks.total\\`(\\`tasks\\` 为 null 时视为无任务,跳过检查)\n\n **如果全部完成:** 直接进入下一步。\n\n **如果有未完成的 artifact 或 task:**\n - 显示警告,列出未完成项\n - 用 **AskUserQuestion** 确认是否继续\n - 用户确认后继续,不阻塞\n\n3. **生成摘要**\n\n 读取 \\`marchen/changes/<name>/proposal.md\\`,从中生成一句话中文摘要(≤50字),概括这次变更做了什么。摘要应包含关键语义词,便于后续 AI 检索。\n\n4. **执行归档**\n\n \\`\\`\\`bash\n marchen archive <name> --summary \"<生成的摘要>\" --json\n \\`\\`\\`\n\n 解析返回的 JSON 获取归档结果。\n\n5. **显示结果**\n\n \\`\\`\\`\n 变更 \"<name>\" 已归档\n Schema: <schema>\n 归档到: <archivedTo>\n \\`\\`\\`\n\n 如有警告(未完成项),一并显示。\n\n**护栏**\n\n- 未提供名称时必须用 AskUserQuestion 让用户选择\n- 用 \\`status --json\\` 检查完成度,不要自己读文件判断\n- 警告不阻塞归档,只提醒 + 确认\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const SKILL_EXPLORE = `---\nname: marchen-explore\ndescription: 进入探索模式 — 思考想法、调查问题、厘清需求。适用于用户想在动手之前先理清思路。\n---\n\n进入探索模式。深入思考,自由可视化,跟随对话走向任何方向。\n\n**重要:探索模式只用于思考,不用于实现。** 可以读文件、搜索代码、调查代码库,但绝不能写代码或实现功能。如果用户要求实现,提醒他们先退出探索模式并用 \\`/marchen:propose\\` 创建变更。可以创建 Marchen artifact(proposal、design、spec)——那是捕获思考,不是实现。\n\n**这是一种姿态,不是工作流。** 没有固定步骤、没有必须的顺序、没有强制输出。你是帮助用户探索的思考伙伴。\n\n**输入**:\\`/marchen:explore\\` 后面可以跟任何内容:\n- 模糊的想法:\"实时协作\"\n- 具体的问题:\"auth 系统越来越难维护了\"\n- 变更名称:\"add-dark-mode\"(在该变更上下文中探索)\n- 方案比较:\"postgres vs sqlite\"\n- 什么都不带也行\n\n---\n\n## 姿态\n\n- **好奇,不武断** — 提出自然涌现的问题,不按脚本走\n- **开放线索,不审问** — 展示多个有趣方向,让用户跟随感兴趣的。不要把他们引导到单一路径上\n- **视觉化** — 大量使用 ASCII 图表来辅助思考\n- **自适应** — 跟随有趣的线索,有新信息时及时转向\n- **耐心** — 不急于下结论,让问题的形状自然浮现\n- **接地气** — 在相关时探索实际代码库,不要空谈理论\n\n---\n\n## 你可以做什么\n\n根据用户带来的内容,你可能会:\n\n**探索问题空间**\n- 提出从用户所说内容中自然涌现的澄清问题\n- 挑战假设\n- 重新框定问题\n- 寻找类比\n\n**调查代码库**\n- 映射与讨论相关的现有架构\n- 找到集成点\n- 识别已有的模式\n- 发现隐藏的复杂性\n\n**比较方案**\n- 头脑风暴多种方案\n- 构建比较表\n- 勾勒权衡\n- 推荐路径(如果被问到)\n\n**可视化**\n\\`\\`\\`\n┌─────────────────────────────────────────┐\n│ Use ASCII diagrams liberally │\n├─────────────────────────────────────────┤\n│ │\n│ ┌────────┐ ┌────────┐ │\n│ │ State │────────▶│ State │ │\n│ │ A │ │ B │ │\n│ └────────┘ └────────┘ │\n│ │\n│ System diagrams, state machines, │\n│ data flows, architecture sketches, │\n│ dependency graphs, comparison tables │\n│ │\n└─────────────────────────────────────────┘\n\\`\\`\\`\n\n**发现风险和未知**\n- 识别可能出错的地方\n- 找到理解上的空白\n- 建议 spike 或调查\n\n---\n\n## Marchen 感知\n\n你了解 Marchen 系统。自然地使用它,不要强制。\n\n### 检查上下文\n\n开始时快速检查现有状态和历史:\n\n1. 当前变更:\n\\`\\`\\`bash\nmarchen list --json\n\\`\\`\\`\n\n2. 变更历史概览:\n\\`\\`\\`bash\ncat marchen/changelog.md\n\\`\\`\\`\n这是所有已归档变更的索引,每条包含日期、变更名和一句话摘要。先扫一遍找到与用户话题相关的条目。如果找到,直接读对应 archive 目录下的 proposal.md 或 design.md 了解详情。\n\n3. 语义搜索:\n\n这是 RAG 搜索,不是 grep——构造语义完整的短语,不要用单个泛词。\n\n\\`\\`\\`bash\nmarchen search \"<语义完整的查询短语>\" --json\n\\`\\`\\`\n\n**查询构造指引:**\n- 用描述性短语,不用单个词:\n \"初始化适配多个 agent 客户端\" → \\`\"multi-agent provider 初始化\"\\`\n \"之前怎么处理错误的\" → \\`\"错误处理 error handling 重构\"\\`\n \"暗色模式的设计决策\" → \\`\"dark mode 设计方案\"\\`\n- 中英文混合效果更好(归档内容里中英都有)\n- 如果结果不理想,换个角度重新构造查询\n\n如果有匹配结果(score >= 0.4),读取对应 archive 目录下的 design.md 或 proposal.md 了解详细决策。\n\n如果 \\`marchen search\\` 不可用(命令报错),回退到 changelog.md + 手动读 archive 目录。\n\n这告诉你:\n- 是否有进行中的变更\n- 它们的名称、schema 和状态\n- 项目过去做过哪些变更\n- 用户可能在做什么\n\n如果用户提到了特定变更名称,读取它的 artifact 作为上下文。\n\n### 没有变更时\n\n自由思考。当洞察结晶时,根据复杂度推荐下一步:\n\n**判断标准:**\n- \\`/marchen:lite\\` — bug 修复、小改动、单一任务组、不需要设计文档\n- \\`/marchen:propose\\` — 新功能、多步骤、需要 design/specs、涉及多模块\n\n**推荐方式:** 直接在回复中输出推荐,说明理由,让用户自行输入命令。示例:\n\n> 想法差不多成型了。这个改动比较简单(只涉及一个文件的小调整),建议用 \\`/marchen:lite\\` 直接走轻量流程。\n>\n> 如果你觉得需要更完整的设计文档,也可以用 \\`/marchen:propose\\`。\n\n根据讨论内容给出你的推荐和理由,但让用户自己决定输入哪个命令。\n\n### 有变更时\n\n如果用户提到了变更或你发现某个变更相关:\n\n1. **读取已有 artifact 作为上下文**\n - \\`marchen/changes/<name>/proposal.md\\`\n - \\`marchen/changes/<name>/design.md\\`\n - \\`marchen/changes/<name>/tasks.md\\`\n - 等\n\n2. **在对话中自然引用**\n - \"你的 design 提到用 Redis,但我们刚发现 SQLite 更合适……\"\n - \"proposal 把范围限定在付费用户,但我们现在觉得应该面向所有人……\"\n\n3. **在做出决策时提议捕获**\n\n | 洞察类型 | 捕获到哪里 |\n |---------|-----------|\n | 发现新需求 | \\`specs/<capability>/spec.md\\` |\n | 需求变更 | \\`specs/<capability>/spec.md\\` |\n | 做出设计决策 | \\`design.md\\` |\n | 范围变更 | \\`proposal.md\\` |\n | 发现新工作 | \\`tasks.md\\` |\n | 假设被推翻 | 相关 artifact |\n\n 示例:\n - \"这是一个设计决策。要记录到 design.md 吗?\"\n - \"这是新需求。要加到 specs 里吗?\"\n - \"这改变了范围。要更新 proposal 吗?\"\n\n4. **用户决定** — 提议后继续。不施压,不自动捕获。\n\n---\n\n## 不必做的事\n\n- 按脚本走\n- 每次问同样的问题\n- 产出特定 artifact\n- 得出结论\n- 如果有价值的岔路就不必守住话题\n- 简短(这是思考时间)\n\n---\n\n## 结束探索\n\n没有固定的结束方式。探索可能:\n\n- **流向下一阶段**:用 **AskUserQuestion** 提供 \\`/marchen:lite\\` 和 \\`/marchen:propose\\` 选项,附带推荐理由\n- **更新 artifact**:\"已将这些决策更新到 design.md\"\n- **只是提供清晰度**:用户得到了需要的,继续前进\n- **稍后继续**:\"随时可以继续\"\n\n当想法结晶时,你可以提供总结——但不是必须的。有时候思考过程本身就是价值。\n\n---\n\n## 护栏\n\n- **不实现** — 绝不写代码或实现功能。创建 Marchen artifact 可以,写应用代码不行\n- **不伪装理解** — 不清楚就深挖\n- **不催促** — 探索是思考时间,不是任务时间\n- **不强制结构** — 让模式自然浮现\n- **不自动捕获** — 提议保存洞察,不要直接做\n- **要可视化** — 一张好图胜过千言万语\n- **要探索代码库** — 让讨论扎根于现实\n- **要质疑假设** — 包括用户的和你自己的\n`\n\nexport const SKILL_LITE = `---\nname: marchen-lite\ndescription: 一键式轻量变更流程。创建 lite 变更、实现任务、询问归档,一气呵成。适合 bug 修复、小改动。\n---\n\n一键式轻量变更 — 使用 lite schema 创建变更,自动实现任务,完成后询问归档。\n适合 bug 修复、小改动、explore 之后的快速执行。\n\n---\n\n**输入**:用户的请求应包含变更名称(kebab-case)或变更描述。\n\n**流程**\n\n1. **确定变更名称**\n\n 如果提供了输入,直接使用或从描述中提取 kebab-case 名称(如\"修复登录 bug\" → \\`fix-login-bug\\`)。\n\n 如果没有输入,用 **AskUserQuestion** 工具询问:\n > \"你想做什么变更?描述一下你要构建或修复的内容。\"\n\n 从回答中提取 kebab-case 名称。\n\n **重要**:必须理解用户想做什么才能继续。\n\n2. **创建变更目录**\n\n \\`\\`\\`bash\n marchen new <name> --schema lite\n \\`\\`\\`\n\n 创建 \\`marchen/changes/<name>/\\` 目录,包含 \\`.metadata.yaml\\` 和 \\`tasks.md\\` 骨架。\n\n 如果同名变更已存在,用 **AskUserQuestion** 询问用户是继续已有变更还是换个名称。\n\n3. **获取 tasks 指令**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 确认变更创建成功,然后获取 tasks 的创建指令:\n\n \\`\\`\\`bash\n marchen instructions <name> tasks --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`template\\`:tasks.md 的骨架结构(含 \\`## 背景\\` 章节)\n - \\`instruction\\`:如何填充 tasks 的指导文本\n - \\`outputPath\\`:写入路径(\\`tasks.md\\`)\n - \\`context\\`:上下文信息(lite schema 下为空数组)\n\n4. **填充 tasks.md**\n\n 根据用户描述 + \\`instruction\\` 指引 + \\`template\\` 结构,填充 tasks.md:\n - \\`## 背景\\`:简要说明变更目的和方案\n - 任务列表:按组分类,checkbox 格式\n\n 写入 \\`marchen/changes/<name>/tasks.md\\`。\n\n 如果用户描述太模糊,用 **AskUserQuestion** 澄清关键信息。\n\n5. **开始实现**\n\n 获取实现指令:\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的信息数组\n - \\`instruction\\`:实现指引\n - \\`changeDir\\`:变更目录绝对路径\n\n 显示:\"变更: \\`<name>\\` | 任务: 0/N | 开始实现...\"\n\n 对每个未完成任务:\n - 显示 \"任务 N/M: <描述>\"\n - 实现代码改动\n - 在 tasks.md 中勾选:\\`- [ ]\\` → \\`- [x]\\`\n 文件路径:\\`<changeDir>/tasks.md\\`\n - 显示 \"✓ 完成\"\n - 继续下一个\n\n **暂停条件:**\n - 任务不清晰 → 询问用户\n - 发现设计问题 → 建议更新 artifact\n - 遇到错误或阻塞 → 报告并等待\n - 用户中断\n\n 暂停时显示:\"暂停于任务 N/M: <原因>\",流程结束。\n\n6. **全部完成 → 询问归档**\n\n 所有任务完成后,用 **AskUserQuestion** 询问:\n\n > \"全部任务已完成 (N/N),是否归档这个变更?\"\n > - 归档\n > - 暂不归档\n\n **如果用户选择归档:**\n\n 读取 \\`marchen/changes/<name>/tasks.md\\` 的背景段,生成一句话中文摘要(≤50字)。\n\n \\`\\`\\`bash\n marchen archive <name> --summary \"<摘要>\" --json\n \\`\\`\\`\n\n 显示:\n \\`\\`\\`\n 变更 \"<name>\" 已归档\n 归档到: <archivedTo>\n \\`\\`\\`\n\n **如果用户选择暂不归档:**\n\n 显示:\"好的,后续可以用 \\`/marchen:archive <name>\\` 归档。\"\n\n**护栏**\n\n- 必须使用 \\`--schema lite\\` 创建变更\n- tasks.md 的 \\`## 背景\\` 章节必须填写,不能留空\n- 任务粒度要小到一个会话内能完成\n- 如果上下文关键信息不清楚,询问用户;但小疑问优先做合理判断,保持节奏\n- 已存在同名变更时必须询问用户,不要覆盖\n- 实现前必须读 context 中的 artifact 内容\n- 每完成一个任务立即勾选 checkbox,不要攒着\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- \\`instruction\\` 是给你的指引,不要把它原样复制到代码注释或 tasks.md 中\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const SKILL_PROPOSE_PREVIEW = `---\nname: marchen-propose-preview\ndescription: 预览 propose 生成的变更摘要。从 proposal/design/specs/tasks 浓缩为终端卡片,便于人快速 review,决定下一步是 apply 还是改 propose。\ndisable-model-invocation: true\nargument-hint: <change-name>\n---\n\n预览一个变更的浓缩摘要 — 纯终端输出,不写文件。\n\n适用于 propose 完成后人快速 review,避免来回翻 4~7 个 artifact 文件。\n\n---\n\n**输入**:用户的请求应包含变更名称,或可从上下文推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"Preview 变更: \\`<name>\\`\"\n\n2. **获取 artifact 内容**\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`schemaName\\`:\\`\"full\"\\` / \\`\"lite\"\\`\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的内容数组(\\`id\\` / \\`status\\` / \\`content\\`)\n\n **如果 state 为 \\`blocked\\`**:打印 \"变更未填完,先用 /marchen:propose 补齐 artifact 再预览\",结束。不要强行摘要半成品。\n\n3. **生成卡片并直接打印**\n\n 根据 \\`schemaName\\` 选模板:\n - \\`full\\` → 四段:改了什么 / 关键决策 / 影响范围 / 风险\n - \\`lite\\` → 两段:改了什么 / 任务概览\n\n 严格按下面的\"摘要规则\"生成,输出为单个卡片,禁止加任何解释段落。\n\n4. **末尾追加一行下一步提示**\n\n \\`\\`\\`\n /marchen:apply <name> 开始实现\n /marchen:propose <name> 修改提案\n \\`\\`\\`\n\n---\n\n## 摘要规则(必须严格遵守)\n\n**通用约束:**\n\n- 卡片框宽 70 字符(含 \\`│\\` 边框),便于主流终端整齐显示\n- 每行内容(去掉边框后)≤ 60 字符;超出必须截断/合并/重写,不许折行\n- 禁止粘贴 artifact 原文片段,所有内容必须重新组织、压缩\n- 中文为主,技术名词保留英文(如 \\`SearchManager\\`、\\`SDK\\`)\n- 宁可少写,不要堆。摘不下就合并或截断,在框底加 \\`更多详情见 marchen/changes/<name>/\\`\n\n**full schema 段落上限:**\n\n| 段落 | 上限 | 来源 |\n|---------|------------------|---------------------------------|\n| 改了什么 | 6 条 bullet | proposal 的\"能力\"小节 |\n| 关键决策 | 5 条 bullet | design 的\"决策\"小节 |\n| 影响范围 | 8 节点 ASCII 图 | proposal 的\"影响范围\" + design |\n| 风险 | 3 条 bullet | design 的\"风险与权衡\" |\n\n**lite schema 段落上限:**\n\n| 段落 | 上限 | 来源 |\n|---------|------------------|-------------------------------|\n| 改了什么 | 6 条 bullet | tasks.md 任务组标题 + 推断 |\n| 任务概览 | 1 行/任务组 | tasks.md 一级标题 + 完成进度 |\n\n**影响范围图退化策略:**\n\n如果节点数超 8 个 / 关系不清晰 / 没有明显依赖结构 → 改用 bullet 列模块名,不要硬画歪斜的 ASCII。\n\n**任务进度条规则(lite):**\n\n进度条固定 10 格宽,按 \\`completed/total\\` 比例画。例如 \\`8/8\\` → \\`██████████\\`,\\`3/5\\` → \\`██████░░░░\\`。\n\n---\n\n## 输出样例\n\n**full schema:**\n\n\\`\\`\\`\n╭─ <name> ────────────────────────────── full · <N> 任务 ─╮\n│ │\n│ <一句话动机,从 proposal \"动机\" 段提炼,≤ 55 字> │\n│ │\n│ ━━ 改了什么 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ ✚ <capability-name> <一句话能力描述> │\n│ ... │\n│ │\n│ ━━ 关键决策 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ 1. <决策内容> │\n│ ... │\n│ │\n│ ━━ 影响范围 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ <ASCII 图 或 bullet 模块列表> │\n│ │\n│ ━━ 风险 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ • <风险> │\n│ │\n╰──────────────────────────────────────────────────────────╯\n\\`\\`\\`\n\n**lite schema:**\n\n\\`\\`\\`\n╭─ <name> ──────────────────────────── lite · <N> 任务 ─╮\n│ │\n│ <一句话动机,从 tasks.md \"背景\" 段提炼> │\n│ │\n│ ━━ 改了什么 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ • <推断的核心变更> │\n│ │\n│ ━━ 任务概览 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ 1. <任务组标题> ██████████ N/M ✓ │\n│ │\n╰───────────────────────────────────────────────────────╯\n\\`\\`\\`\n\n**护栏**\n\n- 不写文件,纯 stdout 输出\n- 不调用 AskUserQuestion 询问\"要打印什么\",遵守摘要规则即可\n- 不展开补全 artifact 信息——artifact 里没有的,摘要里就不该有\n- state 为 \\`blocked\\` 时拒绝生成,不强行摘要半成品\n- 不要在卡片外加解释段落,只打印卡片 + 下一步提示\n- \\`instruction\\` 字段(apply JSON 里有)是给 apply 用的,不是给 preview 的,忽略它\n`\n\nexport const SKILL_PROPOSE = `---\nname: marchen-propose\ndescription: 提出新变更,创建并填充所有 artifact。适用于用户想快速描述需求并生成完整的 proposal、specs、design、tasks。\n---\n\n提出新变更 — 创建变更目录并按依赖顺序生成所有 artifact。\n\n将创建以下 artifact:\n- proposal.md(动机和变更内容)\n- specs/(每个能力的需求规格)\n- design.md(技术方案)\n- tasks.md(实现任务清单)\n\n完成后可用 /marchen:apply 开始实现。\n\n---\n\n**输入**:用户的请求应包含变更名称(kebab-case)或变更描述。\n\n**流程**\n\n1. **确定变更名称**\n\n 如果提供了输入,直接使用或从描述中提取 kebab-case 名称(如\"添加用户认证\" → \\`add-user-auth\\`)。\n\n 如果没有输入,用 **AskUserQuestion** 工具询问:\n > \"你想做什么变更?描述一下你要构建或修复的内容。\"\n\n 从回答中提取 kebab-case 名称。\n\n **重要**:必须理解用户想做什么才能继续。\n\n2. **创建变更目录**\n\n \\`\\`\\`bash\n marchen new <name>\n \\`\\`\\`\n\n 创建 \\`marchen/changes/<name>/\\` 目录和 \\`.metadata.yaml\\`。\n\n 如果同名变更已存在,用 **AskUserQuestion** 询问用户是继续已有变更还是换个名称。\n\n3. **循环创建 artifact**\n\n 用 **TaskCreate** 工具创建任务列表追踪进度。\n\n 循环执行以下步骤:\n\n a. **查询当前状态**\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n 返回 JSON 包含:\n - \\`workflow.next\\`:下一个应该创建的 artifact ID,全部完成时为 \\`null\\`\n - \\`workflow.ready\\`:当前可以创建的 artifact 列表\n - \\`workflow.blocked\\`:被阻塞的 artifact 列表\n - \\`artifacts\\`:每个 artifact 的状态详情(\\`id\\`、\\`status\\`、\\`path\\`)\n\n 如果 \\`workflow.next\\` 为 \\`null\\` → 全部完成,跳到第 4 步。\n\n b. **获取创建指令**\n \\`\\`\\`bash\n marchen instructions <name> <workflow.next> --json\n \\`\\`\\`\n 返回 JSON 包含:\n - \\`template\\`:artifact 的 markdown 骨架结构,用它作为输出文件的框架\n - \\`instruction\\`:如何填充该 artifact 的指导文本\n - \\`outputPath\\`:写入路径(相对于变更目录)\n - \\`context\\`:上下文 artifact 的信息数组,每项包含 \\`id\\`、\\`status\\`、\\`content\\`(已填充的内容直接在这里,不需要额外读文件)\n - \\`unlocks\\`:完成此 artifact 后解锁的 artifact 列表\n\n c. **创建 artifact**\n\n 根据 artifact 类型处理:\n\n **普通 artifact(proposal / design / tasks):**\n - 读取 \\`context\\` 中 \\`status\\` 为 \\`filled\\` 的 \\`content\\` 作为上下文\n - 按 \\`instruction\\` 指引 + \\`template\\` 结构生成内容\n - 写入 \\`marchen/changes/<name>/<outputPath>\\`\n - 写入后验证文件存在\n\n **specs(目录型 artifact,outputPath 为 \\`specs/\\`):**\n - 读取 proposal 内容(在 \\`context\\` 中,\\`id\\` 为 \\`proposal\\` 的 \\`content\\`)\n - 从 proposal 的\"能力\"章节提取能力列表(kebab-case 名称)\n - 为每个能力:\n - 创建目录 \\`marchen/changes/<name>/specs/<capability>/\\`\n - 按 \\`template\\` 结构 + \\`instruction\\` 指引生成 spec 内容\n - 写入 \\`specs/<capability>/spec.md\\`\n - 写入后验证每个 spec 文件存在\n\n **如果 proposal 的上下文不够清晰**(用户描述太模糊):\n - 用 **AskUserQuestion** 澄清关键信息\n - 然后继续创建\n\n d. 显示进度:\"已创建 \\`<artifact-id>\\`\",标记任务完成,回到步骤 a。\n\n4. **显示最终状态**\n\n \\`\\`\\`bash\n marchen status <name>\n \\`\\`\\`\n\n**输出**\n\n完成后显示:\n- 变更名称和目录位置\n- 已创建的 artifact 列表及简要说明\n- 用纯文字(不调用 AskUserQuestion,不自动执行)提示下一步两个并列选项,由用户自行决定:\n\n \\`\\`\\`\n 下一步:\n /marchen:apply <name> 直接开始实现\n /marchen:propose-preview <name> 先看一眼浓缩摘要再决定\n \\`\\`\\`\n\n**护栏**\n\n- 按依赖顺序创建,不跳过 artifact\n- 每次循环创建一个 artifact(specs 算一个,但包含多个文件)\n- 写入后验证文件存在再继续下一个\n- 如果上下文关键信息不清楚,询问用户;但小疑问优先做合理判断,保持节奏\n- 已存在同名变更时必须询问用户,不要覆盖\n- \\`instruction\\` 是给你的指引,不要把它原样复制到 artifact 文件中\n- 使用 AskUserQuestion 时,选项不超过 4 个;需要更多选项时合并或分步询问\n`\n\nexport const SKILL_REVIEW = `---\nname: marchen-review\ndescription: 对照变更意图检查代码实现的完整性和一致性,支持基于 chrome-devtools MCP 的 UI 场景验证。\n---\n\n对照变更的 artifact 检查代码改动,必要时驱动浏览器验证 UI 行为,报告遗漏、偏差和阻塞。\n\n---\n\n**输入**:用户的请求应包含变更名称,或可从上下文推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"Review 变更: \\`<name>\\`\"\n\n2. **嗅探 diff 中的 UI 改动**\n\n 执行 \\`git diff --name-only HEAD\\`,判断改动是否涉及前端 UI(组件、样式、模板、静态资源等)。命中则记下命中文件,用于下一步的提示。\n\n3. **AskUserQuestion 选择 review 模式**\n\n 用 **AskUserQuestion** 让用户三选一:\n\n - **代码 review**(默认):对照 artifact 检查 git diff,不跑代码\n - **UI 验证**:用 chrome-devtools MCP 实际打开页面验证 specs 中的场景\n - **两者都做**:先代码、再 UI\n\n 若上一步命中 UI 文件,在问题描述里附一句:\"检测到 diff 涉及 UI 文件(<列出命中文件>),建议选 UI 验证或两者都做。\"\n 未命中时不附加提示,默认推荐\"代码 review\"。\n\n 保存用户选择为 \\`<mode>\\`。\n\n4. **获取意图与改动(公共前置)**\n\n - 执行 \\`marchen instructions <name> apply --json\\`,从返回 JSON 的 \\`context\\` 数组里读取 \\`status\\` 为 \"filled\" 的 artifact(proposal/specs/design/tasks)作为变更意图\n - 执行 \\`git diff HEAD\\`;为空则 \\`git diff HEAD~1\\`;仍为空则报告 \"未检测到代码改动\" 并结束\n\n diff 大到可能挤占 context 时,先 \\`git diff --stat HEAD\\` 看摘要,再按需读单个文件 diff。\n\n5. **代码 review**(mode 为 \"代码 review\" 或 \"两者都做\" 时执行)\n\n 逐条对照变更意图和代码改动,输出报告:\n\n **任务完成度** — tasks 中每个任务是否有对应改动:\n - ✅ 任务: <描述> — 已实现\n - ❌ 任务: <描述> — 未找到对应改动\n\n **一致性检查** — 实现是否符合 design 决策:\n - ✅ <决策> — 已遵守\n - ⚠️ <决策> — 实现有偏差:<说明>\n\n **需求覆盖** — specs 中需求是否被覆盖:\n - ✅ <需求> — 已覆盖\n - ❌ <需求> — 未覆盖\n\n **发现的问题(如有)**\n - <文件:行号> <问题描述>\n\n 全部通过:输出 \"✅ 代码 review 通过。\"\n\n 遇到无法判断的情况(tasks 描述与 diff 对不上但可能是改名了、spec 表述含糊等),直接用 **AskUserQuestion** 就地问用户,不要积攒到最后。\n\n6. **UI 验证**(mode 为 \"UI 验证\" 或 \"两者都做\" 时执行)\n\n ### a. 检测 chrome-devtools MCP 可用性\n\n 尝试调用 chrome-devtools MCP 的只读探测工具(例如列出已打开的页面)。工具不存在或调用失败 → 在报告里写:\n\n > ⏭ chrome-devtools MCP 不可用,跳过 UI 验证。\n > 安装方法:\\`npx chrome-devtools-mcp@latest\\`,并参考所用 AI 工具的 MCP 配置文档将其注册。\n\n 然后跳过本节剩余步骤。\n\n ### b. 提取待验证场景\n\n 优先从变更的 specs 文件中提取所有 \\`#### 场景:\\` 块(按 spec 文件分组)。\n 若 specs 不存在或不含场景 → 退化到从 tasks/proposal 推断 UI 相关验证点,并在报告中标注 \"基于 tasks 推断,覆盖度可能不完整\"。\n 两者都没有可提取场景 → 报告 \"未找到可验证的 UI 场景\" 并跳过本节。\n\n ### c. 推断 dev server URL\n\n 从项目文件(脚本、框架配置、环境变量、README 等)推断 dev server 地址和启动命令,不要硬猜端口。\n\n 推出候选 URL 后用 chrome-devtools MCP 的导航工具打开,再用快照工具确认页面像被测应用(合理 title、含框架 root 节点或变更描述涉及的标志性内容)。看着不像被测应用 → 当作未能推断。\n\n 推不出 URL → 全部场景 ⏭ \"URL unknown\",在报告里说明推断依据,提示用户确认地址后重新运行 review,跳过本节执行步骤。\n\n ### d. 推断到 URL 但 dev server 未启动 → 询问是否帮忙起\n\n 推出来 URL 但导航失败(连接拒绝)→ 主会话 **AskUserQuestion**,附上推断依据和启动命令:\n\n - **帮我起 dev server 然后继续**\n - **我手动起,起完告诉你**\n - **跳过 UI 验证**\n\n 选\"帮我起\":\n 1. 用 Bash 的 \\`run_in_background\\` 执行推断到的启动命令(如 \\`pnpm dev\\`、\\`npm run dev\\`)\n 2. 轮询端口直到可访问;出现明显异常(进程退出 / 长时间无任何输出 / 报错日志)才判定启动失败,让用户手动检查\n 3. 启动成功后继续执行后续步骤,并在 review 结束时显式告知用户后台进程 PID 和端口,提示 \"用完请自行 kill <PID>\"——MUST NOT 自动 kill\n\n 选\"我手动起\":等用户告知启动完成后继续;用户可以新会话执行 \\`/marchen:review\\` 重跑。\n\n 选\"跳过 UI\":所有场景 ⏭ \"dev server 未启动\",跳过后续步骤。\n\n ### e. 乐观执行场景\n\n 对每个待验证场景:\n\n 1. 用 chrome-devtools MCP 的导航工具打开对应路径\n 2. 用快照工具观察页面状态\n 3. 把场景的 GIVEN/WHEN/THEN 翻译成具体交互(点击、填写、读取文本)并执行\n 4. 比对页面状态与场景预期\n\n 遇到阻塞(登录墙、权限不足、缺数据、表单需要真实输入等):**主会话可以就地 AskUserQuestion**——例如\"需要登录账号才能继续,提供测试账号?/ 跳过这个场景 / 暂停 review\",而不必积攒到最后。\n\n 仍无法继续的场景 → 记录 ⏭ + 阻塞原因,跳到下一个。\n 场景翻译不出具体操作(例如纯后端行为描述)→ ⏭ \"不适合 UI 验证\"。\n\n ### f. UI 报告格式\n\n \\`\\`\\`\n ## UI 验证(chrome-devtools MCP)\n\n 测试目标:<URL 或 \"未确定\">\n\n ✅ <场景标题> — 通过\n 说明:<可选简述>\n ❌ <场景标题> — 失败\n 证据:<console error 摘要 / 页面文案 / 关键截图路径>\n ⏭ <场景标题> — 跳过:<阻塞原因>\n \\`\\`\\`\n\n7. **展示报告并按结果分支**\n\n - **全部通过且无 ⏭**:提示 \"可以用 \\`marchen archive <name>\\` 归档。\"\n - **有 ❌ 失败**:提示用户修复后可以再次 review。\n - **有 ⏭ 跳过**:用 **AskUserQuestion** 让用户选择:\n - **补信息后继续**:收集所需信息(账号、数据等),就地再跑被跳过的场景\n - **跳过这些场景,继续归档**:保留报告,提示 \\`marchen archive <name>\\`\n - **暂停去修复**:什么都不做\n\n 如本次 review 启动了后台 dev server,再次提醒用户进程信息(PID/端口)。\n\n**护栏**\n\n- 不要修改任何代码,只报告(review 不是 apply)\n- UI 验证阻塞即停,不要硬闯(不登录、不猜数据、不绕权限);要继续就向用户索取信息\n- 起 dev server 必须先获得用户授权(AskUserQuestion);起完不自动 kill,告知 PID 让用户自行管理\n- 用户提供的凭据/账号等敏感数据:不写入任何 artifact,不在报告里复述明文,截图前对密码/token/邮箱等敏感字段脱敏或回避\n- 大 diff 先看 \\`git diff --stat\\`,按需读单个文件,避免灌爆 context\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\n/** Skill 模板定义 */\nexport interface SkillTemplate {\n readonly dirName: string\n readonly content: string\n}\n\n/** 所有 skill 模板 */\nexport const SKILL_TEMPLATES: Record<string, SkillTemplate> = {\n apply: { dirName: 'marchen-apply', content: SKILL_APPLY },\n archive: { dirName: 'marchen-archive', content: SKILL_ARCHIVE },\n explore: { dirName: 'marchen-explore', content: SKILL_EXPLORE },\n lite: { dirName: 'marchen-lite', content: SKILL_LITE },\n 'propose-preview': { dirName: 'marchen-propose-preview', content: SKILL_PROPOSE_PREVIEW },\n propose: { dirName: 'marchen-propose', content: SKILL_PROPOSE },\n review: { dirName: 'marchen-review', content: SKILL_REVIEW },\n}\n","import { promises as nodeFs } from 'node:fs'\nimport { FileSystemError } from '@marchen/shared'\n\n// ============================================================\n// 目录操作\n// ============================================================\n\n/**\n * 递归创建目录,如果目录已存在则静默跳过\n */\nexport async function ensureDir(path: string): Promise<void> {\n try {\n await nodeFs.mkdir(path, { recursive: true })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw error\n }\n }\n}\n\n/**\n * 检查路径是否存在(文件或目录)\n */\nexport async function exists(path: string): Promise<boolean> {\n try {\n await nodeFs.access(path)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * 移动目录到新位置\n *\n * @param src - 源目录路径\n * @param dest - 目标目录路径\n * @throws {MarchenError} 源目录不存在时抛出\n */\nexport async function moveDir(src: string, dest: string): Promise<void> {\n try {\n await nodeFs.rename(src, dest)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new FileSystemError('目录不存在', src)\n }\n throw error\n }\n}\n\n/**\n * 列举目录下的直接子条目\n */\nexport async function listDir(path: string): Promise<string[]> {\n try {\n return await nodeFs.readdir(path)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new FileSystemError('目录不存在', path)\n }\n throw error\n }\n}\n","import { promises as nodeFs } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { FileSystemError } from '@marchen/shared'\nimport { ensureDir } from './directory.js'\n\n// ============================================================\n// 文件读写\n// ============================================================\n\n/**\n * 读取文件内容(UTF-8)\n */\nexport async function readFile(path: string): Promise<string> {\n try {\n return await nodeFs.readFile(path, 'utf-8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new FileSystemError('文件不存在', path)\n }\n throw error\n }\n}\n\n/**\n * 写入文件内容(UTF-8),自动创建父目录\n */\nexport async function writeFile(path: string, content: string): Promise<void> {\n await ensureDir(dirname(path))\n await nodeFs.writeFile(path, content, 'utf-8')\n}\n\n/**\n * 追加内容到文件末尾(UTF-8),自动创建父目录\n */\nexport async function appendFile(path: string, content: string): Promise<void> {\n await ensureDir(dirname(path))\n await nodeFs.appendFile(path, content, 'utf-8')\n}\n","import { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport {\n ARCHIVE_DIRECTORY_NAME,\n CHANGE_DIRECTORY_NAME,\n SPEC_DIRECTORY_NAME,\n} from '@marchen/shared'\n\n// ============================================================\n// 路径解析\n// ============================================================\n\n/**\n * 解析 workspace 根目录路径\n */\nexport function resolveWorkspaceRoot(fromPath = process.cwd()): string {\n return resolve(fromPath)\n}\n\n/**\n * 获取规范目录路径\n */\nexport function getSpecDirectory(root = process.cwd()): string {\n return resolve(root, SPEC_DIRECTORY_NAME)\n}\n\n/**\n * 获取变更目录路径\n */\nexport function getChangeDirectory(root = process.cwd()): string {\n return resolve(getSpecDirectory(root), CHANGE_DIRECTORY_NAME)\n}\n\n/**\n * 获取归档目录路径\n */\nexport function getArchiveDirectory(root = process.cwd()): string {\n return resolve(getSpecDirectory(root), ARCHIVE_DIRECTORY_NAME)\n}\n\n/**\n * 获取包根目录路径(基于 import.meta.url)\n */\nexport function getPackageRoot(importMetaUrl: string): string {\n return dirname(fileURLToPath(importMetaUrl))\n}\n","import { FileSystemError } from '@marchen/shared'\nimport yaml from 'js-yaml'\nimport { readFile, writeFile } from './file.js'\n\n// ============================================================\n// YAML 读写\n// ============================================================\n\n/**\n * 读取 YAML 文件并解析为对象\n */\nexport async function readYaml<T>(path: string): Promise<T> {\n const content = await readFile(path)\n try {\n return yaml.load(content) as T\n } catch (error) {\n throw new FileSystemError(\n 'YAML 解析失败',\n path,\n error instanceof Error ? error : undefined,\n )\n }\n}\n\n/**\n * 将对象序列化为 YAML 并写入文件(2 空格缩进)\n */\nexport async function writeYaml(path: string, data: unknown): Promise<void> {\n const content = yaml.dump(data, { indent: 2 })\n await writeFile(path, content)\n}\n","import type {\n ApplyProgress,\n ApplyState,\n ArchiveOptions,\n ArchiveResult,\n ArtifactContentStatus,\n ArtifactStatusDetail,\n ChangeMetadata,\n ContextInfo,\n InstructionsResult,\n SchemaDefinition,\n StatusResult,\n TaskItem,\n WorkflowStatus,\n} from '@marchen/shared'\nimport type { Workspace } from './workspace.js'\nimport { join } from 'node:path'\nimport {\n APPLY_INSTRUCTION,\n DEFAULT_SCHEMA_NAME,\n getSchema,\n} from '@marchen/config'\nimport {\n appendFile,\n ensureDir,\n exists,\n listDir,\n moveDir,\n readFile,\n readYaml,\n writeFile,\n writeYaml,\n} from '@marchen/fs'\nimport {\n METADATA_FILE_NAME,\n StateError,\n ValidationError,\n} from '@marchen/shared'\n\n/** kebab-case 校验正则 */\nconst KEBAB_CASE_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\n/**\n * 变更管理器\n *\n * 统一管理变更的创建、查询、列出等操作。\n * 通过构造函数接收 Workspace 实例获取路径上下文。\n */\nexport class ChangeManager {\n /**\n * @param workspace - 工作区实例,提供路径上下文\n */\n constructor(private readonly workspace: Workspace) {}\n\n /**\n * 校验变更名称是否为合法的 kebab-case 格式\n *\n * @param name - 变更名称\n * @returns 名称是否合法\n */\n static isValidName(name: string): boolean {\n return KEBAB_CASE_REGEX.test(name)\n }\n\n /**\n * 创建一个新的变更\n *\n * 在 marchen/changes/ 下创建变更目录,写入元数据和初始 artifact 文件。\n * 会依次校验:Marchen 是否已初始化、名称格式、是否重名。\n *\n * @param name - 变更名称(kebab-case)\n * @param schemaName - schema 名称,默认 full\n * @throws {MarchenError} 未初始化、名称格式错误或变更已存在时抛出\n */\n async create(name: string, schemaName?: string): Promise<void> {\n // 校验初始化状态\n await this.ensureInitialized()\n\n // 校验名称格式\n if (!ChangeManager.isValidName(name)) {\n throw new ValidationError(\n `变更名称 \"${name}\" 不合法,请使用 kebab-case 格式(如 add-dark-mode)`,\n )\n }\n\n // 校验是否重名\n const changeDir = join(this.workspace.changeDir, name)\n if (await exists(changeDir)) {\n throw new ValidationError(`变更 \"${name}\" 已存在`)\n }\n\n // 查找 schema 定义\n const resolvedSchemaName = schemaName ?? DEFAULT_SCHEMA_NAME\n const schema = getSchema(resolvedSchemaName)\n\n // 创建变更根目录\n await ensureDir(changeDir)\n\n // 根据 schema 的 artifacts 创建初始文件/目录\n for (const artifact of schema.artifacts) {\n if (artifact.generates.endsWith('/')) {\n // 目录类型(如 specs/)\n await ensureDir(join(changeDir, artifact.generates))\n }\n }\n\n // 写入元数据文件\n const metadata: ChangeMetadata = {\n name,\n schema: resolvedSchemaName,\n createdAt: new Date().toISOString(),\n status: 'open',\n }\n await writeYaml(join(changeDir, METADATA_FILE_NAME), metadata)\n }\n\n /**\n * 归档一个已完成的变更\n *\n * 将变更目录从 marchen/changes/ 移动到 marchen/archive/,\n * 目标目录名格式为 YYYY-MM-DD-<name>。同时更新元数据的 status 和 archivedAt,\n * 并追加一行条目到 changelog.md。\n *\n * @param name - 变更名称\n * @param options - 归档选项(可选 summary)\n * @returns 归档结果\n * @throws {ValidationError} 变更不存在或目标目录已存在时抛出\n * @throws {StateError} 未初始化时抛出\n */\n async archive(\n name: string,\n options?: ArchiveOptions,\n ): Promise<ArchiveResult> {\n await this.ensureInitialized()\n\n const changeDir = join(this.workspace.changeDir, name)\n if (!(await exists(changeDir))) {\n throw new ValidationError(`变更 \"${name}\" 不存在`)\n }\n\n // 读取元数据\n const metadataPath = join(changeDir, METADATA_FILE_NAME)\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n const now = new Date()\n const archivedAt = now.toISOString()\n const datePrefix = archivedAt.slice(0, 10)\n const archiveDir = join(this.workspace.archiveDir, `${datePrefix}-${name}`)\n\n // 检查目标目录是否已存在\n if (await exists(archiveDir)) {\n throw new ValidationError(`归档目标 \"${datePrefix}-${name}\" 已存在`)\n }\n\n // 更新元数据\n await writeYaml(metadataPath, {\n ...metadata,\n status: 'archived' as const,\n archivedAt,\n })\n\n // 移动到 archive 目录\n await moveDir(changeDir, archiveDir)\n\n // 写入 changelog\n await this.appendChangelog(name, datePrefix, options?.summary)\n\n // 更新搜索索引\n await this.updateSearchIndex()\n\n return {\n name,\n schema: metadata.schema,\n archivedTo: archiveDir,\n archivedAt,\n }\n }\n\n /**\n * 追加一行条目到 changelog.md\n *\n * changelog.md 不存在时自动创建(兼容未 re-init 的旧项目)。\n * summary 写入前会 trim 并替换换行为空格。\n *\n * @param name - 变更名称\n * @param datePrefix - 日期前缀(YYYY-MM-DD)\n * @param summary - 可选摘要文本\n */\n private async appendChangelog(\n name: string,\n datePrefix: string,\n summary?: string,\n ): Promise<void> {\n const changelogPath = this.workspace.changelogPath\n\n // changelog.md 不存在时先创建\n if (!(await exists(changelogPath))) {\n await writeFile(changelogPath, '# 变更日志\\n')\n }\n\n const link = `[${name}](./archive/${datePrefix}-${name}/)`\n const sanitizedSummary = summary?.trim().replace(/[\\r\\n]+/g, ' ')\n const entry = sanitizedSummary\n ? `- ${datePrefix}: ${link} — ${sanitizedSummary}`\n : `- ${datePrefix}: ${link}`\n\n await appendFile(changelogPath, `${entry}\\n`)\n }\n\n /** 更新搜索索引(失败时静默,超时 30 秒) */\n private async updateSearchIndex(): Promise<void> {\n try {\n await Promise.race([\n this.doUpdateSearchIndex(),\n new Promise<void>((resolve) => setTimeout(resolve, 30_000)),\n ])\n } catch {\n // 索引失败不影响归档\n }\n }\n\n private async doUpdateSearchIndex(): Promise<void> {\n const { SearchManager } = await import('./search-manager.js')\n const search = new SearchManager(this.workspace)\n if (await search.isAvailable()) {\n await search.indexChange()\n await search.close()\n }\n }\n\n /**\n * 列出所有 open 状态的变更\n *\n * 扫描 marchen/changes/ 目录,读取各变更的元数据,\n * 返回按创建时间降序排列的列表。跳过缺失元数据的目录。\n *\n * @returns 变更元数据数组,按 createdAt 降序排列\n * @throws {MarchenError} 未初始化时抛出\n */\n async list(): Promise<ChangeMetadata[]> {\n await this.ensureInitialized()\n\n const entries = await listDir(this.workspace.changeDir)\n\n const changes: ChangeMetadata[] = []\n\n // 读取每个变更的元数据\n for (const name of entries) {\n const metadataPath = join(\n this.workspace.changeDir,\n name,\n METADATA_FILE_NAME,\n )\n\n if (!(await exists(metadataPath))) {\n continue\n }\n\n try {\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n changes.push(metadata)\n } catch {\n continue\n }\n }\n\n // 按创建时间降序排列(最新的在前)\n changes.sort((a, b) => {\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()\n })\n\n return changes\n }\n\n /**\n * 查看变更的 artifact 状态和工作流建议\n *\n * 检测各 artifact 的内容状态(不只是文件存在性),\n * 计算工作流建议(ready/blocked/next),解析 tasks 进度。\n *\n * @param name - 变更名称\n * @returns 状态结果\n * @throws {MarchenError} 未初始化或变更不存在时抛出\n */\n async status(name: string): Promise<StatusResult> {\n await this.ensureInitialized()\n\n const changeDir = join(this.workspace.changeDir, name)\n if (!(await exists(changeDir))) {\n throw new ValidationError(`变更 \"${name}\" 不存在`)\n }\n\n // 读取元数据获取 schema\n const metadataPath = join(changeDir, METADATA_FILE_NAME)\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n const schema = getSchema(metadata.schema)\n\n // 逐个检测 artifact 的内容状态\n // specs 是目录类型,需要特殊处理;其余为单文件类型\n const artifacts: ArtifactStatusDetail[] = []\n for (const artifact of schema.artifacts) {\n const artifactPath = join(changeDir, artifact.generates)\n\n if (artifact.id === 'specs') {\n const result = await this.detectSpecsStatus(artifactPath)\n artifacts.push({ id: artifact.id, path: artifact.generates, ...result })\n } else {\n const status = await this.detectContentStatus(artifactPath)\n artifacts.push({ id: artifact.id, status, path: artifact.generates })\n }\n }\n\n // 根据各 artifact 的内容状态,计算工作流建议(哪些可以开始、哪些被阻塞)\n const statusMap = new Map(artifacts.map((a) => [a.id, a.status]))\n const workflow = this.computeWorkflow(schema, statusMap)\n\n // tasks 进度独立于 artifact 状态:只要 tasks.md 有实质内容就解析 checkbox\n // 这样 artifact status 只反映\"有没有内容\",进度信息放在 tasks 字段\n const tasksArtifact = artifacts.find((a) => a.id === 'tasks')\n let tasks: StatusResult['tasks'] = null\n if (tasksArtifact && tasksArtifact.status === 'filled') {\n const tasksPath = join(changeDir, 'tasks.md')\n const content = await readFile(tasksPath)\n const items = this.parseTaskItems(content)\n tasks = {\n total: items.length,\n completed: items.filter((item) => item.completed).length,\n items,\n }\n }\n\n return { name, schema: metadata.schema, artifacts, workflow, tasks }\n }\n\n /**\n * 获取指定 artifact 的创建指令\n *\n * 返回模板、指导文本、依赖 artifacts 的内容和完成后解锁的 artifacts。\n *\n * @param name - 变更名称\n * @param artifactId - artifact 标识符\n * @returns 指令结果\n * @throws {MarchenError} 未初始化、变更不存在或 artifact 不存在时抛出\n */\n async getInstructions(\n name: string,\n artifactId: string,\n ): Promise<InstructionsResult> {\n await this.ensureInitialized()\n\n const changeDir = join(this.workspace.changeDir, name)\n if (!(await exists(changeDir))) {\n throw new ValidationError(`变更 \"${name}\" 不存在`)\n }\n\n // 查找 artifact 定义,校验 artifactId 合法性\n const metadataPath = join(changeDir, METADATA_FILE_NAME)\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n const schema = getSchema(metadata.schema)\n\n const artifactDef = schema.artifacts.find((a) => a.id === artifactId)\n if (!artifactDef) {\n throw new ValidationError(\n `Artifact \"${artifactId}\" 不存在,可用的 artifact: ${schema.artifacts.map((a) => a.id).join(', ')}`,\n )\n }\n\n // 从 artifact 定义获取模板和指导文本\n const template = artifactDef.template ?? ''\n const instruction = artifactDef.instruction\n\n // 构建上下文信息:读取每个依赖 artifact 的状态和内容\n // specs 目录需要拼接所有 spec 文件内容;单文件直接读取\n const context: ContextInfo[] = []\n for (const depId of artifactDef.requires) {\n const depDef = schema.artifacts.find((a) => a.id === depId)\n if (!depDef) continue\n\n const depPath = join(changeDir, depDef.generates)\n\n if (depId === 'specs') {\n const specsResult = await this.detectSpecsStatus(depPath)\n const content =\n specsResult.status === 'filled'\n ? await this.readSpecsContent(depPath)\n : null\n context.push({\n id: depId,\n status: specsResult.status,\n path: depDef.generates,\n content,\n })\n } else {\n const status = await this.detectContentStatus(depPath)\n const content = status === 'filled' ? await readFile(depPath) : null\n context.push({ id: depId, status, path: depDef.generates, content })\n }\n }\n\n // 计算 unlocks(谁把当前 artifact 列为依赖)\n const unlocks = schema.artifacts\n .filter((a) => a.requires.includes(artifactId))\n .map((a) => a.id)\n\n return {\n changeName: name,\n artifactId,\n schemaName: metadata.schema,\n changeDir,\n outputPath: artifactDef.generates,\n template,\n instruction,\n context,\n unlocks,\n state: null,\n progress: null,\n }\n }\n\n /**\n * 检测单文件 artifact 的内容状态\n *\n * 判断逻辑:去掉 HTML 注释、空行、纯 markdown 标题行后,\n * 剩余内容超过 20 字符算 filled,否则算 empty(模板骨架)。\n *\n * 阈值 20 字符是粗粒度信号,Skill 会读文件内容自己做精确判断。\n *\n * @param filePath - 文件绝对路径\n * @returns 内容状态:missing / empty / filled\n */\n private async detectContentStatus(\n filePath: string,\n ): Promise<ArtifactContentStatus> {\n if (!(await exists(filePath))) {\n return 'missing'\n }\n\n const content = await readFile(filePath)\n const stripped = content\n .replace(/<!--[\\s\\S]*?-->/g, '') // 去掉 HTML 注释\n .split('\\n')\n .filter((line) => line.trim() !== '') // 去掉空行\n .filter((line) => !/^#{1,6}\\s/.test(line.trim())) // 去掉纯标题行\n .join('\\n')\n .trim()\n\n return stripped.length > 20 ? 'filled' : 'empty'\n }\n\n /**\n * 检测 specs 目录的内容状态和 capabilities 列表\n *\n * specs 是目录类型 artifact,状态判断规则:\n * - 目录不存在 → missing\n * - 目录存在但无子目录 → no-content\n * - 有子目录但存在未填充的 spec.md → no-content(返回 capabilities 列表)\n * - 所有子目录的 spec.md 都有实质内容 → filled\n *\n * @param specsPath - specs 目录绝对路径\n * @returns 状态和 capabilities(子目录名列表)\n */\n private async detectSpecsStatus(specsPath: string): Promise<{\n status: ArtifactContentStatus\n capabilities: string[]\n }> {\n if (!(await exists(specsPath))) {\n return { status: 'missing', capabilities: [] }\n }\n\n const entries = await listDir(specsPath)\n if (entries.length === 0) {\n return { status: 'no-content', capabilities: [] }\n }\n\n // 检查所有 spec 文件的内容状态\n // 只有全部 spec.md 都是 filled 才算 filled,否则算 no-content\n let allFilled = true\n for (const entry of entries) {\n const specFile = join(specsPath, entry, 'spec.md')\n if (!(await exists(specFile))) {\n allFilled = false\n continue\n }\n const status = await this.detectContentStatus(specFile)\n if (status !== 'filled') {\n allFilled = false\n }\n }\n\n return {\n status: allFilled ? 'filled' : 'no-content',\n capabilities: entries,\n }\n }\n\n /**\n * 读取 specs 目录下所有 spec 文件内容并拼接\n *\n * 每个文件以 `--- specs/<name>/spec.md ---` 作为分隔标记,\n * 方便 LLM 识别各 spec 的边界。\n *\n * @param specsPath - specs 目录绝对路径\n * @returns 拼接后的完整内容\n */\n private async readSpecsContent(specsPath: string): Promise<string> {\n const entries = await listDir(specsPath)\n const parts: string[] = []\n\n for (const entry of entries) {\n const specFile = join(specsPath, entry, 'spec.md')\n if (await exists(specFile)) {\n const content = await readFile(specFile)\n parts.push(`--- specs/${entry}/spec.md ---\\n${content}`)\n }\n }\n\n return parts.join('\\n\\n')\n }\n\n /**\n * 根据 schema 的依赖关系计算工作流状态\n *\n * 通用实现:遍历 schema 的所有 artifacts,\n * 根据 requires 字段判断每个 artifact 是 ready 还是 blocked。\n *\n * @param schema - schema 定义\n * @param statuses - artifact id → 内容状态的映射\n * @returns 工作流状态(next / ready / blocked)\n */\n private computeWorkflow(\n schema: SchemaDefinition,\n statuses: Map<string, ArtifactContentStatus>,\n ): WorkflowStatus {\n const isFilled = (id: string): boolean => statuses.get(id) === 'filled'\n\n const ready: string[] = []\n const blocked: string[] = []\n\n for (const artifact of schema.artifacts) {\n if (isFilled(artifact.id)) continue\n\n const depsReady = artifact.requires.every(isFilled)\n if (depsReady) {\n ready.push(artifact.id)\n } else {\n blocked.push(artifact.id)\n }\n }\n\n return { next: ready[0] ?? null, ready, blocked }\n }\n\n /**\n * 解析 tasks.md 内容中的 checkbox 条目\n */\n private parseTaskItems(content: string): TaskItem[] {\n return [...content.matchAll(/^- \\[([ x])\\] (.+)$/gm)].map((match) => ({\n description: match[2]!,\n completed: match[1] === 'x',\n }))\n }\n\n /**\n * 从 tasks.md 内容计算任务进度\n */\n private parseTaskProgress(content: string): ApplyProgress {\n const items = this.parseTaskItems(content)\n const completed = items.filter((i) => i.completed).length\n return {\n total: items.length,\n completed,\n remaining: items.length - completed,\n }\n }\n\n /**\n * 获取 apply 阶段的指令\n *\n * 收集所有 artifact 的内容作为上下文,计算任务进度和状态。\n *\n * @param name - 变更名称\n * @returns 指令结果(state/progress 有值,outputPath/template/unlocks 为 null)\n */\n async getApplyInstructions(name: string): Promise<InstructionsResult> {\n await this.ensureInitialized()\n\n const changeDir = join(this.workspace.changeDir, name)\n if (!(await exists(changeDir))) {\n throw new ValidationError(`变更 \"${name}\" 不存在`)\n }\n\n const metadataPath = join(changeDir, METADATA_FILE_NAME)\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n const schema = getSchema(metadata.schema)\n\n // 收集所有 artifact 的上下文\n const context: ContextInfo[] = []\n for (const artifact of schema.artifacts) {\n const artifactPath = join(changeDir, artifact.generates)\n\n if (artifact.id === 'specs') {\n const specsResult = await this.detectSpecsStatus(artifactPath)\n const content =\n specsResult.status === 'filled'\n ? await this.readSpecsContent(artifactPath)\n : null\n context.push({\n id: artifact.id,\n status: specsResult.status,\n path: artifact.generates,\n content,\n })\n } else {\n const status = await this.detectContentStatus(artifactPath)\n const content =\n status === 'filled' ? await readFile(artifactPath) : null\n context.push({\n id: artifact.id,\n status,\n path: artifact.generates,\n content,\n })\n }\n }\n\n // 计算 state 和 progress\n const tasksContext = context.find((c) => c.id === 'tasks')\n let state: ApplyState\n let progress: ApplyProgress\n\n if (\n !tasksContext ||\n !tasksContext.content ||\n tasksContext.status !== 'filled'\n ) {\n state = 'blocked'\n progress = { total: 0, completed: 0, remaining: 0 }\n } else {\n progress = this.parseTaskProgress(tasksContext.content)\n state = progress.remaining === 0 ? 'all_done' : 'ready'\n }\n\n const instruction = APPLY_INSTRUCTION\n\n return {\n changeName: name,\n artifactId: 'apply',\n schemaName: metadata.schema,\n changeDir,\n outputPath: null,\n template: null,\n instruction,\n context,\n unlocks: null,\n state,\n progress,\n }\n }\n\n /**\n * 确保 Marchen 已初始化,否则抛出错误\n */\n private async ensureInitialized(): Promise<void> {\n const initialized = await this.workspace.isInitialized()\n if (!initialized) {\n throw new StateError('Marchen 尚未初始化', '运行 marchen init 初始化')\n }\n }\n}\n","import type {\n AgentProvider,\n PackageBoundary,\n UpdateResult,\n WorkspaceConfig,\n} from '@marchen/shared'\nimport { join } from 'node:path'\nimport {\n AGENT_PROVIDERS,\n COMMAND_TEMPLATES,\n DEFAULT_PROVIDER_IDS,\n SKILL_TEMPLATES,\n} from '@marchen/config'\nimport {\n ensureDir,\n exists,\n getArchiveDirectory,\n getChangeDirectory,\n getSpecDirectory,\n readYaml,\n resolveWorkspaceRoot,\n writeFile,\n writeYaml,\n} from '@marchen/fs'\nimport { CONFIG_FILE_NAME, DEFAULT_HF_ENDPOINT } from '@marchen/shared'\n\n/** 初始化选项 */\nexport interface InitializeOptions {\n /** 要安装的 AI 工具 provider ID 列表 */\n readonly providers?: readonly string[]\n /** CLI 版本号,传入时写入 config.yaml */\n readonly version?: string\n /** 是否启用搜索,写入 config.yaml 的 search.enabled */\n readonly searchEnabled?: boolean\n}\n\n/** 更新选项 */\nexport interface UpdateOptions {\n /** 当前 CLI 版本号 */\n readonly version: string\n}\n\n/**\n * 工作区上下文\n *\n * 统一管理 workspace 的路径计算、初始化检查和初始化操作。\n * 所有路径在构造时一次性计算完成,整个生命周期共享。\n */\nexport class Workspace {\n /** 工作区根目录 */\n readonly root: string\n\n /** 规范目录路径(marchen/) */\n readonly specDir: string\n\n /** 变更目录路径(marchen/changes/) */\n readonly changeDir: string\n\n /** 归档目录路径(marchen/archive/) */\n readonly archiveDir: string\n\n /** 变更日志路径(marchen/changelog.md) */\n readonly changelogPath: string\n\n /** 搜索索引数据库路径(marchen/.search/index.sqlite) */\n readonly searchDbPath: string\n\n /** 包边界信息 */\n readonly packageBoundaries: readonly PackageBoundary[] = [\n { name: '@marchen/shared', dependsOn: [] },\n { name: '@marchen/config', dependsOn: ['@marchen/shared'] },\n { name: '@marchen/fs', dependsOn: ['@marchen/shared'] },\n {\n name: '@marchen/core',\n dependsOn: ['@marchen/config', '@marchen/fs', '@marchen/shared'],\n },\n ]\n\n /**\n * @param root - 工作区根目录路径,默认为 process.cwd()\n */\n constructor(root?: string) {\n this.root = resolveWorkspaceRoot(root)\n this.specDir = getSpecDirectory(this.root)\n this.changeDir = getChangeDirectory(this.root)\n this.archiveDir = getArchiveDirectory(this.root)\n this.changelogPath = join(this.specDir, 'changelog.md')\n this.searchDbPath = join(this.specDir, '.search', 'index.sqlite')\n }\n\n /**\n * 检查 Marchen 是否已初始化\n *\n * @returns 如果 marchen 目录存在则返回 true\n */\n async isInitialized(): Promise<boolean> {\n return await exists(this.specDir)\n }\n\n /**\n * 初始化 Marchen 目录结构\n *\n * 创建标准目录结构和默认配置文件:\n * - marchen/config.yaml\n * - marchen/changes/\n * - marchen/archive/\n *\n * @param options - 初始化选项\n */\n async initialize(options?: InitializeOptions): Promise<void> {\n const providerIds = options?.providers ?? DEFAULT_PROVIDER_IDS\n const providers = providerIds\n .map((id) => AGENT_PROVIDERS[id])\n .filter((p): p is AgentProvider => p != null)\n\n // 创建目录结构\n await ensureDir(this.specDir)\n await ensureDir(this.changeDir)\n await ensureDir(this.archiveDir)\n await ensureDir(join(this.specDir, '.search'))\n\n // 写入默认配置\n const configPath = join(this.specDir, CONFIG_FILE_NAME)\n const configData: Record<string, unknown> = {\n schema: 'full',\n providers: [...providerIds],\n }\n if (options?.version) {\n configData.version = options.version\n }\n configData.search = { enabled: options?.searchEnabled ?? false }\n configData.models = { endpoint: DEFAULT_HF_ENDPOINT }\n await writeYaml(configPath, configData)\n\n // 创建 .gitkeep 占位文件\n await writeFile(join(this.changeDir, '.gitkeep'), '')\n await writeFile(join(this.archiveDir, '.gitkeep'), '')\n\n // 创建 changelog.md(已存在时跳过)\n if (!(await exists(this.changelogPath))) {\n await writeFile(this.changelogPath, '# 变更日志\\n')\n }\n\n // 为每个 provider 生成 skill 和 command 文件\n for (const provider of providers) {\n await this.generateSkills(provider.skillDir)\n if (provider.commandDir) {\n await this.generateCommands(provider.commandDir)\n }\n }\n }\n\n /**\n * 读取 config.yaml 配置\n *\n * @returns config.yaml 的完整内容\n */\n async readConfig(): Promise<WorkspaceConfig> {\n const configPath = join(this.specDir, CONFIG_FILE_NAME)\n return await readYaml<WorkspaceConfig>(configPath)\n }\n\n /**\n * 更新 skill/command 文件到最新版本\n *\n * 读取 config.yaml 中的 providers 列表,覆盖写入最新模板文件,\n * 并更新 version 字段。版本一致时跳过。\n *\n * @param options - 更新选项,包含当前 CLI 版本号\n * @returns 更新结果\n */\n async update(options: UpdateOptions): Promise<UpdateResult> {\n const configPath = join(this.specDir, CONFIG_FILE_NAME)\n const config = await readYaml<Record<string, unknown>>(configPath)\n\n // 版本一致时跳过,避免重复写入\n const previousVersion = (config.version as string) ?? null\n if (previousVersion === options.version) {\n return {\n previousVersion,\n currentVersion: options.version,\n providersUpdated: [],\n skillCount: 0,\n commandCount: 0,\n }\n }\n\n // 老项目 config.yaml 可能没有 providers 字段,fallback 到默认值\n const rawProviders = config.providers as string[] | undefined\n const providerIds =\n rawProviders && rawProviders.length > 0\n ? rawProviders\n : [...DEFAULT_PROVIDER_IDS]\n const providers = providerIds\n .map((id) => AGENT_PROVIDERS[id])\n .filter((p): p is AgentProvider => p != null)\n\n const providerNames: string[] = []\n let skillCount = 0\n let commandCount = 0\n const skillTemplateCount = Object.keys(SKILL_TEMPLATES).length\n const commandTemplateCount = Object.keys(COMMAND_TEMPLATES).length\n\n // 覆盖写入最新模板文件\n for (const provider of providers) {\n await this.generateSkills(provider.skillDir)\n skillCount += skillTemplateCount\n if (provider.commandDir) {\n await this.generateCommands(provider.commandDir)\n commandCount += commandTemplateCount\n }\n providerNames.push(provider.name)\n }\n\n // 补全缺失的配置字段并持久化\n config.version = options.version\n if (!config.providers) {\n config.providers = [...providerIds]\n }\n // 迁移旧 search.mode → search.enabled\n const rawSearch = config.search as Record<string, unknown> | undefined\n if (rawSearch && 'mode' in rawSearch) {\n config.search = { enabled: rawSearch.mode === 'semantic' }\n }\n if (!config.search) {\n config.search = { enabled: false }\n }\n // 补全 models.endpoint 默认值(老 config 不覆盖已有值)\n const rawModels = config.models as Record<string, unknown> | undefined\n if (!rawModels) {\n config.models = { endpoint: DEFAULT_HF_ENDPOINT }\n } else if (rawModels.endpoint == null) {\n config.models = { ...rawModels, endpoint: DEFAULT_HF_ENDPOINT }\n }\n await writeYaml(configPath, config)\n\n return {\n previousVersion,\n currentVersion: options.version,\n providersUpdated: providerNames,\n skillCount,\n commandCount,\n }\n }\n\n /**\n *\n * @param skillDir - skill 根目录的相对路径\n */\n private async generateSkills(skillDir: string): Promise<void> {\n for (const template of Object.values(SKILL_TEMPLATES)) {\n const dir = join(this.root, skillDir, template.dirName)\n await ensureDir(dir)\n await writeFile(join(dir, 'SKILL.md'), template.content)\n }\n }\n\n /**\n * 生成 command 文件到指定目录\n *\n * @param commandDir - command 目录的相对路径\n */\n private async generateCommands(commandDir: string): Promise<void> {\n const dir = join(this.root, commandDir)\n await ensureDir(dir)\n for (const template of Object.values(COMMAND_TEMPLATES)) {\n await writeFile(join(dir, template.fileName), template.content)\n }\n }\n}\n","import { ChangeManager, Workspace } from '@marchen/core'\n\n/**\n * 创建 CLI 上下文,包含 workspace 和 change manager 实例\n */\nexport function createContext(): {\n workspace: Workspace\n changes: ChangeManager\n} {\n const workspace = new Workspace()\n const changes = new ChangeManager(workspace)\n return { workspace, changes }\n}\n","import * as p from '@clack/prompts'\nimport {\n FileSystemError,\n MarchenError,\n StateError,\n ValidationError,\n} from '@marchen/shared'\n\n/**\n * 统一错误处理,按错误类型差异化展示\n */\nexport function handleError(error: unknown): never {\n if (error instanceof StateError) {\n p.log.error(error.message)\n if (error.hint) p.log.info(error.hint)\n } else if (error instanceof ValidationError) {\n p.log.warn(error.message)\n } else if (error instanceof FileSystemError) {\n p.log.error(`${error.message}: ${error.path}`)\n } else if (error instanceof MarchenError) {\n p.log.error(error.message)\n } else {\n p.log.error(`未知错误: ${error}`)\n }\n process.exit(1)\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 注册 archive 命令\n *\n * 归档一个已完成的变更,将其移动到 archive 目录\n *\n * @param program - Commander 程序实例\n */\nexport function registerArchiveCommand(program: Command): void {\n program\n .command('archive')\n .description('归档一个已完成的变更')\n .argument('<name>', '变更名称')\n .option('--json', '输出 JSON 格式')\n .option('--summary <text>', '变更摘要(写入 changelog)')\n .action(\n async (name: string, options: { json?: boolean; summary?: string }) => {\n try {\n const { changes } = createContext()\n const result = await changes.archive(name, {\n summary: options.summary,\n })\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2))\n return\n }\n\n p.intro('Marchen CLI')\n p.log.success(`变更 \"${name}\" 归档成功`)\n p.outro(`运行 marchen list 查看剩余变更`)\n } catch (error) {\n handleError(error)\n }\n },\n )\n}\n","import type { ModelDownloadProgress } from '@marchen/core'\n\n/** 模型类型显示名称 */\nexport const MODEL_LABELS: Record<string, string> = {\n embed: 'Embedding',\n generate: 'Query Expansion',\n rerank: 'Reranker',\n}\n\n/** 格式化模型下载进度 */\nexport function formatModelProgress(progress: ModelDownloadProgress): string {\n const name = MODEL_LABELS[progress.model] ?? progress.model\n\n switch (progress.stage) {\n case 'checking':\n return `检查模型 ${name}...`\n case 'downloading': {\n if (progress.downloadedBytes && progress.totalBytes) {\n const pct = Math.round(\n (progress.downloadedBytes / progress.totalBytes) * 100,\n )\n const mb = (progress.downloadedBytes / 1024 / 1024).toFixed(1)\n const total = (progress.totalBytes / 1024 / 1024).toFixed(0)\n return `下载模型 ${name}... ${mb}/${total} MB (${pct}%)`\n }\n return `下载模型 ${name}...`\n }\n case 'verifying':\n return `校验模型 ${name}...`\n case 'ready':\n return `模型 ${name} 就绪`\n default:\n return `准备模型 ${name}...`\n }\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { AGENT_PROVIDERS } from '@marchen/config'\nimport { SearchManager } from '@marchen/core'\nimport { createContext } from '../utils/context.js'\nimport { formatModelProgress } from '../utils/model-progress.js'\n\n/**\n * 注册 init 命令\n *\n * 初始化 Marchen 目录结构,创建 marchen/ 目录及默认配置\n *\n * @param program - Commander 程序实例\n */\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('初始化 Marchen 目录结构')\n .option('--force', '强制覆盖已存在的目录')\n .action(async (options) => {\n p.intro('Marchen CLI')\n\n const { workspace } = createContext()\n\n // 检查是否已初始化\n const alreadyExists = await workspace.isInitialized()\n if (alreadyExists && !options.force) {\n const confirm = await p.confirm({\n message: 'Marchen 目录已存在,是否覆盖?',\n })\n\n if (p.isCancel(confirm) || !confirm) {\n p.cancel('操作已取消')\n process.exit(0)\n }\n }\n\n // 选择 AI 工具\n const providerOptions = Object.values(AGENT_PROVIDERS).map(\n (provider) => ({\n value: provider.id,\n label: provider.name,\n }),\n )\n\n const selectedProviders = await p.multiselect({\n message: '选择要安装的 AI 工具集成',\n options: providerOptions,\n initialValues: ['claude-code'],\n required: true,\n })\n\n if (p.isCancel(selectedProviders)) {\n p.cancel('操作已取消')\n process.exit(0)\n }\n\n const version = program.version() as string\n\n // 是否启用搜索\n const searchEnabled = await p.confirm({\n message: '是否启用搜索?(需下载约 2GB 模型)',\n initialValue: false,\n })\n\n if (p.isCancel(searchEnabled)) {\n p.cancel('操作已取消')\n process.exit(0)\n }\n\n // 执行初始化\n await workspace.initialize({\n providers: selectedProviders,\n version,\n searchEnabled,\n })\n\n const names = (selectedProviders as string[])\n .map((id) => AGENT_PROVIDERS[id]?.name ?? id)\n .join(', ')\n p.log.success(`已为 ${names} 生成 skills 文件`)\n\n // 启用搜索时下载模型\n if (searchEnabled) {\n const searchManager = new SearchManager(workspace)\n const spinner = p.spinner()\n spinner.start('下载搜索模型...')\n await searchManager.ensureModels({\n onProgress: (prog) => {\n spinner.message(formatModelProgress(prog))\n },\n })\n spinner.stop('Hybrid Search 已启用')\n }\n\n p.outro('Marchen 初始化成功!')\n })\n}\n","import type { Command } from 'commander'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 注册 instructions 命令\n *\n * 返回指定 artifact 的创建指令,包含模板、指导文本、依赖 artifact 的内容\n * 和完成后解锁的 artifact 列表。\n *\n * 默认输出 JSON 格式(--json 标志可选但行为一致),主要给 Skill 消费。\n * 人类直接使用的场景较少。\n *\n * @param program - Commander 程序实例\n */\nexport function registerInstructionsCommand(program: Command): void {\n program\n .command('instructions')\n .description('获取 artifact 的创建指令')\n .argument('<name>', '变更名称')\n .argument(\n '<artifact-id>',\n 'artifact 标识符 (proposal/specs/design/tasks/apply)',\n )\n .option('--json', '输出 JSON 格式(默认行为)')\n .action(async (name: string, artifactId: string) => {\n try {\n const { changes } = createContext()\n const result =\n artifactId === 'apply'\n ? await changes.getApplyInstructions(name)\n : await changes.getInstructions(name, artifactId)\n console.log(JSON.stringify(result, null, 2))\n } catch (error) {\n handleError(error)\n }\n })\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 将 ISO 时间字符串转换为相对时间描述\n *\n * @param isoDate - ISO 8601 格式的日期字符串\n * @returns 相对时间描述(如「刚刚」「3 小时前」「5 天前」)\n */\nexport function timeAgo(isoDate: string): string {\n const seconds = Math.floor((Date.now() - new Date(isoDate).getTime()) / 1000)\n\n if (seconds < 60) return '刚刚'\n if (seconds < 3600) return `${Math.floor(seconds / 60)} 分钟前`\n if (seconds < 86400) return `${Math.floor(seconds / 3600)} 小时前`\n return `${Math.floor(seconds / 86400)} 天前`\n}\n\n/**\n * 注册 list 命令\n *\n * 列出所有 open 状态的变更,展示名称、schema 和创建时间\n *\n * @param program - Commander 程序实例\n */\nexport function registerListCommand(program: Command): void {\n program\n .command('list')\n .description('列出所有 open 状态的变更')\n .option('--json', '输出 JSON 格式')\n .action(async (options: { json?: boolean }) => {\n try {\n const { changes: changeManager } = createContext()\n const changes = await changeManager.list()\n\n if (options.json) {\n console.log(JSON.stringify(changes, null, 2))\n return\n }\n\n p.intro('Marchen CLI')\n\n if (changes.length === 0) {\n p.log.info('暂无 open 状态的变更')\n p.outro('运行 marchen new <name> 创建一个变更')\n return\n }\n\n // 计算各列最大宽度,用于对齐\n const nameWidth = Math.max(4, ...changes.map((c) => c.name.length))\n const schemaWidth = Math.max(6, ...changes.map((c) => c.schema.length))\n\n // 构建表格内容\n const header = `${'名称'.padEnd(nameWidth - 2)} ${'Schema'.padEnd(schemaWidth)} 创建时间`\n const separator = `${'─'.repeat(nameWidth)} ${'─'.repeat(schemaWidth)} ${'─'.repeat(8)}`\n\n const rows = changes.map((change) => {\n return `${change.name.padEnd(nameWidth)} ${change.schema.padEnd(schemaWidth)} ${timeAgo(change.createdAt)}`\n })\n\n // 合并为一个字符串一次输出,避免 clack 分隔\n const table = [header, separator, ...rows].join('\\n')\n\n p.log.info(`共 ${changes.length} 个 open 变更:\\n\\n${table}`)\n\n p.outro('运行 marchen status <name> 查看变更详情')\n } catch (error) {\n handleError(error)\n }\n })\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { SCHEMAS } from '@marchen/config'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 注册 new 命令\n *\n * 创建一个新的变更,包含目录结构和元数据\n *\n * @param program - Commander 程序实例\n */\nexport function registerNewCommand(program: Command): void {\n program\n .command('new')\n .description('创建一个新的变更')\n .argument('<name>', '变更名称(kebab-case,如 add-dark-mode)')\n .option('--schema <name>', '工作流 schema', 'full')\n .action(async (name: string, options: { schema: string }) => {\n p.intro('Marchen CLI')\n\n // 校验 schema 名称\n if (!SCHEMAS[options.schema]) {\n const available = Object.keys(SCHEMAS).join(', ')\n p.log.error(\n `Schema \"${options.schema}\" 不存在,可用的 schema: ${available}`,\n )\n process.exit(1)\n }\n\n const spinner = p.spinner()\n spinner.start(`正在创建变更 \"${name}\"...`)\n\n try {\n const { changes } = createContext()\n await changes.create(name, options.schema)\n spinner.stop(`变更 \"${name}\" 创建成功(schema: ${options.schema})`)\n } catch (error) {\n spinner.stop('创建失败')\n handleError(error)\n }\n\n p.outro(`运行 marchen list 查看所有变更`)\n })\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { SearchManager, Workspace } from '@marchen/core'\nimport pc from 'picocolors'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 注册 search 命令\n *\n * 搜索归档变更历史(Hybrid Search)\n *\n * @param program - Commander 程序实例\n */\nexport function registerSearchCommand(program: Command): void {\n program\n .command('search <query>')\n .description('搜索归档变更历史')\n .option('-n, --limit <number>', '结果数量', '5')\n .option('--min-score <number>', '最低分数阈值', '0.3')\n .option('--json', '输出 JSON 格式')\n .option('--rebuild', '重建索引后搜索')\n .action(\n async (\n query: string,\n options: {\n limit?: string\n minScore?: string\n json?: boolean\n rebuild?: boolean\n },\n ) => {\n const workspace = new Workspace()\n const search = new SearchManager(workspace)\n const spinner = options.json ? null : p.spinner()\n\n try {\n if (!(await search.isAvailable())) {\n p.log.error('搜索功能不可用(qmd 加载失败)')\n process.exit(1)\n }\n\n // 检查搜索是否启用\n let searchEnabled = false\n try {\n const config = await workspace.readConfig()\n searchEnabled = config.search?.enabled ?? false\n } catch {\n // config.yaml 不存在时视为未启用\n }\n\n if (!searchEnabled) {\n p.log.error(\n '搜索未启用。请在 config.yaml 中设置 search.enabled: true 后运行 marchen update',\n )\n process.exit(1)\n }\n\n spinner?.start('准备搜索引擎...')\n await search.prepare()\n spinner?.stop('搜索引擎就绪')\n\n if (options.rebuild) {\n spinner?.start('正在重建索引...')\n await search.index()\n spinner?.stop('索引重建完成')\n }\n\n spinner?.start('搜索中...')\n\n const results = await search.search(query, {\n limit: Number(options.limit),\n minScore: Number(options.minScore),\n })\n\n spinner?.stop('搜索完成')\n\n if (options.json) {\n console.log(JSON.stringify(results, null, 2))\n await search.close()\n return\n }\n\n if (results.length === 0) {\n p.log.info('未找到匹配结果')\n await search.close()\n return\n }\n\n for (const r of results) {\n const score = Math.round(r.score * 100)\n const scoreColor =\n score >= 70\n ? pc.green(`${score}%`)\n : score >= 40\n ? pc.yellow(`${score}%`)\n : pc.dim(`${score}%`)\n p.log.info(`${pc.bold(r.path)} ${scoreColor}`)\n if (r.snippet) {\n p.log.message(pc.dim(r.snippet))\n }\n }\n\n await search.close()\n } catch (error) {\n spinner?.stop('搜索失败')\n handleError(error)\n }\n },\n )\n}\n","import type { ArtifactContentStatus } from '@marchen/shared'\nimport type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport pc from 'picocolors'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/** 各内容状态对应的 emoji 图标 */\nconst STATUS_ICONS: Record<ArtifactContentStatus, string> = {\n filled: '✅',\n empty: '⬜',\n missing: '⬜',\n 'no-content': '⬜',\n}\n\n/** 为状态文字上色 */\nfunction colorizeStatus(status: string): string {\n switch (status) {\n case 'filled':\n return pc.green(status)\n case 'empty':\n return pc.yellow(status)\n case 'blocked':\n return pc.red(status)\n default:\n return pc.dim(status)\n }\n}\n\n/** 为进度数字上色,附带进度条:全部完成绿色、部分完成黄色、零完成灰色 */\nfunction colorizeProgress(completed: number, total: number): string {\n const barWidth = 10\n const filled = total > 0 ? Math.round((completed / total) * barWidth) : 0\n const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled)\n const text = `[${bar}] ${completed}/${total} 完成`\n if (completed === total) return pc.green(text)\n if (completed > 0) return pc.yellow(text)\n return pc.dim(text)\n}\n\n/**\n * 注册 status 命令\n *\n * 展示指定变更的 artifact 内容状态、工作流建议和 task 进度。\n * 支持两种输出模式:\n * - 默认:人类友好的终端 UI(emoji + 格式化文本)\n * - --json:结构化 JSON(给 Skill 消费)\n *\n * @param program - Commander 程序实例\n */\nexport function registerStatusCommand(program: Command): void {\n program\n .command('status')\n .description('查看变更的 artifact 状态和工作流建议')\n .argument('<name>', '变更名称')\n .option('--json', '输出 JSON 格式')\n .action(async (name: string, options: { json?: boolean }) => {\n try {\n const { changes } = createContext()\n const result = await changes.status(name)\n\n // JSON 模式:直接输出结构化数据,供 Skill 消费\n if (options.json) {\n console.log(JSON.stringify(result, null, 2))\n return\n }\n\n // ── 以下为人类友好的终端 UI 输出 ──\n\n p.intro('Marchen CLI')\n\n // 展示变更基本信息(名称加粗 + schema 灰色,一行展示)\n p.log.info(`${pc.bold(result.name)} · ${pc.dim(result.schema)}`)\n\n // 构建 artifact 状态列表,每行格式:<图标> <名称> <状态> [附加信息]\n // 示例输出:\n // ✅ proposal filled\n // 🔒 specs no-content (等待 proposal)\n // ⬜ design empty\n // 🔒 tasks empty (等待 specs, design)\n const artifactLines = result.artifacts.map((a) => {\n // 被阻塞的 artifact 用 🔒 图标,否则根据内容状态选择图标\n const isBlocked = result.workflow.blocked.includes(a.id)\n const icon = isBlocked ? '🔒' : STATUS_ICONS[a.status]\n const statusText = isBlocked ? 'blocked' : a.status\n let line = `${icon} ${a.id.padEnd(12)} ${colorizeStatus(statusText)}`\n\n // specs 类型额外展示 capability 数量\n if (a.capabilities) {\n line += ` (${a.capabilities.length} capabilities)`\n }\n\n // 被阻塞时,追加提示:具体在等待哪些依赖完成\n if (isBlocked) {\n const deps = getDependencies(a.id)\n const missingDeps = deps.filter(\n (d) =>\n result.artifacts.find((art) => art.id === d)?.status !==\n 'filled',\n )\n if (missingDeps.length > 0) {\n line += ` (等待 ${missingDeps.join(', ')})`\n }\n }\n return line\n })\n p.log.info(`Artifacts\\n${artifactLines.join('\\n')}`)\n\n // 展示 Artifacts + Tasks 总进度汇总\n const filledCount = result.artifacts.filter(\n (a) => a.status === 'filled',\n ).length\n const totalArtifacts = result.artifacts.length\n let progressLine = `Artifacts: ${colorizeProgress(filledCount, totalArtifacts)}`\n if (result.tasks) {\n progressLine += `\\nTasks: ${colorizeProgress(result.tasks.completed, result.tasks.total)}`\n }\n p.log.info(progressLine)\n\n // 展示下一步建议,引导用户继续工作流\n if (result.workflow.next) {\n p.outro(`下一步: ${pc.cyan(result.workflow.next)}`)\n } else {\n p.outro('所有 artifact 已就绪')\n }\n } catch (error) {\n handleError(error)\n }\n })\n}\n\n/**\n * 获取 artifact 的依赖列表\n *\n * 硬编码 full schema 的依赖关系,用于人类友好输出中\n * 展示被阻塞 artifact 等待的依赖名称。\n *\n * @param id - artifact 标识符\n * @returns 依赖的 artifact id 列表\n */\nfunction getDependencies(id: string): string[] {\n const deps: Record<string, string[]> = {\n proposal: [],\n specs: ['proposal'],\n design: ['proposal'],\n tasks: ['specs', 'design'],\n }\n return deps[id] ?? []\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { SearchManager } from '@marchen/core'\nimport { StateError } from '@marchen/shared'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\nimport { formatModelProgress } from '../utils/model-progress.js'\n\n/**\n * 注册 update 命令\n *\n * 更新 skill/command 文件到最新版本,并按 config.yaml 同步搜索模型状态\n *\n * @param program - Commander 程序实例\n */\nexport function registerUpdateCommand(program: Command): void {\n program\n .command('update')\n .description('更新 skill/command 文件到最新版本')\n .action(async () => {\n p.intro('Marchen CLI')\n\n try {\n const { workspace } = createContext()\n const version = program.version() as string\n\n if (!(await workspace.isInitialized())) {\n throw new StateError('Marchen 尚未初始化', '请先运行 marchen init')\n }\n\n const result = await workspace.update({ version })\n\n if (result.providersUpdated.length === 0) {\n p.log.info(`已是最新版本 (${result.currentVersion})`)\n } else {\n const prev = result.previousVersion ?? 'unknown'\n p.log.info(`版本: ${prev} → ${result.currentVersion}`)\n\n for (const name of result.providersUpdated) {\n p.log.success(`已更新 ${name} skills`)\n }\n\n p.log.success('config.yaml 版本已更新')\n }\n\n // 按 search.enabled 同步模型状态\n const config = await workspace.readConfig()\n const searchEnabled = config.search?.enabled ?? false\n\n if (searchEnabled) {\n const searchManager = new SearchManager(workspace)\n let downloaded = false\n const spinner = p.spinner()\n spinner.start('检查搜索模型...')\n await searchManager.ensureModels({\n onProgress: (prog) => {\n if (prog.stage === 'downloading') downloaded = true\n spinner.message(formatModelProgress(prog))\n },\n })\n spinner.stop(\n downloaded ? 'Hybrid Search 已启用' : 'Hybrid Search 已就绪',\n )\n }\n\n p.outro('更新完成!')\n } catch (error) {\n handleError(error)\n }\n })\n}\n","import { createRequire } from 'node:module'\nimport { Command } from 'commander'\nimport { registerArchiveCommand } from './commands/archive.js'\nimport { registerInitCommand } from './commands/init.js'\nimport { registerInstructionsCommand } from './commands/instructions.js'\nimport { registerListCommand } from './commands/list.js'\nimport { registerNewCommand } from './commands/new.js'\nimport { registerSearchCommand } from './commands/search.js'\nimport { registerStatusCommand } from './commands/status.js'\nimport { registerUpdateCommand } from './commands/update.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json') as { version: string }\n\n/**\n * 构建 CLI 程序实例\n *\n * 注册所有命令并返回 Commander 程序实例\n */\nexport function buildCliProgram(): Command {\n const program = new Command()\n\n program\n .name('marchen')\n .description('Workflow harness for AI coding agents')\n .version(version, '-v, --version')\n\n registerArchiveCommand(program)\n registerInitCommand(program)\n registerInstructionsCommand(program)\n registerListCommand(program)\n registerNewCommand(program)\n registerSearchCommand(program)\n registerStatusCommand(program)\n registerUpdateCommand(program)\n\n return program\n}\n","import { buildCliProgram } from './program.js'\n\nexport { buildCliProgram } from './program.js'\n\nconst program = buildCliProgram()\nprogram.parse(process.argv)\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["nodeFs","nodeFs"],"sources":["../../../packages/shared/src/errors.ts","../../../packages/config/src/generated/command-templates.ts","../../../packages/config/src/providers.ts","../../../packages/config/src/templates.ts","../../../packages/config/src/schema.ts","../../../packages/config/src/generated/skill-templates.ts","../../../packages/fs/src/directory.ts","../../../packages/fs/src/file.ts","../../../packages/fs/src/paths.ts","../../../packages/fs/src/yaml.ts","../../../packages/core/src/change-manager.ts","../../../packages/core/src/workspace.ts","../src/utils/context.ts","../src/utils/error.ts","../src/commands/archive.ts","../src/utils/model-progress.ts","../src/commands/init.ts","../src/commands/instructions.ts","../src/commands/list.ts","../src/commands/new.ts","../src/commands/search.ts","../src/commands/status.ts","../src/commands/update.ts","../src/program.ts","../src/index.ts"],"sourcesContent":["/**\n * Marchen 统一错误基类\n *\n * 所有业务错误的根类,CLI 层可用 instanceof MarchenError 兜底捕获\n */\nexport class MarchenError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'MarchenError'\n }\n}\n\n/**\n * 用户输入校验错误\n *\n * 名称不合法、变更已存在/不存在等用户操作问题\n */\nexport class ValidationError extends MarchenError {\n constructor(message: string) {\n super(message)\n this.name = 'ValidationError'\n }\n}\n\n/**\n * 状态前置条件错误\n *\n * 工作区未初始化等系统状态不满足的场景,可附带操作建议\n */\nexport class StateError extends MarchenError {\n constructor(\n message: string,\n public readonly hint?: string,\n ) {\n super(message)\n this.name = 'StateError'\n }\n}\n\n/**\n * 文件系统操作错误\n *\n * 文件/目录不存在、YAML 解析失败等 IO 异常\n */\nexport class FileSystemError extends MarchenError {\n constructor(\n message: string,\n public readonly path: string,\n public readonly cause?: Error,\n ) {\n super(message)\n this.name = 'FileSystemError'\n }\n}\n","// 此文件由 scripts/generate-templates.ts 自动生成,请勿手动修改\n\nexport const COMMAND_APPLY = `---\nname: \"Marchen: Apply\"\ndescription: 按变更的 tasks.md 逐个实现任务\ncategory: Workflow\ntags: [workflow, implementation]\n---\n\n按变更的 tasks.md 逐个实现任务,完成后勾选 checkbox。\n\n---\n\n**输入**:\\`/marchen:apply\\` 后面跟变更名称,或省略自动推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"使用变更: \\`<name>\\`\"\n\n2. **获取实现指令**\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的信息数组,每项包含 \\`id\\`、\\`status\\`、\\`path\\`、\\`content\\`\n - \\`instruction\\`:实现指引\n - \\`changeDir\\`:变更目录绝对路径\n\n 根据 \\`state\\` 处理:\n - \\`\"blocked\"\\` → 提示先完成 artifacts(\\`/marchen:propose\\`)\n - \\`\"all_done\"\\` → 提示归档(\\`/marchen:archive\\`)\n - \\`\"ready\"\\` → 继续\n\n3. **显示进度**\n\n 从返回的 JSON 读取并显示:\n \"变更: \\`<name>\\` | 进度: N/M | 下一个: <第一个未完成任务的描述>\"\n\n4. **逐个实现任务**\n\n 从 \\`context\\` 中读取所有 artifact 内容作为上下文。\n\n 对每个未完成任务:\n - 显示 \"任务 N/M: <描述>\"\n - 实现代码改动\n - 在 tasks.md 中勾选:\\`- [ ]\\` → \\`- [x]\\`\n 文件路径:\\`<changeDir>/tasks.md\\`\n - 显示 \"✓ 完成\"\n - 继续下一个\n\n **暂停条件:**\n - 任务不清晰 → 询问用户\n - 发现设计问题 → 建议用 \\`/marchen:update\\` 修订计划\n - 遇到错误或阻塞 → 报告并等待\n - 用户中断\n\n5. **显示结果**\n\n 全部完成时:\n \"全部完成 (N/N),可以用 \\`/marchen:review\\` 检查实现,或直接 \\`/marchen:archive\\` 归档。\"\n\n 暂停时:\n \"暂停于任务 N/M: <原因>\"\n\n**护栏**\n\n- 实现前必须读 context 中的 artifact 内容\n- 每完成一个任务立即勾选 checkbox,不要攒着\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- 如果实现过程中遇到不确定的设计决策,可以用 \\`marchen search \"<关键词>\" --json\\` 搜索历史变更中的相关方案\n- 使用 AskUserQuestion 时,选项不超过 4 个\n- \\`instruction\\` 是给你的指引,不要原样复制到代码注释中\n`\n\nexport const COMMAND_ARCHIVE = `---\nname: \"Marchen: Archive\"\ndescription: 归档已完成的变更\ncategory: Workflow\ntags: [workflow, archive]\n---\n\n归档已完成的变更,检查完成度后执行归档。\n\n---\n\n**输入**:用户的请求可包含变更名称(如 \\`/marchen:archive add-auth\\`),也可不带。\n\n**流程**\n\n1. **确定变更名称**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n **重要**:不要猜测或自动选择,必须让用户确认。\n\n2. **检查完成度**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 解析 JSON,检查:\n - \\`artifacts\\`:每个 artifact 的 \\`status\\` 是否为 \\`filled\\`\n - \\`tasks.completed\\` vs \\`tasks.total\\`(\\`tasks\\` 为 null 时视为无任务,跳过检查)\n\n **如果全部完成:** 直接进入下一步。\n\n **如果有未完成的 artifact 或 task:**\n - 显示警告,列出未完成项\n - 用 **AskUserQuestion** 确认是否继续\n - 用户确认后继续,不阻塞\n\n3. **生成摘要**\n\n 读取 \\`marchen/changes/<name>/proposal.md\\`,从中生成一句话中文摘要(≤50字),概括这次变更做了什么。摘要应包含关键语义词,便于后续 AI 检索。\n\n4. **执行归档**\n\n \\`\\`\\`bash\n marchen archive <name> --summary \"<生成的摘要>\" --json\n \\`\\`\\`\n\n 解析返回的 JSON 获取归档结果。\n\n5. **显示结果**\n\n \\`\\`\\`\n 变更 \"<name>\" 已归档\n Schema: <schema>\n 归档到: <archivedTo>\n \\`\\`\\`\n\n 如有警告(未完成项),一并显示。\n\n**护栏**\n\n- 未提供名称时必须用 AskUserQuestion 让用户选择\n- 用 \\`status --json\\` 检查完成度,不要自己读文件判断\n- 警告不阻塞归档,只提醒 + 确认\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const COMMAND_EXPLORE = `---\nname: \"Marchen: Explore\"\ndescription: 进入探索模式 — 思考想法、调查问题、厘清需求\ncategory: Workflow\ntags: [workflow, explore, thinking]\n---\n\n进入探索模式。深入思考,自由可视化,跟随对话走向任何方向。\n\n**重要:探索模式只用于思考,不用于实现。** 可以读文件、搜索代码、调查代码库,但绝不能写代码或实现功能。如果用户要求实现,提醒他们先退出探索模式并用 \\`/marchen:propose\\` 创建变更。可以创建 Marchen artifact(proposal、design、spec)——那是捕获思考,不是实现。\n\n**这是一种姿态,不是工作流。** 没有固定步骤、没有必须的顺序、没有强制输出。你是帮助用户探索的思考伙伴。\n\n**输入**:\\`/marchen:explore\\` 后面可以跟任何内容:\n- 模糊的想法:\"实时协作\"\n- 具体的问题:\"auth 系统越来越难维护了\"\n- 变更名称:\"add-dark-mode\"(在该变更上下文中探索)\n- 方案比较:\"postgres vs sqlite\"\n- 什么都不带也行\n\n---\n\n## 姿态\n\n- **好奇,不武断** — 提出自然涌现的问题,不按脚本走\n- **开放线索,不审问** — 展示多个有趣方向,让用户跟随感兴趣的。不要把他们引导到单一路径上\n- **视觉化** — 大量使用 ASCII 图表来辅助思考\n- **自适应** — 跟随有趣的线索,有新信息时及时转向\n- **耐心** — 不急于下结论,让问题的形状自然浮现\n- **接地气** — 在相关时探索实际代码库,不要空谈理论\n\n---\n\n## 你可以做什么\n\n根据用户带来的内容,你可能会:\n\n**探索问题空间**\n- 提出从用户所说内容中自然涌现的澄清问题\n- 挑战假设\n- 重新框定问题\n- 寻找类比\n\n**调查代码库**\n- 映射与讨论相关的现有架构\n- 找到集成点\n- 识别已有的模式\n- 发现隐藏的复杂性\n\n**比较方案**\n- 头脑风暴多种方案\n- 构建比较表\n- 勾勒权衡\n- 推荐路径(如果被问到)\n\n**可视化**\n\\`\\`\\`\n┌─────────────────────────────────────────┐\n│ Use ASCII diagrams liberally │\n├─────────────────────────────────────────┤\n│ │\n│ ┌────────┐ ┌────────┐ │\n│ │ State │────────▶│ State │ │\n│ │ A │ │ B │ │\n│ └────────┘ └────────┘ │\n│ │\n│ System diagrams, state machines, │\n│ data flows, architecture sketches, │\n│ dependency graphs, comparison tables │\n│ │\n└─────────────────────────────────────────┘\n\\`\\`\\`\n\n**发现风险和未知**\n- 识别可能出错的地方\n- 找到理解上的空白\n- 建议 spike 或调查\n\n---\n\n## Marchen 感知\n\n你了解 Marchen 系统。自然地使用它,不要强制。\n\n### 检查上下文\n\n开始时快速检查现有状态和历史:\n\n1. 当前变更:\n\\`\\`\\`bash\nmarchen list --json\n\\`\\`\\`\n\n2. 变更历史概览:\n\\`\\`\\`bash\ncat marchen/changelog.md\n\\`\\`\\`\n这是所有已归档变更的索引,每条包含日期、变更名和一句话摘要。先扫一遍找到与用户话题相关的条目。如果找到,直接读对应 archive 目录下的 proposal.md 或 design.md 了解详情。\n\n3. 语义搜索:\n\n这是 RAG 搜索,不是 grep——构造语义完整的短语,不要用单个泛词。\n\n\\`\\`\\`bash\nmarchen search \"<语义完整的查询短语>\" --json\n\\`\\`\\`\n\n**查询构造指引:**\n- 用描述性短语,不用单个词:\n \"初始化适配多个 agent 客户端\" → \\`\"multi-agent provider 初始化\"\\`\n \"之前怎么处理错误的\" → \\`\"错误处理 error handling 重构\"\\`\n \"暗色模式的设计决策\" → \\`\"dark mode 设计方案\"\\`\n- 中英文混合效果更好(归档内容里中英都有)\n- 如果结果不理想,换个角度重新构造查询\n\n如果有匹配结果(score >= 0.4),读取对应 archive 目录下的 design.md 或 proposal.md 了解详细决策。\n\n如果 \\`marchen search\\` 不可用(命令报错),回退到 changelog.md + 手动读 archive 目录。\n\n这告诉你:\n- 是否有进行中的变更\n- 它们的名称、schema 和状态\n- 项目过去做过哪些变更\n- 用户可能在做什么\n\n如果用户提到了特定变更名称,读取它的 artifact 作为上下文。\n\n### 没有变更时\n\n自由思考。当洞察结晶时,根据复杂度推荐下一步:\n\n**判断标准:**\n- \\`/marchen:lite\\` — bug 修复、小改动、单一任务组、不需要设计文档\n- \\`/marchen:propose\\` — 新功能、多步骤、需要 design/specs、涉及多模块\n\n**推荐方式:** 直接在回复中输出推荐,说明理由,让用户自行输入命令。示例:\n\n> 想法差不多成型了。这个改动比较简单(只涉及一个文件的小调整),建议用 \\`/marchen:lite\\` 直接走轻量流程。\n>\n> 如果你觉得需要更完整的设计文档,也可以用 \\`/marchen:propose\\`。\n\n根据讨论内容给出你的推荐和理由,但让用户自己决定输入哪个命令。\n\n### 有变更时\n\n如果用户提到了变更或你发现某个变更相关:\n\n1. **读取已有 artifact 作为上下文**\n - \\`marchen/changes/<name>/proposal.md\\`\n - \\`marchen/changes/<name>/design.md\\`\n - \\`marchen/changes/<name>/tasks.md\\`\n - 等\n\n2. **在对话中自然引用**\n - \"你的 design 提到用 Redis,但我们刚发现 SQLite 更合适……\"\n - \"proposal 把范围限定在付费用户,但我们现在觉得应该面向所有人……\"\n\n3. **在做出决策时提议捕获**\n\n | 洞察类型 | 捕获到哪里 |\n |---------|-----------|\n | 发现新需求 | \\`specs/<capability>/spec.md\\` |\n | 需求变更 | \\`specs/<capability>/spec.md\\` |\n | 做出设计决策 | \\`design.md\\` |\n | 范围变更 | \\`proposal.md\\` |\n | 发现新工作 | \\`tasks.md\\` |\n | 假设被推翻 | 相关 artifact |\n\n 示例:\n - \"这是一个设计决策。要记录到 design.md 吗?\"\n - \"这是新需求。要加到 specs 里吗?\"\n - \"这改变了范围。要更新 proposal 吗?\"\n\n4. **用户决定** — 提议后继续。不施压,不自动捕获。\n\n---\n\n## 不必做的事\n\n- 按脚本走\n- 每次问同样的问题\n- 产出特定 artifact\n- 得出结论\n- 如果有价值的岔路就不必守住话题\n- 简短(这是思考时间)\n\n---\n\n## 结束探索\n\n没有固定的结束方式。探索可能:\n\n- **流向下一阶段**:用 **AskUserQuestion** 提供 \\`/marchen:lite\\` 和 \\`/marchen:propose\\` 选项,附带推荐理由\n- **更新 artifact**:\"已将这些决策更新到 design.md\"\n- **只是提供清晰度**:用户得到了需要的,继续前进\n- **稍后继续**:\"随时可以继续\"\n\n当想法结晶时,你可以提供总结——但不是必须的。有时候思考过程本身就是价值。\n\n---\n\n## 护栏\n\n- **不实现** — 绝不写代码或实现功能。创建 Marchen artifact 可以,写应用代码不行\n- **不伪装理解** — 不清楚就深挖\n- **不催促** — 探索是思考时间,不是任务时间\n- **不强制结构** — 让模式自然浮现\n- **不自动捕获** — 提议保存洞察,不要直接做\n- **要可视化** — 一张好图胜过千言万语\n- **要探索代码库** — 让讨论扎根于现实\n- **要质疑假设** — 包括用户的和你自己的\n`\n\nexport const COMMAND_LITE = `---\nname: \"Marchen: Lite\"\ndescription: 一键式轻量变更流程。创建 lite 变更、实现任务、询问归档,一气呵成\ncategory: Workflow\ntags: [workflow, lite]\n---\n\n一键式轻量变更 — 使用 lite schema 创建变更,自动实现任务,完成后询问归档。\n适合 bug 修复、小改动、explore 之后的快速执行。\n\n---\n\n**输入**:\\`/marchen:lite\\` 后面跟变更名称(kebab-case)或变更描述。\n\n**流程**\n\n1. **确定变更名称**\n\n 如果提供了输入,直接使用或从描述中提取 kebab-case 名称(如\"修复登录 bug\" → \\`fix-login-bug\\`)。\n\n 如果没有输入,用 **AskUserQuestion** 工具询问:\n > \"你想做什么变更?描述一下你要构建或修复的内容。\"\n\n 从回答中提取 kebab-case 名称。\n\n **重要**:必须理解用户想做什么才能继续。\n\n2. **创建变更目录**\n\n \\`\\`\\`bash\n marchen new <name> --schema lite\n \\`\\`\\`\n\n 创建 \\`marchen/changes/<name>/\\` 目录,包含 \\`.metadata.yaml\\` 和 \\`tasks.md\\` 骨架。\n\n 如果同名变更已存在,用 **AskUserQuestion** 询问用户是继续已有变更还是换个名称。\n\n3. **获取 tasks 指令**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 确认变更创建成功,然后获取 tasks 的创建指令:\n\n \\`\\`\\`bash\n marchen instructions <name> tasks --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`template\\`:tasks.md 的骨架结构(含 \\`## 背景\\` 章节)\n - \\`instruction\\`:如何填充 tasks 的指导文本\n - \\`outputPath\\`:写入路径(\\`tasks.md\\`)\n - \\`context\\`:上下文信息(lite schema 下为空数组)\n\n4. **填充 tasks.md**\n\n 根据用户描述 + \\`instruction\\` 指引 + \\`template\\` 结构,填充 tasks.md:\n - \\`## 背景\\`:简要说明变更目的和方案\n - 任务列表:按组分类,checkbox 格式\n\n 写入 \\`marchen/changes/<name>/tasks.md\\`。\n\n 如果用户描述太模糊,用 **AskUserQuestion** 澄清关键信息。\n\n5. **开始实现**\n\n 获取实现指令:\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的信息数组\n - \\`instruction\\`:实现指引\n - \\`changeDir\\`:变更目录绝对路径\n\n 显示:\"变更: \\`<name>\\` | 任务: 0/N | 开始实现...\"\n\n 对每个未完成任务:\n - 显示 \"任务 N/M: <描述>\"\n - 实现代码改动\n - 在 tasks.md 中勾选:\\`- [ ]\\` → \\`- [x]\\`\n 文件路径:\\`<changeDir>/tasks.md\\`\n - 显示 \"✓ 完成\"\n - 继续下一个\n\n **暂停条件:**\n - 任务不清晰 → 询问用户\n - 发现设计问题 → 建议用 \\`/marchen:update\\` 修订计划\n - 遇到错误或阻塞 → 报告并等待\n - 用户中断\n\n 暂停时显示:\"暂停于任务 N/M: <原因>\",流程结束。\n\n6. **全部完成 → 询问归档**\n\n 所有任务完成后,用 **AskUserQuestion** 询问:\n\n > \"全部任务已完成 (N/N),是否归档这个变更?\"\n > - 归档\n > - 暂不归档\n\n **如果用户选择归档:**\n\n 读取 \\`marchen/changes/<name>/tasks.md\\` 的背景段,生成一句话中文摘要(≤50字)。\n\n \\`\\`\\`bash\n marchen archive <name> --summary \"<摘要>\" --json\n \\`\\`\\`\n\n 显示:\n \\`\\`\\`\n 变更 \"<name>\" 已归档\n 归档到: <archivedTo>\n \\`\\`\\`\n\n **如果用户选择暂不归档:**\n\n 显示:\"好的,后续可以用 \\`/marchen:archive <name>\\` 归档。\"\n\n**护栏**\n\n- 必须使用 \\`--schema lite\\` 创建变更\n- tasks.md 的 \\`## 背景\\` 章节必须填写,不能留空\n- 任务粒度要小到一个会话内能完成\n- 如果上下文关键信息不清楚,询问用户;但小疑问优先做合理判断,保持节奏\n- 已存在同名变更时必须询问用户,不要覆盖\n- 实现前必须读 context 中的 artifact 内容\n- 每完成一个任务立即勾选 checkbox,不要攒着\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- \\`instruction\\` 是给你的指引,不要把它原样复制到代码注释或 tasks.md 中\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const COMMAND_PROPOSE_PREVIEW = `---\nname: \"Marchen: Propose Preview\"\ndescription: 预览 propose 生成的变更摘要\ncategory: Workflow\ntags: [workflow, preview]\n---\n\n预览一个变更的浓缩摘要 — 纯终端输出,不写文件。\n\n适用于 propose 完成后人快速 review,避免来回翻 4~7 个 artifact 文件。\n\n---\n\n**输入**:\\`/marchen:propose-preview\\` 后面跟变更名称,或省略自动推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"Preview 变更: \\`<name>\\`\"\n\n2. **获取 artifact 内容**\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`schemaName\\`:\\`\"full\"\\` / \\`\"lite\"\\`\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的内容数组(\\`id\\` / \\`status\\` / \\`content\\`)\n\n **如果 state 为 \\`blocked\\`**:打印 \"变更未填完,先用 /marchen:propose 补齐 artifact 再预览\",结束。不要强行摘要半成品。\n\n3. **生成卡片并直接打印**\n\n 根据 \\`schemaName\\` 选模板:\n - \\`full\\` → 四段:改了什么 / 关键决策 / 影响范围 / 风险\n - \\`lite\\` → 两段:改了什么 / 任务概览\n\n 严格按下面的\"摘要规则\"生成,输出为单个卡片,禁止加任何解释段落。\n\n4. **末尾追加一行下一步提示**\n\n \\`\\`\\`\n /marchen:apply <name> 开始实现\n /marchen:update <name> 修订细节(方案、需求、任务的局部调整)\n /marchen:propose <name> 重新提案(变更意图或方向改变)\n \\`\\`\\`\n\n---\n\n## 摘要规则(必须严格遵守)\n\n**通用约束:**\n\n- 卡片框宽 70 字符(含 \\`│\\` 边框),便于主流终端整齐显示\n- 每行内容(去掉边框后)≤ 60 字符;超出必须截断/合并/重写,不许折行\n- 禁止粘贴 artifact 原文片段,所有内容必须重新组织、压缩\n- 中文为主,技术名词保留英文(如 \\`SearchManager\\`、\\`SDK\\`)\n- 宁可少写,不要堆。摘不下就合并或截断,在框底加 \\`更多详情见 marchen/changes/<name>/\\`\n\n**full schema 段落上限:**\n\n| 段落 | 上限 | 来源 |\n|---------|------------------|---------------------------------|\n| 改了什么 | 6 条 bullet | proposal 的\"能力\"小节 |\n| 关键决策 | 5 条 bullet | design 的\"决策\"小节 |\n| 影响范围 | 8 节点 ASCII 图 | proposal 的\"影响范围\" + design |\n| 风险 | 3 条 bullet | design 的\"风险与权衡\" |\n\n**lite schema 段落上限:**\n\n| 段落 | 上限 | 来源 |\n|---------|------------------|-------------------------------|\n| 改了什么 | 6 条 bullet | tasks.md 任务组标题 + 推断 |\n| 任务概览 | 1 行/任务组 | tasks.md 一级标题 + 完成进度 |\n\n**影响范围图退化策略:**\n\n如果节点数超 8 个 / 关系不清晰 / 没有明显依赖结构 → 改用 bullet 列模块名,不要硬画歪斜的 ASCII。\n\n**任务进度条规则(lite):**\n\n进度条固定 10 格宽,按 \\`completed/total\\` 比例画。例如 \\`8/8\\` → \\`██████████\\`,\\`3/5\\` → \\`██████░░░░\\`。\n\n---\n\n## 输出样例\n\n**full schema:**\n\n\\`\\`\\`\n╭─ <name> ────────────────────────────── full · <N> 任务 ─╮\n│ │\n│ <一句话动机,从 proposal \"动机\" 段提炼,≤ 55 字> │\n│ │\n│ ━━ 改了什么 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ ✚ <capability-name> <一句话能力描述> │\n│ ... │\n│ │\n│ ━━ 关键决策 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ 1. <决策内容> │\n│ ... │\n│ │\n│ ━━ 影响范围 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ <ASCII 图 或 bullet 模块列表> │\n│ │\n│ ━━ 风险 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ • <风险> │\n│ │\n╰──────────────────────────────────────────────────────────╯\n\\`\\`\\`\n\n**lite schema:**\n\n\\`\\`\\`\n╭─ <name> ──────────────────────────── lite · <N> 任务 ─╮\n│ │\n│ <一句话动机,从 tasks.md \"背景\" 段提炼> │\n│ │\n│ ━━ 改了什么 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ • <推断的核心变更> │\n│ │\n│ ━━ 任务概览 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ 1. <任务组标题> ██████████ N/M ✓ │\n│ │\n╰───────────────────────────────────────────────────────╯\n\\`\\`\\`\n\n**护栏**\n\n- 不写文件,纯 stdout 输出\n- 不调用 AskUserQuestion 询问\"要打印什么\",遵守摘要规则即可\n- 不展开补全 artifact 信息——artifact 里没有的,摘要里就不该有\n- state 为 \\`blocked\\` 时拒绝生成,不强行摘要半成品\n- 不要在卡片外加解释段落,只打印卡片 + 下一步提示\n- \\`instruction\\` 字段(apply JSON 里有)是给 apply 用的,不是给 preview 的,忽略它\n`\n\nexport const COMMAND_PROPOSE = `---\nname: \"Marchen: Propose\"\ndescription: 提出新变更,创建并填充所有 artifact\ncategory: Workflow\ntags: [workflow, artifacts]\n---\n\n提出新变更 — 创建变更目录并按依赖顺序生成所有 artifact。\n\n将创建以下 artifact:\n- proposal.md(动机和变更内容)\n- specs/(每个能力的需求规格)\n- design.md(技术方案)\n- tasks.md(实现任务清单)\n\n完成后可用 /marchen:apply 开始实现。\n\n---\n\n**输入**:\\`/marchen:propose\\` 后面跟变更名称(kebab-case)或变更描述。\n\n**流程**\n\n1. **确定变更名称**\n\n 如果提供了输入,直接使用或从描述中提取 kebab-case 名称(如\"添加用户认证\" → \\`add-user-auth\\`)。\n\n 如果没有输入,用 **AskUserQuestion** 工具询问:\n > \"你想做什么变更?描述一下你要构建或修复的内容。\"\n\n 从回答中提取 kebab-case 名称。\n\n **重要**:必须理解用户想做什么才能继续。\n\n2. **创建变更目录**\n\n \\`\\`\\`bash\n marchen new <name>\n \\`\\`\\`\n\n 创建 \\`marchen/changes/<name>/\\` 目录和 \\`.metadata.yaml\\`。\n\n 如果同名变更已存在,用 **AskUserQuestion** 询问用户是继续已有变更还是换个名称。\n\n3. **循环创建 artifact**\n\n 用 **TaskCreate** 工具创建任务列表追踪进度。\n\n 循环执行以下步骤:\n\n a. **查询当前状态**\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n 返回 JSON 包含:\n - \\`workflow.next\\`:下一个应该创建的 artifact ID,全部完成时为 \\`null\\`\n - \\`workflow.ready\\`:当前可以创建的 artifact 列表\n - \\`workflow.blocked\\`:被阻塞的 artifact 列表\n - \\`artifacts\\`:每个 artifact 的状态详情(\\`id\\`、\\`status\\`、\\`path\\`)\n\n 如果 \\`workflow.next\\` 为 \\`null\\` → 全部完成,跳到第 4 步。\n\n b. **获取创建指令**\n \\`\\`\\`bash\n marchen instructions <name> <workflow.next> --json\n \\`\\`\\`\n 返回 JSON 包含:\n - \\`template\\`:artifact 的 markdown 骨架结构,用它作为输出文件的框架\n - \\`instruction\\`:如何填充该 artifact 的指导文本\n - \\`outputPath\\`:写入路径(相对于变更目录)\n - \\`context\\`:上下文 artifact 的信息数组,每项包含 \\`id\\`、\\`status\\`、\\`content\\`(已填充的内容直接在这里,不需要额外读文件)\n - \\`unlocks\\`:完成此 artifact 后解锁的 artifact 列表\n\n c. **创建 artifact**\n\n 根据 artifact 类型处理:\n\n **普通 artifact(proposal / design / tasks):**\n - 读取 \\`context\\` 中 \\`status\\` 为 \\`filled\\` 的 \\`content\\` 作为上下文\n - 按 \\`instruction\\` 指引 + \\`template\\` 结构生成内容\n - 写入 \\`marchen/changes/<name>/<outputPath>\\`\n - 写入后验证文件存在\n\n **specs(目录型 artifact,outputPath 为 \\`specs/\\`):**\n - 读取 proposal 内容(在 \\`context\\` 中,\\`id\\` 为 \\`proposal\\` 的 \\`content\\`)\n - 从 proposal 的\"能力\"章节提取能力列表(kebab-case 名称)\n - 为每个能力:\n - 创建目录 \\`marchen/changes/<name>/specs/<capability>/\\`\n - 按 \\`template\\` 结构 + \\`instruction\\` 指引生成 spec 内容\n - 写入 \\`specs/<capability>/spec.md\\`\n - 写入后验证每个 spec 文件存在\n\n **如果 proposal 的上下文不够清晰**(用户描述太模糊):\n - 用 **AskUserQuestion** 澄清关键信息\n - 然后继续创建\n\n d. 显示进度:\"已创建 \\`<artifact-id>\\`\",标记任务完成,回到步骤 a。\n\n4. **显示最终状态**\n\n \\`\\`\\`bash\n marchen status <name>\n \\`\\`\\`\n\n**输出**\n\n完成后显示:\n- 变更名称和目录位置\n- 已创建的 artifact 列表及简要说明\n- 用纯文字(不调用 AskUserQuestion,不自动执行)提示下一步两个并列选项,由用户自行决定:\n\n \\`\\`\\`\n 下一步:\n /marchen:apply <name> 直接开始实现\n /marchen:propose-preview <name> 先看一眼浓缩摘要再决定\n \\`\\`\\`\n\n**护栏**\n\n- 按依赖顺序创建,不跳过 artifact\n- 每次循环创建一个 artifact(specs 算一个,但包含多个文件)\n- 写入后验证文件存在再继续下一个\n- 如果上下文关键信息不清楚,询问用户;但小疑问优先做合理判断,保持节奏\n- 已存在同名变更时必须询问用户,不要覆盖\n- \\`instruction\\` 是给你的指引,不要把它原样复制到 artifact 文件中\n- 使用 AskUserQuestion 时,选项不超过 4 个;需要更多选项时合并或分步询问\n`\n\nexport const COMMAND_REVIEW = `---\nname: \"Marchen: Review\"\ndescription: 对照变更意图检查代码实现,支持 chrome-devtools MCP 的 UI 场景验证\ncategory: Workflow\ntags: [workflow, review]\n---\n\n对照变更的 artifact 检查代码改动,必要时驱动浏览器验证 UI 行为,报告遗漏、偏差和阻塞。\n\n---\n\n**输入**:\\`/marchen:review\\` 后面跟变更名称,或省略自动推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"Review 变更: \\`<name>\\`\"\n\n2. **嗅探 diff 中的 UI 改动**\n\n 执行 \\`git diff --name-only HEAD\\`,判断改动是否涉及前端 UI(组件、样式、模板、静态资源等)。命中则记下命中文件,用于下一步的提示。\n\n3. **AskUserQuestion 选择 review 模式**\n\n 用 **AskUserQuestion** 让用户三选一:\n\n - **代码 review**(默认):对照 artifact 检查 git diff,不跑代码\n - **UI 验证**:用 chrome-devtools MCP 实际打开页面验证 specs 中的场景\n - **两者都做**:先代码、再 UI\n\n 若上一步命中 UI 文件,在问题描述里附一句:\"检测到 diff 涉及 UI 文件(<列出命中文件>),建议选 UI 验证或两者都做。\"\n 未命中时不附加提示,默认推荐\"代码 review\"。\n\n 保存用户选择为 \\`<mode>\\`。\n\n4. **获取意图与改动(公共前置)**\n\n - 执行 \\`marchen instructions <name> apply --json\\`,从返回 JSON 的 \\`context\\` 数组里读取 \\`status\\` 为 \"filled\" 的 artifact(proposal/specs/design/tasks)作为变更意图\n - 执行 \\`git diff HEAD\\`;为空则 \\`git diff HEAD~1\\`;仍为空则报告 \"未检测到代码改动\" 并结束\n\n diff 大到可能挤占 context 时,先 \\`git diff --stat HEAD\\` 看摘要,再按需读单个文件 diff。\n\n5. **代码 review**(mode 为 \"代码 review\" 或 \"两者都做\" 时执行)\n\n 逐条对照变更意图和代码改动,输出报告:\n\n **任务完成度** — tasks 中每个任务是否有对应改动:\n - ✅ 任务: <描述> — 已实现\n - ❌ 任务: <描述> — 未找到对应改动\n\n **一致性检查** — 实现是否符合 design 决策:\n - ✅ <决策> — 已遵守\n - ⚠️ <决策> — 实现有偏差:<说明>\n\n **需求覆盖** — specs 中需求是否被覆盖:\n - ✅ <需求> — 已覆盖\n - ❌ <需求> — 未覆盖\n\n **发现的问题(如有)**\n - <文件:行号> <问题描述>\n\n 全部通过:输出 \"✅ 代码 review 通过。\"\n\n 遇到无法判断的情况(tasks 描述与 diff 对不上但可能是改名了、spec 表述含糊等),直接用 **AskUserQuestion** 就地问用户,不要积攒到最后。\n\n6. **UI 验证**(mode 为 \"UI 验证\" 或 \"两者都做\" 时执行)\n\n ### a. 检测 chrome-devtools MCP 可用性\n\n 尝试调用 chrome-devtools MCP 的只读探测工具(例如列出已打开的页面)。工具不存在或调用失败 → 在报告里写:\n\n > ⏭ chrome-devtools MCP 不可用,跳过 UI 验证。\n > 安装方法:\\`npx chrome-devtools-mcp@latest\\`,并参考所用 AI 工具的 MCP 配置文档将其注册。\n\n 然后跳过本节剩余步骤。\n\n ### b. 提取待验证场景\n\n 优先从变更的 specs 文件中提取所有 \\`#### 场景:\\` 块(按 spec 文件分组)。\n 若 specs 不存在或不含场景 → 退化到从 tasks/proposal 推断 UI 相关验证点,并在报告中标注 \"基于 tasks 推断,覆盖度可能不完整\"。\n 两者都没有可提取场景 → 报告 \"未找到可验证的 UI 场景\" 并跳过本节。\n\n ### c. 推断 dev server URL\n\n 从项目文件(脚本、框架配置、环境变量、README 等)推断 dev server 地址和启动命令,不要硬猜端口。\n\n 推出候选 URL 后用 chrome-devtools MCP 的导航工具打开,再用快照工具确认页面像被测应用(合理 title、含框架 root 节点或变更描述涉及的标志性内容)。看着不像被测应用 → 当作未能推断。\n\n 推不出 URL → 全部场景 ⏭ \"URL unknown\",在报告里说明推断依据,提示用户确认地址后重新运行 review,跳过本节执行步骤。\n\n ### d. 推断到 URL 但 dev server 未启动 → 询问是否帮忙起\n\n 推出来 URL 但导航失败(连接拒绝)→ 主会话 **AskUserQuestion**,附上推断依据和启动命令:\n\n - **帮我起 dev server 然后继续**\n - **我手动起,起完告诉你**\n - **跳过 UI 验证**\n\n 选\"帮我起\":\n 1. 用 Bash 的 \\`run_in_background\\` 执行推断到的启动命令(如 \\`pnpm dev\\`、\\`npm run dev\\`)\n 2. 轮询端口直到可访问;出现明显异常(进程退出 / 长时间无任何输出 / 报错日志)才判定启动失败,让用户手动检查\n 3. 启动成功后继续执行后续步骤,并在 review 结束时显式告知用户后台进程 PID 和端口,提示 \"用完请自行 kill <PID>\"——MUST NOT 自动 kill\n\n 选\"我手动起\":等用户告知启动完成后继续;用户可以新会话执行 \\`/marchen:review\\` 重跑。\n\n 选\"跳过 UI\":所有场景 ⏭ \"dev server 未启动\",跳过后续步骤。\n\n ### e. 乐观执行场景\n\n 对每个待验证场景:\n\n 1. 用 chrome-devtools MCP 的导航工具打开对应路径\n 2. 用快照工具观察页面状态\n 3. 把场景的 GIVEN/WHEN/THEN 翻译成具体交互(点击、填写、读取文本)并执行\n 4. 比对页面状态与场景预期\n\n 遇到阻塞(登录墙、权限不足、缺数据、表单需要真实输入等):**主会话可以就地 AskUserQuestion**——例如\"需要登录账号才能继续,提供测试账号?/ 跳过这个场景 / 暂停 review\",而不必积攒到最后。\n\n 仍无法继续的场景 → 记录 ⏭ + 阻塞原因,跳到下一个。\n 场景翻译不出具体操作(例如纯后端行为描述)→ ⏭ \"不适合 UI 验证\"。\n\n ### f. UI 报告格式\n\n \\`\\`\\`\n ## UI 验证(chrome-devtools MCP)\n\n 测试目标:<URL 或 \"未确定\">\n\n ✅ <场景标题> — 通过\n 说明:<可选简述>\n ❌ <场景标题> — 失败\n 证据:<console error 摘要 / 页面文案 / 关键截图路径>\n ⏭ <场景标题> — 跳过:<阻塞原因>\n \\`\\`\\`\n\n7. **展示报告并按结果分支**\n\n - **全部通过且无 ⏭**:提示 \"可以用 \\`/marchen:archive\\` 归档。\"\n - **有 ❌ 失败**:提示用户修复后可以再次 \\`/marchen:review\\`。\n - **有 ⏭ 跳过**:用 **AskUserQuestion** 让用户选择:\n - **补信息后继续**:收集所需信息(账号、数据等),就地再跑被跳过的场景\n - **跳过这些场景,继续归档**:保留报告,提示 \\`/marchen:archive\\`\n - **暂停去修复**:什么都不做\n\n 如本次 review 启动了后台 dev server,再次提醒用户进程信息(PID/端口)。\n\n**护栏**\n\n- 不要修改任何代码,只报告(review 不是 apply)\n- UI 验证阻塞即停,不要硬闯(不登录、不猜数据、不绕权限);要继续就向用户索取信息\n- 起 dev server 必须先获得用户授权(AskUserQuestion);起完不自动 kill,告知 PID 让用户自行管理\n- 用户提供的凭据/账号等敏感数据:不写入任何 artifact,不在报告里复述明文,截图前对密码/token/邮箱等敏感字段脱敏或回避\n- 大 diff 先看 \\`git diff --stat\\`,按需读单个文件,避免灌爆 context\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const COMMAND_UPDATE = `---\nname: \"Marchen: Update\"\ndescription: 修订变更的已有规划产物并保持彼此一致,绝不修改代码\ncategory: Workflow\ntags: [workflow, artifacts]\n---\n\n修订变更的已有规划产物,并保持彼此一致。绝不修改代码。\n\n---\n\n**输入**:\\`/marchen:update\\` 后面跟变更名称(如 \\`/marchen:update add-auth\\`),可附带修改诉求;省略名称时自动推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择,并明示\"使用变更: \\`<name>\\`\"\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选,选项展示名称、schema、任务进度、创建时间(\\`createdAt\\`),最近创建的标记\"(推荐)\"\n - open 变更较多、创建时间不足以判断时,可用 \\`ls -dt marchen/changes/*/\\` 按最近改动排序辅助推荐(该命令不可用时忽略,退回 createdAt)\n\n **重要**:多个候选时绝不猜测或自动选定,始终让用户决定。\n\n2. **获取产物清单**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`schema\\`:该变更使用的工作流 schema\n - \\`artifacts[]\\`:各产物的 \\`id\\`、\\`status\\`(filled / empty / missing / no-content)和 \\`path\\`(相对变更目录)\n - specs 类型的产物额外带 \\`capabilities[]\\`,实际文件为 \\`specs/<capability>/spec.md\\`\n - \\`workflow\\` 与 \\`tasks\\`:依赖状态与任务进度\n\n 产物的 id 和路径来自当前 schema——**不要假设产物名字,不要基于硬编码的产物名做分支判断**。自定义 schema 必须原样可用。\n\n 可编辑对象是 \\`status\\` 为 \\`filled\\` 的产物文件,真实路径为 \\`marchen/changes/<name>/<path>\\`(specs 按 capabilities 展开到各 spec.md)。\n\n3. **理解诉求**\n\n - 用户提出了具体修改(\"design 现在改用 X\")→ 以此作为起点编辑。\n - 只说\"update\"/\"让它自洽\" → 当作一致性审查:通读已有产物,互相对照,找出矛盾、缺口和重复。\n\n4. **读取并调和**\n\n - 读取诉求涉及的产物,以及该变更其余 filled 产物。\n - 先落实用户要求的修改,然后逐个检查其他产物与它是否一致——**任意方向**:改后置产物可能需要回改前置产物,不是只有顺流而下。构建顺序是好用的阅读顺序,不是修订方向的约束。\n - 记录所有因此不一致、缺失或矛盾的地方。\n - 只修订已存在的文件。**不要创建**尚不存在的产物,也不要在 specs 下新建 capability 目录——记录下来,第 6 步告知用户。\n - 变更本来就自洽时,直接说明,不做任何修改。\n\n5. **逐个产物确认后写入**\n\n - 展示每处拟修订的内容和理由,用户确认后才写入。\n - 用户拒绝的修订不写,该产物保持原样。\n - 需要大幅重写时,先获取该产物的格式规则和模板:\n\n \\`\\`\\`bash\n marchen instructions <name> <artifact-id> --json\n \\`\\`\\`\n\n6. **指出下一步(仅建议,绝不代为执行)**\n\n - 还有 empty / missing 的产物 → 告知用户,建议按 \\`marchen instructions\\` 的指引补全。\n - 变更已实现过(tasks 已勾选)→ 代码可能已和修订后的计划不符,建议 \\`/marchen:apply\\` 把差异带进代码。\n - 全部完成且已实现 → 建议 \\`/marchen:archive\\`。\n\n**输出**\n\n每次调用结束时展示:\n- 修订了哪些产物(以及哪些拟修订被用户拒绝)\n- 哪些缺失产物被记录但未创建\n- 变更当前所处状态,和推荐的下一条命令\n\n**护栏**\n\n- 只改规划产物——**绝不修改实现代码**(update 不是 apply)。修订后的计划意味着要改代码时,停下来,指向 \\`/marchen:apply\\`。\n- 使用 \\`marchen status\\` 报告的产物 id 和路径;绝不基于硬编码产物名分支。\n- 只编辑已存在的文件;不推进构建前沿:不创建新产物、不在 specs 下新建 capability——补全是 propose/instructions 的职责。\n- 每处修改写入前必须经用户确认;update 是专门的修订动作,不是 explore 式的顺手捕获。\n- 如果诉求改变的是变更的**意图**而非细化,建议用 \\`/marchen:propose\\` 重开一个新变更(\"修订 vs 重开\"启发式)。\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\n/** Command 模板定义 */\nexport interface CommandTemplate {\n readonly fileName: string\n readonly content: string\n}\n\n/** 所有 command 模板 */\nexport const COMMAND_TEMPLATES: Record<string, CommandTemplate> = {\n apply: { fileName: 'apply.md', content: COMMAND_APPLY },\n archive: { fileName: 'archive.md', content: COMMAND_ARCHIVE },\n explore: { fileName: 'explore.md', content: COMMAND_EXPLORE },\n lite: { fileName: 'lite.md', content: COMMAND_LITE },\n 'propose-preview': { fileName: 'propose-preview.md', content: COMMAND_PROPOSE_PREVIEW },\n propose: { fileName: 'propose.md', content: COMMAND_PROPOSE },\n review: { fileName: 'review.md', content: COMMAND_REVIEW },\n update: { fileName: 'update.md', content: COMMAND_UPDATE },\n}\n","import type { AgentProvider } from '@marchen/shared'\n\nexport const AGENT_PROVIDERS: Record<string, AgentProvider> = {\n antigravity: {\n id: 'antigravity',\n name: 'Antigravity',\n skillDir: '.agent/skills',\n },\n 'claude-code': {\n id: 'claude-code',\n name: 'Claude Code',\n skillDir: '.claude/skills',\n commandDir: '.claude/commands/marchen',\n },\n codex: {\n id: 'codex',\n name: 'Codex',\n skillDir: '.codex/skills',\n },\n copilot: {\n id: 'copilot',\n name: 'GitHub Copilot',\n skillDir: '.github/skills',\n },\n cursor: {\n id: 'cursor',\n name: 'Cursor',\n skillDir: '.cursor/skills',\n },\n 'gemini-cli': {\n id: 'gemini-cli',\n name: 'Gemini CLI',\n skillDir: '.gemini/skills',\n },\n kilocode: {\n id: 'kilocode',\n name: 'Kilo Code',\n skillDir: '.kilocode/skills',\n },\n kiro: {\n id: 'kiro',\n name: 'Kiro',\n skillDir: '.kiro/skills',\n },\n opencode: {\n id: 'opencode',\n name: 'OpenCode',\n skillDir: '.opencode/skills',\n },\n windsurf: {\n id: 'windsurf',\n name: 'Windsurf',\n skillDir: '.windsurf/skills',\n },\n}\n\nexport const DEFAULT_PROVIDER_IDS: readonly string[] = ['claude-code']\n","/**\n * Artifact 模板内容\n *\n * 每个 artifact 的初始 markdown 骨架,创建变更时写入对应文件\n */\n\n/** proposal.md 模板 */\nexport const PROPOSAL_TEMPLATE = `## 动机\n\n<!-- 说明这个变更的动机。解决什么问题?为什么现在做? -->\n\n## 变更内容\n\n<!-- 描述具体变更内容。列出新增的能力、修改或移除的部分。 -->\n\n## 能力\n\n### 新增能力\n<!-- 新增的能力。使用 kebab-case 命名,每个会生成 specs/<name>/spec.md -->\n\n### 修改能力\n<!-- 需要修改的现有能力。仅当规范级别的行为发生变化时才列出。 -->\n\n## 影响范围\n\n<!-- 受影响的代码、API、依赖或系统 -->\n`\n\n/** design.md 模板 */\nexport const DESIGN_TEMPLATE = `## 背景\n\n<!-- 背景和当前状态 -->\n\n## 目标与非目标\n\n**目标:**\n<!-- 这个设计要达成什么 -->\n\n**非目标:**\n<!-- 明确排除在范围之外的内容 -->\n\n## 决策\n\n<!-- 关键技术决策和理由 -->\n\n## 风险与权衡\n\n<!-- 已知风险和权衡 -->\n`\n\n/** spec.md 模板 */\nexport const SPEC_TEMPLATE = `## 目的\n\n<!-- 这个能力的领域职责,一两句话 -->\n\n### 需求: <!-- 需求名称 -->\n\n<!-- 系统 SHALL/MUST 做什么(必须有描述文本) -->\n\n#### 场景: <!-- 场景名称 -->\n\n- **GIVEN** <!-- 前置条件/上下文 -->\n- **WHEN** <!-- 触发动作 -->\n- **THEN** <!-- 期望结果 -->\n- **AND** <!-- 追加条件或结果 -->\n`\n\n/** tasks.md 模板 */\nexport const TASKS_TEMPLATE = `## 1. <!-- 任务组名称 -->\n\n- [ ] 1.1 <!-- 任务描述 -->\n- [ ] 1.2 <!-- 任务描述 -->\n`\n","import type { SchemaDefinition } from '@marchen/shared'\nimport { ValidationError } from '@marchen/shared'\nimport {\n DESIGN_TEMPLATE,\n PROPOSAL_TEMPLATE,\n SPEC_TEMPLATE,\n TASKS_TEMPLATE,\n} from './templates.js'\n\n/**\n * lite schema 的 tasks 模板(带背景章节)\n */\nconst LITE_TASKS_TEMPLATE = `## 背景\n\n<!-- 简要说明这个变更的目的和方案 -->\n\n## 1. <!-- 任务组名称 -->\n\n- [ ] 1.1 <!-- 任务描述 -->\n- [ ] 1.2 <!-- 任务描述 -->\n`\n\n/**\n * apply 阶段的指导文本(所有 schema 共用)\n */\nexport const APPLY_INSTRUCTION = `按 tasks.md 逐个实现任务,完成后勾选 checkbox。\n- 每完成一个任务立即将 - [ ] 改为 - [x]\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- 如果发现设计问题,暂停并建议更新 artifact`\n\n/**\n * 内置 schema 定义\n *\n * full: 完整工作流 proposal → specs → design → tasks\n * lite: 轻量工作流,只有 tasks(适合 explore 之后快速执行)\n */\nexport const SCHEMAS: Record<string, SchemaDefinition> = {\n full: {\n name: 'full',\n artifacts: [\n {\n id: 'proposal',\n generates: 'proposal.md',\n requires: [],\n template: PROPOSAL_TEMPLATE,\n instruction: `根据用户的描述,填写 proposal 的各个部分。\n- 动机:说明为什么要做这个变更,解决什么问题\n- 变更内容:列出具体改动\n- 能力:列出需要创建 spec 的 capability(kebab-case 命名),每个会生成 specs/<name>/spec.md\n- 影响范围:说明涉及的代码和系统\n保持简洁,1-2 页。聚焦\"为什么\"和\"做什么\",不要写实现细节。`,\n },\n {\n id: 'specs',\n generates: 'specs/',\n requires: ['proposal'],\n template: SPEC_TEMPLATE,\n instruction: `根据 proposal 中列出的能力,为每个能力创建 specs/<name>/spec.md。\n\n格式规则:\n- 以 '## 目的' 开头,一两句话说明这个能力的领域职责\n- 每个需求用 '### 需求:' 开头,标题后必须有描述文本(用 SHALL/MUST 表述行为)\n- 每个场景用 '#### 场景:' 开头(必须 4 个 #)\n- 场景内容用列表格式,关键字加粗:- **GIVEN** / - **WHEN** / - **THEN** / - **AND**\n- GIVEN 描述前置条件(可省略,但推荐写明)\n- 每个需求至少一个场景\n- 场景应该是可测试的(每个场景对应一个潜在的 test case)\n\n内容约束:\n- 只描述可观察行为,不描述实现方式\n- 不包含内部类名、函数名、框架/库选择等实现细节(这些属于 design.md)\n- 使用 RFC 2119 关键字表达需求强度:SHALL/MUST(必须)、SHOULD(推荐)、MAY(可选)`,\n },\n {\n id: 'design',\n generates: 'design.md',\n requires: ['proposal'],\n template: DESIGN_TEMPLATE,\n instruction: `根据 proposal 的动机和 specs 的需求,设计技术实现方案。\n- 背景:当前状态和约束\n- 目标与非目标:明确范围\n- 决策:关键技术选择和理由(为什么选 X 而不是 Y)\n- 风险与权衡:已知限制和可能出错的地方\n聚焦架构和方案,不要写逐行实现细节。`,\n },\n {\n id: 'tasks',\n generates: 'tasks.md',\n requires: ['specs', 'design'],\n template: TASKS_TEMPLATE,\n instruction: `根据 specs 的需求和 design 的技术方案,拆分实现任务。\n- 按任务组分组,每组用 ## 标题\n- 每个任务用 checkbox 格式:- [ ] X.Y 描述\n- 任务粒度要小到一个会话内能完成\n- 按依赖顺序排列(先做的在前)`,\n },\n ],\n },\n lite: {\n name: 'lite',\n artifacts: [\n {\n id: 'tasks',\n generates: 'tasks.md',\n requires: [],\n template: LITE_TASKS_TEMPLATE,\n instruction: `根据背景描述,拆分实现任务。\n- 按任务组分组,每组用 ## 标题\n- 每个任务用 checkbox 格式:- [ ] X.Y 描述\n- 任务粒度要小到一个会话内能完成\n- 按依赖顺序排列(先做的在前)`,\n },\n ],\n },\n}\n\n/** 默认 schema 名称 */\nexport const DEFAULT_SCHEMA_NAME = 'full'\n\n/**\n * 按名称查找 schema\n *\n * @param name - schema 名称\n * @returns SchemaDefinition\n * @throws {ValidationError} schema 不存在时抛出,附带可用列表\n */\nexport function getSchema(name: string): SchemaDefinition {\n const schema = SCHEMAS[name]\n if (!schema) {\n const available = Object.keys(SCHEMAS).join(', ')\n throw new ValidationError(\n `Schema \"${name}\" 不存在,可用的 schema: ${available}`,\n )\n }\n return schema\n}\n","// 此文件由 scripts/generate-templates.ts 自动生成,请勿手动修改\n\nexport const SKILL_APPLY = `---\nname: marchen-apply\ndescription: 按变更的 tasks.md 逐个实现任务。适用于用户想按任务清单逐步实现代码。\n---\n\n按变更的 tasks.md 逐个实现任务,完成后勾选 checkbox。\n\n---\n\n**输入**:用户的请求应包含变更名称,或可从上下文推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"使用变更: \\`<name>\\`\"\n\n2. **获取实现指令**\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的信息数组,每项包含 \\`id\\`、\\`status\\`、\\`path\\`、\\`content\\`\n - \\`instruction\\`:实现指引\n - \\`changeDir\\`:变更目录绝对路径\n\n 根据 \\`state\\` 处理:\n - \\`\"blocked\"\\` → 提示先完成 artifacts\n - \\`\"all_done\"\\` → 提示归档\n - \\`\"ready\"\\` → 继续\n\n3. **显示进度**\n\n 从返回的 JSON 读取并显示:\n \"变更: \\`<name>\\` | 进度: N/M | 下一个: <第一个未完成任务的描述>\"\n\n4. **逐个实现任务**\n\n 从 \\`context\\` 中读取所有 artifact 内容作为上下文。\n\n 对每个未完成任务:\n - 显示 \"任务 N/M: <描述>\"\n - 实现代码改动\n - 在 tasks.md 中勾选:\\`- [ ]\\` → \\`- [x]\\`\n 文件路径:\\`<changeDir>/tasks.md\\`\n - 显示 \"✓ 完成\"\n - 继续下一个\n\n **暂停条件:**\n - 任务不清晰 → 询问用户\n - 发现设计问题 → 建议用 \\`/marchen:update\\` 修订计划\n - 遇到错误或阻塞 → 报告并等待\n - 用户中断\n\n5. **显示结果**\n\n 全部完成时:\n \"全部完成 (N/N),可以用 \\`marchen review <name>\\` 检查实现,或直接 \\`marchen archive <name>\\` 归档。\"\n\n 暂停时:\n \"暂停于任务 N/M: <原因>\"\n\n**护栏**\n\n- 实现前必须读 context 中的 artifact 内容\n- 每完成一个任务立即勾选 checkbox,不要攒着\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- 如果实现过程中遇到不确定的设计决策,可以用 \\`marchen search \"<关键词>\" --json\\` 搜索历史变更中的相关方案\n- \\`instruction\\` 是给你的指引,不要原样复制到代码注释中\n`\n\nexport const SKILL_ARCHIVE = `---\nname: marchen-archive\ndescription: 归档已完成的变更。检查完成度后执行归档,适用于实现完毕后收尾。\n---\n\n归档已完成的变更,检查完成度后执行归档。\n\n---\n\n**输入**:用户的请求可包含变更名称(如 \\`/marchen:archive add-auth\\`),也可不带。\n\n**流程**\n\n1. **确定变更名称**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n **重要**:不要猜测或自动选择,必须让用户确认。\n\n2. **检查完成度**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 解析 JSON,检查:\n - \\`artifacts\\`:每个 artifact 的 \\`status\\` 是否为 \\`filled\\`\n - \\`tasks.completed\\` vs \\`tasks.total\\`(\\`tasks\\` 为 null 时视为无任务,跳过检查)\n\n **如果全部完成:** 直接进入下一步。\n\n **如果有未完成的 artifact 或 task:**\n - 显示警告,列出未完成项\n - 用 **AskUserQuestion** 确认是否继续\n - 用户确认后继续,不阻塞\n\n3. **生成摘要**\n\n 读取 \\`marchen/changes/<name>/proposal.md\\`,从中生成一句话中文摘要(≤50字),概括这次变更做了什么。摘要应包含关键语义词,便于后续 AI 检索。\n\n4. **执行归档**\n\n \\`\\`\\`bash\n marchen archive <name> --summary \"<生成的摘要>\" --json\n \\`\\`\\`\n\n 解析返回的 JSON 获取归档结果。\n\n5. **显示结果**\n\n \\`\\`\\`\n 变更 \"<name>\" 已归档\n Schema: <schema>\n 归档到: <archivedTo>\n \\`\\`\\`\n\n 如有警告(未完成项),一并显示。\n\n**护栏**\n\n- 未提供名称时必须用 AskUserQuestion 让用户选择\n- 用 \\`status --json\\` 检查完成度,不要自己读文件判断\n- 警告不阻塞归档,只提醒 + 确认\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const SKILL_EXPLORE = `---\nname: marchen-explore\ndescription: 进入探索模式 — 思考想法、调查问题、厘清需求。适用于用户想在动手之前先理清思路。\n---\n\n进入探索模式。深入思考,自由可视化,跟随对话走向任何方向。\n\n**重要:探索模式只用于思考,不用于实现。** 可以读文件、搜索代码、调查代码库,但绝不能写代码或实现功能。如果用户要求实现,提醒他们先退出探索模式并用 \\`/marchen:propose\\` 创建变更。可以创建 Marchen artifact(proposal、design、spec)——那是捕获思考,不是实现。\n\n**这是一种姿态,不是工作流。** 没有固定步骤、没有必须的顺序、没有强制输出。你是帮助用户探索的思考伙伴。\n\n**输入**:\\`/marchen:explore\\` 后面可以跟任何内容:\n- 模糊的想法:\"实时协作\"\n- 具体的问题:\"auth 系统越来越难维护了\"\n- 变更名称:\"add-dark-mode\"(在该变更上下文中探索)\n- 方案比较:\"postgres vs sqlite\"\n- 什么都不带也行\n\n---\n\n## 姿态\n\n- **好奇,不武断** — 提出自然涌现的问题,不按脚本走\n- **开放线索,不审问** — 展示多个有趣方向,让用户跟随感兴趣的。不要把他们引导到单一路径上\n- **视觉化** — 大量使用 ASCII 图表来辅助思考\n- **自适应** — 跟随有趣的线索,有新信息时及时转向\n- **耐心** — 不急于下结论,让问题的形状自然浮现\n- **接地气** — 在相关时探索实际代码库,不要空谈理论\n\n---\n\n## 你可以做什么\n\n根据用户带来的内容,你可能会:\n\n**探索问题空间**\n- 提出从用户所说内容中自然涌现的澄清问题\n- 挑战假设\n- 重新框定问题\n- 寻找类比\n\n**调查代码库**\n- 映射与讨论相关的现有架构\n- 找到集成点\n- 识别已有的模式\n- 发现隐藏的复杂性\n\n**比较方案**\n- 头脑风暴多种方案\n- 构建比较表\n- 勾勒权衡\n- 推荐路径(如果被问到)\n\n**可视化**\n\\`\\`\\`\n┌─────────────────────────────────────────┐\n│ Use ASCII diagrams liberally │\n├─────────────────────────────────────────┤\n│ │\n│ ┌────────┐ ┌────────┐ │\n│ │ State │────────▶│ State │ │\n│ │ A │ │ B │ │\n│ └────────┘ └────────┘ │\n│ │\n│ System diagrams, state machines, │\n│ data flows, architecture sketches, │\n│ dependency graphs, comparison tables │\n│ │\n└─────────────────────────────────────────┘\n\\`\\`\\`\n\n**发现风险和未知**\n- 识别可能出错的地方\n- 找到理解上的空白\n- 建议 spike 或调查\n\n---\n\n## Marchen 感知\n\n你了解 Marchen 系统。自然地使用它,不要强制。\n\n### 检查上下文\n\n开始时快速检查现有状态和历史:\n\n1. 当前变更:\n\\`\\`\\`bash\nmarchen list --json\n\\`\\`\\`\n\n2. 变更历史概览:\n\\`\\`\\`bash\ncat marchen/changelog.md\n\\`\\`\\`\n这是所有已归档变更的索引,每条包含日期、变更名和一句话摘要。先扫一遍找到与用户话题相关的条目。如果找到,直接读对应 archive 目录下的 proposal.md 或 design.md 了解详情。\n\n3. 语义搜索:\n\n这是 RAG 搜索,不是 grep——构造语义完整的短语,不要用单个泛词。\n\n\\`\\`\\`bash\nmarchen search \"<语义完整的查询短语>\" --json\n\\`\\`\\`\n\n**查询构造指引:**\n- 用描述性短语,不用单个词:\n \"初始化适配多个 agent 客户端\" → \\`\"multi-agent provider 初始化\"\\`\n \"之前怎么处理错误的\" → \\`\"错误处理 error handling 重构\"\\`\n \"暗色模式的设计决策\" → \\`\"dark mode 设计方案\"\\`\n- 中英文混合效果更好(归档内容里中英都有)\n- 如果结果不理想,换个角度重新构造查询\n\n如果有匹配结果(score >= 0.4),读取对应 archive 目录下的 design.md 或 proposal.md 了解详细决策。\n\n如果 \\`marchen search\\` 不可用(命令报错),回退到 changelog.md + 手动读 archive 目录。\n\n这告诉你:\n- 是否有进行中的变更\n- 它们的名称、schema 和状态\n- 项目过去做过哪些变更\n- 用户可能在做什么\n\n如果用户提到了特定变更名称,读取它的 artifact 作为上下文。\n\n### 没有变更时\n\n自由思考。当洞察结晶时,根据复杂度推荐下一步:\n\n**判断标准:**\n- \\`/marchen:lite\\` — bug 修复、小改动、单一任务组、不需要设计文档\n- \\`/marchen:propose\\` — 新功能、多步骤、需要 design/specs、涉及多模块\n\n**推荐方式:** 直接在回复中输出推荐,说明理由,让用户自行输入命令。示例:\n\n> 想法差不多成型了。这个改动比较简单(只涉及一个文件的小调整),建议用 \\`/marchen:lite\\` 直接走轻量流程。\n>\n> 如果你觉得需要更完整的设计文档,也可以用 \\`/marchen:propose\\`。\n\n根据讨论内容给出你的推荐和理由,但让用户自己决定输入哪个命令。\n\n### 有变更时\n\n如果用户提到了变更或你发现某个变更相关:\n\n1. **读取已有 artifact 作为上下文**\n - \\`marchen/changes/<name>/proposal.md\\`\n - \\`marchen/changes/<name>/design.md\\`\n - \\`marchen/changes/<name>/tasks.md\\`\n - 等\n\n2. **在对话中自然引用**\n - \"你的 design 提到用 Redis,但我们刚发现 SQLite 更合适……\"\n - \"proposal 把范围限定在付费用户,但我们现在觉得应该面向所有人……\"\n\n3. **在做出决策时提议捕获**\n\n | 洞察类型 | 捕获到哪里 |\n |---------|-----------|\n | 发现新需求 | \\`specs/<capability>/spec.md\\` |\n | 需求变更 | \\`specs/<capability>/spec.md\\` |\n | 做出设计决策 | \\`design.md\\` |\n | 范围变更 | \\`proposal.md\\` |\n | 发现新工作 | \\`tasks.md\\` |\n | 假设被推翻 | 相关 artifact |\n\n 示例:\n - \"这是一个设计决策。要记录到 design.md 吗?\"\n - \"这是新需求。要加到 specs 里吗?\"\n - \"这改变了范围。要更新 proposal 吗?\"\n\n4. **用户决定** — 提议后继续。不施压,不自动捕获。\n\n---\n\n## 不必做的事\n\n- 按脚本走\n- 每次问同样的问题\n- 产出特定 artifact\n- 得出结论\n- 如果有价值的岔路就不必守住话题\n- 简短(这是思考时间)\n\n---\n\n## 结束探索\n\n没有固定的结束方式。探索可能:\n\n- **流向下一阶段**:用 **AskUserQuestion** 提供 \\`/marchen:lite\\` 和 \\`/marchen:propose\\` 选项,附带推荐理由\n- **更新 artifact**:\"已将这些决策更新到 design.md\"\n- **只是提供清晰度**:用户得到了需要的,继续前进\n- **稍后继续**:\"随时可以继续\"\n\n当想法结晶时,你可以提供总结——但不是必须的。有时候思考过程本身就是价值。\n\n---\n\n## 护栏\n\n- **不实现** — 绝不写代码或实现功能。创建 Marchen artifact 可以,写应用代码不行\n- **不伪装理解** — 不清楚就深挖\n- **不催促** — 探索是思考时间,不是任务时间\n- **不强制结构** — 让模式自然浮现\n- **不自动捕获** — 提议保存洞察,不要直接做\n- **要可视化** — 一张好图胜过千言万语\n- **要探索代码库** — 让讨论扎根于现实\n- **要质疑假设** — 包括用户的和你自己的\n`\n\nexport const SKILL_LITE = `---\nname: marchen-lite\ndescription: 一键式轻量变更流程。创建 lite 变更、实现任务、询问归档,一气呵成。适合 bug 修复、小改动。\n---\n\n一键式轻量变更 — 使用 lite schema 创建变更,自动实现任务,完成后询问归档。\n适合 bug 修复、小改动、explore 之后的快速执行。\n\n---\n\n**输入**:用户的请求应包含变更名称(kebab-case)或变更描述。\n\n**流程**\n\n1. **确定变更名称**\n\n 如果提供了输入,直接使用或从描述中提取 kebab-case 名称(如\"修复登录 bug\" → \\`fix-login-bug\\`)。\n\n 如果没有输入,用 **AskUserQuestion** 工具询问:\n > \"你想做什么变更?描述一下你要构建或修复的内容。\"\n\n 从回答中提取 kebab-case 名称。\n\n **重要**:必须理解用户想做什么才能继续。\n\n2. **创建变更目录**\n\n \\`\\`\\`bash\n marchen new <name> --schema lite\n \\`\\`\\`\n\n 创建 \\`marchen/changes/<name>/\\` 目录,包含 \\`.metadata.yaml\\` 和 \\`tasks.md\\` 骨架。\n\n 如果同名变更已存在,用 **AskUserQuestion** 询问用户是继续已有变更还是换个名称。\n\n3. **获取 tasks 指令**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 确认变更创建成功,然后获取 tasks 的创建指令:\n\n \\`\\`\\`bash\n marchen instructions <name> tasks --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`template\\`:tasks.md 的骨架结构(含 \\`## 背景\\` 章节)\n - \\`instruction\\`:如何填充 tasks 的指导文本\n - \\`outputPath\\`:写入路径(\\`tasks.md\\`)\n - \\`context\\`:上下文信息(lite schema 下为空数组)\n\n4. **填充 tasks.md**\n\n 根据用户描述 + \\`instruction\\` 指引 + \\`template\\` 结构,填充 tasks.md:\n - \\`## 背景\\`:简要说明变更目的和方案\n - 任务列表:按组分类,checkbox 格式\n\n 写入 \\`marchen/changes/<name>/tasks.md\\`。\n\n 如果用户描述太模糊,用 **AskUserQuestion** 澄清关键信息。\n\n5. **开始实现**\n\n 获取实现指令:\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的信息数组\n - \\`instruction\\`:实现指引\n - \\`changeDir\\`:变更目录绝对路径\n\n 显示:\"变更: \\`<name>\\` | 任务: 0/N | 开始实现...\"\n\n 对每个未完成任务:\n - 显示 \"任务 N/M: <描述>\"\n - 实现代码改动\n - 在 tasks.md 中勾选:\\`- [ ]\\` → \\`- [x]\\`\n 文件路径:\\`<changeDir>/tasks.md\\`\n - 显示 \"✓ 完成\"\n - 继续下一个\n\n **暂停条件:**\n - 任务不清晰 → 询问用户\n - 发现设计问题 → 建议用 \\`/marchen:update\\` 修订计划\n - 遇到错误或阻塞 → 报告并等待\n - 用户中断\n\n 暂停时显示:\"暂停于任务 N/M: <原因>\",流程结束。\n\n6. **全部完成 → 询问归档**\n\n 所有任务完成后,用 **AskUserQuestion** 询问:\n\n > \"全部任务已完成 (N/N),是否归档这个变更?\"\n > - 归档\n > - 暂不归档\n\n **如果用户选择归档:**\n\n 读取 \\`marchen/changes/<name>/tasks.md\\` 的背景段,生成一句话中文摘要(≤50字)。\n\n \\`\\`\\`bash\n marchen archive <name> --summary \"<摘要>\" --json\n \\`\\`\\`\n\n 显示:\n \\`\\`\\`\n 变更 \"<name>\" 已归档\n 归档到: <archivedTo>\n \\`\\`\\`\n\n **如果用户选择暂不归档:**\n\n 显示:\"好的,后续可以用 \\`/marchen:archive <name>\\` 归档。\"\n\n**护栏**\n\n- 必须使用 \\`--schema lite\\` 创建变更\n- tasks.md 的 \\`## 背景\\` 章节必须填写,不能留空\n- 任务粒度要小到一个会话内能完成\n- 如果上下文关键信息不清楚,询问用户;但小疑问优先做合理判断,保持节奏\n- 已存在同名变更时必须询问用户,不要覆盖\n- 实现前必须读 context 中的 artifact 内容\n- 每完成一个任务立即勾选 checkbox,不要攒着\n- 改动最小化,只做任务要求的事\n- 不确定就暂停问,不要猜\n- \\`instruction\\` 是给你的指引,不要把它原样复制到代码注释或 tasks.md 中\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const SKILL_PROPOSE_PREVIEW = `---\nname: marchen-propose-preview\ndescription: 预览 propose 生成的变更摘要。从 proposal/design/specs/tasks 浓缩为终端卡片,便于人快速 review,决定下一步是 apply 还是改 propose。\ndisable-model-invocation: true\nargument-hint: <change-name>\n---\n\n预览一个变更的浓缩摘要 — 纯终端输出,不写文件。\n\n适用于 propose 完成后人快速 review,避免来回翻 4~7 个 artifact 文件。\n\n---\n\n**输入**:用户的请求应包含变更名称,或可从上下文推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"Preview 变更: \\`<name>\\`\"\n\n2. **获取 artifact 内容**\n\n \\`\\`\\`bash\n marchen instructions <name> apply --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`schemaName\\`:\\`\"full\"\\` / \\`\"lite\"\\`\n - \\`state\\`:\\`\"ready\"\\` / \\`\"blocked\"\\` / \\`\"all_done\"\\`\n - \\`progress\\`:\\`{ total, completed, remaining }\\`\n - \\`context\\`:所有 artifact 的内容数组(\\`id\\` / \\`status\\` / \\`content\\`)\n\n **如果 state 为 \\`blocked\\`**:打印 \"变更未填完,先用 /marchen:propose 补齐 artifact 再预览\",结束。不要强行摘要半成品。\n\n3. **生成卡片并直接打印**\n\n 根据 \\`schemaName\\` 选模板:\n - \\`full\\` → 四段:改了什么 / 关键决策 / 影响范围 / 风险\n - \\`lite\\` → 两段:改了什么 / 任务概览\n\n 严格按下面的\"摘要规则\"生成,输出为单个卡片,禁止加任何解释段落。\n\n4. **末尾追加一行下一步提示**\n\n \\`\\`\\`\n /marchen:apply <name> 开始实现\n /marchen:update <name> 修订细节(方案、需求、任务的局部调整)\n /marchen:propose <name> 重新提案(变更意图或方向改变)\n \\`\\`\\`\n\n---\n\n## 摘要规则(必须严格遵守)\n\n**通用约束:**\n\n- 卡片框宽 70 字符(含 \\`│\\` 边框),便于主流终端整齐显示\n- 每行内容(去掉边框后)≤ 60 字符;超出必须截断/合并/重写,不许折行\n- 禁止粘贴 artifact 原文片段,所有内容必须重新组织、压缩\n- 中文为主,技术名词保留英文(如 \\`SearchManager\\`、\\`SDK\\`)\n- 宁可少写,不要堆。摘不下就合并或截断,在框底加 \\`更多详情见 marchen/changes/<name>/\\`\n\n**full schema 段落上限:**\n\n| 段落 | 上限 | 来源 |\n|---------|------------------|---------------------------------|\n| 改了什么 | 6 条 bullet | proposal 的\"能力\"小节 |\n| 关键决策 | 5 条 bullet | design 的\"决策\"小节 |\n| 影响范围 | 8 节点 ASCII 图 | proposal 的\"影响范围\" + design |\n| 风险 | 3 条 bullet | design 的\"风险与权衡\" |\n\n**lite schema 段落上限:**\n\n| 段落 | 上限 | 来源 |\n|---------|------------------|-------------------------------|\n| 改了什么 | 6 条 bullet | tasks.md 任务组标题 + 推断 |\n| 任务概览 | 1 行/任务组 | tasks.md 一级标题 + 完成进度 |\n\n**影响范围图退化策略:**\n\n如果节点数超 8 个 / 关系不清晰 / 没有明显依赖结构 → 改用 bullet 列模块名,不要硬画歪斜的 ASCII。\n\n**任务进度条规则(lite):**\n\n进度条固定 10 格宽,按 \\`completed/total\\` 比例画。例如 \\`8/8\\` → \\`██████████\\`,\\`3/5\\` → \\`██████░░░░\\`。\n\n---\n\n## 输出样例\n\n**full schema:**\n\n\\`\\`\\`\n╭─ <name> ────────────────────────────── full · <N> 任务 ─╮\n│ │\n│ <一句话动机,从 proposal \"动机\" 段提炼,≤ 55 字> │\n│ │\n│ ━━ 改了什么 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ ✚ <capability-name> <一句话能力描述> │\n│ ... │\n│ │\n│ ━━ 关键决策 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ 1. <决策内容> │\n│ ... │\n│ │\n│ ━━ 影响范围 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ <ASCII 图 或 bullet 模块列表> │\n│ │\n│ ━━ 风险 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ • <风险> │\n│ │\n╰──────────────────────────────────────────────────────────╯\n\\`\\`\\`\n\n**lite schema:**\n\n\\`\\`\\`\n╭─ <name> ──────────────────────────── lite · <N> 任务 ─╮\n│ │\n│ <一句话动机,从 tasks.md \"背景\" 段提炼> │\n│ │\n│ ━━ 改了什么 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ • <推断的核心变更> │\n│ │\n│ ━━ 任务概览 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │\n│ 1. <任务组标题> ██████████ N/M ✓ │\n│ │\n╰───────────────────────────────────────────────────────╯\n\\`\\`\\`\n\n**护栏**\n\n- 不写文件,纯 stdout 输出\n- 不调用 AskUserQuestion 询问\"要打印什么\",遵守摘要规则即可\n- 不展开补全 artifact 信息——artifact 里没有的,摘要里就不该有\n- state 为 \\`blocked\\` 时拒绝生成,不强行摘要半成品\n- 不要在卡片外加解释段落,只打印卡片 + 下一步提示\n- \\`instruction\\` 字段(apply JSON 里有)是给 apply 用的,不是给 preview 的,忽略它\n`\n\nexport const SKILL_PROPOSE = `---\nname: marchen-propose\ndescription: 提出新变更,创建并填充所有 artifact。适用于用户想快速描述需求并生成完整的 proposal、specs、design、tasks。\n---\n\n提出新变更 — 创建变更目录并按依赖顺序生成所有 artifact。\n\n将创建以下 artifact:\n- proposal.md(动机和变更内容)\n- specs/(每个能力的需求规格)\n- design.md(技术方案)\n- tasks.md(实现任务清单)\n\n完成后可用 /marchen:apply 开始实现。\n\n---\n\n**输入**:用户的请求应包含变更名称(kebab-case)或变更描述。\n\n**流程**\n\n1. **确定变更名称**\n\n 如果提供了输入,直接使用或从描述中提取 kebab-case 名称(如\"添加用户认证\" → \\`add-user-auth\\`)。\n\n 如果没有输入,用 **AskUserQuestion** 工具询问:\n > \"你想做什么变更?描述一下你要构建或修复的内容。\"\n\n 从回答中提取 kebab-case 名称。\n\n **重要**:必须理解用户想做什么才能继续。\n\n2. **创建变更目录**\n\n \\`\\`\\`bash\n marchen new <name>\n \\`\\`\\`\n\n 创建 \\`marchen/changes/<name>/\\` 目录和 \\`.metadata.yaml\\`。\n\n 如果同名变更已存在,用 **AskUserQuestion** 询问用户是继续已有变更还是换个名称。\n\n3. **循环创建 artifact**\n\n 用 **TaskCreate** 工具创建任务列表追踪进度。\n\n 循环执行以下步骤:\n\n a. **查询当前状态**\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n 返回 JSON 包含:\n - \\`workflow.next\\`:下一个应该创建的 artifact ID,全部完成时为 \\`null\\`\n - \\`workflow.ready\\`:当前可以创建的 artifact 列表\n - \\`workflow.blocked\\`:被阻塞的 artifact 列表\n - \\`artifacts\\`:每个 artifact 的状态详情(\\`id\\`、\\`status\\`、\\`path\\`)\n\n 如果 \\`workflow.next\\` 为 \\`null\\` → 全部完成,跳到第 4 步。\n\n b. **获取创建指令**\n \\`\\`\\`bash\n marchen instructions <name> <workflow.next> --json\n \\`\\`\\`\n 返回 JSON 包含:\n - \\`template\\`:artifact 的 markdown 骨架结构,用它作为输出文件的框架\n - \\`instruction\\`:如何填充该 artifact 的指导文本\n - \\`outputPath\\`:写入路径(相对于变更目录)\n - \\`context\\`:上下文 artifact 的信息数组,每项包含 \\`id\\`、\\`status\\`、\\`content\\`(已填充的内容直接在这里,不需要额外读文件)\n - \\`unlocks\\`:完成此 artifact 后解锁的 artifact 列表\n\n c. **创建 artifact**\n\n 根据 artifact 类型处理:\n\n **普通 artifact(proposal / design / tasks):**\n - 读取 \\`context\\` 中 \\`status\\` 为 \\`filled\\` 的 \\`content\\` 作为上下文\n - 按 \\`instruction\\` 指引 + \\`template\\` 结构生成内容\n - 写入 \\`marchen/changes/<name>/<outputPath>\\`\n - 写入后验证文件存在\n\n **specs(目录型 artifact,outputPath 为 \\`specs/\\`):**\n - 读取 proposal 内容(在 \\`context\\` 中,\\`id\\` 为 \\`proposal\\` 的 \\`content\\`)\n - 从 proposal 的\"能力\"章节提取能力列表(kebab-case 名称)\n - 为每个能力:\n - 创建目录 \\`marchen/changes/<name>/specs/<capability>/\\`\n - 按 \\`template\\` 结构 + \\`instruction\\` 指引生成 spec 内容\n - 写入 \\`specs/<capability>/spec.md\\`\n - 写入后验证每个 spec 文件存在\n\n **如果 proposal 的上下文不够清晰**(用户描述太模糊):\n - 用 **AskUserQuestion** 澄清关键信息\n - 然后继续创建\n\n d. 显示进度:\"已创建 \\`<artifact-id>\\`\",标记任务完成,回到步骤 a。\n\n4. **显示最终状态**\n\n \\`\\`\\`bash\n marchen status <name>\n \\`\\`\\`\n\n**输出**\n\n完成后显示:\n- 变更名称和目录位置\n- 已创建的 artifact 列表及简要说明\n- 用纯文字(不调用 AskUserQuestion,不自动执行)提示下一步两个并列选项,由用户自行决定:\n\n \\`\\`\\`\n 下一步:\n /marchen:apply <name> 直接开始实现\n /marchen:propose-preview <name> 先看一眼浓缩摘要再决定\n \\`\\`\\`\n\n**护栏**\n\n- 按依赖顺序创建,不跳过 artifact\n- 每次循环创建一个 artifact(specs 算一个,但包含多个文件)\n- 写入后验证文件存在再继续下一个\n- 如果上下文关键信息不清楚,询问用户;但小疑问优先做合理判断,保持节奏\n- 已存在同名变更时必须询问用户,不要覆盖\n- \\`instruction\\` 是给你的指引,不要把它原样复制到 artifact 文件中\n- 使用 AskUserQuestion 时,选项不超过 4 个;需要更多选项时合并或分步询问\n`\n\nexport const SKILL_REVIEW = `---\nname: marchen-review\ndescription: 对照变更意图检查代码实现的完整性和一致性,支持基于 chrome-devtools MCP 的 UI 场景验证。\n---\n\n对照变更的 artifact 检查代码改动,必要时驱动浏览器验证 UI 行为,报告遗漏、偏差和阻塞。\n\n---\n\n**输入**:用户的请求应包含变更名称,或可从上下文推断。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选\n\n 显示:\"Review 变更: \\`<name>\\`\"\n\n2. **嗅探 diff 中的 UI 改动**\n\n 执行 \\`git diff --name-only HEAD\\`,判断改动是否涉及前端 UI(组件、样式、模板、静态资源等)。命中则记下命中文件,用于下一步的提示。\n\n3. **AskUserQuestion 选择 review 模式**\n\n 用 **AskUserQuestion** 让用户三选一:\n\n - **代码 review**(默认):对照 artifact 检查 git diff,不跑代码\n - **UI 验证**:用 chrome-devtools MCP 实际打开页面验证 specs 中的场景\n - **两者都做**:先代码、再 UI\n\n 若上一步命中 UI 文件,在问题描述里附一句:\"检测到 diff 涉及 UI 文件(<列出命中文件>),建议选 UI 验证或两者都做。\"\n 未命中时不附加提示,默认推荐\"代码 review\"。\n\n 保存用户选择为 \\`<mode>\\`。\n\n4. **获取意图与改动(公共前置)**\n\n - 执行 \\`marchen instructions <name> apply --json\\`,从返回 JSON 的 \\`context\\` 数组里读取 \\`status\\` 为 \"filled\" 的 artifact(proposal/specs/design/tasks)作为变更意图\n - 执行 \\`git diff HEAD\\`;为空则 \\`git diff HEAD~1\\`;仍为空则报告 \"未检测到代码改动\" 并结束\n\n diff 大到可能挤占 context 时,先 \\`git diff --stat HEAD\\` 看摘要,再按需读单个文件 diff。\n\n5. **代码 review**(mode 为 \"代码 review\" 或 \"两者都做\" 时执行)\n\n 逐条对照变更意图和代码改动,输出报告:\n\n **任务完成度** — tasks 中每个任务是否有对应改动:\n - ✅ 任务: <描述> — 已实现\n - ❌ 任务: <描述> — 未找到对应改动\n\n **一致性检查** — 实现是否符合 design 决策:\n - ✅ <决策> — 已遵守\n - ⚠️ <决策> — 实现有偏差:<说明>\n\n **需求覆盖** — specs 中需求是否被覆盖:\n - ✅ <需求> — 已覆盖\n - ❌ <需求> — 未覆盖\n\n **发现的问题(如有)**\n - <文件:行号> <问题描述>\n\n 全部通过:输出 \"✅ 代码 review 通过。\"\n\n 遇到无法判断的情况(tasks 描述与 diff 对不上但可能是改名了、spec 表述含糊等),直接用 **AskUserQuestion** 就地问用户,不要积攒到最后。\n\n6. **UI 验证**(mode 为 \"UI 验证\" 或 \"两者都做\" 时执行)\n\n ### a. 检测 chrome-devtools MCP 可用性\n\n 尝试调用 chrome-devtools MCP 的只读探测工具(例如列出已打开的页面)。工具不存在或调用失败 → 在报告里写:\n\n > ⏭ chrome-devtools MCP 不可用,跳过 UI 验证。\n > 安装方法:\\`npx chrome-devtools-mcp@latest\\`,并参考所用 AI 工具的 MCP 配置文档将其注册。\n\n 然后跳过本节剩余步骤。\n\n ### b. 提取待验证场景\n\n 优先从变更的 specs 文件中提取所有 \\`#### 场景:\\` 块(按 spec 文件分组)。\n 若 specs 不存在或不含场景 → 退化到从 tasks/proposal 推断 UI 相关验证点,并在报告中标注 \"基于 tasks 推断,覆盖度可能不完整\"。\n 两者都没有可提取场景 → 报告 \"未找到可验证的 UI 场景\" 并跳过本节。\n\n ### c. 推断 dev server URL\n\n 从项目文件(脚本、框架配置、环境变量、README 等)推断 dev server 地址和启动命令,不要硬猜端口。\n\n 推出候选 URL 后用 chrome-devtools MCP 的导航工具打开,再用快照工具确认页面像被测应用(合理 title、含框架 root 节点或变更描述涉及的标志性内容)。看着不像被测应用 → 当作未能推断。\n\n 推不出 URL → 全部场景 ⏭ \"URL unknown\",在报告里说明推断依据,提示用户确认地址后重新运行 review,跳过本节执行步骤。\n\n ### d. 推断到 URL 但 dev server 未启动 → 询问是否帮忙起\n\n 推出来 URL 但导航失败(连接拒绝)→ 主会话 **AskUserQuestion**,附上推断依据和启动命令:\n\n - **帮我起 dev server 然后继续**\n - **我手动起,起完告诉你**\n - **跳过 UI 验证**\n\n 选\"帮我起\":\n 1. 用 Bash 的 \\`run_in_background\\` 执行推断到的启动命令(如 \\`pnpm dev\\`、\\`npm run dev\\`)\n 2. 轮询端口直到可访问;出现明显异常(进程退出 / 长时间无任何输出 / 报错日志)才判定启动失败,让用户手动检查\n 3. 启动成功后继续执行后续步骤,并在 review 结束时显式告知用户后台进程 PID 和端口,提示 \"用完请自行 kill <PID>\"——MUST NOT 自动 kill\n\n 选\"我手动起\":等用户告知启动完成后继续;用户可以新会话执行 \\`/marchen:review\\` 重跑。\n\n 选\"跳过 UI\":所有场景 ⏭ \"dev server 未启动\",跳过后续步骤。\n\n ### e. 乐观执行场景\n\n 对每个待验证场景:\n\n 1. 用 chrome-devtools MCP 的导航工具打开对应路径\n 2. 用快照工具观察页面状态\n 3. 把场景的 GIVEN/WHEN/THEN 翻译成具体交互(点击、填写、读取文本)并执行\n 4. 比对页面状态与场景预期\n\n 遇到阻塞(登录墙、权限不足、缺数据、表单需要真实输入等):**主会话可以就地 AskUserQuestion**——例如\"需要登录账号才能继续,提供测试账号?/ 跳过这个场景 / 暂停 review\",而不必积攒到最后。\n\n 仍无法继续的场景 → 记录 ⏭ + 阻塞原因,跳到下一个。\n 场景翻译不出具体操作(例如纯后端行为描述)→ ⏭ \"不适合 UI 验证\"。\n\n ### f. UI 报告格式\n\n \\`\\`\\`\n ## UI 验证(chrome-devtools MCP)\n\n 测试目标:<URL 或 \"未确定\">\n\n ✅ <场景标题> — 通过\n 说明:<可选简述>\n ❌ <场景标题> — 失败\n 证据:<console error 摘要 / 页面文案 / 关键截图路径>\n ⏭ <场景标题> — 跳过:<阻塞原因>\n \\`\\`\\`\n\n7. **展示报告并按结果分支**\n\n - **全部通过且无 ⏭**:提示 \"可以用 \\`marchen archive <name>\\` 归档。\"\n - **有 ❌ 失败**:提示用户修复后可以再次 review。\n - **有 ⏭ 跳过**:用 **AskUserQuestion** 让用户选择:\n - **补信息后继续**:收集所需信息(账号、数据等),就地再跑被跳过的场景\n - **跳过这些场景,继续归档**:保留报告,提示 \\`marchen archive <name>\\`\n - **暂停去修复**:什么都不做\n\n 如本次 review 启动了后台 dev server,再次提醒用户进程信息(PID/端口)。\n\n**护栏**\n\n- 不要修改任何代码,只报告(review 不是 apply)\n- UI 验证阻塞即停,不要硬闯(不登录、不猜数据、不绕权限);要继续就向用户索取信息\n- 起 dev server 必须先获得用户授权(AskUserQuestion);起完不自动 kill,告知 PID 让用户自行管理\n- 用户提供的凭据/账号等敏感数据:不写入任何 artifact,不在报告里复述明文,截图前对密码/token/邮箱等敏感字段脱敏或回避\n- 大 diff 先看 \\`git diff --stat\\`,按需读单个文件,避免灌爆 context\n- 使用 AskUserQuestion 时,选项不超过 4 个\n`\n\nexport const SKILL_UPDATE = `---\nname: marchen-update\ndescription: 修订变更的已有规划产物并双向调和保持一致。适用于用户想修改变更的计划(proposal/specs/design/tasks)、把新决策合入计划、或在编辑后让各产物重新对齐。只改规划产物,绝不修改实现代码。\n---\n\n修订变更的已有规划产物,并保持彼此一致。绝不修改代码。\n\n---\n\n**输入**:用户的请求应包含变更名称,或可从上下文推断;通常还带着修改诉求(如\"design 改用 X\")。\n\n**流程**\n\n1. **选择变更**\n\n 有名称就用,没有则:\n - 从对话上下文推断\n - 只有一个 open 变更时自动选择,并明示\"使用变更: \\`<name>\\`\"\n - 多个变更时 \\`marchen list --json\\` + **AskUserQuestion** 让用户选,选项展示名称、schema、任务进度、创建时间(\\`createdAt\\`),最近创建的标记\"(推荐)\"\n - open 变更较多、创建时间不足以判断时,可用 \\`ls -dt marchen/changes/*/\\` 按最近改动排序辅助推荐(该命令不可用时忽略,退回 createdAt)\n\n **重要**:多个候选时绝不猜测或自动选定,始终让用户决定。\n\n2. **获取产物清单**\n\n \\`\\`\\`bash\n marchen status <name> --json\n \\`\\`\\`\n\n 返回 JSON 包含:\n - \\`schema\\`:该变更使用的工作流 schema\n - \\`artifacts[]\\`:各产物的 \\`id\\`、\\`status\\`(filled / empty / missing / no-content)和 \\`path\\`(相对变更目录)\n - specs 类型的产物额外带 \\`capabilities[]\\`,实际文件为 \\`specs/<capability>/spec.md\\`\n - \\`workflow\\` 与 \\`tasks\\`:依赖状态与任务进度\n\n 产物的 id 和路径来自当前 schema——**不要假设产物名字,不要基于硬编码的产物名做分支判断**。自定义 schema 必须原样可用。\n\n 可编辑对象是 \\`status\\` 为 \\`filled\\` 的产物文件,真实路径为 \\`marchen/changes/<name>/<path>\\`(specs 按 capabilities 展开到各 spec.md)。\n\n3. **理解诉求**\n\n - 用户提出了具体修改(\"design 现在改用 X\")→ 以此作为起点编辑。\n - 只说\"update\"/\"让它自洽\" → 当作一致性审查:通读已有产物,互相对照,找出矛盾、缺口和重复。\n\n4. **读取并调和**\n\n - 读取诉求涉及的产物,以及该变更其余 filled 产物。\n - 先落实用户要求的修改,然后逐个检查其他产物与它是否一致——**任意方向**:改后置产物可能需要回改前置产物,不是只有顺流而下。构建顺序是好用的阅读顺序,不是修订方向的约束。\n - 记录所有因此不一致、缺失或矛盾的地方。\n - 只修订已存在的文件。**不要创建**尚不存在的产物,也不要在 specs 下新建 capability 目录——记录下来,第 6 步告知用户。\n - 变更本来就自洽时,直接说明,不做任何修改。\n\n5. **逐个产物确认后写入**\n\n - 展示每处拟修订的内容和理由,用户确认后才写入。\n - 用户拒绝的修订不写,该产物保持原样。\n - 需要大幅重写时,先获取该产物的格式规则和模板:\n\n \\`\\`\\`bash\n marchen instructions <name> <artifact-id> --json\n \\`\\`\\`\n\n6. **指出下一步(仅建议,绝不代为执行)**\n\n - 还有 empty / missing 的产物 → 告知用户,建议按 \\`marchen instructions\\` 的指引补全。\n - 变更已实现过(tasks 已勾选)→ 代码可能已和修订后的计划不符,建议 \\`/marchen:apply\\` 把差异带进代码。\n - 全部完成且已实现 → 建议 \\`/marchen:archive\\`。\n\n**输出**\n\n每次调用结束时展示:\n- 修订了哪些产物(以及哪些拟修订被用户拒绝)\n- 哪些缺失产物被记录但未创建\n- 变更当前所处状态,和推荐的下一条命令\n\n**护栏**\n\n- 只改规划产物——**绝不修改实现代码**(update 不是 apply)。修订后的计划意味着要改代码时,停下来,指向 \\`/marchen:apply\\`。\n- 使用 \\`marchen status\\` 报告的产物 id 和路径;绝不基于硬编码产物名分支。\n- 只编辑已存在的文件;不推进构建前沿:不创建新产物、不在 specs 下新建 capability——补全是 propose/instructions 的职责。\n- 每处修改写入前必须经用户确认;update 是专门的修订动作,不是 explore 式的顺手捕获。\n- 如果诉求改变的是变更的**意图**而非细化,建议用 \\`/marchen:propose\\` 重开一个新变更(\"修订 vs 重开\"启发式)。\n`\n\n/** Skill 模板定义 */\nexport interface SkillTemplate {\n readonly dirName: string\n readonly content: string\n}\n\n/** 所有 skill 模板 */\nexport const SKILL_TEMPLATES: Record<string, SkillTemplate> = {\n apply: { dirName: 'marchen-apply', content: SKILL_APPLY },\n archive: { dirName: 'marchen-archive', content: SKILL_ARCHIVE },\n explore: { dirName: 'marchen-explore', content: SKILL_EXPLORE },\n lite: { dirName: 'marchen-lite', content: SKILL_LITE },\n 'propose-preview': { dirName: 'marchen-propose-preview', content: SKILL_PROPOSE_PREVIEW },\n propose: { dirName: 'marchen-propose', content: SKILL_PROPOSE },\n review: { dirName: 'marchen-review', content: SKILL_REVIEW },\n update: { dirName: 'marchen-update', content: SKILL_UPDATE },\n}\n","import { promises as nodeFs } from 'node:fs'\nimport { FileSystemError } from '@marchen/shared'\n\n// ============================================================\n// 目录操作\n// ============================================================\n\n/**\n * 递归创建目录,如果目录已存在则静默跳过\n */\nexport async function ensureDir(path: string): Promise<void> {\n try {\n await nodeFs.mkdir(path, { recursive: true })\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'EEXIST') {\n throw error\n }\n }\n}\n\n/**\n * 检查路径是否存在(文件或目录)\n */\nexport async function exists(path: string): Promise<boolean> {\n try {\n await nodeFs.access(path)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * 移动目录到新位置\n *\n * @param src - 源目录路径\n * @param dest - 目标目录路径\n * @throws {MarchenError} 源目录不存在时抛出\n */\nexport async function moveDir(src: string, dest: string): Promise<void> {\n try {\n await nodeFs.rename(src, dest)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new FileSystemError('目录不存在', src)\n }\n throw error\n }\n}\n\n/**\n * 列举目录下的直接子条目\n */\nexport async function listDir(path: string): Promise<string[]> {\n try {\n return await nodeFs.readdir(path)\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new FileSystemError('目录不存在', path)\n }\n throw error\n }\n}\n","import { promises as nodeFs } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { FileSystemError } from '@marchen/shared'\nimport { ensureDir } from './directory.js'\n\n// ============================================================\n// 文件读写\n// ============================================================\n\n/**\n * 读取文件内容(UTF-8)\n */\nexport async function readFile(path: string): Promise<string> {\n try {\n return await nodeFs.readFile(path, 'utf-8')\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new FileSystemError('文件不存在', path)\n }\n throw error\n }\n}\n\n/**\n * 写入文件内容(UTF-8),自动创建父目录\n */\nexport async function writeFile(path: string, content: string): Promise<void> {\n await ensureDir(dirname(path))\n await nodeFs.writeFile(path, content, 'utf-8')\n}\n\n/**\n * 追加内容到文件末尾(UTF-8),自动创建父目录\n */\nexport async function appendFile(path: string, content: string): Promise<void> {\n await ensureDir(dirname(path))\n await nodeFs.appendFile(path, content, 'utf-8')\n}\n","import { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport {\n ARCHIVE_DIRECTORY_NAME,\n CHANGE_DIRECTORY_NAME,\n SPEC_DIRECTORY_NAME,\n} from '@marchen/shared'\n\n// ============================================================\n// 路径解析\n// ============================================================\n\n/**\n * 解析 workspace 根目录路径\n */\nexport function resolveWorkspaceRoot(fromPath = process.cwd()): string {\n return resolve(fromPath)\n}\n\n/**\n * 获取规范目录路径\n */\nexport function getSpecDirectory(root = process.cwd()): string {\n return resolve(root, SPEC_DIRECTORY_NAME)\n}\n\n/**\n * 获取变更目录路径\n */\nexport function getChangeDirectory(root = process.cwd()): string {\n return resolve(getSpecDirectory(root), CHANGE_DIRECTORY_NAME)\n}\n\n/**\n * 获取归档目录路径\n */\nexport function getArchiveDirectory(root = process.cwd()): string {\n return resolve(getSpecDirectory(root), ARCHIVE_DIRECTORY_NAME)\n}\n\n/**\n * 获取包根目录路径(基于 import.meta.url)\n */\nexport function getPackageRoot(importMetaUrl: string): string {\n return dirname(fileURLToPath(importMetaUrl))\n}\n","import { FileSystemError } from '@marchen/shared'\nimport yaml from 'js-yaml'\nimport { readFile, writeFile } from './file.js'\n\n// ============================================================\n// YAML 读写\n// ============================================================\n\n/**\n * 读取 YAML 文件并解析为对象\n */\nexport async function readYaml<T>(path: string): Promise<T> {\n const content = await readFile(path)\n try {\n return yaml.load(content) as T\n } catch (error) {\n throw new FileSystemError(\n 'YAML 解析失败',\n path,\n error instanceof Error ? error : undefined,\n )\n }\n}\n\n/**\n * 将对象序列化为 YAML 并写入文件(2 空格缩进)\n */\nexport async function writeYaml(path: string, data: unknown): Promise<void> {\n const content = yaml.dump(data, { indent: 2 })\n await writeFile(path, content)\n}\n","import type {\n ApplyProgress,\n ApplyState,\n ArchiveOptions,\n ArchiveResult,\n ArtifactContentStatus,\n ArtifactStatusDetail,\n ChangeMetadata,\n ContextInfo,\n InstructionsResult,\n SchemaDefinition,\n StatusResult,\n TaskItem,\n WorkflowStatus,\n} from '@marchen/shared'\nimport type { Workspace } from './workspace.js'\nimport { join } from 'node:path'\nimport {\n APPLY_INSTRUCTION,\n DEFAULT_SCHEMA_NAME,\n getSchema,\n} from '@marchen/config'\nimport {\n appendFile,\n ensureDir,\n exists,\n listDir,\n moveDir,\n readFile,\n readYaml,\n writeFile,\n writeYaml,\n} from '@marchen/fs'\nimport {\n METADATA_FILE_NAME,\n StateError,\n ValidationError,\n} from '@marchen/shared'\n\n/** kebab-case 校验正则 */\nconst KEBAB_CASE_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/\n\n/**\n * 变更管理器\n *\n * 统一管理变更的创建、查询、列出等操作。\n * 通过构造函数接收 Workspace 实例获取路径上下文。\n */\nexport class ChangeManager {\n /**\n * @param workspace - 工作区实例,提供路径上下文\n */\n constructor(private readonly workspace: Workspace) {}\n\n /**\n * 校验变更名称是否为合法的 kebab-case 格式\n *\n * @param name - 变更名称\n * @returns 名称是否合法\n */\n static isValidName(name: string): boolean {\n return KEBAB_CASE_REGEX.test(name)\n }\n\n /**\n * 创建一个新的变更\n *\n * 在 marchen/changes/ 下创建变更目录,写入元数据和初始 artifact 文件。\n * 会依次校验:Marchen 是否已初始化、名称格式、是否重名。\n *\n * @param name - 变更名称(kebab-case)\n * @param schemaName - schema 名称,默认 full\n * @throws {MarchenError} 未初始化、名称格式错误或变更已存在时抛出\n */\n async create(name: string, schemaName?: string): Promise<void> {\n // 校验初始化状态\n await this.ensureInitialized()\n\n // 校验名称格式\n if (!ChangeManager.isValidName(name)) {\n throw new ValidationError(\n `变更名称 \"${name}\" 不合法,请使用 kebab-case 格式(如 add-dark-mode)`,\n )\n }\n\n // 校验是否重名\n const changeDir = join(this.workspace.changeDir, name)\n if (await exists(changeDir)) {\n throw new ValidationError(`变更 \"${name}\" 已存在`)\n }\n\n // 查找 schema 定义\n const resolvedSchemaName = schemaName ?? DEFAULT_SCHEMA_NAME\n const schema = getSchema(resolvedSchemaName)\n\n // 创建变更根目录\n await ensureDir(changeDir)\n\n // 根据 schema 的 artifacts 创建初始文件/目录\n for (const artifact of schema.artifacts) {\n if (artifact.generates.endsWith('/')) {\n // 目录类型(如 specs/)\n await ensureDir(join(changeDir, artifact.generates))\n }\n }\n\n // 写入元数据文件\n const metadata: ChangeMetadata = {\n name,\n schema: resolvedSchemaName,\n createdAt: new Date().toISOString(),\n status: 'open',\n }\n await writeYaml(join(changeDir, METADATA_FILE_NAME), metadata)\n }\n\n /**\n * 归档一个已完成的变更\n *\n * 将变更目录从 marchen/changes/ 移动到 marchen/archive/,\n * 目标目录名格式为 YYYY-MM-DD-<name>。同时更新元数据的 status 和 archivedAt,\n * 并追加一行条目到 changelog.md。\n *\n * @param name - 变更名称\n * @param options - 归档选项(可选 summary)\n * @returns 归档结果\n * @throws {ValidationError} 变更不存在或目标目录已存在时抛出\n * @throws {StateError} 未初始化时抛出\n */\n async archive(\n name: string,\n options?: ArchiveOptions,\n ): Promise<ArchiveResult> {\n await this.ensureInitialized()\n\n const changeDir = join(this.workspace.changeDir, name)\n if (!(await exists(changeDir))) {\n throw new ValidationError(`变更 \"${name}\" 不存在`)\n }\n\n // 读取元数据\n const metadataPath = join(changeDir, METADATA_FILE_NAME)\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n const now = new Date()\n const archivedAt = now.toISOString()\n const datePrefix = archivedAt.slice(0, 10)\n const archiveDir = join(this.workspace.archiveDir, `${datePrefix}-${name}`)\n\n // 检查目标目录是否已存在\n if (await exists(archiveDir)) {\n throw new ValidationError(`归档目标 \"${datePrefix}-${name}\" 已存在`)\n }\n\n // 更新元数据\n await writeYaml(metadataPath, {\n ...metadata,\n status: 'archived' as const,\n archivedAt,\n })\n\n // 移动到 archive 目录\n await moveDir(changeDir, archiveDir)\n\n // 写入 changelog\n await this.appendChangelog(name, datePrefix, options?.summary)\n\n // 更新搜索索引\n await this.updateSearchIndex()\n\n return {\n name,\n schema: metadata.schema,\n archivedTo: archiveDir,\n archivedAt,\n }\n }\n\n /**\n * 追加一行条目到 changelog.md\n *\n * changelog.md 不存在时自动创建(兼容未 re-init 的旧项目)。\n * summary 写入前会 trim 并替换换行为空格。\n *\n * @param name - 变更名称\n * @param datePrefix - 日期前缀(YYYY-MM-DD)\n * @param summary - 可选摘要文本\n */\n private async appendChangelog(\n name: string,\n datePrefix: string,\n summary?: string,\n ): Promise<void> {\n const changelogPath = this.workspace.changelogPath\n\n // changelog.md 不存在时先创建\n if (!(await exists(changelogPath))) {\n await writeFile(changelogPath, '# 变更日志\\n')\n }\n\n const link = `[${name}](./archive/${datePrefix}-${name}/)`\n const sanitizedSummary = summary?.trim().replace(/[\\r\\n]+/g, ' ')\n const entry = sanitizedSummary\n ? `- ${datePrefix}: ${link} — ${sanitizedSummary}`\n : `- ${datePrefix}: ${link}`\n\n await appendFile(changelogPath, `${entry}\\n`)\n }\n\n /** 更新搜索索引(失败时静默,超时 30 秒) */\n private async updateSearchIndex(): Promise<void> {\n try {\n await Promise.race([\n this.doUpdateSearchIndex(),\n new Promise<void>((resolve) => setTimeout(resolve, 30_000)),\n ])\n } catch {\n // 索引失败不影响归档\n }\n }\n\n private async doUpdateSearchIndex(): Promise<void> {\n const { SearchManager } = await import('./search-manager.js')\n const search = new SearchManager(this.workspace)\n if (await search.isAvailable()) {\n await search.indexChange()\n await search.close()\n }\n }\n\n /**\n * 列出所有 open 状态的变更\n *\n * 扫描 marchen/changes/ 目录,读取各变更的元数据,\n * 返回按创建时间降序排列的列表。跳过缺失元数据的目录。\n *\n * @returns 变更元数据数组,按 createdAt 降序排列\n * @throws {MarchenError} 未初始化时抛出\n */\n async list(): Promise<ChangeMetadata[]> {\n await this.ensureInitialized()\n\n const entries = await listDir(this.workspace.changeDir)\n\n const changes: ChangeMetadata[] = []\n\n // 读取每个变更的元数据\n for (const name of entries) {\n const metadataPath = join(\n this.workspace.changeDir,\n name,\n METADATA_FILE_NAME,\n )\n\n if (!(await exists(metadataPath))) {\n continue\n }\n\n try {\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n changes.push(metadata)\n } catch {\n continue\n }\n }\n\n // 按创建时间降序排列(最新的在前)\n changes.sort((a, b) => {\n return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()\n })\n\n return changes\n }\n\n /**\n * 查看变更的 artifact 状态和工作流建议\n *\n * 检测各 artifact 的内容状态(不只是文件存在性),\n * 计算工作流建议(ready/blocked/next),解析 tasks 进度。\n *\n * @param name - 变更名称\n * @returns 状态结果\n * @throws {MarchenError} 未初始化或变更不存在时抛出\n */\n async status(name: string): Promise<StatusResult> {\n await this.ensureInitialized()\n\n const changeDir = join(this.workspace.changeDir, name)\n if (!(await exists(changeDir))) {\n throw new ValidationError(`变更 \"${name}\" 不存在`)\n }\n\n // 读取元数据获取 schema\n const metadataPath = join(changeDir, METADATA_FILE_NAME)\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n const schema = getSchema(metadata.schema)\n\n // 逐个检测 artifact 的内容状态\n // specs 是目录类型,需要特殊处理;其余为单文件类型\n const artifacts: ArtifactStatusDetail[] = []\n for (const artifact of schema.artifacts) {\n const artifactPath = join(changeDir, artifact.generates)\n\n if (artifact.id === 'specs') {\n const result = await this.detectSpecsStatus(artifactPath)\n artifacts.push({ id: artifact.id, path: artifact.generates, ...result })\n } else {\n const status = await this.detectContentStatus(artifactPath)\n artifacts.push({ id: artifact.id, status, path: artifact.generates })\n }\n }\n\n // 根据各 artifact 的内容状态,计算工作流建议(哪些可以开始、哪些被阻塞)\n const statusMap = new Map(artifacts.map((a) => [a.id, a.status]))\n const workflow = this.computeWorkflow(schema, statusMap)\n\n // tasks 进度独立于 artifact 状态:只要 tasks.md 有实质内容就解析 checkbox\n // 这样 artifact status 只反映\"有没有内容\",进度信息放在 tasks 字段\n const tasksArtifact = artifacts.find((a) => a.id === 'tasks')\n let tasks: StatusResult['tasks'] = null\n if (tasksArtifact && tasksArtifact.status === 'filled') {\n const tasksPath = join(changeDir, 'tasks.md')\n const content = await readFile(tasksPath)\n const items = this.parseTaskItems(content)\n tasks = {\n total: items.length,\n completed: items.filter((item) => item.completed).length,\n items,\n }\n }\n\n return { name, schema: metadata.schema, artifacts, workflow, tasks }\n }\n\n /**\n * 获取指定 artifact 的创建指令\n *\n * 返回模板、指导文本、依赖 artifacts 的内容和完成后解锁的 artifacts。\n *\n * @param name - 变更名称\n * @param artifactId - artifact 标识符\n * @returns 指令结果\n * @throws {MarchenError} 未初始化、变更不存在或 artifact 不存在时抛出\n */\n async getInstructions(\n name: string,\n artifactId: string,\n ): Promise<InstructionsResult> {\n await this.ensureInitialized()\n\n const changeDir = join(this.workspace.changeDir, name)\n if (!(await exists(changeDir))) {\n throw new ValidationError(`变更 \"${name}\" 不存在`)\n }\n\n // 查找 artifact 定义,校验 artifactId 合法性\n const metadataPath = join(changeDir, METADATA_FILE_NAME)\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n const schema = getSchema(metadata.schema)\n\n const artifactDef = schema.artifacts.find((a) => a.id === artifactId)\n if (!artifactDef) {\n throw new ValidationError(\n `Artifact \"${artifactId}\" 不存在,可用的 artifact: ${schema.artifacts.map((a) => a.id).join(', ')}`,\n )\n }\n\n // 从 artifact 定义获取模板和指导文本\n const template = artifactDef.template ?? ''\n const instruction = artifactDef.instruction\n\n // 构建上下文信息:读取每个依赖 artifact 的状态和内容\n // specs 目录需要拼接所有 spec 文件内容;单文件直接读取\n const context: ContextInfo[] = []\n for (const depId of artifactDef.requires) {\n const depDef = schema.artifacts.find((a) => a.id === depId)\n if (!depDef) continue\n\n const depPath = join(changeDir, depDef.generates)\n\n if (depId === 'specs') {\n const specsResult = await this.detectSpecsStatus(depPath)\n const content =\n specsResult.status === 'filled'\n ? await this.readSpecsContent(depPath)\n : null\n context.push({\n id: depId,\n status: specsResult.status,\n path: depDef.generates,\n content,\n })\n } else {\n const status = await this.detectContentStatus(depPath)\n const content = status === 'filled' ? await readFile(depPath) : null\n context.push({ id: depId, status, path: depDef.generates, content })\n }\n }\n\n // 计算 unlocks(谁把当前 artifact 列为依赖)\n const unlocks = schema.artifacts\n .filter((a) => a.requires.includes(artifactId))\n .map((a) => a.id)\n\n return {\n changeName: name,\n artifactId,\n schemaName: metadata.schema,\n changeDir,\n outputPath: artifactDef.generates,\n template,\n instruction,\n context,\n unlocks,\n state: null,\n progress: null,\n }\n }\n\n /**\n * 检测单文件 artifact 的内容状态\n *\n * 判断逻辑:去掉 HTML 注释、空行、纯 markdown 标题行后,\n * 剩余内容超过 20 字符算 filled,否则算 empty(模板骨架)。\n *\n * 阈值 20 字符是粗粒度信号,Skill 会读文件内容自己做精确判断。\n *\n * @param filePath - 文件绝对路径\n * @returns 内容状态:missing / empty / filled\n */\n private async detectContentStatus(\n filePath: string,\n ): Promise<ArtifactContentStatus> {\n if (!(await exists(filePath))) {\n return 'missing'\n }\n\n const content = await readFile(filePath)\n const stripped = content\n .replace(/<!--[\\s\\S]*?-->/g, '') // 去掉 HTML 注释\n .split('\\n')\n .filter((line) => line.trim() !== '') // 去掉空行\n .filter((line) => !/^#{1,6}\\s/.test(line.trim())) // 去掉纯标题行\n .join('\\n')\n .trim()\n\n return stripped.length > 20 ? 'filled' : 'empty'\n }\n\n /**\n * 检测 specs 目录的内容状态和 capabilities 列表\n *\n * specs 是目录类型 artifact,状态判断规则:\n * - 目录不存在 → missing\n * - 目录存在但无子目录 → no-content\n * - 有子目录但存在未填充的 spec.md → no-content(返回 capabilities 列表)\n * - 所有子目录的 spec.md 都有实质内容 → filled\n *\n * @param specsPath - specs 目录绝对路径\n * @returns 状态和 capabilities(子目录名列表)\n */\n private async detectSpecsStatus(specsPath: string): Promise<{\n status: ArtifactContentStatus\n capabilities: string[]\n }> {\n if (!(await exists(specsPath))) {\n return { status: 'missing', capabilities: [] }\n }\n\n const entries = await listDir(specsPath)\n if (entries.length === 0) {\n return { status: 'no-content', capabilities: [] }\n }\n\n // 检查所有 spec 文件的内容状态\n // 只有全部 spec.md 都是 filled 才算 filled,否则算 no-content\n let allFilled = true\n for (const entry of entries) {\n const specFile = join(specsPath, entry, 'spec.md')\n if (!(await exists(specFile))) {\n allFilled = false\n continue\n }\n const status = await this.detectContentStatus(specFile)\n if (status !== 'filled') {\n allFilled = false\n }\n }\n\n return {\n status: allFilled ? 'filled' : 'no-content',\n capabilities: entries,\n }\n }\n\n /**\n * 读取 specs 目录下所有 spec 文件内容并拼接\n *\n * 每个文件以 `--- specs/<name>/spec.md ---` 作为分隔标记,\n * 方便 LLM 识别各 spec 的边界。\n *\n * @param specsPath - specs 目录绝对路径\n * @returns 拼接后的完整内容\n */\n private async readSpecsContent(specsPath: string): Promise<string> {\n const entries = await listDir(specsPath)\n const parts: string[] = []\n\n for (const entry of entries) {\n const specFile = join(specsPath, entry, 'spec.md')\n if (await exists(specFile)) {\n const content = await readFile(specFile)\n parts.push(`--- specs/${entry}/spec.md ---\\n${content}`)\n }\n }\n\n return parts.join('\\n\\n')\n }\n\n /**\n * 根据 schema 的依赖关系计算工作流状态\n *\n * 通用实现:遍历 schema 的所有 artifacts,\n * 根据 requires 字段判断每个 artifact 是 ready 还是 blocked。\n *\n * @param schema - schema 定义\n * @param statuses - artifact id → 内容状态的映射\n * @returns 工作流状态(next / ready / blocked)\n */\n private computeWorkflow(\n schema: SchemaDefinition,\n statuses: Map<string, ArtifactContentStatus>,\n ): WorkflowStatus {\n const isFilled = (id: string): boolean => statuses.get(id) === 'filled'\n\n const ready: string[] = []\n const blocked: string[] = []\n\n for (const artifact of schema.artifacts) {\n if (isFilled(artifact.id)) continue\n\n const depsReady = artifact.requires.every(isFilled)\n if (depsReady) {\n ready.push(artifact.id)\n } else {\n blocked.push(artifact.id)\n }\n }\n\n return { next: ready[0] ?? null, ready, blocked }\n }\n\n /**\n * 解析 tasks.md 内容中的 checkbox 条目\n */\n private parseTaskItems(content: string): TaskItem[] {\n return [...content.matchAll(/^- \\[([ x])\\] (.+)$/gm)].map((match) => ({\n description: match[2]!,\n completed: match[1] === 'x',\n }))\n }\n\n /**\n * 从 tasks.md 内容计算任务进度\n */\n private parseTaskProgress(content: string): ApplyProgress {\n const items = this.parseTaskItems(content)\n const completed = items.filter((i) => i.completed).length\n return {\n total: items.length,\n completed,\n remaining: items.length - completed,\n }\n }\n\n /**\n * 获取 apply 阶段的指令\n *\n * 收集所有 artifact 的内容作为上下文,计算任务进度和状态。\n *\n * @param name - 变更名称\n * @returns 指令结果(state/progress 有值,outputPath/template/unlocks 为 null)\n */\n async getApplyInstructions(name: string): Promise<InstructionsResult> {\n await this.ensureInitialized()\n\n const changeDir = join(this.workspace.changeDir, name)\n if (!(await exists(changeDir))) {\n throw new ValidationError(`变更 \"${name}\" 不存在`)\n }\n\n const metadataPath = join(changeDir, METADATA_FILE_NAME)\n const metadata = await readYaml<ChangeMetadata>(metadataPath)\n const schema = getSchema(metadata.schema)\n\n // 收集所有 artifact 的上下文\n const context: ContextInfo[] = []\n for (const artifact of schema.artifacts) {\n const artifactPath = join(changeDir, artifact.generates)\n\n if (artifact.id === 'specs') {\n const specsResult = await this.detectSpecsStatus(artifactPath)\n const content =\n specsResult.status === 'filled'\n ? await this.readSpecsContent(artifactPath)\n : null\n context.push({\n id: artifact.id,\n status: specsResult.status,\n path: artifact.generates,\n content,\n })\n } else {\n const status = await this.detectContentStatus(artifactPath)\n const content =\n status === 'filled' ? await readFile(artifactPath) : null\n context.push({\n id: artifact.id,\n status,\n path: artifact.generates,\n content,\n })\n }\n }\n\n // 计算 state 和 progress\n const tasksContext = context.find((c) => c.id === 'tasks')\n let state: ApplyState\n let progress: ApplyProgress\n\n if (\n !tasksContext ||\n !tasksContext.content ||\n tasksContext.status !== 'filled'\n ) {\n state = 'blocked'\n progress = { total: 0, completed: 0, remaining: 0 }\n } else {\n progress = this.parseTaskProgress(tasksContext.content)\n state = progress.remaining === 0 ? 'all_done' : 'ready'\n }\n\n const instruction = APPLY_INSTRUCTION\n\n return {\n changeName: name,\n artifactId: 'apply',\n schemaName: metadata.schema,\n changeDir,\n outputPath: null,\n template: null,\n instruction,\n context,\n unlocks: null,\n state,\n progress,\n }\n }\n\n /**\n * 确保 Marchen 已初始化,否则抛出错误\n */\n private async ensureInitialized(): Promise<void> {\n const initialized = await this.workspace.isInitialized()\n if (!initialized) {\n throw new StateError('Marchen 尚未初始化', '运行 marchen init 初始化')\n }\n }\n}\n","import type {\n AgentProvider,\n PackageBoundary,\n UpdateResult,\n WorkspaceConfig,\n} from '@marchen/shared'\nimport { join } from 'node:path'\nimport {\n AGENT_PROVIDERS,\n COMMAND_TEMPLATES,\n DEFAULT_PROVIDER_IDS,\n SKILL_TEMPLATES,\n} from '@marchen/config'\nimport {\n ensureDir,\n exists,\n getArchiveDirectory,\n getChangeDirectory,\n getSpecDirectory,\n readYaml,\n resolveWorkspaceRoot,\n writeFile,\n writeYaml,\n} from '@marchen/fs'\nimport { CONFIG_FILE_NAME, DEFAULT_HF_ENDPOINT } from '@marchen/shared'\n\n/** 初始化选项 */\nexport interface InitializeOptions {\n /** 要安装的 AI 工具 provider ID 列表 */\n readonly providers?: readonly string[]\n /** CLI 版本号,传入时写入 config.yaml */\n readonly version?: string\n /** 是否启用搜索,写入 config.yaml 的 search.enabled */\n readonly searchEnabled?: boolean\n}\n\n/** 更新选项 */\nexport interface UpdateOptions {\n /** 当前 CLI 版本号 */\n readonly version: string\n}\n\n/**\n * 工作区上下文\n *\n * 统一管理 workspace 的路径计算、初始化检查和初始化操作。\n * 所有路径在构造时一次性计算完成,整个生命周期共享。\n */\nexport class Workspace {\n /** 工作区根目录 */\n readonly root: string\n\n /** 规范目录路径(marchen/) */\n readonly specDir: string\n\n /** 变更目录路径(marchen/changes/) */\n readonly changeDir: string\n\n /** 归档目录路径(marchen/archive/) */\n readonly archiveDir: string\n\n /** 变更日志路径(marchen/changelog.md) */\n readonly changelogPath: string\n\n /** 搜索索引数据库路径(marchen/.search/index.sqlite) */\n readonly searchDbPath: string\n\n /** 包边界信息 */\n readonly packageBoundaries: readonly PackageBoundary[] = [\n { name: '@marchen/shared', dependsOn: [] },\n { name: '@marchen/config', dependsOn: ['@marchen/shared'] },\n { name: '@marchen/fs', dependsOn: ['@marchen/shared'] },\n {\n name: '@marchen/core',\n dependsOn: ['@marchen/config', '@marchen/fs', '@marchen/shared'],\n },\n ]\n\n /**\n * @param root - 工作区根目录路径,默认为 process.cwd()\n */\n constructor(root?: string) {\n this.root = resolveWorkspaceRoot(root)\n this.specDir = getSpecDirectory(this.root)\n this.changeDir = getChangeDirectory(this.root)\n this.archiveDir = getArchiveDirectory(this.root)\n this.changelogPath = join(this.specDir, 'changelog.md')\n this.searchDbPath = join(this.specDir, '.search', 'index.sqlite')\n }\n\n /**\n * 检查 Marchen 是否已初始化\n *\n * @returns 如果 marchen 目录存在则返回 true\n */\n async isInitialized(): Promise<boolean> {\n return await exists(this.specDir)\n }\n\n /**\n * 初始化 Marchen 目录结构\n *\n * 创建标准目录结构和默认配置文件:\n * - marchen/config.yaml\n * - marchen/changes/\n * - marchen/archive/\n *\n * @param options - 初始化选项\n */\n async initialize(options?: InitializeOptions): Promise<void> {\n const providerIds = options?.providers ?? DEFAULT_PROVIDER_IDS\n const providers = providerIds\n .map((id) => AGENT_PROVIDERS[id])\n .filter((p): p is AgentProvider => p != null)\n\n // 创建目录结构\n await ensureDir(this.specDir)\n await ensureDir(this.changeDir)\n await ensureDir(this.archiveDir)\n await ensureDir(join(this.specDir, '.search'))\n\n // 写入默认配置\n const configPath = join(this.specDir, CONFIG_FILE_NAME)\n const configData: Record<string, unknown> = {\n schema: 'full',\n providers: [...providerIds],\n }\n if (options?.version) {\n configData.version = options.version\n }\n configData.search = { enabled: options?.searchEnabled ?? false }\n configData.models = { endpoint: DEFAULT_HF_ENDPOINT }\n await writeYaml(configPath, configData)\n\n // 创建 .gitkeep 占位文件\n await writeFile(join(this.changeDir, '.gitkeep'), '')\n await writeFile(join(this.archiveDir, '.gitkeep'), '')\n\n // 创建 changelog.md(已存在时跳过)\n if (!(await exists(this.changelogPath))) {\n await writeFile(this.changelogPath, '# 变更日志\\n')\n }\n\n // 为每个 provider 生成 skill 和 command 文件\n for (const provider of providers) {\n await this.generateSkills(provider.skillDir)\n if (provider.commandDir) {\n await this.generateCommands(provider.commandDir)\n }\n }\n }\n\n /**\n * 读取 config.yaml 配置\n *\n * @returns config.yaml 的完整内容\n */\n async readConfig(): Promise<WorkspaceConfig> {\n const configPath = join(this.specDir, CONFIG_FILE_NAME)\n return await readYaml<WorkspaceConfig>(configPath)\n }\n\n /**\n * 更新 skill/command 文件到最新版本\n *\n * 读取 config.yaml 中的 providers 列表,覆盖写入最新模板文件,\n * 并更新 version 字段。版本一致时跳过。\n *\n * @param options - 更新选项,包含当前 CLI 版本号\n * @returns 更新结果\n */\n async update(options: UpdateOptions): Promise<UpdateResult> {\n const configPath = join(this.specDir, CONFIG_FILE_NAME)\n const config = await readYaml<Record<string, unknown>>(configPath)\n\n // 版本一致时跳过,避免重复写入\n const previousVersion = (config.version as string) ?? null\n if (previousVersion === options.version) {\n return {\n previousVersion,\n currentVersion: options.version,\n providersUpdated: [],\n skillCount: 0,\n commandCount: 0,\n }\n }\n\n // 老项目 config.yaml 可能没有 providers 字段,fallback 到默认值\n const rawProviders = config.providers as string[] | undefined\n const providerIds =\n rawProviders && rawProviders.length > 0\n ? rawProviders\n : [...DEFAULT_PROVIDER_IDS]\n const providers = providerIds\n .map((id) => AGENT_PROVIDERS[id])\n .filter((p): p is AgentProvider => p != null)\n\n const providerNames: string[] = []\n let skillCount = 0\n let commandCount = 0\n const skillTemplateCount = Object.keys(SKILL_TEMPLATES).length\n const commandTemplateCount = Object.keys(COMMAND_TEMPLATES).length\n\n // 覆盖写入最新模板文件\n for (const provider of providers) {\n await this.generateSkills(provider.skillDir)\n skillCount += skillTemplateCount\n if (provider.commandDir) {\n await this.generateCommands(provider.commandDir)\n commandCount += commandTemplateCount\n }\n providerNames.push(provider.name)\n }\n\n // 补全缺失的配置字段并持久化\n config.version = options.version\n if (!config.providers) {\n config.providers = [...providerIds]\n }\n // 迁移旧 search.mode → search.enabled\n const rawSearch = config.search as Record<string, unknown> | undefined\n if (rawSearch && 'mode' in rawSearch) {\n config.search = { enabled: rawSearch.mode === 'semantic' }\n }\n if (!config.search) {\n config.search = { enabled: false }\n }\n // 补全 models.endpoint 默认值(老 config 不覆盖已有值)\n const rawModels = config.models as Record<string, unknown> | undefined\n if (!rawModels) {\n config.models = { endpoint: DEFAULT_HF_ENDPOINT }\n } else if (rawModels.endpoint == null) {\n config.models = { ...rawModels, endpoint: DEFAULT_HF_ENDPOINT }\n }\n await writeYaml(configPath, config)\n\n return {\n previousVersion,\n currentVersion: options.version,\n providersUpdated: providerNames,\n skillCount,\n commandCount,\n }\n }\n\n /**\n *\n * @param skillDir - skill 根目录的相对路径\n */\n private async generateSkills(skillDir: string): Promise<void> {\n for (const template of Object.values(SKILL_TEMPLATES)) {\n const dir = join(this.root, skillDir, template.dirName)\n await ensureDir(dir)\n await writeFile(join(dir, 'SKILL.md'), template.content)\n }\n }\n\n /**\n * 生成 command 文件到指定目录\n *\n * @param commandDir - command 目录的相对路径\n */\n private async generateCommands(commandDir: string): Promise<void> {\n const dir = join(this.root, commandDir)\n await ensureDir(dir)\n for (const template of Object.values(COMMAND_TEMPLATES)) {\n await writeFile(join(dir, template.fileName), template.content)\n }\n }\n}\n","import { ChangeManager, Workspace } from '@marchen/core'\n\n/**\n * 创建 CLI 上下文,包含 workspace 和 change manager 实例\n */\nexport function createContext(): {\n workspace: Workspace\n changes: ChangeManager\n} {\n const workspace = new Workspace()\n const changes = new ChangeManager(workspace)\n return { workspace, changes }\n}\n","import * as p from '@clack/prompts'\nimport {\n FileSystemError,\n MarchenError,\n StateError,\n ValidationError,\n} from '@marchen/shared'\n\n/**\n * 统一错误处理,按错误类型差异化展示\n */\nexport function handleError(error: unknown): never {\n if (error instanceof StateError) {\n p.log.error(error.message)\n if (error.hint) p.log.info(error.hint)\n } else if (error instanceof ValidationError) {\n p.log.warn(error.message)\n } else if (error instanceof FileSystemError) {\n p.log.error(`${error.message}: ${error.path}`)\n } else if (error instanceof MarchenError) {\n p.log.error(error.message)\n } else {\n p.log.error(`未知错误: ${error}`)\n }\n process.exit(1)\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 注册 archive 命令\n *\n * 归档一个已完成的变更,将其移动到 archive 目录\n *\n * @param program - Commander 程序实例\n */\nexport function registerArchiveCommand(program: Command): void {\n program\n .command('archive')\n .description('归档一个已完成的变更')\n .argument('<name>', '变更名称')\n .option('--json', '输出 JSON 格式')\n .option('--summary <text>', '变更摘要(写入 changelog)')\n .action(\n async (name: string, options: { json?: boolean; summary?: string }) => {\n try {\n const { changes } = createContext()\n const result = await changes.archive(name, {\n summary: options.summary,\n })\n\n if (options.json) {\n console.log(JSON.stringify(result, null, 2))\n return\n }\n\n p.intro('Marchen CLI')\n p.log.success(`变更 \"${name}\" 归档成功`)\n p.outro(`运行 marchen list 查看剩余变更`)\n } catch (error) {\n handleError(error)\n }\n },\n )\n}\n","import type { ModelDownloadProgress } from '@marchen/core'\n\n/** 模型类型显示名称 */\nexport const MODEL_LABELS: Record<string, string> = {\n embed: 'Embedding',\n generate: 'Query Expansion',\n rerank: 'Reranker',\n}\n\n/** 格式化模型下载进度 */\nexport function formatModelProgress(progress: ModelDownloadProgress): string {\n const name = MODEL_LABELS[progress.model] ?? progress.model\n\n switch (progress.stage) {\n case 'checking':\n return `检查模型 ${name}...`\n case 'downloading': {\n if (progress.downloadedBytes && progress.totalBytes) {\n const pct = Math.round(\n (progress.downloadedBytes / progress.totalBytes) * 100,\n )\n const mb = (progress.downloadedBytes / 1024 / 1024).toFixed(1)\n const total = (progress.totalBytes / 1024 / 1024).toFixed(0)\n return `下载模型 ${name}... ${mb}/${total} MB (${pct}%)`\n }\n return `下载模型 ${name}...`\n }\n case 'verifying':\n return `校验模型 ${name}...`\n case 'ready':\n return `模型 ${name} 就绪`\n default:\n return `准备模型 ${name}...`\n }\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { AGENT_PROVIDERS } from '@marchen/config'\nimport { SearchManager } from '@marchen/core'\nimport { createContext } from '../utils/context.js'\nimport { formatModelProgress } from '../utils/model-progress.js'\n\n/**\n * 注册 init 命令\n *\n * 初始化 Marchen 目录结构,创建 marchen/ 目录及默认配置\n *\n * @param program - Commander 程序实例\n */\nexport function registerInitCommand(program: Command): void {\n program\n .command('init')\n .description('初始化 Marchen 目录结构')\n .option('--force', '强制覆盖已存在的目录')\n .action(async (options) => {\n p.intro('Marchen CLI')\n\n const { workspace } = createContext()\n\n // 检查是否已初始化\n const alreadyExists = await workspace.isInitialized()\n if (alreadyExists && !options.force) {\n const confirm = await p.confirm({\n message: 'Marchen 目录已存在,是否覆盖?',\n })\n\n if (p.isCancel(confirm) || !confirm) {\n p.cancel('操作已取消')\n process.exit(0)\n }\n }\n\n // 选择 AI 工具\n const providerOptions = Object.values(AGENT_PROVIDERS).map(\n (provider) => ({\n value: provider.id,\n label: provider.name,\n }),\n )\n\n const selectedProviders = await p.multiselect({\n message: '选择要安装的 AI 工具集成',\n options: providerOptions,\n initialValues: ['claude-code'],\n required: true,\n })\n\n if (p.isCancel(selectedProviders)) {\n p.cancel('操作已取消')\n process.exit(0)\n }\n\n const version = program.version() as string\n\n // 是否启用搜索\n const searchEnabled = await p.confirm({\n message: '是否启用搜索?(需下载约 2GB 模型)',\n initialValue: false,\n })\n\n if (p.isCancel(searchEnabled)) {\n p.cancel('操作已取消')\n process.exit(0)\n }\n\n // 执行初始化\n await workspace.initialize({\n providers: selectedProviders,\n version,\n searchEnabled,\n })\n\n const names = (selectedProviders as string[])\n .map((id) => AGENT_PROVIDERS[id]?.name ?? id)\n .join(', ')\n p.log.success(`已为 ${names} 生成 skills 文件`)\n\n // 启用搜索时下载模型\n if (searchEnabled) {\n const searchManager = new SearchManager(workspace)\n const spinner = p.spinner()\n spinner.start('下载搜索模型...')\n await searchManager.ensureModels({\n onProgress: (prog) => {\n spinner.message(formatModelProgress(prog))\n },\n })\n spinner.stop('Hybrid Search 已启用')\n }\n\n p.outro('Marchen 初始化成功!')\n })\n}\n","import type { Command } from 'commander'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 注册 instructions 命令\n *\n * 返回指定 artifact 的创建指令,包含模板、指导文本、依赖 artifact 的内容\n * 和完成后解锁的 artifact 列表。\n *\n * 默认输出 JSON 格式(--json 标志可选但行为一致),主要给 Skill 消费。\n * 人类直接使用的场景较少。\n *\n * @param program - Commander 程序实例\n */\nexport function registerInstructionsCommand(program: Command): void {\n program\n .command('instructions')\n .description('获取 artifact 的创建指令')\n .argument('<name>', '变更名称')\n .argument(\n '<artifact-id>',\n 'artifact 标识符 (proposal/specs/design/tasks/apply)',\n )\n .option('--json', '输出 JSON 格式(默认行为)')\n .action(async (name: string, artifactId: string) => {\n try {\n const { changes } = createContext()\n const result =\n artifactId === 'apply'\n ? await changes.getApplyInstructions(name)\n : await changes.getInstructions(name, artifactId)\n console.log(JSON.stringify(result, null, 2))\n } catch (error) {\n handleError(error)\n }\n })\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 将 ISO 时间字符串转换为相对时间描述\n *\n * @param isoDate - ISO 8601 格式的日期字符串\n * @returns 相对时间描述(如「刚刚」「3 小时前」「5 天前」)\n */\nexport function timeAgo(isoDate: string): string {\n const seconds = Math.floor((Date.now() - new Date(isoDate).getTime()) / 1000)\n\n if (seconds < 60) return '刚刚'\n if (seconds < 3600) return `${Math.floor(seconds / 60)} 分钟前`\n if (seconds < 86400) return `${Math.floor(seconds / 3600)} 小时前`\n return `${Math.floor(seconds / 86400)} 天前`\n}\n\n/**\n * 注册 list 命令\n *\n * 列出所有 open 状态的变更,展示名称、schema 和创建时间\n *\n * @param program - Commander 程序实例\n */\nexport function registerListCommand(program: Command): void {\n program\n .command('list')\n .description('列出所有 open 状态的变更')\n .option('--json', '输出 JSON 格式')\n .action(async (options: { json?: boolean }) => {\n try {\n const { changes: changeManager } = createContext()\n const changes = await changeManager.list()\n\n if (options.json) {\n console.log(JSON.stringify(changes, null, 2))\n return\n }\n\n p.intro('Marchen CLI')\n\n if (changes.length === 0) {\n p.log.info('暂无 open 状态的变更')\n p.outro('运行 marchen new <name> 创建一个变更')\n return\n }\n\n // 计算各列最大宽度,用于对齐\n const nameWidth = Math.max(4, ...changes.map((c) => c.name.length))\n const schemaWidth = Math.max(6, ...changes.map((c) => c.schema.length))\n\n // 构建表格内容\n const header = `${'名称'.padEnd(nameWidth - 2)} ${'Schema'.padEnd(schemaWidth)} 创建时间`\n const separator = `${'─'.repeat(nameWidth)} ${'─'.repeat(schemaWidth)} ${'─'.repeat(8)}`\n\n const rows = changes.map((change) => {\n return `${change.name.padEnd(nameWidth)} ${change.schema.padEnd(schemaWidth)} ${timeAgo(change.createdAt)}`\n })\n\n // 合并为一个字符串一次输出,避免 clack 分隔\n const table = [header, separator, ...rows].join('\\n')\n\n p.log.info(`共 ${changes.length} 个 open 变更:\\n\\n${table}`)\n\n p.outro('运行 marchen status <name> 查看变更详情')\n } catch (error) {\n handleError(error)\n }\n })\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { SCHEMAS } from '@marchen/config'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 注册 new 命令\n *\n * 创建一个新的变更,包含目录结构和元数据\n *\n * @param program - Commander 程序实例\n */\nexport function registerNewCommand(program: Command): void {\n program\n .command('new')\n .description('创建一个新的变更')\n .argument('<name>', '变更名称(kebab-case,如 add-dark-mode)')\n .option('--schema <name>', '工作流 schema', 'full')\n .action(async (name: string, options: { schema: string }) => {\n p.intro('Marchen CLI')\n\n // 校验 schema 名称\n if (!SCHEMAS[options.schema]) {\n const available = Object.keys(SCHEMAS).join(', ')\n p.log.error(\n `Schema \"${options.schema}\" 不存在,可用的 schema: ${available}`,\n )\n process.exit(1)\n }\n\n const spinner = p.spinner()\n spinner.start(`正在创建变更 \"${name}\"...`)\n\n try {\n const { changes } = createContext()\n await changes.create(name, options.schema)\n spinner.stop(`变更 \"${name}\" 创建成功(schema: ${options.schema})`)\n } catch (error) {\n spinner.stop('创建失败')\n handleError(error)\n }\n\n p.outro(`运行 marchen list 查看所有变更`)\n })\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { SearchManager, Workspace } from '@marchen/core'\nimport pc from 'picocolors'\nimport { handleError } from '../utils/error.js'\n\n/**\n * 注册 search 命令\n *\n * 搜索归档变更历史(Hybrid Search)\n *\n * @param program - Commander 程序实例\n */\nexport function registerSearchCommand(program: Command): void {\n program\n .command('search <query>')\n .description('搜索归档变更历史')\n .option('-n, --limit <number>', '结果数量', '5')\n .option('--min-score <number>', '最低分数阈值', '0.3')\n .option('--json', '输出 JSON 格式')\n .option('--rebuild', '重建索引后搜索')\n .action(\n async (\n query: string,\n options: {\n limit?: string\n minScore?: string\n json?: boolean\n rebuild?: boolean\n },\n ) => {\n const workspace = new Workspace()\n const search = new SearchManager(workspace)\n const spinner = options.json ? null : p.spinner()\n\n try {\n if (!(await search.isAvailable())) {\n p.log.error('搜索功能不可用(qmd 加载失败)')\n process.exit(1)\n }\n\n // 检查搜索是否启用\n let searchEnabled = false\n try {\n const config = await workspace.readConfig()\n searchEnabled = config.search?.enabled ?? false\n } catch {\n // config.yaml 不存在时视为未启用\n }\n\n if (!searchEnabled) {\n p.log.error(\n '搜索未启用。请在 config.yaml 中设置 search.enabled: true 后运行 marchen update',\n )\n process.exit(1)\n }\n\n spinner?.start('准备搜索引擎...')\n await search.prepare()\n spinner?.stop('搜索引擎就绪')\n\n if (options.rebuild) {\n spinner?.start('正在重建索引...')\n await search.index()\n spinner?.stop('索引重建完成')\n }\n\n spinner?.start('搜索中...')\n\n const results = await search.search(query, {\n limit: Number(options.limit),\n minScore: Number(options.minScore),\n })\n\n spinner?.stop('搜索完成')\n\n if (options.json) {\n console.log(JSON.stringify(results, null, 2))\n await search.close()\n return\n }\n\n if (results.length === 0) {\n p.log.info('未找到匹配结果')\n await search.close()\n return\n }\n\n for (const r of results) {\n const score = Math.round(r.score * 100)\n const scoreColor =\n score >= 70\n ? pc.green(`${score}%`)\n : score >= 40\n ? pc.yellow(`${score}%`)\n : pc.dim(`${score}%`)\n p.log.info(`${pc.bold(r.path)} ${scoreColor}`)\n if (r.snippet) {\n p.log.message(pc.dim(r.snippet))\n }\n }\n\n await search.close()\n } catch (error) {\n spinner?.stop('搜索失败')\n handleError(error)\n }\n },\n )\n}\n","import type { ArtifactContentStatus } from '@marchen/shared'\nimport type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport pc from 'picocolors'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\n\n/** 各内容状态对应的 emoji 图标 */\nconst STATUS_ICONS: Record<ArtifactContentStatus, string> = {\n filled: '✅',\n empty: '⬜',\n missing: '⬜',\n 'no-content': '⬜',\n}\n\n/** 为状态文字上色 */\nfunction colorizeStatus(status: string): string {\n switch (status) {\n case 'filled':\n return pc.green(status)\n case 'empty':\n return pc.yellow(status)\n case 'blocked':\n return pc.red(status)\n default:\n return pc.dim(status)\n }\n}\n\n/** 为进度数字上色,附带进度条:全部完成绿色、部分完成黄色、零完成灰色 */\nfunction colorizeProgress(completed: number, total: number): string {\n const barWidth = 10\n const filled = total > 0 ? Math.round((completed / total) * barWidth) : 0\n const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled)\n const text = `[${bar}] ${completed}/${total} 完成`\n if (completed === total) return pc.green(text)\n if (completed > 0) return pc.yellow(text)\n return pc.dim(text)\n}\n\n/**\n * 注册 status 命令\n *\n * 展示指定变更的 artifact 内容状态、工作流建议和 task 进度。\n * 支持两种输出模式:\n * - 默认:人类友好的终端 UI(emoji + 格式化文本)\n * - --json:结构化 JSON(给 Skill 消费)\n *\n * @param program - Commander 程序实例\n */\nexport function registerStatusCommand(program: Command): void {\n program\n .command('status')\n .description('查看变更的 artifact 状态和工作流建议')\n .argument('<name>', '变更名称')\n .option('--json', '输出 JSON 格式')\n .action(async (name: string, options: { json?: boolean }) => {\n try {\n const { changes } = createContext()\n const result = await changes.status(name)\n\n // JSON 模式:直接输出结构化数据,供 Skill 消费\n if (options.json) {\n console.log(JSON.stringify(result, null, 2))\n return\n }\n\n // ── 以下为人类友好的终端 UI 输出 ──\n\n p.intro('Marchen CLI')\n\n // 展示变更基本信息(名称加粗 + schema 灰色,一行展示)\n p.log.info(`${pc.bold(result.name)} · ${pc.dim(result.schema)}`)\n\n // 构建 artifact 状态列表,每行格式:<图标> <名称> <状态> [附加信息]\n // 示例输出:\n // ✅ proposal filled\n // 🔒 specs no-content (等待 proposal)\n // ⬜ design empty\n // 🔒 tasks empty (等待 specs, design)\n const artifactLines = result.artifacts.map((a) => {\n // 被阻塞的 artifact 用 🔒 图标,否则根据内容状态选择图标\n const isBlocked = result.workflow.blocked.includes(a.id)\n const icon = isBlocked ? '🔒' : STATUS_ICONS[a.status]\n const statusText = isBlocked ? 'blocked' : a.status\n let line = `${icon} ${a.id.padEnd(12)} ${colorizeStatus(statusText)}`\n\n // specs 类型额外展示 capability 数量\n if (a.capabilities) {\n line += ` (${a.capabilities.length} capabilities)`\n }\n\n // 被阻塞时,追加提示:具体在等待哪些依赖完成\n if (isBlocked) {\n const deps = getDependencies(a.id)\n const missingDeps = deps.filter(\n (d) =>\n result.artifacts.find((art) => art.id === d)?.status !==\n 'filled',\n )\n if (missingDeps.length > 0) {\n line += ` (等待 ${missingDeps.join(', ')})`\n }\n }\n return line\n })\n p.log.info(`Artifacts\\n${artifactLines.join('\\n')}`)\n\n // 展示 Artifacts + Tasks 总进度汇总\n const filledCount = result.artifacts.filter(\n (a) => a.status === 'filled',\n ).length\n const totalArtifacts = result.artifacts.length\n let progressLine = `Artifacts: ${colorizeProgress(filledCount, totalArtifacts)}`\n if (result.tasks) {\n progressLine += `\\nTasks: ${colorizeProgress(result.tasks.completed, result.tasks.total)}`\n }\n p.log.info(progressLine)\n\n // 展示下一步建议,引导用户继续工作流\n if (result.workflow.next) {\n p.outro(`下一步: ${pc.cyan(result.workflow.next)}`)\n } else {\n p.outro('所有 artifact 已就绪')\n }\n } catch (error) {\n handleError(error)\n }\n })\n}\n\n/**\n * 获取 artifact 的依赖列表\n *\n * 硬编码 full schema 的依赖关系,用于人类友好输出中\n * 展示被阻塞 artifact 等待的依赖名称。\n *\n * @param id - artifact 标识符\n * @returns 依赖的 artifact id 列表\n */\nfunction getDependencies(id: string): string[] {\n const deps: Record<string, string[]> = {\n proposal: [],\n specs: ['proposal'],\n design: ['proposal'],\n tasks: ['specs', 'design'],\n }\n return deps[id] ?? []\n}\n","import type { Command } from 'commander'\nimport * as p from '@clack/prompts'\nimport { SearchManager } from '@marchen/core'\nimport { StateError } from '@marchen/shared'\nimport { createContext } from '../utils/context.js'\nimport { handleError } from '../utils/error.js'\nimport { formatModelProgress } from '../utils/model-progress.js'\n\n/**\n * 注册 update 命令\n *\n * 更新 skill/command 文件到最新版本,并按 config.yaml 同步搜索模型状态\n *\n * @param program - Commander 程序实例\n */\nexport function registerUpdateCommand(program: Command): void {\n program\n .command('update')\n .description('更新 skill/command 文件到最新版本')\n .action(async () => {\n p.intro('Marchen CLI')\n\n try {\n const { workspace } = createContext()\n const version = program.version() as string\n\n if (!(await workspace.isInitialized())) {\n throw new StateError('Marchen 尚未初始化', '请先运行 marchen init')\n }\n\n const result = await workspace.update({ version })\n\n if (result.providersUpdated.length === 0) {\n p.log.info(`已是最新版本 (${result.currentVersion})`)\n } else {\n const prev = result.previousVersion ?? 'unknown'\n p.log.info(`版本: ${prev} → ${result.currentVersion}`)\n\n for (const name of result.providersUpdated) {\n p.log.success(`已更新 ${name} skills`)\n }\n\n p.log.success('config.yaml 版本已更新')\n }\n\n // 按 search.enabled 同步模型状态\n const config = await workspace.readConfig()\n const searchEnabled = config.search?.enabled ?? false\n\n if (searchEnabled) {\n const searchManager = new SearchManager(workspace)\n let downloaded = false\n const spinner = p.spinner()\n spinner.start('检查搜索模型...')\n await searchManager.ensureModels({\n onProgress: (prog) => {\n if (prog.stage === 'downloading') downloaded = true\n spinner.message(formatModelProgress(prog))\n },\n })\n spinner.stop(\n downloaded ? 'Hybrid Search 已启用' : 'Hybrid Search 已就绪',\n )\n }\n\n p.outro('更新完成!')\n } catch (error) {\n handleError(error)\n }\n })\n}\n","import { createRequire } from 'node:module'\nimport { Command } from 'commander'\nimport { registerArchiveCommand } from './commands/archive.js'\nimport { registerInitCommand } from './commands/init.js'\nimport { registerInstructionsCommand } from './commands/instructions.js'\nimport { registerListCommand } from './commands/list.js'\nimport { registerNewCommand } from './commands/new.js'\nimport { registerSearchCommand } from './commands/search.js'\nimport { registerStatusCommand } from './commands/status.js'\nimport { registerUpdateCommand } from './commands/update.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json') as { version: string }\n\n/**\n * 构建 CLI 程序实例\n *\n * 注册所有命令并返回 Commander 程序实例\n */\nexport function buildCliProgram(): Command {\n const program = new Command()\n\n program\n .name('marchen')\n .description('Workflow harness for AI coding agents')\n .version(version, '-v, --version')\n\n registerArchiveCommand(program)\n registerInitCommand(program)\n registerInstructionsCommand(program)\n registerListCommand(program)\n registerNewCommand(program)\n registerSearchCommand(program)\n registerStatusCommand(program)\n registerUpdateCommand(program)\n\n return program\n}\n","import { buildCliProgram } from './program.js'\n\nexport { buildCliProgram } from './program.js'\n\nconst program = buildCliProgram()\nprogram.parse(process.argv)\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "marchen",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "A harness for AI coding agents — structured workflows and long-term memory",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,6 +15,14 @@
15
15
  "files": [
16
16
  "dist"
17
17
  ],
18
+ "scripts": {
19
+ "build": "tsdown --config tsdown.config.ts",
20
+ "dev": "tsdown --config tsdown.config.ts --watch",
21
+ "lint": "eslint .",
22
+ "lint:fix": "eslint . --fix",
23
+ "test": "vitest run --config vitest.config.ts",
24
+ "typecheck": "tsc --noEmit -p tsconfig.json"
25
+ },
18
26
  "dependencies": {
19
27
  "@clack/prompts": "^0.10.1",
20
28
  "@tobilu/qmd": "^2.1.0",
@@ -24,15 +32,7 @@
24
32
  "picocolors": "^1.1.1"
25
33
  },
26
34
  "devDependencies": {
27
- "@marchen/core": "0.8.0",
28
- "@marchen/shared": "0.8.0"
29
- },
30
- "scripts": {
31
- "build": "tsdown --config tsdown.config.ts",
32
- "dev": "tsdown --config tsdown.config.ts --watch",
33
- "lint": "eslint .",
34
- "lint:fix": "eslint . --fix",
35
- "test": "vitest run --config vitest.config.ts",
36
- "typecheck": "tsc --noEmit -p tsconfig.json"
35
+ "@marchen/core": "workspace:*",
36
+ "@marchen/shared": "workspace:*"
37
37
  }
38
- }
38
+ }