flower-trellis 0.4.10 → 0.4.12-beta.0

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/README.md CHANGED
@@ -131,22 +131,33 @@ flower-trellis self-check --json --target .
131
131
  - **本地一致性检查**:每次启动都会比对当前安装的 `flower-trellis` / 捆绑 Trellis 版本与项目 `.trellis/.flower-manifest.json` / `.trellis/.version`;不受 `intervalHours` 限制。
132
132
  - **远程版本探测**:访问 npm registry 读取 `flower-trellis` dist-tags;受 `intervalHours` 节流,带超时,失败静默。
133
133
 
134
- 策略和缓存统一保存在 `.trellis/.flower-manifest.json` 的 `updateCheck`:
134
+ 策略保存在 `.trellis/.flower-manifest.json` 的 `updateCheck`;运行缓存保存在 gitignored 的 `.trellis/.flower-update-check.tmp`,避免 `lastCheckedAt` 等字段反复污染 git。
135
+
136
+ manifest 中只保留用户策略:
135
137
 
136
138
  ```json
137
139
  {
138
140
  "updateCheck": {
139
141
  "enabled": true,
140
142
  "policy": "ask",
141
- "intervalHours": 8,
143
+ "intervalHours": 8
144
+ }
145
+ }
146
+ ```
147
+
148
+ tmp 中保存本地运行缓存:
149
+
150
+ ```json
151
+ {
142
152
  "lastCheckedAt": "2026-07-07T00:00:00.000Z",
143
153
  "lastRemote": { "latest": "0.4.2", "beta": null },
144
154
  "lastStatus": "update_available",
145
155
  "lastErrorCode": null
146
- }
147
156
  }
148
157
  ```
149
158
 
159
+ 旧项目如果已经在 manifest 的 `updateCheck` 里带有缓存字段,新版本会先读取兼容,并在下一次写入时清理这些旧字段。
160
+
150
161
  `policy` 可选:
151
162
 
152
163
  | policy | 行为 |
@@ -213,6 +224,10 @@ flower banner → 平台多选菜单 → Trellis 原生交互(模板 / monorepo
213
224
  npm i -g flower-trellis@latest && flower-trellis update
214
225
  ```
215
226
 
227
+ - **通用技能**:`flower-trellis update` 会用新版快照覆盖仓库中已经启用的 common skill,
228
+ 未启用项不会自动安装;若某个已安装 common skill 已从新版快照移除,更新会精确删除其
229
+ `.codex/skills` / `.claude/skills` 或历史 `.agents/skills` 副本。
230
+
216
231
  ## 开发
217
232
 
218
233
  skill-garden 强化包源以 **git submodule** 形式挂在 `vendor/skill-garden`,克隆时需一并拉取:
@@ -15,6 +15,7 @@ description: "启动、恢复和推进 Trellis 自动任务循环。用于用户
15
15
  - run 进入 `blocked` 后不要用 `start --force` 新建 run 来纠正参数;先补齐缺失 route/context,然后用 `retry-blocked` 在同一个 run 内恢复。
16
16
  - runner 默认输出是紧凑 JSON,只包含当前 action、队列计数、简短 blocked/pending/completed 列表和最近少量决策摘要;排障时才给 `status` / `resume` / `next` / `record` / `retry-blocked` 加 `--verbose` 读取完整 item、blocked detail 和 decision data。
17
17
  - 默认 profile 是 `commit-only`:自动推进到本地 commit,不 push、不发布、不归档。
18
+ - 普通 `trellis-push` 默认 commit + push 不改变 auto-loop 的授权边界;auto-loop 始终只走专用 commit-only 预授权,不得因普通流程文案而推送远端。
18
19
  - 多任务只按用户显式给出的任务顺序执行;同一 worktree 不并发。
19
20
  - 启动 runner 前先完成 route 准备度判断:已有当前任务 runtime route 决策或个人 `.trellis/.route-prefs.tmp` 时可启动;没有时先进入 `trellis-route` 正常询问 / fallback,写入真实决策后再启动。
20
21
  - auto-loop 不默认写 `route_authorization`;只有用户本次明确给出的临时 route 策略,才能通过 `--route-implement` / `--route-check` 传给 runner,且不能当成模型真实执行结果。
@@ -298,6 +298,20 @@ git log --oneline -10
298
298
 
299
299
  ---
300
300
 
301
+ ## Post-check 停止边界
302
+
303
+ 汇总报告输出后立即停止,等待用户继续。普通流程的本轮输出只允许包含:
304
+
305
+ - 各检查维度的状态与问题数
306
+ - 已执行的验证命令和结果
307
+ - 未覆盖的环境验证或剩余风险
308
+ - 总体结论
309
+ - 下一步指向现有 Phase 3.3,再到 Phase 3.4 `trellis-push`
310
+
311
+ 本轮禁止出现 commit message、`Proposed commits`、拟提交/暂存文件、commit-only 决策或“回复 `ok` 执行提交”。这些内容属于后续 Phase 3.4,并且只能由 `trellis-push` 生成。运行中的 auto-loop 仍按 runner 的 `record` + `next` 规则继续,不受普通 post-check stop 影响。
312
+
313
+ ---
314
+
301
315
  ## 修复问题(需用户确认)
302
316
 
303
317
  如果发现问题,**先展示报告,获得用户确认后再修复**。
@@ -340,3 +354,4 @@ git log --oneline -10
340
354
  - ❌ 委派给子 agent 跑各 Step(见执行模式段)
341
355
  - ❌ Complex 任务下只对照 PRD,忽略 `design.md` / `implement.md`(三层都要查)
342
356
  - ❌ 把 `implement.md` 的 validation commands 真跑算到 Step 1(Step 1 是静态对照;真跑归 Step 3 或用户执行)
357
+ - ❌ 汇总报告后自行草拟 commit message / `Proposed commits` / 文件提交计划,或要求用户回复 `ok` 提交
@@ -6,7 +6,7 @@ description: "提交并推送配置仓库,可选合并到目标分支,并同
6
6
 
7
7
  一键完成 commit → push → 可选 merge 到目标分支 → 写入任务进度快照 → 输出结果。
8
8
 
9
- 核心原则:**先计划、一次确认、后执行**。在任何 `git add`、`git commit`、`git push`、`git merge` 之前,必须先展示完整执行计划,并获得用户确认。
9
+ 核心原则:**唯一入口、先计划、一次确认、后执行**。Phase 3.4 的 commit message、文件范围和 Git 动作只能由本 skill 生成与执行;其他 skill 或主 agent 自行草拟的 `Proposed commits` 不是合法替代。在任何 `git add`、`git commit`、`git push`、`git merge` 之前,必须先展示执行计划,并获得用户确认。
10
10
 
11
11
  例外:当 `trellis-auto-loop` 已显式以 `profile=commit-only` 启动,且 runner 当前有 `outstanding_action.action=commit_only` 时,用户启动 auto-loop 本身就是本次 run 内任务相关本地提交的预授权。此时仍必须由 AI 生成并自检提交计划,但可跳过二次聊天确认;计划只允许 commit-only、不 push/merge/release/archive,并在成功后把 commit hash 回写 runner。预检不安全时只标记当前 auto-loop item blocked/skipped,后续队列仍可继续。
12
12
 
@@ -58,7 +58,7 @@ Step 6 输出结果
58
58
 
59
59
  | 语义 | 说明 | 用户怎么说 |
60
60
  |------|------|-----------|
61
- | 默认 | 自动检测所有有变更的仓库并处理 | `/trellis-push` |
61
+ | 默认 | 自动检测所有有变更的仓库,commit 后 push 当前分支;merge 仍仅按已确认配置执行 | `/trellis-push` |
62
62
  | commit-only | 只 commit 不 push,跳过 push / merge | `只提交不推` / `commit-only` |
63
63
  | 指定仓库 | 只处理指定仓库 | `只 push 前端` / `push frontend` |
64
64
  | 重新配置 | 本次允许重新指定 merge 目标,并回写 `merge_target` | `重新配置 push 目标分支` / `reconfigure push` |
@@ -104,12 +104,14 @@ python3 ./.trellis/scripts/task.py current
104
104
  python3 ./.trellis/scripts/push_snapshot.py status --json
105
105
  ```
106
106
 
107
- - **无活动任务**(命令退出码非 0 或输出为空):在计划中标记 `snapshot: 跳过(无活动任务)`。
107
+ - **无活动任务**(命令退出码非 0 或输出为空):在计划顶部标记“无活动任务”,并显示 `Spec review: 跳过`、`Snapshot: 跳过`、`Bookkeeping: 跳过`。只有当前会话能明确归属的文件可进入 planned files;其他 dirty 文件全部视为未识别并默认排除,用户明确指定范围后才重新生成计划。
108
108
  - **有活动任务**:解析输出拿到 task 路径(形如 `.trellis/tasks/MM-DD-name/`),并读取下列内容:
109
109
  - `<task_dir>/implement.md`(如有)— 步骤清单,AI 推断进度的主依据
110
110
  - `push_snapshot.py status --json` 的 `snapshot` / `summary`(如有)— 上次推送时的进度基线;若返回 `no-snapshot` / `no-current-task`,按无上次 snapshot 继续
111
111
  - `<task_dir>/task.json` 的 `base_branch` 字段 — 用于 `git log <base_branch>..HEAD` 圈定本任务 commit 范围
112
112
 
113
+ 普通活动任务进入本 skill 时,应已有现有 Phase 3.3 的最近结论。计划中显示 `Spec review: 无需更新` 或已更新的 spec 文件;如果当前上下文无法确认 Phase 3.3 结论,不要猜测,返回现有 Phase 3.3 流程后再重新进入本 skill。auto-loop 的 `run_spec_update` 结果按 runner 当前 action/context 读取。
114
+
113
115
  ---
114
116
 
115
117
  ## Step 1: 预检并收集仓库状态
@@ -138,6 +140,7 @@ git log <base_branch>..HEAD --oneline
138
140
  - 有未合并状态、rebase 状态或 merge 冲突残留:立即停止,展示状态;不要继续生成执行计划。
139
141
  - 当前分支为空、detached HEAD、或无法确认分支:停止并说明原因。
140
142
  - dirty 文件必须按来源分组:**AI 本轮编辑** 与 **未识别 dirty 文件**。未识别 dirty 文件默认不纳入提交计划。
143
+ - 无活动任务时,不能因为“仓库里都是 dirty”就把它们视为当前工作;无法从当前会话操作证据归属的文件全部进入未识别 dirty 分组。
141
144
  - 如果父仓(含 `.trellis/` 的仓库)后续要写 snapshot,先记录父仓当前 `git status --porcelain`,供 Step 5 判断:
142
145
  - 是否存在未合并 / 冲突状态;
143
146
  - 是否存在与本次 bookkeeping 无关的 staged 文件;
@@ -163,12 +166,26 @@ git log <base_branch>..HEAD --oneline
163
166
  - push 计划(默认 / commit-only / 已有 upstream / 需要 `-u`)
164
167
  - merge 计划(跳过 / 合并到配置目标 / 合并到临时目标 / 重新配置后回写)
165
168
 
169
+ 计划内部必须始终保留 exact planned files 和 exact retained/unrecognized files。紧凑排版只改变对话展示,不改变执行时的精确 `git add <files>` 集合。
170
+
166
171
  commit message 生成规则:
167
172
 
168
173
  - 读取最近 5 条 commit 参考风格。
169
174
  - 类型前缀使用 `feat` / `fix` / `docs` / `chore` / `refactor` 等。
170
175
  - 简短描述变更内容。
171
176
  - 默认使用中文描述,除非项目历史明显使用英文。
177
+ - 用户明确提供 commit message 时优先使用;除非该内容无法作为 Git message 使用,否则不要擅自改写。
178
+
179
+ ### 2.1.1 紧凑展示规则
180
+
181
+ - 顶部先显示 mode、仓库数、独立 commit 数、planned file 总数、风险数和执行顺序。
182
+ - 每个仓库按 commit message → branch/upstream → diff stat → 文件摘要 → push/merge 动作的顺序展示。
183
+ - 单仓 planned files 不超过 8 个:逐项完整展示。
184
+ - 单仓 planned files 超过 8 个:按目录归组,文件摘要区最多 12 行,并提示可回复 `展开文件`;展开时必须展示同一份 exact planned files,不能重新猜测范围。
185
+ - 未识别 dirty、已 staged、冲突、跨任务文件和其他风险文件始终逐项完整展示,不受 8 文件 / 12 行限制。
186
+ - 空分组直接省略;风险为零时只在顶部显示一次,不重复输出多行“无”。
187
+ - Spec review、最近验证结果、snapshot 和 bookkeeping 各压缩为一行;失败、部分完成或需要决策时才展开详情。没有可靠的最近验证结果时写“未提供”,不要猜测。
188
+ - 多仓库逐仓独立生成 commit message 和 Git 动作,但普通模式只确认一次。任务级 Spec review、snapshot 和父仓 bookkeeping 放在所有业务仓库之后。
172
189
 
173
190
  ### 2.2 生成 snapshot 草案
174
191
 
@@ -181,7 +198,7 @@ commit message 生成规则:
181
198
  - 上次 `last_push_snapshot`(如有)
182
199
  - 本次业务提交计划中的变更内容
183
200
 
184
- 计划中展示:
201
+ 计划内部保留以下完整 snapshot 字段:
185
202
 
186
203
  ```markdown
187
204
  任务进度快照计划:
@@ -217,51 +234,56 @@ commit message 生成规则:
217
234
 
218
235
  > `pushed_commits`、`snapshot_at`、实际 branch / commit hash 是运行后字段。用户确认的是 snapshot 的语义内容和写入动作;执行成功后由 AI 按实际结果补齐。
219
236
 
237
+ 对话计划只展示 snapshot 的语义摘要,例如“完成 Step 1-3,下一步 Step 5”;不要默认铺开完整 JSON。用户要求展开 snapshot 时再展示完整字段。
238
+
220
239
  `push_mode` 记录本轮确认的模式。默认实际 push 模式写 `"push"`;commit-only 写 `"commit-only"`;snapshot-only 写 `"snapshot-only"`;指定仓库、reconfigure 或临时目标可写对应模式名。
221
240
 
222
241
  commit-only 模式下,字段名仍保持 `pushed_commits` 以兼容恢复逻辑;值记录本次生成的本地 commit hash,`push_mode` 写 `"commit-only"`,并在 `notes` 中注明“commit-only:本地已提交,未推送”。
223
242
 
224
243
  ### 2.3 展示确认模板
225
244
 
226
- 使用以下格式向用户展示完整计划:
245
+ 使用以下格式向用户展示计划:
227
246
 
228
247
  ```markdown
229
- ## trellis-push 执行计划
248
+ ## Trellis Push 计划
249
+
250
+ [<PUSH / COMMIT-ONLY / SNAPSHOT>] <N> 个仓库 · <N> 个 commit · <N> 个文件 · 风险 <N>
251
+ [无活动任务时追加:无活动任务]
252
+ 顺序:<repo-a> -> <repo-b> -> <任务记录,若有>
230
253
 
231
- 模式:<默认 / commit-only / snapshot-only / 指定仓库 / reconfigure / 临时目标>
254
+ ### 1. <package>
232
255
 
233
- ### 业务仓库
256
+ `<type>(<scope>): <description>`
234
257
 
235
- 1. <package> (`<path>`)
236
- - 分支:<current_branch> -> <upstream origin/current_branch>
237
- - AI 本轮编辑,拟提交:
238
- - <file-a>
239
- - <file-b>
240
- - 未识别 dirty,默认不提交:
241
- - <unknown-file 或 无>
242
- - 用户要求纳入的未识别文件:
243
- - <file 或 无>
244
- - commit message:`<type>(<scope>): <description>`
245
- - push:<执行 / 跳过(commit-only)>
246
- - merge:<跳过 / 合并到 target / 临时合并到 target / 重新配置为 target>
258
+ 分支:`<current_branch>` -> `<upstream 或 origin/current_branch>`
259
+ 变更:<N> 个文件 · `+<adds> -<deletes>`
247
260
 
248
- ### 任务快照
261
+ <不超过 8 个时逐项列出;超过 8 个时按目录归组,最多 12 行,并提示“回复 `展开文件` 查看精确列表”>
249
262
 
250
- - <跳过原因,或 snapshot 草案>
263
+ Push:<执行 / 跳过(commit-only)>
264
+ Merge:<仅计划中需要时显示>
251
265
 
252
- ### 父仓 bookkeeping
266
+ ### 风险(仅风险数大于 0 时显示)
253
267
 
254
- - <跳过原因,或仅提交 `<task_dir>/task.json`>
255
- - 无关未暂存 dirty:<list 或 无>(保留未提交,不阻塞 snapshot commit)
256
- - 无关 staged / 冲突 / 目标文件预脏:<list 或 无>(有则停止,需处理后重新计划)
268
+ - <所有未识别 dirty / staged / conflict / cross-task 文件逐项列出>
257
269
 
258
- 确认后将按上述计划执行。回复 `ok` / `行` / `确认` 执行;回复 `skip snapshot` 跳过快照;回复修改意见则先更新计划;回复 `manual` / `我自己来` 则停止。
270
+ ### 任务上下文
271
+
272
+ Spec review:<无需更新 / 已更新 <spec 文件> / 跳过(无活动任务)>
273
+ 验证:<最近可靠汇总 / 未提供>
274
+ Snapshot:<语义摘要 / 跳过(无活动任务)>
275
+ Bookkeeping:<计划摘要 / 跳过(无活动任务)>
276
+
277
+ 执行:<commit -> push -> 可选 merge -> snapshot/bookkeeping>
278
+
279
+ 确认执行请回复 `确认`。可调整:`只提交`、`修改 message`、`展开文件`、`跳过 snapshot`、`manual`。
259
280
  ```
260
281
 
261
282
  确认规则:
262
283
 
263
284
  - 用户确认前不得暂存。
264
285
  - 用户修改 commit message、merge 目标或 snapshot 草案时,更新计划并重新展示确认。
286
+ - 用户请求 `展开文件` 时只展开当前计划的 exact planned files,不改变计划,也不视为执行确认。
265
287
  - 用户要求纳入未识别 dirty 文件时,必须明确列出这些文件后重新确认。
266
288
  - 用户拒绝文件范围或选择手动处理时,立即停止,不执行任何 git 写操作。
267
289
 
@@ -431,19 +453,24 @@ git remote -v | grep -E "^origin\s+"
431
453
 
432
454
  ## Step 6: 输出结果
433
455
 
434
- 使用结果表汇总:
456
+ 结果复用计划的视觉顺序,先给总览,再逐仓报告实际 commit/push,最后报告任务记录与保留文件:
435
457
 
436
458
  ```markdown
437
- ## Push 结果
459
+ ## Trellis Push 结果
460
+
461
+ [完成 / 部分完成] <N> 个仓库 · <N> 个 commit
462
+
463
+ ### 1. <package>
464
+
465
+ `<short-hash> <actual commit message>`
466
+ 分支:`<branch>` -> `<upstream>`
467
+ 状态:<已推送 / 仅本地提交 / 已合并 / 失败>
438
468
 
439
- | 仓库 | 分支 | 目标 | commit | 状态 |
440
- |------|------|------|--------|------|
441
- | frontend | v1.3 | test | abc1234 feat(...): ... | 已合并 |
442
- | backend | v1.3 | - | def5678 fix(...): ... | 已推送,跳过合并 |
469
+ ### 任务上下文
443
470
 
444
- 任务进度快照:已写入 `<task_dir>/task.json`,完成 Step 1-3,下一步 Step 5。
445
- 父仓同步:已提交并推送 `chore(task): update <task_name> push snapshot`。
446
- 父仓存在未暂存 dirty 时补充:这些文件已保留未暂存,未纳入 snapshot commit:<list>
471
+ Snapshot:<已写入摘要 / 跳过>
472
+ Bookkeeping:<commit/push 摘要 / 跳过>
473
+ 保留未提交:<无则省略;有则逐项列出>
447
474
  ```
448
475
 
449
476
  如果部分仓库已成功、后续失败,必须明确列出:
@@ -474,6 +501,9 @@ git remote -v | grep -E "^origin\s+"
474
501
  - ❌ `git add -A` 或 `git add .`
475
502
  - ❌ 把未识别 dirty 文件静默纳入提交
476
503
  - ❌ commit message 未展示给用户就提交
504
+ - ❌ 在 `trellis-push` 外把 check 报告改造成 `Proposed commits`,或自行选择 commit-only
505
+ - ❌ planned files 超过 8 个时无限铺开普通文件,或折叠任何风险文件
506
+ - ❌ 无活动任务时把无法证明来源的 dirty 文件默认纳入计划
477
507
  - ❌ push 后临时追问 merge,而不是在计划中提前确认
478
508
  - ❌ snapshot 语义内容未确认就写入 task.json
479
509
  - ❌ 父仓 snapshot commit 混入业务代码提交
@@ -15,6 +15,7 @@ description: "启动、恢复和推进 Trellis 自动任务循环。用于用户
15
15
  - run 进入 `blocked` 后不要用 `start --force` 新建 run 来纠正参数;先补齐缺失 route/context,然后用 `retry-blocked` 在同一个 run 内恢复。
16
16
  - runner 默认输出是紧凑 JSON,只包含当前 action、队列计数、简短 blocked/pending/completed 列表和最近少量决策摘要;排障时才给 `status` / `resume` / `next` / `record` / `retry-blocked` 加 `--verbose` 读取完整 item、blocked detail 和 decision data。
17
17
  - 默认 profile 是 `commit-only`:自动推进到本地 commit,不 push、不发布、不归档。
18
+ - 普通 `trellis-push` 默认 commit + push 不改变 auto-loop 的授权边界;auto-loop 始终只走专用 commit-only 预授权,不得因普通流程文案而推送远端。
18
19
  - 多任务只按用户显式给出的任务顺序执行;同一 worktree 不并发。
19
20
  - 启动 runner 前先完成 route 准备度判断:已有当前任务 runtime route 决策或个人 `.trellis/.route-prefs.tmp` 时可启动;没有时先进入 `trellis-route` 正常询问 / fallback,写入真实决策后再启动。
20
21
  - auto-loop 不默认写 `route_authorization`;只有用户本次明确给出的临时 route 策略,才能通过 `--route-implement` / `--route-check` 传给 runner,且不能当成模型真实执行结果。
@@ -298,6 +298,20 @@ git log --oneline -10
298
298
 
299
299
  ---
300
300
 
301
+ ## Post-check 停止边界
302
+
303
+ 汇总报告输出后立即停止,等待用户继续。普通流程的本轮输出只允许包含:
304
+
305
+ - 各检查维度的状态与问题数
306
+ - 已执行的验证命令和结果
307
+ - 未覆盖的环境验证或剩余风险
308
+ - 总体结论
309
+ - 下一步指向现有 Phase 3.3,再到 Phase 3.4 `trellis-push`
310
+
311
+ 本轮禁止出现 commit message、`Proposed commits`、拟提交/暂存文件、commit-only 决策或“回复 `ok` 执行提交”。这些内容属于后续 Phase 3.4,并且只能由 `trellis-push` 生成。运行中的 auto-loop 仍按 runner 的 `record` + `next` 规则继续,不受普通 post-check stop 影响。
312
+
313
+ ---
314
+
301
315
  ## 修复问题(需用户确认)
302
316
 
303
317
  如果发现问题,**先展示报告,获得用户确认后再修复**。
@@ -340,3 +354,4 @@ git log --oneline -10
340
354
  - ❌ 委派给子 agent 跑各 Step(见执行模式段)
341
355
  - ❌ Complex 任务下只对照 PRD,忽略 `design.md` / `implement.md`(三层都要查)
342
356
  - ❌ 把 `implement.md` 的 validation commands 真跑算到 Step 1(Step 1 是静态对照;真跑归 Step 3 或用户执行)
357
+ - ❌ 汇总报告后自行草拟 commit message / `Proposed commits` / 文件提交计划,或要求用户回复 `ok` 提交
@@ -6,7 +6,7 @@ description: "提交并推送配置仓库,可选合并到目标分支,并同
6
6
 
7
7
  一键完成 commit → push → 可选 merge 到目标分支 → 写入任务进度快照 → 输出结果。
8
8
 
9
- 核心原则:**先计划、一次确认、后执行**。在任何 `git add`、`git commit`、`git push`、`git merge` 之前,必须先展示完整执行计划,并获得用户确认。
9
+ 核心原则:**唯一入口、先计划、一次确认、后执行**。Phase 3.4 的 commit message、文件范围和 Git 动作只能由本 skill 生成与执行;其他 skill 或主 agent 自行草拟的 `Proposed commits` 不是合法替代。在任何 `git add`、`git commit`、`git push`、`git merge` 之前,必须先展示执行计划,并获得用户确认。
10
10
 
11
11
  例外:当 `trellis-auto-loop` 已显式以 `profile=commit-only` 启动,且 runner 当前有 `outstanding_action.action=commit_only` 时,用户启动 auto-loop 本身就是本次 run 内任务相关本地提交的预授权。此时仍必须由 AI 生成并自检提交计划,但可跳过二次聊天确认;计划只允许 commit-only、不 push/merge/release/archive,并在成功后把 commit hash 回写 runner。预检不安全时只标记当前 auto-loop item blocked/skipped,后续队列仍可继续。
12
12
 
@@ -58,7 +58,7 @@ Step 6 输出结果
58
58
 
59
59
  | 语义 | 说明 | 用户怎么说 |
60
60
  |------|------|-----------|
61
- | 默认 | 自动检测所有有变更的仓库并处理 | `/trellis-push` |
61
+ | 默认 | 自动检测所有有变更的仓库,commit 后 push 当前分支;merge 仍仅按已确认配置执行 | `/trellis-push` |
62
62
  | commit-only | 只 commit 不 push,跳过 push / merge | `只提交不推` / `commit-only` |
63
63
  | 指定仓库 | 只处理指定仓库 | `只 push 前端` / `push frontend` |
64
64
  | 重新配置 | 本次允许重新指定 merge 目标,并回写 `merge_target` | `重新配置 push 目标分支` / `reconfigure push` |
@@ -104,12 +104,14 @@ python3 ./.trellis/scripts/task.py current
104
104
  python3 ./.trellis/scripts/push_snapshot.py status --json
105
105
  ```
106
106
 
107
- - **无活动任务**(命令退出码非 0 或输出为空):在计划中标记 `snapshot: 跳过(无活动任务)`。
107
+ - **无活动任务**(命令退出码非 0 或输出为空):在计划顶部标记“无活动任务”,并显示 `Spec review: 跳过`、`Snapshot: 跳过`、`Bookkeeping: 跳过`。只有当前会话能明确归属的文件可进入 planned files;其他 dirty 文件全部视为未识别并默认排除,用户明确指定范围后才重新生成计划。
108
108
  - **有活动任务**:解析输出拿到 task 路径(形如 `.trellis/tasks/MM-DD-name/`),并读取下列内容:
109
109
  - `<task_dir>/implement.md`(如有)— 步骤清单,AI 推断进度的主依据
110
110
  - `push_snapshot.py status --json` 的 `snapshot` / `summary`(如有)— 上次推送时的进度基线;若返回 `no-snapshot` / `no-current-task`,按无上次 snapshot 继续
111
111
  - `<task_dir>/task.json` 的 `base_branch` 字段 — 用于 `git log <base_branch>..HEAD` 圈定本任务 commit 范围
112
112
 
113
+ 普通活动任务进入本 skill 时,应已有现有 Phase 3.3 的最近结论。计划中显示 `Spec review: 无需更新` 或已更新的 spec 文件;如果当前上下文无法确认 Phase 3.3 结论,不要猜测,返回现有 Phase 3.3 流程后再重新进入本 skill。auto-loop 的 `run_spec_update` 结果按 runner 当前 action/context 读取。
114
+
113
115
  ---
114
116
 
115
117
  ## Step 1: 预检并收集仓库状态
@@ -138,6 +140,7 @@ git log <base_branch>..HEAD --oneline
138
140
  - 有未合并状态、rebase 状态或 merge 冲突残留:立即停止,展示状态;不要继续生成执行计划。
139
141
  - 当前分支为空、detached HEAD、或无法确认分支:停止并说明原因。
140
142
  - dirty 文件必须按来源分组:**AI 本轮编辑** 与 **未识别 dirty 文件**。未识别 dirty 文件默认不纳入提交计划。
143
+ - 无活动任务时,不能因为“仓库里都是 dirty”就把它们视为当前工作;无法从当前会话操作证据归属的文件全部进入未识别 dirty 分组。
141
144
  - 如果父仓(含 `.trellis/` 的仓库)后续要写 snapshot,先记录父仓当前 `git status --porcelain`,供 Step 5 判断:
142
145
  - 是否存在未合并 / 冲突状态;
143
146
  - 是否存在与本次 bookkeeping 无关的 staged 文件;
@@ -163,12 +166,26 @@ git log <base_branch>..HEAD --oneline
163
166
  - push 计划(默认 / commit-only / 已有 upstream / 需要 `-u`)
164
167
  - merge 计划(跳过 / 合并到配置目标 / 合并到临时目标 / 重新配置后回写)
165
168
 
169
+ 计划内部必须始终保留 exact planned files 和 exact retained/unrecognized files。紧凑排版只改变对话展示,不改变执行时的精确 `git add <files>` 集合。
170
+
166
171
  commit message 生成规则:
167
172
 
168
173
  - 读取最近 5 条 commit 参考风格。
169
174
  - 类型前缀使用 `feat` / `fix` / `docs` / `chore` / `refactor` 等。
170
175
  - 简短描述变更内容。
171
176
  - 默认使用中文描述,除非项目历史明显使用英文。
177
+ - 用户明确提供 commit message 时优先使用;除非该内容无法作为 Git message 使用,否则不要擅自改写。
178
+
179
+ ### 2.1.1 紧凑展示规则
180
+
181
+ - 顶部先显示 mode、仓库数、独立 commit 数、planned file 总数、风险数和执行顺序。
182
+ - 每个仓库按 commit message → branch/upstream → diff stat → 文件摘要 → push/merge 动作的顺序展示。
183
+ - 单仓 planned files 不超过 8 个:逐项完整展示。
184
+ - 单仓 planned files 超过 8 个:按目录归组,文件摘要区最多 12 行,并提示可回复 `展开文件`;展开时必须展示同一份 exact planned files,不能重新猜测范围。
185
+ - 未识别 dirty、已 staged、冲突、跨任务文件和其他风险文件始终逐项完整展示,不受 8 文件 / 12 行限制。
186
+ - 空分组直接省略;风险为零时只在顶部显示一次,不重复输出多行“无”。
187
+ - Spec review、最近验证结果、snapshot 和 bookkeeping 各压缩为一行;失败、部分完成或需要决策时才展开详情。没有可靠的最近验证结果时写“未提供”,不要猜测。
188
+ - 多仓库逐仓独立生成 commit message 和 Git 动作,但普通模式只确认一次。任务级 Spec review、snapshot 和父仓 bookkeeping 放在所有业务仓库之后。
172
189
 
173
190
  ### 2.2 生成 snapshot 草案
174
191
 
@@ -181,7 +198,7 @@ commit message 生成规则:
181
198
  - 上次 `last_push_snapshot`(如有)
182
199
  - 本次业务提交计划中的变更内容
183
200
 
184
- 计划中展示:
201
+ 计划内部保留以下完整 snapshot 字段:
185
202
 
186
203
  ```markdown
187
204
  任务进度快照计划:
@@ -217,51 +234,56 @@ commit message 生成规则:
217
234
 
218
235
  > `pushed_commits`、`snapshot_at`、实际 branch / commit hash 是运行后字段。用户确认的是 snapshot 的语义内容和写入动作;执行成功后由 AI 按实际结果补齐。
219
236
 
237
+ 对话计划只展示 snapshot 的语义摘要,例如“完成 Step 1-3,下一步 Step 5”;不要默认铺开完整 JSON。用户要求展开 snapshot 时再展示完整字段。
238
+
220
239
  `push_mode` 记录本轮确认的模式。默认实际 push 模式写 `"push"`;commit-only 写 `"commit-only"`;snapshot-only 写 `"snapshot-only"`;指定仓库、reconfigure 或临时目标可写对应模式名。
221
240
 
222
241
  commit-only 模式下,字段名仍保持 `pushed_commits` 以兼容恢复逻辑;值记录本次生成的本地 commit hash,`push_mode` 写 `"commit-only"`,并在 `notes` 中注明“commit-only:本地已提交,未推送”。
223
242
 
224
243
  ### 2.3 展示确认模板
225
244
 
226
- 使用以下格式向用户展示完整计划:
245
+ 使用以下格式向用户展示计划:
227
246
 
228
247
  ```markdown
229
- ## trellis-push 执行计划
248
+ ## Trellis Push 计划
249
+
250
+ [<PUSH / COMMIT-ONLY / SNAPSHOT>] <N> 个仓库 · <N> 个 commit · <N> 个文件 · 风险 <N>
251
+ [无活动任务时追加:无活动任务]
252
+ 顺序:<repo-a> -> <repo-b> -> <任务记录,若有>
230
253
 
231
- 模式:<默认 / commit-only / snapshot-only / 指定仓库 / reconfigure / 临时目标>
254
+ ### 1. <package>
232
255
 
233
- ### 业务仓库
256
+ `<type>(<scope>): <description>`
234
257
 
235
- 1. <package> (`<path>`)
236
- - 分支:<current_branch> -> <upstream origin/current_branch>
237
- - AI 本轮编辑,拟提交:
238
- - <file-a>
239
- - <file-b>
240
- - 未识别 dirty,默认不提交:
241
- - <unknown-file 或 无>
242
- - 用户要求纳入的未识别文件:
243
- - <file 或 无>
244
- - commit message:`<type>(<scope>): <description>`
245
- - push:<执行 / 跳过(commit-only)>
246
- - merge:<跳过 / 合并到 target / 临时合并到 target / 重新配置为 target>
258
+ 分支:`<current_branch>` -> `<upstream 或 origin/current_branch>`
259
+ 变更:<N> 个文件 · `+<adds> -<deletes>`
247
260
 
248
- ### 任务快照
261
+ <不超过 8 个时逐项列出;超过 8 个时按目录归组,最多 12 行,并提示“回复 `展开文件` 查看精确列表”>
249
262
 
250
- - <跳过原因,或 snapshot 草案>
263
+ Push:<执行 / 跳过(commit-only)>
264
+ Merge:<仅计划中需要时显示>
251
265
 
252
- ### 父仓 bookkeeping
266
+ ### 风险(仅风险数大于 0 时显示)
253
267
 
254
- - <跳过原因,或仅提交 `<task_dir>/task.json`>
255
- - 无关未暂存 dirty:<list 或 无>(保留未提交,不阻塞 snapshot commit)
256
- - 无关 staged / 冲突 / 目标文件预脏:<list 或 无>(有则停止,需处理后重新计划)
268
+ - <所有未识别 dirty / staged / conflict / cross-task 文件逐项列出>
257
269
 
258
- 确认后将按上述计划执行。回复 `ok` / `行` / `确认` 执行;回复 `skip snapshot` 跳过快照;回复修改意见则先更新计划;回复 `manual` / `我自己来` 则停止。
270
+ ### 任务上下文
271
+
272
+ Spec review:<无需更新 / 已更新 <spec 文件> / 跳过(无活动任务)>
273
+ 验证:<最近可靠汇总 / 未提供>
274
+ Snapshot:<语义摘要 / 跳过(无活动任务)>
275
+ Bookkeeping:<计划摘要 / 跳过(无活动任务)>
276
+
277
+ 执行:<commit -> push -> 可选 merge -> snapshot/bookkeeping>
278
+
279
+ 确认执行请回复 `确认`。可调整:`只提交`、`修改 message`、`展开文件`、`跳过 snapshot`、`manual`。
259
280
  ```
260
281
 
261
282
  确认规则:
262
283
 
263
284
  - 用户确认前不得暂存。
264
285
  - 用户修改 commit message、merge 目标或 snapshot 草案时,更新计划并重新展示确认。
286
+ - 用户请求 `展开文件` 时只展开当前计划的 exact planned files,不改变计划,也不视为执行确认。
265
287
  - 用户要求纳入未识别 dirty 文件时,必须明确列出这些文件后重新确认。
266
288
  - 用户拒绝文件范围或选择手动处理时,立即停止,不执行任何 git 写操作。
267
289
 
@@ -431,19 +453,24 @@ git remote -v | grep -E "^origin\s+"
431
453
 
432
454
  ## Step 6: 输出结果
433
455
 
434
- 使用结果表汇总:
456
+ 结果复用计划的视觉顺序,先给总览,再逐仓报告实际 commit/push,最后报告任务记录与保留文件:
435
457
 
436
458
  ```markdown
437
- ## Push 结果
459
+ ## Trellis Push 结果
460
+
461
+ [完成 / 部分完成] <N> 个仓库 · <N> 个 commit
462
+
463
+ ### 1. <package>
464
+
465
+ `<short-hash> <actual commit message>`
466
+ 分支:`<branch>` -> `<upstream>`
467
+ 状态:<已推送 / 仅本地提交 / 已合并 / 失败>
438
468
 
439
- | 仓库 | 分支 | 目标 | commit | 状态 |
440
- |------|------|------|--------|------|
441
- | frontend | v1.3 | test | abc1234 feat(...): ... | 已合并 |
442
- | backend | v1.3 | - | def5678 fix(...): ... | 已推送,跳过合并 |
469
+ ### 任务上下文
443
470
 
444
- 任务进度快照:已写入 `<task_dir>/task.json`,完成 Step 1-3,下一步 Step 5。
445
- 父仓同步:已提交并推送 `chore(task): update <task_name> push snapshot`。
446
- 父仓存在未暂存 dirty 时补充:这些文件已保留未暂存,未纳入 snapshot commit:<list>
471
+ Snapshot:<已写入摘要 / 跳过>
472
+ Bookkeeping:<commit/push 摘要 / 跳过>
473
+ 保留未提交:<无则省略;有则逐项列出>
447
474
  ```
448
475
 
449
476
  如果部分仓库已成功、后续失败,必须明确列出:
@@ -474,6 +501,9 @@ git remote -v | grep -E "^origin\s+"
474
501
  - ❌ `git add -A` 或 `git add .`
475
502
  - ❌ 把未识别 dirty 文件静默纳入提交
476
503
  - ❌ commit message 未展示给用户就提交
504
+ - ❌ 在 `trellis-push` 外把 check 报告改造成 `Proposed commits`,或自行选择 commit-only
505
+ - ❌ planned files 超过 8 个时无限铺开普通文件,或折叠任何风险文件
506
+ - ❌ 无活动任务时把无法证明来源的 dirty 文件默认纳入计划
477
507
  - ❌ push 后临时追问 merge,而不是在计划中提前确认
478
508
  - ❌ snapshot 语义内容未确认就写入 task.json
479
509
  - ❌ 父仓 snapshot commit 混入业务代码提交
@@ -7,8 +7,9 @@ At project-local knowledge boundaries, run `python3 ./.trellis/scripts/spec_rout
7
7
  Inline workflow-state is not an inline route decision. Phase 2.1/2.2 must reuse explicit target-matched `route_decision`; otherwise invoke `trellis-route`. If unavailable, read local `trellis-route/SKILL.md`, show numbered choices, and wait.
8
8
  Summaries, preferences, `codex-mode`, raw `.runtime`, and empty/stale prefs are not route evidence unless `trellis-route` validates them; user reselect/override wins.
9
9
  Ignore lower direct-edit/check shortcuts. Do not default inline just because this state is inline or helper is unavailable. Dispatch subagents only when route selected subagent.
10
- After `trellis-check` / `trellis-check-all`, stop and report; point the user to Phase 3.4 `trellis-push` (or commit-only when needed). Do not run `/trellis:finish-work` unless the user explicitly asks after Phase 3.4 is complete.
10
+ After `trellis-check` / `trellis-check-all`, stop and report only check results, validations, residual risks, conclusion, and next steps; do not draft commit messages/files or ask for commit confirmation. Point the user to the existing Phase 3.3 flow and then Phase 3.4 `trellis-push`. Do not run `/trellis:finish-work` unless the user explicitly asks after Phase 3.4 is complete.
11
11
  This guard overrides any lower `Flow: ... -> /trellis:finish-work` line in this state block.
12
- At Phase 3.4, code commit/push goes through `trellis-push` (commit-only mode for commit-without-push); never bare `git commit`/`git push` on code (hub: Code Commit Confirmation Gate).
12
+ At Phase 3.4, load `trellis-push`; ordinary mode defaults to commit + push, and commit-only requires explicit user intent or valid auto-loop preauthorization. Never synthesize a substitute commit plan or run bare `git commit`/`git push` on code (hub: Code Commit Confirmation Gate).
13
+ This guard fully disables the lower Phase 3.4 `Proposed commits` / local-only / no-push walkthrough; do not reuse any part of it.
13
14
  Push snapshot recovery: follow the hub; use `push_snapshot.py status --json` only when needed.
14
15
  <!-- END skill-garden workflow-state in_progress_inline v0.6 -->
@@ -7,8 +7,9 @@ At project-local knowledge boundaries, run `python3 ./.trellis/scripts/spec_rout
7
7
  Phase 2.1/2.2: reuse only explicit target-matched `route_decision`; otherwise invoke `trellis-route`. If skill invocation is unavailable, read local `trellis-route/SKILL.md`, show numbered choices, and wait.
8
8
  Summaries, preferences, `codex-mode`, raw `.runtime`, and empty/stale prefs are not route evidence unless `trellis-route` validates them; user reselect/override wins.
9
9
  Ignore lower direct-dispatch shortcuts. Do not spawn `trellis-implement` or `trellis-check*` unless route selected subagent. If route cannot be resolved, do not default inline.
10
- After `trellis-check` / `trellis-check-all`, stop and report; point the user to Phase 3.4 `trellis-push` (or commit-only when needed). Do not run `/trellis:finish-work` unless the user explicitly asks after Phase 3.4 is complete.
10
+ After `trellis-check` / `trellis-check-all`, stop and report only check results, validations, residual risks, conclusion, and next steps; do not draft commit messages/files or ask for commit confirmation. Point the user to the existing Phase 3.3 flow and then Phase 3.4 `trellis-push`. Do not run `/trellis:finish-work` unless the user explicitly asks after Phase 3.4 is complete.
11
11
  This guard overrides any lower `Flow: ... -> /trellis:finish-work` line in this state block.
12
- At Phase 3.4, code commit/push goes through `trellis-push` (commit-only mode for commit-without-push); never bare `git commit`/`git push` on code (hub: Code Commit Confirmation Gate).
12
+ At Phase 3.4, load `trellis-push`; ordinary mode defaults to commit + push, and commit-only requires explicit user intent or valid auto-loop preauthorization. Never synthesize a substitute commit plan or run bare `git commit`/`git push` on code (hub: Code Commit Confirmation Gate).
13
+ This guard fully disables the lower Phase 3.4 `Proposed commits` / local-only / no-push walkthrough; do not reuse any part of it.
13
14
  Push snapshot recovery: follow the hub; use `push_snapshot.py status --json` only when needed.
14
15
  <!-- END skill-garden workflow-state in_progress v0.6 -->
@@ -84,13 +84,19 @@ At Phase 2.1/2.2, this gate overrides lower "Active Task Routing" rows that say
84
84
 
85
85
  After `trellis-check` or `trellis-check-all` finishes, stop and report the result. If checks pass, the next allowed workflow steps are Phase 3.3 `trellis-update-spec` and Phase 3.4 `trellis-push`/commit confirmation (commit-only when needed); do not archive the task or imply it is ready to wrap up solely because checks passed. `/trellis:finish-work` is explicit-only: run it only after Phase 3.4 is complete and the user asks to wrap up, archive, or finish the task.
86
86
 
87
+ The ordinary post-check report may contain only check dimensions/results, executed validations, residual risks, the conclusion, and the next-step pointer. It must not draft a commit message, show `Proposed commits` or planned/staged files, choose commit-only, ask the user to reply `ok` to commit, or perform Phase 3.3/3.4 work. Stop after the report and wait for the user to continue.
88
+
87
89
  During a running `trellis-auto-loop`, the runner's `record` + `next` replaces the post-check stop gate: after a check pass, record the result, then continue to spec update / commit-only according to `.trellis/scripts/auto_loop.py`. Outside auto-loop, keep the normal stop gate.
88
90
 
89
91
  #### Code Commit Confirmation Gate
90
92
 
91
93
  Code commit/push belongs only to Phase 3.4 and must go through `trellis-push`; the main session must not run bare `git commit` / `git push` for code.
92
94
 
93
- `trellis-push` confirmation must show both the exact file list to stage and the drafted commit message. Before the user approves that concrete list + message, do not `git add`, commit, or push; never use `git add -A` / `git add .`.
95
+ Entering Phase 3.4 means loading and following `trellis-push`; drafting a commit message or a file plan outside that skill is not an equivalent substitute. Ordinary `trellis-push` defaults to commit + push. Use commit-only only when the user explicitly asks for commit without push, or when a running auto-loop satisfies the dedicated commit-only preauthorization gate.
96
+
97
+ This gate fully supersedes the lower Phase 3.4 walkthrough that drafts `Proposed commits`, runs local commits directly, or says never to push. Under skill-garden, treat that lower walkthrough as inactive; do not mix any of its plan, confirmation, or execution steps with `trellis-push`.
98
+
99
+ `trellis-push` confirmation must be backed by an exact file set and include the drafted commit message. For each repository, display all planned files when there are at most 8; above 8, group the ordinary planned files by directory in at most 12 file-summary lines and offer `展开文件` to show the same exact set. Unrecognized dirty, staged, conflicted, cross-task, and other risk files must always be shown individually and never folded. Before the user approves that concrete scope + message, do not `git add`, commit, or push; never use `git add -A` / `git add .`.
94
100
 
95
101
  For "commit now, push later", use `trellis-push` commit-only mode; the later push still goes through `trellis-push`. `session_auto_commit` never authorizes code commits; it only affects bookkeeping commits below.
96
102
 
@@ -1,7 +1,7 @@
1
1
  {
2
- "syncedAt": "2026-07-08T03:53:14.101Z",
2
+ "syncedAt": "2026-07-13T10:24:09.928Z",
3
3
  "syncedFrom": "vendor/skill-garden",
4
- "sourceCommit": "6ecfb1b46320d82aeb3e83442235f271d92aa398",
4
+ "sourceCommit": "a020128baca91d8f704ec2b7ab24e9d7719fb113",
5
5
  "common": {
6
6
  "codexSkills": [
7
7
  "craft-rpa",
@@ -16,6 +16,9 @@
16
16
  "humanize-writing",
17
17
  "open-idea",
18
18
  "torrent-analyze"
19
+ ],
20
+ "removedSkills": [
21
+ "sub2api-account-json-fix"
19
22
  ]
20
23
  },
21
24
  "variants": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flower-trellis",
3
- "version": "0.4.10",
3
+ "version": "0.4.12-beta.0",
4
4
  "description": "一键安装/升级 Trellis 并自动融合 skill-garden 强化包(默认 Claude + agents)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -58,9 +58,9 @@
58
58
  "commit-and-tag-version": "^12.7.3"
59
59
  },
60
60
  "flowerReleaseNotes": {
61
- "version": "0.4.10",
61
+ "version": "0.4.12-beta.0",
62
62
  "source": "CHANGELOG.md",
63
- "body": "### 🐛 修复 Bug Fixes\n\n* **flower:** 项目侧待更新提示会显示对应版本更新摘要 ([cc98c02](https://github.com/SilentFlower/flower-trellis/commit/cc98c021eb0536f693f50651854093a4795d9e93))",
63
+ "body": "### 新功能 Features\n\n* **skill:** 优化 Trellis 提交与推送流程 ([4359607](https://github.com/SilentFlower/flower-trellis/commit/4359607156502e378ef83302105ca9f0e4f0f1ac))\n* **skill:** 自动同步已启用的 common skill ([8b36bc7](https://github.com/SilentFlower/flower-trellis/commit/8b36bc7d24e76dd8973c35378e7bb6d9c8a60ff3))",
64
64
  "truncated": false
65
65
  }
66
66
  }
@@ -4,6 +4,7 @@ import {
4
4
  manifestPath,
5
5
  readManifest,
6
6
  readUpdateCheck,
7
+ updateCheckCachePath,
7
8
  writeUpdateCheck,
8
9
  } from "../lib/manifest.js";
9
10
 
@@ -24,7 +25,7 @@ function assertTrellisProject(target) {
24
25
  }
25
26
 
26
27
  /**
27
- * flower-trellis update-check:管理 manifest 内的启动更新检查策略。
28
+ * flower-trellis update-check:管理启动更新检查策略,并展示本地运行缓存。
28
29
  *
29
30
  * @param {object} ctx 见 cli.js 的 parse()
30
31
  */
@@ -35,9 +36,14 @@ export async function updateCheck(ctx) {
35
36
 
36
37
  if (action === "get") {
37
38
  const manifest = readManifest(ctx.target);
39
+ const cachePath = updateCheckCachePath(ctx.target);
38
40
  console.log(`manifest: ${manifestPath(ctx.target)}`);
41
+ console.log(`cache: ${cachePath}`);
39
42
  console.log(JSON.stringify(readUpdateCheck(ctx.target), null, 2));
40
43
  if (!manifest) console.log(" · manifest 不存在,当前显示默认策略");
44
+ if (!fs.existsSync(cachePath)) {
45
+ console.log(" · cache 不存在,当前显示默认或旧 manifest 兼容缓存");
46
+ }
41
47
  return;
42
48
  }
43
49
 
@@ -12,6 +12,7 @@ import { readManifest, writeManifest } from "./manifest.js";
12
12
  import { flowerVersion } from "./versions.js";
13
13
  import { rmrf } from "./fs-utils.js";
14
14
  import { resolveEnhancementSnapshot } from "./enhancement-catalog.js";
15
+ import { syncInstalledCommonSkills } from "./skill-catalog.js";
15
16
 
16
17
  /** 清理升级后可能变空的强化目录(深 → 浅)。 */
17
18
  function pruneEmptyDirs(target) {
@@ -80,11 +81,27 @@ export function applyEnhancements(target, opts = {}) {
80
81
  ` ✓ 铺设 ${installed.length} 个强化项 → ${where.join(" + ") || "(无平台目录)"}`,
81
82
  );
82
83
 
84
+ let commonPaths = [];
85
+ if (skills.length === 0) {
86
+ const common = syncInstalledCommonSkills(target);
87
+ commonPaths = common.refreshedPaths;
88
+ if (common.refreshed.length > 0 || common.removed.length > 0) {
89
+ console.log(
90
+ ` ✓ common skill 已同步:刷新 ${common.refreshed.length} 个,` +
91
+ `清理 ${common.removed.length} 个`,
92
+ );
93
+ } else {
94
+ console.log(" · 未发现已启用或待清理的 common skill");
95
+ }
96
+ }
97
+
83
98
  // 升级清理 + manifest(仅全装时维护)
84
99
  if (skills.length === 0) {
85
100
  const old = readManifest(target);
86
101
  if (old && Array.isArray(old.paths)) {
87
- const keep = new Set(newPaths);
102
+ // 历史版本可能把后来迁入 common skill 记在 paths 中。本轮已确认仍启用的
103
+ // common 路径只用于阻止旧 manifest 误删;新 manifest 仍不接管这些可选技能。
104
+ const keep = new Set([...newPaths, ...commonPaths]);
88
105
  const stale = old.paths.filter((p) => !keep.has(p));
89
106
  let removed = 0;
90
107
  for (const rel of stale) {
@@ -11,17 +11,56 @@ import path from "node:path";
11
11
  * 放在 .trellis/ 下,随项目的 Trellis 生命周期存在(uninstall 删 .trellis 时一并消失)。
12
12
  */
13
13
  const MANIFEST_REL = path.join(".trellis", ".flower-manifest.json");
14
+ const UPDATE_CHECK_CACHE_REL = path.join(".trellis", ".flower-update-check.tmp");
14
15
  const UPDATE_POLICIES = new Set(["off", "notify", "ask", "auto"]);
15
- const DEFAULT_UPDATE_CHECK = {
16
+ const UPDATE_CHECK_POLICY_KEYS = new Set(["enabled", "policy", "intervalHours"]);
17
+ const UPDATE_CHECK_CACHE_KEYS = new Set([
18
+ "lastCheckedAt",
19
+ "lastRemote",
20
+ "lastReleaseNotes",
21
+ "lastStatus",
22
+ "lastErrorCode",
23
+ ]);
24
+ const DEFAULT_UPDATE_CHECK_POLICY = {
16
25
  enabled: true,
17
26
  policy: "ask",
18
27
  intervalHours: 8,
28
+ };
29
+ const DEFAULT_UPDATE_CHECK_CACHE = {
19
30
  lastCheckedAt: null,
20
31
  lastRemote: null,
21
32
  lastReleaseNotes: null,
22
33
  lastStatus: null,
23
34
  lastErrorCode: null,
24
35
  };
36
+ const DEFAULT_UPDATE_CHECK = {
37
+ ...DEFAULT_UPDATE_CHECK_POLICY,
38
+ ...DEFAULT_UPDATE_CHECK_CACHE,
39
+ };
40
+
41
+ /** 判断值是否为普通对象。 */
42
+ function isPlainObject(value) {
43
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
44
+ }
45
+
46
+ /** 判断对象是否显式包含给定字段。 */
47
+ function hasAnyOwn(value, keys) {
48
+ if (!isPlainObject(value)) return false;
49
+ for (const key of keys) {
50
+ if (Object.prototype.hasOwnProperty.call(value, key)) return true;
51
+ }
52
+ return false;
53
+ }
54
+
55
+ /** 从对象里取出指定字段。 */
56
+ function pickOwn(value, keys) {
57
+ const raw = isPlainObject(value) ? value : {};
58
+ const picked = {};
59
+ for (const key of keys) {
60
+ if (Object.prototype.hasOwnProperty.call(raw, key)) picked[key] = raw[key];
61
+ }
62
+ return picked;
63
+ }
25
64
 
26
65
  /**
27
66
  * 归一化 release notes 范围字段。
@@ -73,12 +112,126 @@ function normalizeLastReleaseNotes(value) {
73
112
  };
74
113
  }
75
114
 
76
- /** manifest 文件的绝对路径。 */
115
+ /**
116
+ * 归一化远端 dist-tags 缓存。
117
+ *
118
+ * @param {object|null|undefined} value 原始 lastRemote 字段
119
+ * @returns {{latest:string|null,beta:string|null}|null} 归一化后的 dist-tags
120
+ */
121
+ function normalizeLastRemote(value) {
122
+ return value && typeof value === "object"
123
+ ? {
124
+ latest: typeof value.latest === "string" ? value.latest : null,
125
+ beta: typeof value.beta === "string" ? value.beta : null,
126
+ }
127
+ : null;
128
+ }
129
+
130
+ /**
131
+ * 归一化启动更新检查策略字段。
132
+ *
133
+ * @param {object|null|undefined} value 原始 updateCheck 字段
134
+ * @returns {{enabled:boolean,policy:string,intervalHours:number}} 归一化后的策略
135
+ */
136
+ function normalizeUpdateCheckPolicy(value) {
137
+ const raw = value && typeof value === "object" ? value : {};
138
+ const policy = UPDATE_POLICIES.has(raw.policy) ? raw.policy : DEFAULT_UPDATE_CHECK.policy;
139
+ const interval = Number(raw.intervalHours);
140
+ const intervalHours = Number.isFinite(interval) && interval >= 0
141
+ ? interval
142
+ : DEFAULT_UPDATE_CHECK.intervalHours;
143
+ return {
144
+ enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_UPDATE_CHECK.enabled,
145
+ policy,
146
+ intervalHours,
147
+ };
148
+ }
149
+
150
+ /**
151
+ * 归一化启动更新检查运行缓存字段。
152
+ *
153
+ * @param {object|null|undefined} value 原始缓存字段
154
+ * @returns {{lastCheckedAt:string|null,lastRemote:object|null,lastReleaseNotes:object|null,lastStatus:string|null,lastErrorCode:string|null}} 归一化后的缓存
155
+ */
156
+ function normalizeUpdateCheckCache(value) {
157
+ const raw = value && typeof value === "object" ? value : {};
158
+ return {
159
+ lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : null,
160
+ lastRemote: normalizeLastRemote(raw.lastRemote),
161
+ lastReleaseNotes: normalizeLastReleaseNotes(raw.lastReleaseNotes),
162
+ lastStatus: typeof raw.lastStatus === "string" ? raw.lastStatus : null,
163
+ lastErrorCode: typeof raw.lastErrorCode === "string" ? raw.lastErrorCode : null,
164
+ };
165
+ }
166
+
167
+ /** 读取 tmp 内的 updateCheck 运行缓存;不存在或损坏时返回 null。 */
168
+ function readUpdateCheckCacheFile(target) {
169
+ try {
170
+ return normalizeUpdateCheckCache(
171
+ JSON.parse(fs.readFileSync(updateCheckCachePath(target), "utf8")),
172
+ );
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+
178
+ /** 判断 updateCheck 是否仍含旧版 manifest 缓存字段。 */
179
+ function hasLegacyUpdateCheckCache(updateCheck) {
180
+ return hasAnyOwn(updateCheck, UPDATE_CHECK_CACHE_KEYS);
181
+ }
182
+
183
+ /**
184
+ * 把旧 manifest 内的缓存字段迁移到 tmp。
185
+ *
186
+ * 只在 tmp 尚不存在时迁移,避免旧 manifest 里的陈旧缓存覆盖新版本地缓存。
187
+ */
188
+ function migrateLegacyUpdateCheckCache(target, updateCheck) {
189
+ if (!hasLegacyUpdateCheckCache(updateCheck)) return;
190
+ if (fs.existsSync(updateCheckCachePath(target))) return;
191
+ fs.writeFileSync(
192
+ updateCheckCachePath(target),
193
+ JSON.stringify(normalizeUpdateCheckCache(updateCheck), null, 2) + "\n",
194
+ );
195
+ }
196
+
197
+ /** 写入 tmp 内的 updateCheck 运行缓存。 */
198
+ function writeUpdateCheckCache(target, patch, legacyUpdateCheck) {
199
+ const current = readUpdateCheckCacheFile(target) ||
200
+ normalizeUpdateCheckCache(legacyUpdateCheck);
201
+ const cache = normalizeUpdateCheckCache({
202
+ ...current,
203
+ ...patch,
204
+ });
205
+ fs.writeFileSync(updateCheckCachePath(target), JSON.stringify(cache, null, 2) + "\n");
206
+ return cache;
207
+ }
208
+
209
+ /**
210
+ * manifest 文件的绝对路径。
211
+ *
212
+ * @param {string} target 目标项目根
213
+ * @returns {string} manifest 文件绝对路径
214
+ */
77
215
  export function manifestPath(target) {
78
216
  return path.join(target, MANIFEST_REL);
79
217
  }
80
218
 
81
- /** 读取 manifest;不存在或损坏时返回 null。 */
219
+ /**
220
+ * updateCheck 运行缓存 tmp 文件的绝对路径。
221
+ *
222
+ * @param {string} target 目标项目根
223
+ * @returns {string} updateCheck 运行缓存文件绝对路径
224
+ */
225
+ export function updateCheckCachePath(target) {
226
+ return path.join(target, UPDATE_CHECK_CACHE_REL);
227
+ }
228
+
229
+ /**
230
+ * 读取 manifest;不存在或损坏时返回 null。
231
+ *
232
+ * @param {string} target 目标项目根
233
+ * @returns {object|null} manifest 内容
234
+ */
82
235
  export function readManifest(target) {
83
236
  try {
84
237
  return JSON.parse(fs.readFileSync(manifestPath(target), "utf8"));
@@ -96,55 +249,65 @@ export function readManifest(target) {
96
249
  * @returns {{enabled:boolean,policy:string,intervalHours:number,lastCheckedAt:string|null,lastRemote:object|null,lastReleaseNotes:object|null,lastStatus:string|null,lastErrorCode:string|null}} 归一化后的 updateCheck
97
250
  */
98
251
  export function normalizeUpdateCheck(value) {
99
- const raw = value && typeof value === "object" ? value : {};
100
- const policy = UPDATE_POLICIES.has(raw.policy) ? raw.policy : DEFAULT_UPDATE_CHECK.policy;
101
- const interval = Number(raw.intervalHours);
102
- const intervalHours = Number.isFinite(interval) && interval >= 0
103
- ? interval
104
- : DEFAULT_UPDATE_CHECK.intervalHours;
105
- const lastRemote = raw.lastRemote && typeof raw.lastRemote === "object"
106
- ? {
107
- latest: typeof raw.lastRemote.latest === "string" ? raw.lastRemote.latest : null,
108
- beta: typeof raw.lastRemote.beta === "string" ? raw.lastRemote.beta : null,
109
- }
110
- : null;
111
-
112
252
  return {
113
- enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_UPDATE_CHECK.enabled,
114
- policy,
115
- intervalHours,
116
- lastCheckedAt: typeof raw.lastCheckedAt === "string" ? raw.lastCheckedAt : null,
117
- lastRemote,
118
- lastReleaseNotes: normalizeLastReleaseNotes(raw.lastReleaseNotes),
119
- lastStatus: typeof raw.lastStatus === "string" ? raw.lastStatus : null,
120
- lastErrorCode: typeof raw.lastErrorCode === "string" ? raw.lastErrorCode : null,
253
+ ...normalizeUpdateCheckPolicy(value),
254
+ ...normalizeUpdateCheckCache(value),
121
255
  };
122
256
  }
123
257
 
124
258
  /**
125
- * 读取 manifest 里的启动更新检查配置。
259
+ * 读取启动更新检查配置。
260
+ *
261
+ * 返回 manifest 策略与 tmp 运行缓存的合并视图;tmp 不存在时兼容读取旧 manifest 缓存字段。
126
262
  *
127
263
  * @param {string} target 目标项目根
128
264
  * @returns {ReturnType<typeof normalizeUpdateCheck>} 归一化后的配置
129
265
  */
130
266
  export function readUpdateCheck(target) {
131
- return normalizeUpdateCheck(readManifest(target)?.updateCheck);
267
+ const manifestUpdateCheck = readManifest(target)?.updateCheck;
268
+ return {
269
+ ...normalizeUpdateCheckPolicy(manifestUpdateCheck),
270
+ ...(
271
+ readUpdateCheckCacheFile(target) ||
272
+ // 兼容旧版本:首次写入前仍可复用 manifest 里的运行缓存。
273
+ normalizeUpdateCheckCache(manifestUpdateCheck)
274
+ ),
275
+ };
132
276
  }
133
277
 
134
278
  /**
135
- * 写入启动更新检查配置,保留 manifest 其它安装清单字段。
279
+ * 写入启动更新检查配置。
280
+ *
281
+ * 策略字段写入 `.flower-manifest.json`;运行缓存字段写入 `.flower-update-check.tmp`。
282
+ * 若旧 manifest 仍包含缓存字段,本函数会在写入时顺带清理,避免运行态继续进入 git。
136
283
  *
137
284
  * @param {string} target 目标项目根
138
285
  * @param {object} patch 要合并进 updateCheck 的字段
139
- * @returns {object} 写入后的 manifest
286
+ * @returns {object|null} 写入后的 manifest;没有 manifest 且仅写缓存时返回 null
140
287
  */
141
288
  export function writeUpdateCheck(target, patch) {
142
- const current = readManifest(target) || {};
143
- const updateCheck = normalizeUpdateCheck({
144
- ...normalizeUpdateCheck(current.updateCheck),
145
- ...(patch || {}),
146
- });
147
- const next = { ...current, updateCheck };
289
+ const current = readManifest(target);
290
+ const policyPatch = pickOwn(patch, UPDATE_CHECK_POLICY_KEYS);
291
+ const cachePatch = pickOwn(patch, UPDATE_CHECK_CACHE_KEYS);
292
+ const hasPolicyPatch = Object.keys(policyPatch).length > 0;
293
+ const hasCachePatch = Object.keys(cachePatch).length > 0;
294
+ const hasLegacyCache = hasLegacyUpdateCheckCache(current?.updateCheck);
295
+
296
+ if (hasCachePatch && current) {
297
+ writeUpdateCheckCache(target, cachePatch, current.updateCheck);
298
+ } else if (hasLegacyCache) {
299
+ migrateLegacyUpdateCheckCache(target, current.updateCheck);
300
+ }
301
+
302
+ if (!hasPolicyPatch && !hasLegacyCache) return current;
303
+
304
+ const next = {
305
+ ...(current || {}),
306
+ updateCheck: normalizeUpdateCheckPolicy({
307
+ ...normalizeUpdateCheckPolicy(current?.updateCheck),
308
+ ...policyPatch,
309
+ }),
310
+ };
148
311
  fs.writeFileSync(manifestPath(target), JSON.stringify(next, null, 2) + "\n");
149
312
  return next;
150
313
  }
@@ -152,7 +315,8 @@ export function writeUpdateCheck(target, patch) {
152
315
  /**
153
316
  * 写入 manifest。
154
317
  *
155
- * 若调用方没有显式传入 `updateCheck`,保留目标项目已有策略与缓存,避免全装重写时覆盖用户选择。
318
+ * 若调用方没有显式传入 `updateCheck`,保留目标项目已有策略,避免全装重写时覆盖用户选择。
319
+ * 运行缓存会迁移到 `.flower-update-check.tmp`,不得继续写入 manifest。
156
320
  *
157
321
  * @param {string} target 目标项目根
158
322
  * @param {object} data manifest 新内容
@@ -160,10 +324,14 @@ export function writeUpdateCheck(target, patch) {
160
324
  export function writeManifest(target, data) {
161
325
  const current = readManifest(target);
162
326
  const next = { ...data };
327
+ if (hasLegacyUpdateCheckCache(current?.updateCheck)) {
328
+ migrateLegacyUpdateCheckCache(target, current.updateCheck);
329
+ }
163
330
  if (Object.prototype.hasOwnProperty.call(data, "updateCheck")) {
164
- next.updateCheck = normalizeUpdateCheck(data.updateCheck);
331
+ migrateLegacyUpdateCheckCache(target, data.updateCheck);
332
+ next.updateCheck = normalizeUpdateCheckPolicy(data.updateCheck);
165
333
  } else {
166
- next.updateCheck = normalizeUpdateCheck(current?.updateCheck);
334
+ next.updateCheck = normalizeUpdateCheckPolicy(current?.updateCheck);
167
335
  }
168
336
  fs.writeFileSync(manifestPath(target), JSON.stringify(next, null, 2) + "\n");
169
337
  }
@@ -23,7 +23,12 @@ const COMMON_SKILL_DIRS = [
23
23
  },
24
24
  ];
25
25
 
26
- const LEGACY_COMMON_SKILL_DIRS = [".agents/skills", ".claude/skills"];
26
+ const LEGACY_COMMON_SKILL_DIRS = [
27
+ {
28
+ source: ".common/.codex/skills",
29
+ target: ".agents/skills",
30
+ },
31
+ ];
27
32
 
28
33
  const SKILL_DESCRIPTION_OVERRIDES = {
29
34
  "analyze-task": "深度分析并细化任务",
@@ -166,6 +171,56 @@ function listCommonSnapshotNames() {
166
171
  return [...names].sort((a, b) => a.localeCompare(b));
167
172
  }
168
173
 
174
+ /**
175
+ * 汇总 common skill 的 canonical 与历史目标映射。
176
+ *
177
+ * 历史 `.agents/skills` 使用 Codex 快照原地刷新,避免升级时迁移到
178
+ * `.codex/skills` 后产生双副本。
179
+ *
180
+ * @returns {Array<{source:string,target:string}>} common skill 目标映射
181
+ */
182
+ function allCommonSkillDirs() {
183
+ return [...COMMON_SKILL_DIRS, ...LEGACY_COMMON_SKILL_DIRS];
184
+ }
185
+
186
+ /**
187
+ * 判断 manifest 中的 skill 名称能否安全拼接到固定目标目录。
188
+ *
189
+ * @param {unknown} name 待校验名称
190
+ * @returns {name is string} 是否为单一路径段
191
+ */
192
+ function isSafeSkillName(name) {
193
+ return typeof name === "string" &&
194
+ name.length > 0 &&
195
+ name !== "." &&
196
+ name !== ".." &&
197
+ !name.includes("/") &&
198
+ !name.includes("\\");
199
+ }
200
+
201
+ /**
202
+ * 读取随包 manifest 中累计的已移除 common skill 名称。
203
+ *
204
+ * manifest 属于发布快照元数据,读取失败时按空列表降级,不能阻断其它强化更新。
205
+ *
206
+ * @returns {string[]} 排序去重后的 tombstone 名称
207
+ */
208
+ function listRemovedCommonSkillNames() {
209
+ try {
210
+ const manifest = JSON.parse(
211
+ fs.readFileSync(path.join(ENHANCEMENTS_ROOT, "MANIFEST.json"), "utf8"),
212
+ );
213
+ const names = Array.isArray(manifest?.common?.removedSkills)
214
+ ? manifest.common.removedSkills
215
+ : [];
216
+ return [...new Set(names.filter(isSafeSkillName))].sort((a, b) =>
217
+ a.localeCompare(b)
218
+ );
219
+ } catch {
220
+ return [];
221
+ }
222
+ }
223
+
169
224
  /**
170
225
  * 查找通用技能的源 SKILL.md。
171
226
  *
@@ -263,10 +318,9 @@ function preferredSkillDescription(name, description) {
263
318
  * @returns {boolean} 是否已安装
264
319
  */
265
320
  function isCommonSkillInstalled(target, name) {
266
- return [
267
- ...COMMON_SKILL_DIRS.map((dir) => dir.target),
268
- ...LEGACY_COMMON_SKILL_DIRS,
269
- ].some((base) => fs.existsSync(path.join(target, ...base.split("/"), name)));
321
+ return allCommonSkillDirs().some((dir) =>
322
+ fs.existsSync(path.join(target, ...dir.target.split("/"), name))
323
+ );
270
324
  }
271
325
 
272
326
  /**
@@ -391,6 +445,60 @@ export function installCommonSkills(target, names) {
391
445
  return { installed: [...installed], paths, skipped };
392
446
  }
393
447
 
448
+ /**
449
+ * 用当前随包快照同步目标仓库中已经启用的 common skill。
450
+ *
451
+ * 当前快照只覆盖已经存在的精确目标目录,因此不会安装用户未启用的新 skill;
452
+ * tombstone 只删除固定 common 根目录中的历史名称,避免扫描或误删其它用户内容。
453
+ *
454
+ * @param {string} target 目标项目根目录
455
+ * @returns {{refreshed:string[],removed:string[],refreshedPaths:string[],removedPaths:string[]}} 同步结果
456
+ */
457
+ export function syncInstalledCommonSkills(target) {
458
+ const refreshed = new Set();
459
+ const removed = new Set();
460
+ const refreshedPaths = [];
461
+ const removedPaths = [];
462
+ const currentNames = new Set(listCommonSnapshotNames());
463
+
464
+ for (const name of currentNames) {
465
+ for (const dir of allCommonSkillDirs()) {
466
+ const rel = `${dir.target}/${name}`;
467
+ const dst = path.join(target, ...dir.target.split("/"), name);
468
+ if (!fs.existsSync(dst)) continue;
469
+
470
+ const src = path.join(ENHANCEMENTS_ROOT, "common", dir.source, name);
471
+ if (!fs.existsSync(src)) continue;
472
+
473
+ copyPath(src, dst);
474
+ refreshed.add(name);
475
+ refreshedPaths.push(rel);
476
+ }
477
+ }
478
+
479
+ for (const name of listRemovedCommonSkillNames()) {
480
+ // 防御旧 manifest 漂移:重新进入当前快照的名称绝不能被 tombstone 删除。
481
+ if (currentNames.has(name)) continue;
482
+
483
+ for (const dir of allCommonSkillDirs()) {
484
+ const rel = `${dir.target}/${name}`;
485
+ const dst = path.join(target, ...dir.target.split("/"), name);
486
+ if (!fs.existsSync(dst)) continue;
487
+
488
+ rmrf(dst);
489
+ removed.add(name);
490
+ removedPaths.push(rel);
491
+ }
492
+ }
493
+
494
+ return {
495
+ refreshed: [...refreshed],
496
+ removed: [...removed],
497
+ refreshedPaths,
498
+ removedPaths,
499
+ };
500
+ }
501
+
394
502
  /**
395
503
  * 停用指定通用技能,只删除通用技能清单声明过的精确 skill 路径。
396
504
  *
@@ -412,10 +520,7 @@ export function removeCommonSkills(target, variantOverride, names) {
412
520
  }
413
521
 
414
522
  let removedOne = false;
415
- for (const base of [
416
- ...COMMON_SKILL_DIRS.map((dir) => dir.target),
417
- ...LEGACY_COMMON_SKILL_DIRS,
418
- ]) {
523
+ for (const { target: base } of allCommonSkillDirs()) {
419
524
  const rel = `${base}/${name}`;
420
525
  const abs = path.join(target, ...base.split("/"), name);
421
526
  if (fs.existsSync(abs)) {
@@ -427,10 +532,7 @@ export function removeCommonSkills(target, variantOverride, names) {
427
532
  if (!removedOne) skipped.push(name);
428
533
  }
429
534
 
430
- for (const base of [
431
- ...COMMON_SKILL_DIRS.map((dir) => dir.target),
432
- ...LEGACY_COMMON_SKILL_DIRS,
433
- ]) {
535
+ for (const { target: base } of allCommonSkillDirs()) {
434
536
  const abs = path.join(target, ...base.split("/"));
435
537
  try {
436
538
  if (fs.readdirSync(abs).length === 0) fs.rmdirSync(abs);
@@ -348,7 +348,7 @@ export function getUpdateRecommendation(current, tags) {
348
348
  return null;
349
349
  }
350
350
 
351
- /** 尽力而为刷新目标项目 manifest 里的远端探测缓存。 */
351
+ /** 尽力而为刷新目标项目的远端探测缓存。 */
352
352
  function rememberRemoteTags(target, tags, status, releaseNotes = null) {
353
353
  try {
354
354
  if (!readManifest(target)) return;