flower-trellis 0.6.9-beta.0 → 0.6.9
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 +24 -1
- package/enhancements/0.6/.agents/skills/trellis-route/scripts/route_state.py +2 -1
- package/enhancements/0.6/.claude/skills/trellis-route/scripts/route_state.py +2 -1
- package/enhancements/0.6/overrides/bundles/shared-hook-runtime.json +1 -1
- package/enhancements/0.6/overrides/patches/scripts/windows-streams/common-content.py +10 -0
- package/enhancements/0.6/overrides/patches/scripts/windows-streams/common-selector.py +14 -0
- package/enhancements/0.6/overrides/patches/scripts/windows-streams/hook-content.py +7 -0
- package/enhancements/0.6/overrides/patches/scripts/windows-streams/hook-selector.py +6 -0
- package/enhancements/0.6/overrides/patches/scripts/windows-streams/patch.json +186 -0
- package/enhancements/0.6/overrides/patches/scripts/windows-streams/prefix-content.py +1 -0
- package/enhancements/0.6/overrides/patches/scripts/windows-streams/prefix-selector.py +1 -0
- package/enhancements/0.6/scripts/auto_loop.py +21 -10
- package/enhancements/0.6/scripts/decision_log.py +7 -5
- package/enhancements/0.6/scripts/git_evidence.py +2 -1
- package/enhancements/0.6/scripts/pre_check_state.py +1 -1
- package/enhancements/0.6/scripts/task_intent.py +4 -0
- package/enhancements/0.6/scripts/worktree_setup.py +8 -3
- package/enhancements/MANIFEST.json +9 -2
- package/package.json +4 -4
- package/src/assets/flower_session_start.py +40 -15
- package/src/assets/flower_telemetry_hook.py +18 -3
- package/src/cli.js +7 -1
- package/src/commands/self-check.js +3 -2
- package/src/commands/self-update.js +10 -6
- package/src/commands/update.js +33 -19
- package/src/lib/global-trellis-sync.js +60 -8
- package/src/lib/operation-timing.js +50 -0
- package/src/lib/self-check.js +14 -54
- package/src/lib/terminal-state.js +5 -5
- package/src/lib/trellis-runner.js +34 -10
- package/src/lib/trellis-update-entry.js +10 -0
- package/src/lib/trellis-update-fetch.js +18 -0
- package/src/lib/update-check-cache.js +47 -0
- package/src/lib/update-check.js +125 -56
package/README.md
CHANGED
|
@@ -241,13 +241,36 @@ flower-trellis plugin add flower/flower-plugin-author --platform codex --json
|
|
|
241
241
|
|
|
242
242
|
运行 `init` / `update` 时,flower-trellis 会顺带检测**自身**在 npm 上是否有新版本:
|
|
243
243
|
|
|
244
|
-
-
|
|
244
|
+
- **缓存与轻量查询**:复用有效的远程检查缓存;需要联网时先读取版本标签,只有新版摘要需要时才补拉完整发布说明。两次读取共用 5s 网络预算(含响应体),失败不阻断安装/升级。
|
|
245
245
|
- **稳定版安装**:只跟随 npm `latest` 通道;发现稳定新版时安装已确认的精确版本,并使用 `--prefer-online` 避免旧 metadata 缓存。
|
|
246
246
|
- **beta 版安装**:版本号形如 `0.3.0-beta.1`,会同时检查 `beta` 与 `latest`;若 `latest` 已高于当前 beta,优先回归稳定版,否则安装已确认的精确 beta 版本。
|
|
247
247
|
- **发现新版**(交互终端):提示并询问是否立即升级;同意则执行推荐的安装命令,成功后请按提示重新运行命令(升级后强化包随新版更新,可再跑一次 `ft update` 重新叠加)。
|
|
248
248
|
- **非交互**(`-y` 或非 TTY):仅打印一行升级提示,不弹确认、不阻塞。
|
|
249
249
|
- **跳过检测**:经 `npx` 运行(本就是最新版)、或显式 `--no-update-check` / `FLOWER_NO_UPDATE_CHECK=1` 时不检测。
|
|
250
250
|
|
|
251
|
+
### 更新耗时诊断
|
|
252
|
+
|
|
253
|
+
更新慢时可用 `FLOWER_TIMING=1` 输出本地阶段耗时,定位版本检查、全局安装、全局 Trellis 同步、快照、上游更新、Plugin 重放或清理中的等待:
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
FLOWER_TIMING=1 ftl update
|
|
257
|
+
FLOWER_TIMING=1 ftl self-update --target . --yes
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
PowerShell:
|
|
261
|
+
|
|
262
|
+
```powershell
|
|
263
|
+
$env:FLOWER_TIMING = "1"
|
|
264
|
+
ftl update
|
|
265
|
+
Remove-Item Env:FLOWER_TIMING
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
计时写入 stderr,默认关闭,不增加遥测,也不输出参数、项目路径或配置内容;`self-check --json` 的 stdout 仍是 JSON。父阶段包含子阶段,不能把它们相加:`update` 总计在完成菜单前结束,上游阶段可能包含冲突处理等待;`self-update` 流程总计还包含子进程交互等待。全局安装阶段包含 npm 下载、解包、原生模块准备和 postinstall,具体慢在哪里需结合 npm 输出判断。
|
|
269
|
+
|
|
270
|
+
Flower 驱动的更新会跳过捆绑 Trellis 的独立 npm 新版提示查询;独立运行 `trellis` 仍按上游行为检查。标准全局安装通过实际包与启动器证据读取版本,无法证明归属时继续执行兼容探测。
|
|
271
|
+
|
|
272
|
+
开发者可运行 `node scripts/benchmark-update.mjs <优化前提交>`:在临时项目中以 0/200/1000ms 受控延迟对比至少五轮检查,并运行完整 `update --dry-run` 对照。脚本不联网、不执行全局安装;结果用于比较该环境的阶段成本,不代表真实 npm 下载速度。
|
|
273
|
+
|
|
251
274
|
### 启动更新检查
|
|
252
275
|
|
|
253
276
|
运行 `flower-trellis init` / `flower-trellis update` 后,目标项目会安装轻量启动 hook:
|
|
@@ -125,10 +125,11 @@ def _normalize_mode(target: str, mode: Any) -> str | None:
|
|
|
125
125
|
def _current_task(repo_root: Path) -> tuple[str | None, str | None, str | None]:
|
|
126
126
|
"""通过 task.py current --source 获取当前任务和 session key。"""
|
|
127
127
|
result = subprocess.run(
|
|
128
|
-
["
|
|
128
|
+
[sys.executable, "-X", "utf8", str(repo_root / ".trellis/scripts/task.py"), "current", "--source"],
|
|
129
129
|
cwd=repo_root,
|
|
130
130
|
check=False,
|
|
131
131
|
text=True,
|
|
132
|
+
encoding="utf-8",
|
|
132
133
|
capture_output=True,
|
|
133
134
|
)
|
|
134
135
|
current = None
|
|
@@ -125,10 +125,11 @@ def _normalize_mode(target: str, mode: Any) -> str | None:
|
|
|
125
125
|
def _current_task(repo_root: Path) -> tuple[str | None, str | None, str | None]:
|
|
126
126
|
"""通过 task.py current --source 获取当前任务和 session key。"""
|
|
127
127
|
result = subprocess.run(
|
|
128
|
-
["
|
|
128
|
+
[sys.executable, "-X", "utf8", str(repo_root / ".trellis/scripts/task.py"), "current", "--source"],
|
|
129
129
|
cwd=repo_root,
|
|
130
130
|
check=False,
|
|
131
131
|
text=True,
|
|
132
|
+
encoding="utf-8",
|
|
132
133
|
capture_output=True,
|
|
133
134
|
)
|
|
134
135
|
current = None
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
def _configure_stream(stream: object) -> object:
|
|
2
|
+
"""Configure UTF-8 where supported without taking ownership of the stream."""
|
|
3
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
4
|
+
if callable(reconfigure):
|
|
5
|
+
try:
|
|
6
|
+
reconfigure(encoding="utf-8", errors="replace")
|
|
7
|
+
except (OSError, ValueError):
|
|
8
|
+
pass
|
|
9
|
+
# In-memory and host-owned streams must remain attached and open.
|
|
10
|
+
return stream
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
def _configure_stream(stream: object) -> object:
|
|
2
|
+
"""Configure a stream for UTF-8 encoding on Windows."""
|
|
3
|
+
# Try reconfigure() first (Python 3.7+, more reliable)
|
|
4
|
+
if hasattr(stream, "reconfigure"):
|
|
5
|
+
stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
6
|
+
return stream
|
|
7
|
+
# Fallback: detach and rewrap with TextIOWrapper
|
|
8
|
+
elif hasattr(stream, "detach"):
|
|
9
|
+
return io.TextIOWrapper(
|
|
10
|
+
stream.detach(), # type: ignore[union-attr]
|
|
11
|
+
encoding="utf-8",
|
|
12
|
+
errors="replace",
|
|
13
|
+
)
|
|
14
|
+
return stream
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
if sys.platform.startswith("win"):
|
|
2
|
+
import io as _io
|
|
3
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
4
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
5
|
+
elif hasattr(sys.stdout, "detach"):
|
|
6
|
+
sys.stdout = _io.TextIOWrapper(sys.stdout.detach(), encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 2,
|
|
3
|
+
"id": "windows-streams",
|
|
4
|
+
"purpose": "hook_override",
|
|
5
|
+
"operations": [
|
|
6
|
+
{
|
|
7
|
+
"id": "common-stream-ownership",
|
|
8
|
+
"operation": "replace",
|
|
9
|
+
"targets": [
|
|
10
|
+
{
|
|
11
|
+
"kind": "file",
|
|
12
|
+
"path": ".trellis/scripts/common/__init__.py",
|
|
13
|
+
"missing": "error",
|
|
14
|
+
"markerStyle": "hash"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"selector": {
|
|
18
|
+
"type": "literal",
|
|
19
|
+
"source": "common-selector.py"
|
|
20
|
+
},
|
|
21
|
+
"content": {
|
|
22
|
+
"source": "common-content.py"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "subagent-stream-ownership",
|
|
27
|
+
"operation": "replace",
|
|
28
|
+
"targetPolicy": "each-existing",
|
|
29
|
+
"targets": [
|
|
30
|
+
{
|
|
31
|
+
"kind": "file",
|
|
32
|
+
"path": ".claude/hooks/inject-subagent-context.py",
|
|
33
|
+
"missing": "skip",
|
|
34
|
+
"markerStyle": "hash"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"kind": "file",
|
|
38
|
+
"path": ".codex/hooks/inject-subagent-context.py",
|
|
39
|
+
"missing": "skip",
|
|
40
|
+
"markerStyle": "hash"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"kind": "file",
|
|
44
|
+
"path": ".cursor/hooks/inject-subagent-context.py",
|
|
45
|
+
"missing": "skip",
|
|
46
|
+
"markerStyle": "hash"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"kind": "file",
|
|
50
|
+
"path": ".gemini/hooks/inject-subagent-context.py",
|
|
51
|
+
"missing": "skip",
|
|
52
|
+
"markerStyle": "hash"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"kind": "file",
|
|
56
|
+
"path": ".qoder/hooks/inject-subagent-context.py",
|
|
57
|
+
"missing": "skip",
|
|
58
|
+
"markerStyle": "hash"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"kind": "file",
|
|
62
|
+
"path": ".github/copilot/hooks/inject-subagent-context.py",
|
|
63
|
+
"missing": "skip",
|
|
64
|
+
"markerStyle": "hash"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"kind": "file",
|
|
68
|
+
"path": ".codebuddy/hooks/inject-subagent-context.py",
|
|
69
|
+
"missing": "skip",
|
|
70
|
+
"markerStyle": "hash"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"kind": "file",
|
|
74
|
+
"path": ".factory/hooks/inject-subagent-context.py",
|
|
75
|
+
"missing": "skip",
|
|
76
|
+
"markerStyle": "hash"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"kind": "file",
|
|
80
|
+
"path": ".kiro/hooks/inject-subagent-context.py",
|
|
81
|
+
"missing": "skip",
|
|
82
|
+
"markerStyle": "hash"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"kind": "file",
|
|
86
|
+
"path": ".trae/hooks/inject-subagent-context.py",
|
|
87
|
+
"missing": "skip",
|
|
88
|
+
"markerStyle": "hash"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"kind": "file",
|
|
92
|
+
"path": ".zcode/hooks/inject-subagent-context.py",
|
|
93
|
+
"missing": "skip",
|
|
94
|
+
"markerStyle": "hash"
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
"selector": {
|
|
98
|
+
"type": "literal",
|
|
99
|
+
"source": "hook-selector.py"
|
|
100
|
+
},
|
|
101
|
+
"content": {
|
|
102
|
+
"source": "hook-content.py"
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": "subagent-python38-prefix",
|
|
107
|
+
"operation": "replace",
|
|
108
|
+
"targetPolicy": "each-existing",
|
|
109
|
+
"targets": [
|
|
110
|
+
{
|
|
111
|
+
"kind": "file",
|
|
112
|
+
"path": ".claude/hooks/inject-subagent-context.py",
|
|
113
|
+
"missing": "skip",
|
|
114
|
+
"markerStyle": "hash"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"kind": "file",
|
|
118
|
+
"path": ".codex/hooks/inject-subagent-context.py",
|
|
119
|
+
"missing": "skip",
|
|
120
|
+
"markerStyle": "hash"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"kind": "file",
|
|
124
|
+
"path": ".cursor/hooks/inject-subagent-context.py",
|
|
125
|
+
"missing": "skip",
|
|
126
|
+
"markerStyle": "hash"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"kind": "file",
|
|
130
|
+
"path": ".gemini/hooks/inject-subagent-context.py",
|
|
131
|
+
"missing": "skip",
|
|
132
|
+
"markerStyle": "hash"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"kind": "file",
|
|
136
|
+
"path": ".qoder/hooks/inject-subagent-context.py",
|
|
137
|
+
"missing": "skip",
|
|
138
|
+
"markerStyle": "hash"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"kind": "file",
|
|
142
|
+
"path": ".github/copilot/hooks/inject-subagent-context.py",
|
|
143
|
+
"missing": "skip",
|
|
144
|
+
"markerStyle": "hash"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"kind": "file",
|
|
148
|
+
"path": ".codebuddy/hooks/inject-subagent-context.py",
|
|
149
|
+
"missing": "skip",
|
|
150
|
+
"markerStyle": "hash"
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"kind": "file",
|
|
154
|
+
"path": ".factory/hooks/inject-subagent-context.py",
|
|
155
|
+
"missing": "skip",
|
|
156
|
+
"markerStyle": "hash"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"kind": "file",
|
|
160
|
+
"path": ".kiro/hooks/inject-subagent-context.py",
|
|
161
|
+
"missing": "skip",
|
|
162
|
+
"markerStyle": "hash"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"kind": "file",
|
|
166
|
+
"path": ".trae/hooks/inject-subagent-context.py",
|
|
167
|
+
"missing": "skip",
|
|
168
|
+
"markerStyle": "hash"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"kind": "file",
|
|
172
|
+
"path": ".zcode/hooks/inject-subagent-context.py",
|
|
173
|
+
"missing": "skip",
|
|
174
|
+
"markerStyle": "hash"
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
"selector": {
|
|
178
|
+
"type": "literal",
|
|
179
|
+
"source": "prefix-selector.py"
|
|
180
|
+
},
|
|
181
|
+
"content": {
|
|
182
|
+
"source": "prefix-content.py"
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
role = subagent_type[len("trellis-"):] if subagent_type.startswith("trellis-") else subagent_type
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
role = subagent_type.removeprefix("trellis-")
|
|
@@ -282,6 +282,15 @@ def _current_pointer(repo_root: Path) -> Path:
|
|
|
282
282
|
return _auto_dir(repo_root) / "current.json"
|
|
283
283
|
|
|
284
284
|
|
|
285
|
+
def _is_relative_to(path: Path, root: Path) -> bool:
|
|
286
|
+
"""兼容 Python 3.8 的路径包含判断;由调用方决定是否解析软链。"""
|
|
287
|
+
try:
|
|
288
|
+
path.relative_to(root)
|
|
289
|
+
return True
|
|
290
|
+
except ValueError:
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
|
|
285
294
|
def _route_state_helper(repo_root: Path) -> Path | None:
|
|
286
295
|
"""返回本地 trellis-route helper 路径,缺失时返回 None。"""
|
|
287
296
|
candidates = [
|
|
@@ -292,7 +301,7 @@ def _route_state_helper(repo_root: Path) -> Path | None:
|
|
|
292
301
|
# .agents/.claude 就把已安装的路由能力当作缺失。
|
|
293
302
|
candidates.extend(sorted(repo_root.glob(".*/skills/trellis-route/scripts/route_state.py")))
|
|
294
303
|
for path in candidates:
|
|
295
|
-
if path.is_file() and path.resolve()
|
|
304
|
+
if path.is_file() and _is_relative_to(path.resolve(), repo_root.resolve()):
|
|
296
305
|
return path
|
|
297
306
|
return None
|
|
298
307
|
|
|
@@ -306,13 +315,13 @@ def _effective_route_authorization(repo_root: Path, task_ref: str, route_authori
|
|
|
306
315
|
effective: dict[str, str] = {}
|
|
307
316
|
for target, valid_modes in (("implement", VALID_IMPLEMENT_ROUTES), ("check", VALID_CHECK_ROUTES)):
|
|
308
317
|
command = [
|
|
309
|
-
"
|
|
318
|
+
sys.executable, "-X", "utf8", str(helper), "resolve", "--target", target,
|
|
310
319
|
"--read-only", "--task", task_ref,
|
|
311
320
|
]
|
|
312
321
|
mode = authorization.get(target)
|
|
313
322
|
if mode in valid_modes:
|
|
314
323
|
command.extend(["--auto-mode", str(mode)])
|
|
315
|
-
result = subprocess.run(command, cwd=repo_root, check=False, text=True, capture_output=True)
|
|
324
|
+
result = subprocess.run(command, cwd=repo_root, check=False, text=True, encoding="utf-8", capture_output=True)
|
|
316
325
|
try:
|
|
317
326
|
data = json.loads(result.stdout)
|
|
318
327
|
except json.JSONDecodeError:
|
|
@@ -734,8 +743,9 @@ def _normalize_record_file(raw: str) -> str:
|
|
|
734
743
|
if "::" in value:
|
|
735
744
|
repository, path = value.split("::", 1)
|
|
736
745
|
repository = repository.strip() or "."
|
|
737
|
-
|
|
738
|
-
|
|
746
|
+
path = path.strip()
|
|
747
|
+
return _baseline_key(repository, path[2:] if path.startswith("./") else path)
|
|
748
|
+
return _baseline_key(".", value[2:] if value.startswith("./") else value)
|
|
739
749
|
|
|
740
750
|
|
|
741
751
|
def _normalize_repository_root(raw: str) -> str:
|
|
@@ -743,7 +753,7 @@ def _normalize_repository_root(raw: str) -> str:
|
|
|
743
753
|
value = raw.strip()
|
|
744
754
|
if value == ".":
|
|
745
755
|
return "."
|
|
746
|
-
return value.
|
|
756
|
+
return (value[2:] if value.startswith("./") else value).rstrip("/")
|
|
747
757
|
|
|
748
758
|
|
|
749
759
|
def _planning_digest(task_dir: Path) -> tuple[str, list[str]]:
|
|
@@ -1349,10 +1359,11 @@ def _current_session_key(repo_root: Path) -> str | None:
|
|
|
1349
1359
|
return override.strip() or None
|
|
1350
1360
|
|
|
1351
1361
|
result = subprocess.run(
|
|
1352
|
-
["
|
|
1362
|
+
[sys.executable, "-X", "utf8", str(repo_root / ".trellis/scripts/task.py"), "current", "--source"],
|
|
1353
1363
|
cwd=repo_root,
|
|
1354
1364
|
check=False,
|
|
1355
1365
|
text=True,
|
|
1366
|
+
encoding="utf-8",
|
|
1356
1367
|
capture_output=True,
|
|
1357
1368
|
)
|
|
1358
1369
|
for line in result.stdout.splitlines():
|
|
@@ -3883,9 +3894,9 @@ def _validate_decision_file(repo_root: Path, state: dict[str, Any], key: str) ->
|
|
|
3883
3894
|
raise ValueError(f"非法文件路径:{key}")
|
|
3884
3895
|
root = (repo_root / repository).resolve()
|
|
3885
3896
|
target = root / path
|
|
3886
|
-
if not target.resolve()
|
|
3897
|
+
if not _is_relative_to(target.resolve(), root):
|
|
3887
3898
|
raise ValueError(f"文件越过仓库边界:{key}")
|
|
3888
|
-
if any(part.is_symlink() for part in [target, *target.parents] if part != root and part
|
|
3899
|
+
if any(part.is_symlink() for part in [target, *target.parents] if part != root and _is_relative_to(part, root)):
|
|
3889
3900
|
raise ValueError(f"不接受软链文件或父目录:{key}")
|
|
3890
3901
|
if target.exists() and not target.is_file():
|
|
3891
3902
|
raise ValueError(f"目标不是普通文件:{key}")
|
|
@@ -3959,7 +3970,7 @@ def _recovery_summary(state: dict[str, Any], item: dict[str, Any]) -> dict[str,
|
|
|
3959
3970
|
if recovery.get("resolved"):
|
|
3960
3971
|
summary["instruction"] = "纠正已完成;原登记被拒绝时先重试 decide,否则继续原 action 并真实 record。不要重放纠正来推进阶段。"
|
|
3961
3972
|
return summary
|
|
3962
|
-
summary["command"] = ["
|
|
3973
|
+
summary["command"] = [sys.executable, "-X", "utf8", "./.trellis/scripts/auto_loop.py", "reconcile",
|
|
3963
3974
|
"--run-id", state["run_id"], "--task", item["task"],
|
|
3964
3975
|
"--recovery-id", recovery["recovery_id"], "--attempt-id", "<本次唯一ID>",
|
|
3965
3976
|
"--result", "<ok|failed|blocked>", "--summary", "<纠正结论>"]
|
|
@@ -318,11 +318,13 @@ def append_decision(
|
|
|
318
318
|
raise DecisionLogError("choice 必须存在于非空 options 中")
|
|
319
319
|
|
|
320
320
|
events = load_events(task_dir)
|
|
321
|
-
existing_ids = [
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if
|
|
325
|
-
|
|
321
|
+
existing_ids = []
|
|
322
|
+
for previous in _decision_events(events):
|
|
323
|
+
number = str(previous.get("decision_id", ""))
|
|
324
|
+
if number.startswith("DEC-"):
|
|
325
|
+
number = number[4:]
|
|
326
|
+
if number.isdigit():
|
|
327
|
+
existing_ids.append(int(number))
|
|
326
328
|
event = {
|
|
327
329
|
"schema_version": SCHEMA_VERSION,
|
|
328
330
|
"event": "decision",
|
|
@@ -124,7 +124,8 @@ def discover_git_repositories(repo_root: Path) -> list[Path]:
|
|
|
124
124
|
repositories: dict[str, Path] = {_relative_root(root, root): root}
|
|
125
125
|
submodules = _run_git(
|
|
126
126
|
root,
|
|
127
|
-
|
|
127
|
+
# Git 自己返回宿主路径,避免 MSYS pwd 产生原生 Python 无法识别的 /c 或 /tmp 路径。
|
|
128
|
+
["submodule", "foreach", "--recursive", "--quiet", "git rev-parse --show-toplevel"],
|
|
128
129
|
)
|
|
129
130
|
if submodules.returncode != 0:
|
|
130
131
|
raise GitEvidenceError(
|
|
@@ -115,7 +115,7 @@ def _runtime_scope(
|
|
|
115
115
|
if source_type in {"session-corrupt", "session-io_error"}:
|
|
116
116
|
return {
|
|
117
117
|
"status": "error",
|
|
118
|
-
"reason": f"session-runtime-{source_type
|
|
118
|
+
"reason": f"session-runtime-{source_type[len('session-'):]}",
|
|
119
119
|
"context_key": context_key,
|
|
120
120
|
"path": path,
|
|
121
121
|
}
|
|
@@ -60,6 +60,7 @@ def _run_git(repo_root: Path, args: list[str], *, binary: bool = False) -> subpr
|
|
|
60
60
|
cwd=repo_root,
|
|
61
61
|
capture_output=True,
|
|
62
62
|
text=not binary,
|
|
63
|
+
encoding=None if binary else "utf-8",
|
|
63
64
|
check=False,
|
|
64
65
|
)
|
|
65
66
|
|
|
@@ -124,6 +125,8 @@ def _create_planning_task(
|
|
|
124
125
|
"""调用 task.py 创建 planning task 并读取元数据。"""
|
|
125
126
|
command = [
|
|
126
127
|
sys.executable,
|
|
128
|
+
"-X",
|
|
129
|
+
"utf8",
|
|
127
130
|
str(_task_script()),
|
|
128
131
|
"create",
|
|
129
132
|
args.title,
|
|
@@ -144,6 +147,7 @@ def _create_planning_task(
|
|
|
144
147
|
cwd=repo_root,
|
|
145
148
|
capture_output=True,
|
|
146
149
|
text=True,
|
|
150
|
+
encoding="utf-8",
|
|
147
151
|
check=False,
|
|
148
152
|
)
|
|
149
153
|
if result.stderr:
|
|
@@ -80,6 +80,7 @@ def _git_run(
|
|
|
80
80
|
stdout=subprocess.PIPE,
|
|
81
81
|
stderr=subprocess.PIPE,
|
|
82
82
|
text=True,
|
|
83
|
+
encoding="utf-8",
|
|
83
84
|
timeout=timeout,
|
|
84
85
|
)
|
|
85
86
|
except (OSError, subprocess.SubprocessError) as error:
|
|
@@ -441,10 +442,12 @@ def _same_symlink_target(link: Path, expected: Path) -> bool:
|
|
|
441
442
|
"""判断 symlink 是否仍指向 manifest 声明的绝对来源。"""
|
|
442
443
|
try:
|
|
443
444
|
raw_target = Path(os.readlink(link))
|
|
445
|
+
actual = raw_target if raw_target.is_absolute() else link.parent / raw_target
|
|
446
|
+
# Windows 3.12 会保留 readlink 的扩展路径前缀;同一目录的路径文本不一定相等。
|
|
447
|
+
# 按文件身份比较,并让缺失、无权限或损坏的目标继续失败关闭。
|
|
448
|
+
return actual.samefile(expected)
|
|
444
449
|
except OSError:
|
|
445
450
|
return False
|
|
446
|
-
actual = raw_target if raw_target.is_absolute() else link.parent / raw_target
|
|
447
|
-
return actual.resolve(strict=False) == expected.resolve(strict=False)
|
|
448
451
|
|
|
449
452
|
|
|
450
453
|
def _registry_registration_conflicts(
|
|
@@ -516,6 +519,7 @@ def _flower_call(operation: str, **values: Any) -> dict[str, Any]:
|
|
|
516
519
|
input=json.dumps({"operation": operation, **values}, ensure_ascii=False),
|
|
517
520
|
capture_output=True,
|
|
518
521
|
text=True,
|
|
522
|
+
encoding="utf-8",
|
|
519
523
|
timeout=60,
|
|
520
524
|
check=False,
|
|
521
525
|
)
|
|
@@ -1037,12 +1041,13 @@ def _task_directories(target_root: Path) -> set[str]:
|
|
|
1037
1041
|
def _run_target_python(target_root: Path, script: Path, *args: str) -> None:
|
|
1038
1042
|
"""使用当前 Python 解释器运行目标分支自己的 Trellis 脚本。"""
|
|
1039
1043
|
result = subprocess.run(
|
|
1040
|
-
[sys.executable, str(script), *args],
|
|
1044
|
+
[sys.executable, "-X", "utf8", str(script), *args],
|
|
1041
1045
|
cwd=target_root,
|
|
1042
1046
|
check=False,
|
|
1043
1047
|
stdout=subprocess.PIPE,
|
|
1044
1048
|
stderr=subprocess.PIPE,
|
|
1045
1049
|
text=True,
|
|
1050
|
+
encoding="utf-8",
|
|
1046
1051
|
timeout=30,
|
|
1047
1052
|
)
|
|
1048
1053
|
if result.returncode != 0:
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"syncedAt": "2026-09-
|
|
2
|
+
"syncedAt": "2026-09-17T03:40:21.148Z",
|
|
3
3
|
"syncedFrom": "vendor/skill-garden",
|
|
4
|
-
"sourceCommit": "
|
|
4
|
+
"sourceCommit": "94df742fdf6530a2bf8f72df68a5a9a30b929d7d",
|
|
5
5
|
"common": {
|
|
6
6
|
"codexSkills": [
|
|
7
7
|
"aliyun-ops",
|
|
@@ -258,6 +258,13 @@
|
|
|
258
258
|
"scripts/task-store-write-integrity/set-meta-selector.py",
|
|
259
259
|
"scripts/task-store-write-integrity/set-scope-content.py",
|
|
260
260
|
"scripts/task-store-write-integrity/set-scope-selector.py",
|
|
261
|
+
"scripts/windows-streams/common-content.py",
|
|
262
|
+
"scripts/windows-streams/common-selector.py",
|
|
263
|
+
"scripts/windows-streams/hook-content.py",
|
|
264
|
+
"scripts/windows-streams/hook-selector.py",
|
|
265
|
+
"scripts/windows-streams/patch.json",
|
|
266
|
+
"scripts/windows-streams/prefix-content.py",
|
|
267
|
+
"scripts/windows-streams/prefix-selector.py",
|
|
261
268
|
"skills/trellis-before-dev/project-knowledge-discovery/content.md",
|
|
262
269
|
"skills/trellis-before-dev/project-knowledge-discovery/patch.json",
|
|
263
270
|
"skills/trellis-before-dev/project-knowledge-discovery/selector.md",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flower-trellis",
|
|
3
|
-
"version": "0.6.9
|
|
3
|
+
"version": "0.6.9",
|
|
4
4
|
"description": "一键安装/升级 Trellis 并自动融合 skill-garden 强化包(默认 Claude + agents)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
35
|
"scripts": {
|
|
36
|
-
"test": "node scripts/run-js-tests.mjs &&
|
|
36
|
+
"test": "node scripts/run-js-tests.mjs && node scripts/run-python-tests.mjs && node scripts/check-patch-conflicts.mjs && npm run patch:targets:check && node scripts/check-ai-context-budget.mjs && node scripts/check-output-templates.mjs",
|
|
37
37
|
"sync": "node scripts/sync-enhancements.mjs",
|
|
38
38
|
"patch:targets": "node scripts/run-skill-garden-compiled-targets.mjs",
|
|
39
39
|
"patch:targets:check": "node scripts/run-skill-garden-compiled-targets.mjs --check",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
"commit-and-tag-version": "^12.7.3"
|
|
71
71
|
},
|
|
72
72
|
"flowerReleaseNotes": {
|
|
73
|
-
"version": "0.6.9
|
|
73
|
+
"version": "0.6.9",
|
|
74
74
|
"source": "CHANGELOG.md",
|
|
75
|
-
"body": "###
|
|
75
|
+
"body": "### 🐛 修复 Bug Fixes\n\n* **ci:** 修复跨平台初始化、检出与迁移兼容 ([a06facc](https://github.com/SilentFlower/flower-trellis/commit/a06faccdcd7a6e8bd1829f4b41d35c252e814dd5))\n* **ci:** 补齐原生依赖构建并修正 Windows 迁移夹具 ([99ff392](https://github.com/SilentFlower/flower-trellis/commit/99ff392299612912c028686ff9b3c7204bf67aec))\n* **cli:** 修复 Windows 非交互完成后挂起 ([8c71a2b](https://github.com/SilentFlower/flower-trellis/commit/8c71a2b393288b1d7b5530c3d91510d72992fbc1))\n* **compat:** 修复 Windows/Linux Python 运行与跨平台回归 ([311c4ea](https://github.com/SilentFlower/flower-trellis/commit/311c4eac99c7fd9269f62d2b10a36fdda7618b40))\n* **hooks:** 修复跨平台 SessionStart 分段注入 ([5f6a0d4](https://github.com/SilentFlower/flower-trellis/commit/5f6a0d4325e6e8e4d39f230c7ac3a1de9fd0c9af))\n* **test:** 修复 SessionStart CI 对本地身份文件的依赖 ([28d8bcf](https://github.com/SilentFlower/flower-trellis/commit/28d8bcff8bdde2dbcb0b37468ec3937e9d17d543))\n* **update:** 修复共享超时状态与 PTY 受管提示 ([7c2c4dc](https://github.com/SilentFlower/flower-trellis/commit/7c2c4dc8a25c755cfd79864e80bd4a8305f362f5))\n\n\n### ⚡ 性能 Performance\n\n* **update:** 缩短升级等待并补充阶段耗时诊断 ([94788f4](https://github.com/SilentFlower/flower-trellis/commit/94788f4023b2af047e71b4689bc3716bed32fd21))\n\n\n### 📝 文档 Docs\n\n* **spec:** 要求跨平台脚本和指令通过 GitHub Actions 验收 ([87aa9b8](https://github.com/SilentFlower/flower-trellis/commit/87aa9b8936167d422f3752d4bb9f5cd958a41471))",
|
|
76
76
|
"truncated": false
|
|
77
77
|
},
|
|
78
78
|
"optionalDependencies": {
|