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.
- package/README.md +20 -1
- package/enhancements/0.6/overrides/bundles/control-plane-integrity.json +2 -0
- package/enhancements/0.6/overrides/bundles/intent-routing.json +2 -1
- package/enhancements/0.6/overrides/bundles/task-reference-contract.json +24 -0
- package/enhancements/0.6/overrides/conflicts.json +33 -0
- package/enhancements/0.6/overrides/patches/scripts/task-current-query-contract/patch.json +19 -0
- package/enhancements/0.6/overrides/patches/scripts/task-current-query-contract/task-current-content.py +39 -0
- package/enhancements/0.6/overrides/patches/scripts/task-current-query-contract/task-current-selector.py +38 -0
- package/enhancements/0.6/overrides/patches/scripts/task-reference-resolution/patch.json +20 -0
- package/enhancements/0.6/overrides/patches/scripts/task-reference-resolution/task-reference-content.py +64 -0
- package/enhancements/0.6/overrides/patches/scripts/task-reference-resolution/task-reference-selector.py +2 -0
- package/enhancements/0.6/scripts/decision_log.py +29 -24
- package/enhancements/0.6/scripts/task_progress.py +54 -13
- package/enhancements/0.6/scripts/untracked_flow.py +13 -5
- package/enhancements/MANIFEST.json +9 -2
- package/package.json +4 -4
- package/src/assets/flower_session_start.py +195 -0
- package/src/assets/flower_telemetry_hook.py +98 -0
- package/src/builtin-plugins/skill-garden/content-adapter.js +15 -1
- package/src/cli.js +3 -1
- package/src/commands/init.js +36 -1
- package/src/commands/plugin-remote.js +61 -14
- package/src/commands/plugin.js +14 -1
- package/src/commands/self-check.js +20 -0
- package/src/commands/self-update.js +38 -3
- package/src/commands/telemetry.js +30 -1
- package/src/commands/uninstall.js +20 -0
- package/src/commands/update-check.js +19 -0
- package/src/commands/update.js +52 -9
- package/src/commands/worktree.js +114 -0
- package/src/lib/cli-args.js +10 -0
- package/src/lib/developer.js +3 -2
- package/src/lib/flower-assets.js +21 -6
- package/src/lib/platform-patch-adapters.js +46 -8
- package/src/lib/telemetry-context.js +275 -0
- package/src/lib/telemetry-files.js +144 -0
- package/src/lib/telemetry-operation.js +84 -0
- package/src/lib/telemetry-queue.js +310 -0
- package/src/lib/telemetry-sender.js +7 -0
- package/src/lib/telemetry.js +12 -361
- package/src/patches/bundles/flower-platform-integration.json +4 -1
- package/src/patches/platforms/claude/session-start-hooks/patch.json +28 -0
- package/src/patches/platforms/claude/telemetry-hooks/patch.json +59 -0
- package/src/patches/platforms/codex/session-start-hooks/patch.json +3 -2
- package/src/patches/platforms/codex/telemetry-hooks/patch.json +59 -0
- package/src/plugin/application-service.js +5 -22
- package/src/plugin/contracts.js +1 -0
- package/src/plugin/lock-reachability.js +37 -0
- package/src/plugin/schemas/project-files.js +1 -0
- 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
|
-
|
|
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}")
|
|
@@ -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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
463
|
+
parser = argparse.ArgumentParser(description="读取和审查 Trellis AI 决策日志。")
|
|
461
464
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
462
465
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
status.
|
|
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
|
|
91
|
+
from common.task_utils import resolve_task_reference # type: ignore[import-not-found]
|
|
92
92
|
|
|
93
|
-
return
|
|
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(
|
|
498
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
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:
|
|
366
|
+
allow_active_task: 是否允许读取已有活动 task;状态查询会返回不适用结果。
|
|
367
367
|
validate_workspace: 兼容旧调用方的废弃参数;不再读取或校验 Git workspace。
|
|
368
368
|
|
|
369
369
|
Returns:
|
|
370
|
-
``hit`` 或 ``
|
|
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 =
|
|
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:
|
|
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-
|
|
2
|
+
"syncedAt": "2026-09-05T18:24:52.904Z",
|
|
3
3
|
"syncedFrom": "vendor/skill-garden",
|
|
4
|
-
"sourceCommit": "
|
|
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.
|
|
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
|
|
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.
|
|
73
|
+
"version": "0.6.5",
|
|
74
74
|
"source": "CHANGELOG.md",
|
|
75
|
-
"body": "### 🐛 修复 Bug Fixes\n\n* **plugin:**
|
|
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": {
|