flower-trellis 0.6.8 → 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.
Files changed (34) hide show
  1. package/README.md +24 -1
  2. package/enhancements/0.6/.agents/skills/trellis-route/scripts/route_state.py +2 -1
  3. package/enhancements/0.6/.claude/skills/trellis-route/scripts/route_state.py +2 -1
  4. package/enhancements/0.6/overrides/bundles/shared-hook-runtime.json +1 -1
  5. package/enhancements/0.6/overrides/patches/scripts/windows-streams/common-content.py +10 -0
  6. package/enhancements/0.6/overrides/patches/scripts/windows-streams/common-selector.py +14 -0
  7. package/enhancements/0.6/overrides/patches/scripts/windows-streams/hook-content.py +7 -0
  8. package/enhancements/0.6/overrides/patches/scripts/windows-streams/hook-selector.py +6 -0
  9. package/enhancements/0.6/overrides/patches/scripts/windows-streams/patch.json +186 -0
  10. package/enhancements/0.6/overrides/patches/scripts/windows-streams/prefix-content.py +1 -0
  11. package/enhancements/0.6/overrides/patches/scripts/windows-streams/prefix-selector.py +1 -0
  12. package/enhancements/0.6/scripts/auto_loop.py +21 -10
  13. package/enhancements/0.6/scripts/decision_log.py +7 -5
  14. package/enhancements/0.6/scripts/git_evidence.py +2 -1
  15. package/enhancements/0.6/scripts/pre_check_state.py +1 -1
  16. package/enhancements/0.6/scripts/task_intent.py +4 -0
  17. package/enhancements/0.6/scripts/worktree_setup.py +8 -3
  18. package/enhancements/MANIFEST.json +9 -2
  19. package/package.json +4 -4
  20. package/src/assets/flower_session_start.py +40 -15
  21. package/src/assets/flower_telemetry_hook.py +18 -3
  22. package/src/cli.js +7 -1
  23. package/src/commands/self-check.js +3 -2
  24. package/src/commands/self-update.js +10 -6
  25. package/src/commands/update.js +33 -19
  26. package/src/lib/global-trellis-sync.js +60 -8
  27. package/src/lib/operation-timing.js +50 -0
  28. package/src/lib/self-check.js +14 -54
  29. package/src/lib/terminal-state.js +5 -5
  30. package/src/lib/trellis-runner.js +34 -10
  31. package/src/lib/trellis-update-entry.js +10 -0
  32. package/src/lib/trellis-update-fetch.js +18 -0
  33. package/src/lib/update-check-cache.js +47 -0
  34. 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
- - **联网、尽力而为**:带 5s 超时,离线 / 超时 / 失败一律静默跳过,绝不阻断安装/升级主流程。
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
- ["python3", str(repo_root / ".trellis/scripts/task.py"), "current", "--source"],
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
- ["python3", str(repo_root / ".trellis/scripts/task.py"), "current", "--source"],
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
@@ -3,5 +3,5 @@
3
3
  "id": "shared-hook-runtime",
4
4
  "aliases": [],
5
5
  "installMode": "full-only",
6
- "patches": ["hooks/inject-workflow-state/shared-runtime"]
6
+ "patches": ["hooks/inject-workflow-state/shared-runtime", "scripts/windows-streams"]
7
7
  }
@@ -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,7 @@
1
+ if sys.platform.startswith("win"):
2
+ _stdout_reconfigure = getattr(sys.stdout, "reconfigure", None)
3
+ if callable(_stdout_reconfigure):
4
+ try:
5
+ _stdout_reconfigure(encoding="utf-8", errors="replace")
6
+ except (OSError, ValueError):
7
+ pass
@@ -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().is_relative_to(repo_root.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
- "python3", str(helper), "resolve", "--target", target,
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
- return _baseline_key(repository, path.strip().removeprefix("./"))
738
- return _baseline_key(".", value.removeprefix("./"))
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.removeprefix("./").rstrip("/")
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
- ["python3", str(repo_root / ".trellis/scripts/task.py"), "current", "--source"],
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().is_relative_to(root):
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.is_relative_to(root)):
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"] = ["python3", "./.trellis/scripts/auto_loop.py", "reconcile",
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
- int(str(event["decision_id"]).removeprefix("DEC-"))
323
- for event in _decision_events(events)
324
- if str(event.get("decision_id", "")).removeprefix("DEC-").isdigit()
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
- ["submodule", "foreach", "--recursive", "--quiet", "pwd"],
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.removeprefix('session-')}",
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-16T09:48:56.489Z",
2
+ "syncedAt": "2026-09-17T03:40:21.148Z",
3
3
  "syncedFrom": "vendor/skill-garden",
4
- "sourceCommit": "66f538fafba63953cf40b7044bc390055012c4a7",
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.8",
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 && python3 -m unittest discover -s test/python -p 'test_*.py' && node scripts/check-patch-conflicts.mjs && npm run patch:targets:check && node scripts/check-ai-context-budget.mjs && node scripts/check-output-templates.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.8",
73
+ "version": "0.6.9",
74
74
  "source": "CHANGELOG.md",
75
- "body": "### 🐛 修复 Bug Fixes\n\n* **trellis:** 减少重复确认与输出,修复技能之间的流程衔接 ([01f79eb](https://github.com/SilentFlower/flower-trellis/commit/01f79eb94dc61b1283f35187b1756f0d2fbe1206))\n - **任务 Brief**:避免进入实现或恢复会话时重复展示、确认已批准的内容。\n - **任务恢复**:Start 统一交给 Continue 恢复进度,复用已加载的有效上下文。\n - **收尾提交**:归档与会话日志合并为一次精确提交,保留原有自动推送条件。\n - **输出精简**:Push 成功后简要显示结果;Check-All 的问题处置选项只展示一次。\n - **规范更新**:统一 Check-All、Update-Spec 和 Break-Loop 的承接规则;已有规范覆盖时允许无需修改。\n - **需求处理**:修正 PRD 创建顺序、需求准确性与覆盖率检查方向,避免把必要的实现细节误判为需求扩张。\n - **平台与入口**:创建命令时遵循实际平台目录;修正普通检查和软件发版的入口指引。\n* **auto-loop:** 同步恢复自纠与回归验证 ([288bd9f](https://github.com/SilentFlower/flower-trellis/commit/288bd9f1fd029203243cc41cb8bdf7858d831c77))\n* **auto-loop:** 同步框架冲突修复与回归验证 ([f240a17](https://github.com/SilentFlower/flower-trellis/commit/f240a172d16abb6e7826b4ae79e37d8ce9ff5fcc))\n* **check-all:** 同步报告落盘规则与规范 ([0477456](https://github.com/SilentFlower/flower-trellis/commit/04774560d8cde20b6bb6abe025bcaa0bf1bb5514))\n* **codex:** 强化 Astra 工作流确认边界 ([fd14a49](https://github.com/SilentFlower/flower-trellis/commit/fd14a49df6f5086b3097a06a1d1355fd7e0a6915))\n* **hooks:** 修复 Windows 更新检查入口与路径参数 ([ed7e68f](https://github.com/SilentFlower/flower-trellis/commit/ed7e68f0cfac6cead9c88817d82ab795450bd724))\n* **push:** 同步保留变更展示与落盘规则 ([9bcd747](https://github.com/SilentFlower/flower-trellis/commit/9bcd74761777f108be0a7286f0e253c06b0cb0ba))\n* **skills:** 迁移通用技能到共享 agents 目录 ([7ebdf05](https://github.com/SilentFlower/flower-trellis/commit/7ebdf0560ccc53ecd252f6e8b259d417d0a53d78))\n* **trellis:** 同步风险展示优化与回归验证 ([07f17a8](https://github.com/SilentFlower/flower-trellis/commit/07f17a8ea40c72fe62d3c99ffef37beb36c06101))\n\n\n### 🧰 维护 Maintenance\n\n* **trellis:** 同步 Flower 0.6.7 与插件安装记录 ([690f1b4](https://github.com/SilentFlower/flower-trellis/commit/690f1b4e87d4bffdb02800e576acf882382de566))\n* **trellis:** 同步 Flower 0.6.8-beta.0 安装记录 ([2ea16ed](https://github.com/SilentFlower/flower-trellis/commit/2ea16ed3f0ddfe8a7d34d1b132fb3cb55d419344))",
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": {