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 +18 -3
- package/enhancements/0.6/.agents/skills/trellis-auto-loop/SKILL.md +1 -0
- package/enhancements/0.6/.agents/skills/trellis-check-all/SKILL.md +15 -0
- package/enhancements/0.6/.agents/skills/trellis-push/SKILL.md +66 -36
- package/enhancements/0.6/.claude/skills/trellis-auto-loop/SKILL.md +1 -0
- package/enhancements/0.6/.claude/skills/trellis-check-all/SKILL.md +15 -0
- package/enhancements/0.6/.claude/skills/trellis-push/SKILL.md +66 -36
- package/enhancements/0.6/overrides/workflow-states/in_progress-inline.md +3 -2
- package/enhancements/0.6/overrides/workflow-states/in_progress.md +3 -2
- package/enhancements/0.6/overrides/workflow.md +7 -1
- package/enhancements/MANIFEST.json +5 -2
- package/package.json +3 -3
- package/src/commands/update-check.js +7 -1
- package/src/lib/apply-enhancements.js +18 -1
- package/src/lib/manifest.js +205 -37
- package/src/lib/skill-catalog.js +115 -13
- package/src/lib/update-check.js +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
| 默认 |
|
|
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
|
|
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
|
-
##
|
|
248
|
+
## Trellis Push 计划
|
|
249
|
+
|
|
250
|
+
[<PUSH / COMMIT-ONLY / SNAPSHOT>] <N> 个仓库 · <N> 个 commit · <N> 个文件 · 风险 <N>
|
|
251
|
+
[无活动任务时追加:无活动任务]
|
|
252
|
+
顺序:<repo-a> -> <repo-b> -> <任务记录,若有>
|
|
230
253
|
|
|
231
|
-
|
|
254
|
+
### 1. <package>
|
|
232
255
|
|
|
233
|
-
|
|
256
|
+
`<type>(<scope>): <description>`
|
|
234
257
|
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
263
|
+
Push:<执行 / 跳过(commit-only)>
|
|
264
|
+
Merge:<仅计划中需要时显示>
|
|
251
265
|
|
|
252
|
-
###
|
|
266
|
+
### 风险(仅风险数大于 0 时显示)
|
|
253
267
|
|
|
254
|
-
-
|
|
255
|
-
- 无关未暂存 dirty:<list 或 无>(保留未提交,不阻塞 snapshot commit)
|
|
256
|
-
- 无关 staged / 冲突 / 目标文件预脏:<list 或 无>(有则停止,需处理后重新计划)
|
|
268
|
+
- <所有未识别 dirty / staged / conflict / cross-task 文件逐项列出>
|
|
257
269
|
|
|
258
|
-
|
|
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
|
-
|
|
440
|
-
|------|------|------|--------|------|
|
|
441
|
-
| frontend | v1.3 | test | abc1234 feat(...): ... | 已合并 |
|
|
442
|
-
| backend | v1.3 | - | def5678 fix(...): ... | 已推送,跳过合并 |
|
|
469
|
+
### 任务上下文
|
|
443
470
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
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
|
-
| 默认 |
|
|
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
|
|
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
|
-
##
|
|
248
|
+
## Trellis Push 计划
|
|
249
|
+
|
|
250
|
+
[<PUSH / COMMIT-ONLY / SNAPSHOT>] <N> 个仓库 · <N> 个 commit · <N> 个文件 · 风险 <N>
|
|
251
|
+
[无活动任务时追加:无活动任务]
|
|
252
|
+
顺序:<repo-a> -> <repo-b> -> <任务记录,若有>
|
|
230
253
|
|
|
231
|
-
|
|
254
|
+
### 1. <package>
|
|
232
255
|
|
|
233
|
-
|
|
256
|
+
`<type>(<scope>): <description>`
|
|
234
257
|
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
263
|
+
Push:<执行 / 跳过(commit-only)>
|
|
264
|
+
Merge:<仅计划中需要时显示>
|
|
251
265
|
|
|
252
|
-
###
|
|
266
|
+
### 风险(仅风险数大于 0 时显示)
|
|
253
267
|
|
|
254
|
-
-
|
|
255
|
-
- 无关未暂存 dirty:<list 或 无>(保留未提交,不阻塞 snapshot commit)
|
|
256
|
-
- 无关 staged / 冲突 / 目标文件预脏:<list 或 无>(有则停止,需处理后重新计划)
|
|
268
|
+
- <所有未识别 dirty / staged / conflict / cross-task 文件逐项列出>
|
|
257
269
|
|
|
258
|
-
|
|
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
|
-
|
|
440
|
-
|------|------|------|--------|------|
|
|
441
|
-
| frontend | v1.3 | test | abc1234 feat(...): ... | 已合并 |
|
|
442
|
-
| backend | v1.3 | - | def5678 fix(...): ... | 已推送,跳过合并 |
|
|
469
|
+
### 任务上下文
|
|
443
470
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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;
|
|
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,
|
|
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;
|
|
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,
|
|
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
|
|
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-
|
|
2
|
+
"syncedAt": "2026-07-13T10:24:09.928Z",
|
|
3
3
|
"syncedFrom": "vendor/skill-garden",
|
|
4
|
-
"sourceCommit": "
|
|
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.
|
|
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.
|
|
61
|
+
"version": "0.4.12-beta.0",
|
|
62
62
|
"source": "CHANGELOG.md",
|
|
63
|
-
"body": "###
|
|
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
|
|
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
|
-
|
|
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) {
|
package/src/lib/manifest.js
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
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
|
-
|
|
331
|
+
migrateLegacyUpdateCheckCache(target, data.updateCheck);
|
|
332
|
+
next.updateCheck = normalizeUpdateCheckPolicy(data.updateCheck);
|
|
165
333
|
} else {
|
|
166
|
-
next.updateCheck =
|
|
334
|
+
next.updateCheck = normalizeUpdateCheckPolicy(current?.updateCheck);
|
|
167
335
|
}
|
|
168
336
|
fs.writeFileSync(manifestPath(target), JSON.stringify(next, null, 2) + "\n");
|
|
169
337
|
}
|
package/src/lib/skill-catalog.js
CHANGED
|
@@ -23,7 +23,12 @@ const COMMON_SKILL_DIRS = [
|
|
|
23
23
|
},
|
|
24
24
|
];
|
|
25
25
|
|
|
26
|
-
const LEGACY_COMMON_SKILL_DIRS = [
|
|
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
|
-
|
|
268
|
-
|
|
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);
|
package/src/lib/update-check.js
CHANGED
|
@@ -348,7 +348,7 @@ export function getUpdateRecommendation(current, tags) {
|
|
|
348
348
|
return null;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
/**
|
|
351
|
+
/** 尽力而为刷新目标项目的远端探测缓存。 */
|
|
352
352
|
function rememberRemoteTags(target, tags, status, releaseNotes = null) {
|
|
353
353
|
try {
|
|
354
354
|
if (!readManifest(target)) return;
|