flower-trellis 0.6.4 → 0.6.5

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 (50) hide show
  1. package/README.md +20 -1
  2. package/enhancements/0.6/overrides/bundles/control-plane-integrity.json +2 -0
  3. package/enhancements/0.6/overrides/bundles/intent-routing.json +2 -1
  4. package/enhancements/0.6/overrides/bundles/task-reference-contract.json +24 -0
  5. package/enhancements/0.6/overrides/conflicts.json +33 -0
  6. package/enhancements/0.6/overrides/patches/scripts/task-current-query-contract/patch.json +19 -0
  7. package/enhancements/0.6/overrides/patches/scripts/task-current-query-contract/task-current-content.py +39 -0
  8. package/enhancements/0.6/overrides/patches/scripts/task-current-query-contract/task-current-selector.py +38 -0
  9. package/enhancements/0.6/overrides/patches/scripts/task-reference-resolution/patch.json +20 -0
  10. package/enhancements/0.6/overrides/patches/scripts/task-reference-resolution/task-reference-content.py +64 -0
  11. package/enhancements/0.6/overrides/patches/scripts/task-reference-resolution/task-reference-selector.py +2 -0
  12. package/enhancements/0.6/scripts/decision_log.py +29 -24
  13. package/enhancements/0.6/scripts/task_progress.py +54 -13
  14. package/enhancements/0.6/scripts/untracked_flow.py +13 -5
  15. package/enhancements/MANIFEST.json +9 -2
  16. package/package.json +4 -4
  17. package/src/assets/flower_session_start.py +195 -0
  18. package/src/assets/flower_telemetry_hook.py +98 -0
  19. package/src/builtin-plugins/skill-garden/content-adapter.js +15 -1
  20. package/src/cli.js +3 -1
  21. package/src/commands/init.js +36 -1
  22. package/src/commands/plugin-remote.js +61 -14
  23. package/src/commands/plugin.js +14 -1
  24. package/src/commands/self-check.js +20 -0
  25. package/src/commands/self-update.js +38 -3
  26. package/src/commands/telemetry.js +30 -1
  27. package/src/commands/uninstall.js +20 -0
  28. package/src/commands/update-check.js +19 -0
  29. package/src/commands/update.js +52 -9
  30. package/src/commands/worktree.js +114 -0
  31. package/src/lib/cli-args.js +10 -0
  32. package/src/lib/developer.js +3 -2
  33. package/src/lib/flower-assets.js +21 -6
  34. package/src/lib/platform-patch-adapters.js +46 -8
  35. package/src/lib/telemetry-context.js +275 -0
  36. package/src/lib/telemetry-files.js +144 -0
  37. package/src/lib/telemetry-operation.js +84 -0
  38. package/src/lib/telemetry-queue.js +310 -0
  39. package/src/lib/telemetry-sender.js +7 -0
  40. package/src/lib/telemetry.js +12 -361
  41. package/src/patches/bundles/flower-platform-integration.json +4 -1
  42. package/src/patches/platforms/claude/session-start-hooks/patch.json +28 -0
  43. package/src/patches/platforms/claude/telemetry-hooks/patch.json +59 -0
  44. package/src/patches/platforms/codex/session-start-hooks/patch.json +3 -2
  45. package/src/patches/platforms/codex/telemetry-hooks/patch.json +59 -0
  46. package/src/plugin/application-service.js +5 -22
  47. package/src/plugin/contracts.js +1 -0
  48. package/src/plugin/lock-reachability.js +37 -0
  49. package/src/plugin/schemas/project-files.js +1 -0
  50. package/src/plugin/sources/gitlab-provider.js +50 -9
package/README.md CHANGED
@@ -85,7 +85,11 @@ flower-trellis -v
85
85
 
86
86
  > 已全局安装时可直接写 `flower-trellis`、`ftl` 或 `ft`(三者等价);未安装则在命令前加 `npx`。
87
87
 
88
- 为统计安装活跃度和版本分布,CLI 默认在远程版本检查及 `init` / `update` 成功后上报随机设备 ID、Flower/Trellis 版本、开发者名称和运行平台。开发者名称优先读取项目 `.trellis/.developer`,缺失时回退到目标目录可见的 Git `user.name`,并缓存最近一次有效名称供后续上报;不采集 Git 邮箱、MAC、主机名、系统用户名、项目路径或仓库地址。可用 `flower-trellis telemetry disable` 持久停用,或用 `FLOWER_NO_TELEMETRY=1` 临时停用。
88
+ 默认启用安装与使用遥测:远程版本检查及 `init` / `update` 成功后保留原安装快照;新版还记录 Claude Code / Codex(0.6 full 集成)的 UTC 每日活动,以及 `init`、`update`、`self-update`、`plugin add` 的可观测终态。帮助、预览、菜单和未执行的自更新不计操作。
89
+
90
+ 载荷包含随机安装/事件 ID、观测时间、操作系统/架构、明确的 AI 平台、Flower 和捆绑/项目 Trellis/已装强化包版本;操作另含结果、有限错误分类和耗时口径。强化包版本沿用安装时 Flower 发布版本。开发者名称优先取项目 `.trellis/.developer`,其次 Git `user.name` 和缓存,可为空;不采集提示词、对话、session ID、错误原文、命令参数、外部插件 ID、Git 邮箱、MAC、主机名、系统用户名、项目路径或仓库地址。
91
+
92
+ 事件先进入用户配置目录下的 `telemetry-v2/`,后台独立进程一次最多发送 20 条,不等待网络即可结束主命令。本地最多保留 200 条、72 小时,失败退避重试;完全离线、强杀或关闭遥测可能造成样本缺口。`flower-trellis telemetry status` 只读展示待发数、丢弃数和最近诊断;`telemetry disable` 持久停用并清待发记录,已发出的请求无法撤回;`FLOWER_NO_TELEMETRY=1` 临时停用采集和发送且不写本地状态。旧版进程不具备新版共锁协议,不承诺与所有历史二进制之间的并发强一致。
89
93
 
90
94
  ### 命令
91
95
 
@@ -141,6 +145,21 @@ flower-trellis enable --target .
141
145
 
142
146
  项目处于 disabled 时,`flower-trellis update`、`self-update` 的项目更新链,以及 `plugin add/update/remove/replay` 会临时恢复必要入口,完成写操作后再次 detach,并校验最终仍为 disabled;外部 Plugin 内容不会被当作 Trellis 入口删除。直接运行上游 `trellis update` 不经过这一控制面,可能重新生成入口,此时 `flower-trellis status` 会报告 `drifted`。`disable` 也不等同于 `uninstall`:历史数据和恢复能力会继续保留。
143
147
 
148
+ ## Astra 工作流提示
149
+
150
+ Codex 的 SessionStart 在 `startup`、`clear`、`compact` 时,只有事件输入的当前模型精确为 `gpt-6-astra`,才会在 state 分段追加一份英文工作流提示。其他模型(包括 5.5)、缺失模型和未知别名不会追加;普通用户轮次不重复注入。
151
+
152
+ 提示用于辅助遵循技能、工作流、模板和证据陈述,效果仍需行为对照验证。它不能删除宿主或历史指令,也不保证覆盖更高优先级要求。会话中切换模型后,要等下次 SessionStart 才重新判断是否追加。
153
+
154
+ 默认开启。要独立关闭,在项目 `.trellis/config.yaml` 已有的 `codex` 配置下合并这一项,保留原有字段:
155
+
156
+ ```yaml
157
+ codex:
158
+ astra_workflow_hint: false
159
+ ```
160
+
161
+ 设置 `true` 或删除该字段恢复默认。开关只影响后续新增提示;做无历史干扰的对照时应使用新会话。原工作流上下文继续注入,Trellis 全局禁用、resume 和非交互跳过规则保持原有行为。`no-trellis` 仍只跳过本轮 UserPromptSubmit。
162
+
144
163
  ## Flower Plugin
145
164
 
146
165
  Flower Plugin 是 flower-trellis 的标准运行时格式。GitHub 公共仓库可自动识别 Flower、Codex、Claude Code 与 Skill-only 包,并先规范化为标准 Flower package,再进入同一套来源解析、依赖锁定、能力校验、内容投影、事务写入和卸载所有权。外部 `skills/` 与 Claude legacy `commands/*.md` 可导入;hooks、agents、MCP、LSP、monitor、bin、settings、themes、output styles 和 apps 只展示兼容性诊断,不会执行。
@@ -5,6 +5,8 @@
5
5
  "installMode": "full-only",
6
6
  "patches": [
7
7
  "scripts/task-store-write-integrity",
8
+ "scripts/task-current-query-contract",
9
+ "scripts/task-reference-resolution",
8
10
  "scripts/runtime-state-integrity",
9
11
  "scripts/session-context-update-boundary",
10
12
  "hooks/session-start/update-boundary"
@@ -32,6 +32,7 @@
32
32
  "skills/trellis-brainstorm/planning-authorization",
33
33
  "skills/trellis-brainstorm/auto-task-create",
34
34
  "skills/trellis-brainstorm/planning-handoff",
35
- "scripts/task-start-brief-gate"
35
+ "scripts/task-start-brief-gate",
36
+ "scripts/task-current-query-contract"
36
37
  ]
37
38
  }
@@ -0,0 +1,24 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "id": "task-reference-contract",
4
+ "aliases": [
5
+ "decision-log",
6
+ "task-progress",
7
+ "progress",
8
+ "push-snapshot",
9
+ "snapshot",
10
+ "trellis-auto-loop",
11
+ "auto-loop",
12
+ "auto-loop-runner",
13
+ "trellis-finish-work",
14
+ "finish-work",
15
+ "finish-work-enhancement",
16
+ "trellis-continue",
17
+ "continue",
18
+ "progress-recovery",
19
+ "trellis-push",
20
+ "push"
21
+ ],
22
+ "installMode": "full-or-selected",
23
+ "patches": ["scripts/task-reference-resolution"]
24
+ }
@@ -1647,6 +1647,39 @@
1647
1647
  },
1648
1648
  "owner": "trellis-finish-work",
1649
1649
  "reason": "finish-work 只能归档已 completed 任务,不能替代 trellis-push 写完成状态。"
1650
+ },
1651
+ {
1652
+ "id": "task-current-required-query-contract",
1653
+ "severity": "error",
1654
+ "target": ".trellis/scripts/task.py",
1655
+ "whenOperations": ["task-current-query-contract"],
1656
+ "assertion": {
1657
+ "type": "required-literal",
1658
+ "values": [
1659
+ "# BEGIN skill-garden patch task-current-query-contract v0.6",
1660
+ "print(colored(\"No current task set\", Colors.YELLOW))",
1661
+ "\"current_task\": task_obj"
1662
+ ]
1663
+ },
1664
+ "owner": "task-current-query-contract",
1665
+ "reason": "current 查询的有效空状态必须以退出码 0 和明确输出表达。"
1666
+ },
1667
+ {
1668
+ "id": "task-reference-required-strict-resolver",
1669
+ "severity": "error",
1670
+ "target": ".trellis/scripts/common/task_utils.py",
1671
+ "whenOperations": ["task-reference-resolution"],
1672
+ "assertion": {
1673
+ "type": "required-literal",
1674
+ "values": [
1675
+ "# BEGIN skill-garden patch task-reference-resolution v0.6",
1676
+ "def resolve_task_reference(task_ref: str, repo_root: Path) -> Path:",
1677
+ "任务引用存在歧义",
1678
+ "任务引用必须指向活动任务目录"
1679
+ ]
1680
+ },
1681
+ "owner": "task-reference-resolution",
1682
+ "reason": "消费 helper 必须复用确定性解析器,并拒绝歧义或越界路径。"
1650
1683
  }
1651
1684
  ]
1652
1685
  }
@@ -0,0 +1,19 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "id": "task-current-query-contract",
4
+ "purpose": "cli_query_contract",
5
+ "operations": [
6
+ {
7
+ "id": "task-current-query-contract",
8
+ "operation": "replace",
9
+ "targets": [{
10
+ "kind": "file",
11
+ "path": ".trellis/scripts/task.py",
12
+ "missing": "error",
13
+ "markerStyle": "hash"
14
+ }],
15
+ "selector": { "type": "literal", "source": "task-current-selector.py" },
16
+ "content": { "source": "task-current-content.py" }
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,39 @@
1
+ def cmd_current(args: argparse.Namespace) -> int:
2
+ """Show active task."""
3
+ repo_root = get_repo_root()
4
+ active = resolve_active_task(repo_root)
5
+
6
+ if getattr(args, "json", False):
7
+ task_obj = None
8
+ if active.task_path:
9
+ data = read_json(repo_root / active.task_path / FILE_TASK_JSON) or {}
10
+ task_obj = {
11
+ "dir": active.task_path,
12
+ "id": data.get("id") or data.get("name"),
13
+ "title": data.get("title"),
14
+ "status": data.get("status"),
15
+ "parent": data.get("parent"),
16
+ "children": data.get("children", []),
17
+ "branch": data.get("branch"),
18
+ "base_branch": data.get("base_branch"),
19
+ }
20
+ print(json.dumps({
21
+ "current_task": task_obj,
22
+ "source": active.source,
23
+ "stale": active.stale,
24
+ }, ensure_ascii=False))
25
+ return 0
26
+
27
+ if args.source:
28
+ print(f"Current task: {active.task_path or '(none)'}")
29
+ print(f"Source: {active.source}")
30
+ if active.stale:
31
+ print("State: stale")
32
+ return 0
33
+
34
+ if active.task_path:
35
+ print(active.task_path)
36
+ return 0
37
+
38
+ print(colored("No current task set", Colors.YELLOW))
39
+ return 0
@@ -0,0 +1,38 @@
1
+ def cmd_current(args: argparse.Namespace) -> int:
2
+ """Show active task."""
3
+ repo_root = get_repo_root()
4
+ active = resolve_active_task(repo_root)
5
+
6
+ if getattr(args, "json", False):
7
+ task_obj = None
8
+ if active.task_path:
9
+ data = read_json(repo_root / active.task_path / FILE_TASK_JSON) or {}
10
+ task_obj = {
11
+ "dir": active.task_path,
12
+ "id": data.get("id") or data.get("name"),
13
+ "title": data.get("title"),
14
+ "status": data.get("status"),
15
+ "parent": data.get("parent"),
16
+ "children": data.get("children", []),
17
+ "branch": data.get("branch"),
18
+ "base_branch": data.get("base_branch"),
19
+ }
20
+ print(json.dumps({
21
+ "current_task": task_obj,
22
+ "source": active.source,
23
+ "stale": active.stale,
24
+ }, ensure_ascii=False))
25
+ return 0 if active.task_path else 1
26
+
27
+ if args.source:
28
+ print(f"Current task: {active.task_path or '(none)'}")
29
+ print(f"Source: {active.source}")
30
+ if active.stale:
31
+ print("State: stale")
32
+ return 0 if active.task_path else 1
33
+
34
+ if active.task_path:
35
+ print(active.task_path)
36
+ return 0
37
+
38
+ return 1
@@ -0,0 +1,20 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "id": "task-reference-resolution",
4
+ "purpose": "cli_task_reference",
5
+ "operations": [
6
+ {
7
+ "id": "task-reference-resolution",
8
+ "operation": "insert",
9
+ "position": "after",
10
+ "targets": [{
11
+ "kind": "file",
12
+ "path": ".trellis/scripts/common/task_utils.py",
13
+ "missing": "error",
14
+ "markerStyle": "hash"
15
+ }],
16
+ "selector": { "type": "literal", "source": "task-reference-selector.py" },
17
+ "content": { "source": "task-reference-content.py" }
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,64 @@
1
+
2
+
3
+ def resolve_task_reference(task_ref: str, repo_root: Path) -> Path:
4
+ """Resolve an existing active task reference deterministically.
5
+
6
+ Args:
7
+ task_ref: Exact task name, unique suffix, relative path, or absolute path.
8
+ repo_root: Repository root path.
9
+
10
+ Returns:
11
+ Resolved active task directory.
12
+
13
+ Raises:
14
+ ValueError: The reference is empty, ambiguous, missing, or outside active tasks.
15
+ """
16
+ raw = task_ref.strip() if isinstance(task_ref, str) else ""
17
+ if not raw:
18
+ raise ValueError("任务引用不能为空")
19
+
20
+ tasks_dir = get_tasks_dir(repo_root).resolve()
21
+ normalized = raw.replace("\\", "/")
22
+ while normalized.startswith("./"):
23
+ normalized = normalized[2:]
24
+
25
+ candidate = Path(raw)
26
+ if candidate.is_absolute() or "/" in normalized or normalized.startswith(".trellis"):
27
+ candidate = candidate if candidate.is_absolute() else repo_root / Path(normalized)
28
+ try:
29
+ resolved = candidate.resolve()
30
+ except (OSError, RuntimeError) as error:
31
+ raise ValueError(f"无法解析任务引用:{task_ref}") from error
32
+ if resolved.parent != tasks_dir or resolved.name == "archive":
33
+ raise ValueError(f"任务引用必须指向活动任务目录:{task_ref}")
34
+ if not resolved.is_dir():
35
+ raise ValueError(f"任务不存在:{task_ref}")
36
+ return resolved
37
+
38
+ exact = tasks_dir / raw
39
+ if exact.is_dir():
40
+ resolved = exact.resolve()
41
+ if resolved.parent != tasks_dir or resolved.name == "archive":
42
+ raise ValueError(f"任务引用必须指向活动任务目录:{task_ref}")
43
+ return resolved
44
+
45
+ try:
46
+ named_matches = sorted(
47
+ path
48
+ for path in tasks_dir.iterdir()
49
+ if path.is_dir() and path.name != "archive" and path.name.endswith(f"-{raw}")
50
+ )
51
+ except OSError as error:
52
+ raise ValueError(f"无法读取活动任务目录:{tasks_dir}") from error
53
+ matches = []
54
+ for path in named_matches:
55
+ resolved = path.resolve()
56
+ if resolved.parent != tasks_dir or resolved.name == "archive":
57
+ raise ValueError(f"任务引用必须指向活动任务目录:{task_ref}")
58
+ matches.append(resolved)
59
+ if len(matches) == 1:
60
+ return matches[0]
61
+ if len(matches) > 1:
62
+ names = ", ".join(path.name for path in matches)
63
+ raise ValueError(f"任务引用存在歧义:{task_ref};候选:{names};请使用完整目录名")
64
+ raise ValueError(f"任务不存在:{task_ref}")
@@ -0,0 +1,2 @@
1
+ # Fallback to treating as relative path
2
+ return repo_root / Path(normalized)
@@ -47,6 +47,17 @@ def _repo_root() -> Path | None:
47
47
  current = current.parent
48
48
 
49
49
 
50
+ def _load_common_modules(repo_root: Path) -> None:
51
+ """把 Trellis scripts 目录加入 import path。
52
+
53
+ Args:
54
+ repo_root: Trellis 项目根。
55
+ """
56
+ scripts_dir = repo_root / ".trellis/scripts"
57
+ if str(scripts_dir) not in sys.path:
58
+ sys.path.insert(0, str(scripts_dir))
59
+
60
+
50
61
  def _resolve_task_dir(repo_root: Path, task: str) -> Path:
51
62
  """把任务引用解析为活动任务目录。
52
63
 
@@ -60,21 +71,13 @@ def _resolve_task_dir(repo_root: Path, task: str) -> Path:
60
71
  Raises:
61
72
  DecisionLogError: 任务不存在或位于项目外。
62
73
  """
63
- raw = task.strip()
64
- candidate = Path(raw)
65
- candidates = [candidate] if candidate.is_absolute() else [
66
- repo_root / raw,
67
- repo_root / ".trellis/tasks" / raw,
68
- ]
69
- for path in candidates:
70
- try:
71
- resolved = path.resolve()
72
- resolved.relative_to((repo_root / ".trellis/tasks").resolve())
73
- except (OSError, ValueError):
74
- continue
75
- if resolved.is_dir():
76
- return resolved
77
- raise DecisionLogError(f"任务不存在或不在活动任务目录:{task}")
74
+ _load_common_modules(repo_root)
75
+ from common.task_utils import resolve_task_reference # type: ignore[import-not-found]
76
+
77
+ try:
78
+ return resolve_task_reference(task, repo_root)
79
+ except ValueError as exc:
80
+ raise DecisionLogError(str(exc)) from exc
78
81
 
79
82
 
80
83
  def _validate_event(event: Any, line_number: int) -> dict[str, Any]:
@@ -457,19 +460,21 @@ def build_parser() -> argparse.ArgumentParser:
457
460
  Returns:
458
461
  已配置的参数解析器。
459
462
  """
460
- parser = argparse.ArgumentParser(description="Trellis AI decision log.")
463
+ parser = argparse.ArgumentParser(description="读取和审查 Trellis AI 决策日志。")
461
464
  subparsers = parser.add_subparsers(dest="command", required=True)
462
465
 
463
- status = subparsers.add_parser("status")
464
- status.add_argument("--task", required=True)
465
- status.add_argument("--json", action="store_true")
466
+ task_help = "任务完整目录名、项目内路径、允许的绝对路径或唯一短名(例如 cli-invocation-contracts)"
467
+
468
+ status = subparsers.add_parser("status", help="读取任务决策审查状态")
469
+ status.add_argument("--task", required=True, help=task_help)
470
+ status.add_argument("--json", action="store_true", help="输出完整 JSON")
466
471
  status.set_defaults(func=cmd_status)
467
472
 
468
- review = subparsers.add_parser("review")
469
- review.add_argument("--task", required=True)
470
- review.add_argument("--verdict", choices=sorted(VALID_VERDICTS), required=True)
471
- review.add_argument("--decision-id", action="append", default=[])
472
- review.add_argument("--notes")
473
+ review = subparsers.add_parser("review", help="记录人工审查结果")
474
+ review.add_argument("--task", required=True, help=task_help)
475
+ review.add_argument("--verdict", choices=sorted(VALID_VERDICTS), required=True, help="审查结论")
476
+ review.add_argument("--decision-id", action="append", default=[], help="需返工的决策 ID,可重复")
477
+ review.add_argument("--notes", help="人工审查备注")
473
478
  review.set_defaults(func=cmd_review)
474
479
  return parser
475
480
 
@@ -88,9 +88,9 @@ def _rel_path(repo_root: Path, path: Path) -> str:
88
88
  def _resolve_task_dir(repo_root: Path, task_ref: str) -> Path:
89
89
  """解析任务目录引用。"""
90
90
  _load_common_modules(repo_root)
91
- from common.task_utils import resolve_task_dir # type: ignore[import-not-found]
91
+ from common.task_utils import resolve_task_reference # type: ignore[import-not-found]
92
92
 
93
- return resolve_task_dir(task_ref, repo_root)
93
+ return resolve_task_reference(task_ref, repo_root)
94
94
 
95
95
 
96
96
  def _current_task_dir(repo_root: Path) -> Path | None:
@@ -127,6 +127,11 @@ def _utc_date() -> str:
127
127
  return datetime.now(timezone.utc).date().isoformat()
128
128
 
129
129
 
130
+ def _utc_now() -> str:
131
+ """返回秒级 UTC ISO-8601 时间。"""
132
+ return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
133
+
134
+
130
135
  def _legacy_progress(snapshot: dict[str, Any]) -> dict[str, Any]:
131
136
  """把旧 last_push_snapshot 映射为新进度结构。"""
132
137
  completed = snapshot.get("completed_steps")
@@ -273,6 +278,15 @@ def _validate_progress(value: Any) -> tuple[dict[str, Any] | None, list[str]]:
273
278
  return value, []
274
279
 
275
280
 
281
+ def _normalize_progress_for_write(value: Any) -> Any:
282
+ """仅为完全缺失的 updatedAt 补充机械时间字段。"""
283
+ if not isinstance(value, dict) or "updatedAt" in value:
284
+ return value
285
+ normalized = dict(value)
286
+ normalized["updatedAt"] = _utc_now()
287
+ return normalized
288
+
289
+
276
290
  def _print_json(data: dict[str, Any], exit_code: int = 0) -> int:
277
291
  """输出紧凑 JSON。"""
278
292
  print(json.dumps(data, ensure_ascii=False, sort_keys=True))
@@ -379,7 +393,7 @@ def cmd_write(args: argparse.Namespace, repo_root: Path) -> int:
379
393
  print(f"错误:invalid-progress-json:{exc}", file=sys.stderr)
380
394
  return 1
381
395
 
382
- progress, errors = _validate_progress(raw_progress)
396
+ progress, errors = _validate_progress(_normalize_progress_for_write(raw_progress))
383
397
  if progress is None:
384
398
  result = {
385
399
  "status": "error",
@@ -489,13 +503,29 @@ def build_parser() -> argparse.ArgumentParser:
489
503
  """创建命令行解析器。"""
490
504
  parser = argparse.ArgumentParser(description="读取或写入 Trellis 任务进度。")
491
505
  subparsers = parser.add_subparsers(dest="command", required=True)
506
+ task_help = "任务完整目录名、项目内路径、允许的绝对路径或唯一短名(例如 cli-invocation-contracts)"
492
507
 
493
508
  status = subparsers.add_parser("status", help="读取任务进度")
494
- status.add_argument("--task", help="任务目录、路径或任务名")
509
+ status.add_argument("--task", help=task_help)
495
510
  status.add_argument("--json", action="store_true", help="输出 JSON")
496
511
 
497
- write = subparsers.add_parser("write", help="写入任务进度")
498
- write.add_argument("--task", required=True, help="任务目录、路径或任务名")
512
+ write = subparsers.add_parser(
513
+ "write",
514
+ help="写入任务进度",
515
+ formatter_class=argparse.RawDescriptionHelpFormatter,
516
+ epilog="""字段:
517
+ completedSteps 已完成步骤的字符串数组
518
+ partialStep 当前部分完成步骤,字符串或 null
519
+ nextStep 下一步,非空字符串
520
+ notes 补充说明字符串
521
+ updatedAt 可选;缺失时自动生成 UTC 时间,显式空值仍会失败
522
+
523
+ 最小示例:
524
+ task_progress.py write --task cli-invocation-contracts \\
525
+ --progress-json '{"completedSteps":[],"partialStep":null,"nextStep":"继续实现","notes":""}'
526
+ """,
527
+ )
528
+ write.add_argument("--task", required=True, help=task_help)
499
529
  write.add_argument("--progress-json", required=True, help="progress JSON 对象")
500
530
  write.add_argument(
501
531
  "--complete",
@@ -505,7 +535,7 @@ def build_parser() -> argparse.ArgumentParser:
505
535
  write.add_argument("--json", action="store_true", help="输出 JSON")
506
536
 
507
537
  reopen = subparsers.add_parser("reopen", help="把 completed 任务恢复为 in_progress")
508
- reopen.add_argument("--task", required=True, help="任务目录、路径或任务名")
538
+ reopen.add_argument("--task", required=True, help=task_help)
509
539
  reopen.add_argument("--json", action="store_true", help="输出 JSON")
510
540
 
511
541
  return parser
@@ -520,12 +550,23 @@ def main() -> int:
520
550
 
521
551
  parser = build_parser()
522
552
  args = parser.parse_args()
523
- if args.command == "status":
524
- return cmd_status(args, repo_root)
525
- if args.command == "write":
526
- return cmd_write(args, repo_root)
527
- if args.command == "reopen":
528
- return cmd_reopen(args, repo_root)
553
+ try:
554
+ if args.command == "status":
555
+ return cmd_status(args, repo_root)
556
+ if args.command == "write":
557
+ return cmd_write(args, repo_root)
558
+ if args.command == "reopen":
559
+ return cmd_reopen(args, repo_root)
560
+ except ValueError as exc:
561
+ payload = {
562
+ "status": "error",
563
+ "reason": "task-reference-error",
564
+ "message": str(exc),
565
+ }
566
+ if getattr(args, "json", False):
567
+ return _print_json(payload, 1)
568
+ print(f"错误:task-reference-error:{exc}", file=sys.stderr)
569
+ return 1
529
570
  parser.print_help()
530
571
  return 1
531
572
 
@@ -363,11 +363,11 @@ def read_untracked_state(
363
363
  repo_root: Trellis 项目根目录。
364
364
  platform_input: 可选平台 hook 输入。
365
365
  platform: 可选平台名称。
366
- allow_active_task: 是否允许 session 同时已有 task,供 adoption 清理使用。
366
+ allow_active_task: 是否允许读取已有活动 task;状态查询会返回不适用结果。
367
367
  validate_workspace: 兼容旧调用方的废弃参数;不再读取或校验 Git workspace。
368
368
 
369
369
  Returns:
370
- ``hit`` 或 ``miss`` 的结构化状态。
370
+ ``hit``、``miss`` 或 ``not-applicable`` 的结构化状态。
371
371
  """
372
372
  del validate_workspace
373
373
  scope = _runtime_scope(
@@ -376,6 +376,14 @@ def read_untracked_state(
376
376
  platform,
377
377
  allow_active_task=allow_active_task,
378
378
  )
379
+ current_task = scope.get("current_task")
380
+ if allow_active_task and isinstance(current_task, str) and current_task.strip():
381
+ return {
382
+ "status": "not-applicable",
383
+ "reason": "active-task-present",
384
+ "task": current_task,
385
+ "contextKey": scope["context_key"],
386
+ }
379
387
  raw = scope["context"].get(STATE_KEY)
380
388
  if raw is None:
381
389
  return {"status": "miss", "reason": "no-active-work", "contextKey": scope["context_key"]}
@@ -398,7 +406,7 @@ def clear_untracked_state(
398
406
  platform: str | None = None,
399
407
  *,
400
408
  work_id: str | None = None,
401
- allow_active_task: bool = True,
409
+ allow_active_task: bool = False,
402
410
  ) -> dict[str, Any]:
403
411
  """按明确原因清理当前 session 的 untracked 状态。
404
412
 
@@ -408,7 +416,7 @@ def clear_untracked_state(
408
416
  platform_input: 可选平台 hook 输入。
409
417
  platform: 可选平台名称。
410
418
  work_id: 可选的精确事项 ID 防误清。
411
- allow_active_task: 是否允许 session 已绑定 task。
419
+ allow_active_task: 是否显式允许 session 已绑定 task。
412
420
 
413
421
  Returns:
414
422
  清理结果和被清理事项 ID。
@@ -538,7 +546,7 @@ def main(argv: list[str] | None = None) -> int:
538
546
  elif args.command == "advance":
539
547
  payload = advance_stage(repo_root, args.stage)
540
548
  elif args.command == "status":
541
- payload = read_untracked_state(repo_root)
549
+ payload = read_untracked_state(repo_root, allow_active_task=True)
542
550
  elif args.command == "session-start-hint":
543
551
  hint = session_start_hint(repo_root)
544
552
  payload = {"status": "hit", "hint": hint} if hint else {"status": "miss"}
@@ -1,7 +1,7 @@
1
1
  {
2
- "syncedAt": "2026-08-28T18:19:48.301Z",
2
+ "syncedAt": "2026-09-05T18:24:52.904Z",
3
3
  "syncedFrom": "vendor/skill-garden",
4
- "sourceCommit": "124b592037eef542d7faf7ecb3be93b4bfd3aed0",
4
+ "sourceCommit": "6199ab8b2e0259ff695d70797943ea1ae97e099c",
5
5
  "common": {
6
6
  "codexSkills": [
7
7
  "aliyun-ops",
@@ -216,6 +216,12 @@
216
216
  "scripts/session-context-update-boundary/output-content.py",
217
217
  "scripts/session-context-update-boundary/output-selector.py",
218
218
  "scripts/session-context-update-boundary/patch.json",
219
+ "scripts/task-current-query-contract/patch.json",
220
+ "scripts/task-current-query-contract/task-current-content.py",
221
+ "scripts/task-current-query-contract/task-current-selector.py",
222
+ "scripts/task-reference-resolution/patch.json",
223
+ "scripts/task-reference-resolution/task-reference-content.py",
224
+ "scripts/task-reference-resolution/task-reference-selector.py",
219
225
  "scripts/task-start-brief-gate/degraded-content.py",
220
226
  "scripts/task-start-brief-gate/degraded-selector.py",
221
227
  "scripts/task-start-brief-gate/guard-content.py",
@@ -451,6 +457,7 @@
451
457
  "maven-verification.json",
452
458
  "shared-hook-runtime.json",
453
459
  "task-progress-recovery.json",
460
+ "task-reference-contract.json",
454
461
  "trellis-meta.json",
455
462
  "trellis-session-insight.json",
456
463
  "untracked-execution.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flower-trellis",
3
- "version": "0.6.4",
3
+ "version": "0.6.5",
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 --test test/js/*.test.js && 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 && 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",
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.4",
73
+ "version": "0.6.5",
74
74
  "source": "CHANGELOG.md",
75
- "body": "### 🐛 修复 Bug Fixes\n\n* **plugin:** 支持离线冻结未更新的外部 Plugin ([6210cfa](https://github.com/SilentFlower/flower-trellis/commit/6210cfae8d459a3e7b105043e4ecbc23d1b5bed9))",
75
+ "body": "### ✨ 新功能 Features\n\n* **cli:** 优化调用契约与帮助体验 ([b7a39db](https://github.com/SilentFlower/flower-trellis/commit/b7a39dbda2042643c3e15dd47caebe7c83242548))\n* **codex:** 为 Astra 添加 SessionStart 工作流提示 ([1d79835](https://github.com/SilentFlower/flower-trellis/commit/1d79835f4bea1782f5774ff8c14a6263742b1677))\n* **telemetry:** 采集平台活跃与核心操作质量 ([08598bf](https://github.com/SilentFlower/flower-trellis/commit/08598bf26638567b9183150d7c258e2f801046a4))\n\n\n### 🐛 修复 Bug Fixes\n\n* **hooks:** 分段注入启动上下文并保留额度 ([5eaf6af](https://github.com/SilentFlower/flower-trellis/commit/5eaf6afd7c7b088e8421f3b176d0d79ccb9cdba2))\n* **plugin:** 兼容旧 RD Guide 离线升级 ([fcec390](https://github.com/SilentFlower/flower-trellis/commit/fcec3905629dd31bc3f113ac28bf64139311324e))",
76
76
  "truncated": false
77
77
  },
78
78
  "optionalDependencies": {