flower-trellis 0.4.5-beta.1 → 0.4.5-beta.3

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "syncedAt": "2026-07-07T08:47:14.594Z",
2
+ "syncedAt": "2026-07-07T09:44:07.804Z",
3
3
  "syncedFrom": "vendor/skill-garden",
4
4
  "sourceCommit": "5fdebe60e923c91ba912461b99413b5c8e11a38c",
5
5
  "common": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flower-trellis",
3
- "version": "0.4.5-beta.1",
3
+ "version": "0.4.5-beta.3",
4
4
  "description": "一键安装/升级 Trellis 并自动融合 skill-garden 强化包(默认 Claude + agents)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,6 +17,7 @@ from pathlib import Path
17
17
 
18
18
 
19
19
  ACTIONABLE_STATUSES = {"update_available", "project_out_of_sync"}
20
+ SELF_CHECK_TIMEOUT_SECONDS = 30
20
21
 
21
22
 
22
23
  def _debug(message: str) -> None:
@@ -57,7 +58,7 @@ def _run_self_check(project_dir: Path) -> dict | None:
57
58
  text=True,
58
59
  encoding="utf-8",
59
60
  errors="replace",
60
- timeout=8,
61
+ timeout=SELF_CHECK_TIMEOUT_SECONDS,
61
62
  cwd=str(project_dir),
62
63
  )
63
64
  except (subprocess.TimeoutExpired, FileNotFoundError, PermissionError) as exc:
@@ -84,6 +85,8 @@ def _format_context(data: dict) -> str:
84
85
  out_of_sync_reasons = project.get("outOfSyncReasons") or []
85
86
  lines = [
86
87
  "<flower-update>",
88
+ "priority: blocking_confirmation_required",
89
+ "instruction_scope: first_assistant_reply",
87
90
  f"status: {data.get('status')}",
88
91
  f"policy: {data.get('policy')}",
89
92
  f"current_flower: {current.get('flowerVersion')}",
@@ -114,11 +117,22 @@ def _format_context(data: dict) -> str:
114
117
  return "\n".join(lines)
115
118
 
116
119
 
117
- def _emit_context(context: str) -> None:
120
+ def _system_message(data: dict) -> str:
121
+ """生成 Codex / Claude Code 更容易注意到的短系统提示。"""
122
+ ai = data.get("ai") or {}
123
+ command = (data.get("commands") or {}).get("recommended") or ai.get("command")
124
+ if ai.get("mode") == "ask" and command:
125
+ return "flower-trellis 发现可执行更新;必须先询问用户是否执行 recommended_command,确认前禁止运行。"
126
+ if command:
127
+ return "flower-trellis 发现可执行更新;已注入 recommended_command。"
128
+ return "flower-trellis update context injected"
129
+
130
+
131
+ def _emit_context(context: str, system_message: str) -> None:
118
132
  """输出 Codex / Claude Code 都接受的 SessionStart hook JSON。"""
119
133
  result = {
120
134
  "suppressOutput": True,
121
- "systemMessage": "flower-trellis update context injected",
135
+ "systemMessage": system_message,
122
136
  "hookSpecificOutput": {
123
137
  "hookEventName": "SessionStart",
124
138
  "additionalContext": context,
@@ -138,7 +152,7 @@ def main() -> None:
138
152
  data = _run_self_check(_project_dir(hook_input))
139
153
  if not data or data.get("status") not in ACTIONABLE_STATUSES:
140
154
  return
141
- _emit_context(_format_context(data))
155
+ _emit_context(_format_context(data), _system_message(data))
142
156
 
143
157
 
144
158
  if __name__ == "__main__":
@@ -4,6 +4,7 @@ import { FLOWER_UPDATE_HOOK_REL } from "./flower-assets.js";
4
4
 
5
5
  const WORKFLOW_HOOK_SCRIPT = ".claude/hooks/inject-workflow-state.py";
6
6
  const DEFAULT_COMMAND = `python3 ${FLOWER_UPDATE_HOOK_REL}`;
7
+ const FLOWER_UPDATE_TIMEOUT = 30;
7
8
 
8
9
  /** 容错读取 Claude settings JSON。 */
9
10
  function readSettings(settingsPath) {
@@ -45,6 +46,20 @@ function hasFlowerHook(hooks) {
45
46
  );
46
47
  }
47
48
 
49
+ /** 将已有 flower update hook 的超时迁移到当前预算。 */
50
+ function normalizeFlowerHookTimeout(hooks) {
51
+ let changed = false;
52
+ for (const hook of hooks) {
53
+ if (hook?.type !== "command" || typeof hook.command !== "string") continue;
54
+ if (!hook.command.includes(FLOWER_UPDATE_HOOK_REL)) continue;
55
+ if (hook.timeout !== FLOWER_UPDATE_TIMEOUT) {
56
+ hook.timeout = FLOWER_UPDATE_TIMEOUT;
57
+ changed = true;
58
+ }
59
+ }
60
+ return changed;
61
+ }
62
+
48
63
  /** 合并 Claude startup hook,并确保 clear / compact 不运行 update hook。 */
49
64
  function mergeClaudeHooks(settingsPath) {
50
65
  const config = readSettings(settingsPath);
@@ -71,8 +86,9 @@ function mergeClaudeHooks(settingsPath) {
71
86
  if (group.hooks.length !== before) changed = true;
72
87
  }
73
88
 
89
+ changed = normalizeFlowerHookTimeout(startup.hooks) || changed;
74
90
  if (!hasFlowerHook(startup.hooks)) {
75
- startup.hooks.push({ type: "command", command, timeout: 8 });
91
+ startup.hooks.push({ type: "command", command, timeout: FLOWER_UPDATE_TIMEOUT });
76
92
  changed = true;
77
93
  }
78
94
 
@@ -28,6 +28,8 @@ function readProjectTrellisVersion(target) {
28
28
 
29
29
  /** 判断远程探测缓存是否仍在 interval 内。 */
30
30
  function isRemoteCacheFresh(updateCheck, now = new Date()) {
31
+ if (updateCheck.lastStatus === "offline" || updateCheck.lastErrorCode) return false;
32
+ if (!updateCheck.lastRemote) return false;
31
33
  if (!updateCheck.lastCheckedAt) return false;
32
34
  const checkedAt = new Date(updateCheck.lastCheckedAt).getTime();
33
35
  if (!Number.isFinite(checkedAt)) return false;
@@ -332,7 +334,6 @@ export async function buildSelfCheck(target, options = {}) {
332
334
  if (!tags) {
333
335
  if (writeCache && manifest) {
334
336
  writeUpdateCheck(absoluteTarget, {
335
- lastCheckedAt: now.toISOString(),
336
337
  lastStatus: "offline",
337
338
  lastErrorCode: "fetch_failed",
338
339
  });
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { confirm } from "@inquirer/prompts";
3
3
  import chalk from "chalk";
4
+ import { readManifest, readUpdateCheck, writeUpdateCheck } from "./manifest.js";
4
5
  import { isRunningViaNpx } from "./runtime-env.js";
5
6
  import { flowerVersion } from "./versions.js";
6
7
 
@@ -16,8 +17,8 @@ import { flowerVersion } from "./versions.js";
16
17
  const REGISTRY = "https://registry.npmjs.org";
17
18
  /** 待检测的包名(即本包)。 */
18
19
  const PKG = "flower-trellis";
19
- /** 网络探测超时(毫秒)—— init/update 是重操作,2.5s 探测可接受且不显著影响体感。 */
20
- const TIMEOUT_MS = 2500;
20
+ /** 网络探测超时(毫秒)—— 启动检查有 hook 总预算兜底,5s 可降低 registry 偶发慢响应误判。 */
21
+ const TIMEOUT_MS = 5000;
21
22
 
22
23
  /**
23
24
  * 取 npm 上 flower-trellis 的 dist-tags;任何失败(离线/超时/非 200/解析异常)一律
@@ -173,13 +174,29 @@ export function getUpdateRecommendation(current, tags) {
173
174
  return null;
174
175
  }
175
176
 
177
+ /** 尽力而为刷新目标项目 manifest 里的远端探测缓存。 */
178
+ function rememberRemoteTags(target, tags, status) {
179
+ try {
180
+ if (!readManifest(target)) return;
181
+ writeUpdateCheck(target, {
182
+ lastCheckedAt: new Date().toISOString(),
183
+ lastRemote: tags,
184
+ lastStatus: status,
185
+ lastErrorCode: null,
186
+ });
187
+ } catch {
188
+ // 缓存写入只是优化后续启动提示,失败不能影响 init/update 主流程。
189
+ }
190
+ }
191
+
176
192
  /**
177
193
  * 检测 flower-trellis 自身是否有新版本,并按场景提示/引导升级。在 init / update 的
178
194
  * 品牌头部之后、主操作之前调用。
179
195
  *
180
196
  * 短路条件(任一命中即跳过,什么都不打印):关闭开关(`--no-update-check` 使
181
- * `ctx.updateCheck===false`,或环境变量 `FLOWER_NO_UPDATE_CHECK` 非空)、经 npx 运行、
182
- * 网络探测失败、无升级推荐。
197
+ * `ctx.updateCheck===false`,目标 manifest 的 `updateCheck.enabled=false` /
198
+ * `policy=off`,或环境变量 `FLOWER_NO_UPDATE_CHECK` 非空)、经 npx 运行、网络探测失败、
199
+ * 无升级推荐。
183
200
  *
184
201
  * 发现新版时的行为:
185
202
  * - 交互 TTY:打印通知 → confirm 询问是否升级 → 同意则执行推荐的 npm install 命令;
@@ -194,6 +211,8 @@ export function getUpdateRecommendation(current, tags) {
194
211
  export async function checkForUpdate(ctx, commandLabel) {
195
212
  // 1. 关闭开关:显式 flag 或环境变量
196
213
  if (ctx.updateCheck === false || process.env.FLOWER_NO_UPDATE_CHECK) return;
214
+ const updateCheck = readUpdateCheck(ctx.target);
215
+ if (!updateCheck.enabled || updateCheck.policy === "off") return;
197
216
  // 2. npx 本就是最新版,跳过(连通知都不打,避免误导)
198
217
  if (isRunningViaNpx(import.meta.url)) return;
199
218
 
@@ -204,6 +223,7 @@ export async function checkForUpdate(ctx, commandLabel) {
204
223
  // 4. 根据本地版本通道生成推荐;无推荐时不打扰
205
224
  const current = flowerVersion();
206
225
  const recommendation = getUpdateRecommendation(current, tags);
226
+ rememberRemoteTags(ctx.target, tags, recommendation ? "update_available" : "up_to_date");
207
227
  if (!recommendation) return;
208
228
 
209
229
  // 5. 打印发现新版本通知(粉色品牌色,与 banner 一致)