flower-trellis 0.6.4 → 0.6.6
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 +37 -4
- 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/overrides/patches/skills/trellis-brainstorm/planning-handoff/content.md +2 -4
- package/enhancements/0.6/overrides/patches/skills/trellis-brainstorm/planning-handoff/planning-contract-content.md +1 -1
- package/enhancements/0.6/overrides/patches/skills/trellis-brainstorm/planning-handoff/readiness-content.md +2 -2
- package/enhancements/0.6/overrides/patches/skills/trellis-brainstorm/planning-handoff/review-content.md +2 -2
- 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
|
+
}
|
|
@@ -137,9 +137,9 @@
|
|
|
137
137
|
"<!-- BEGIN skill-garden patch brainstorm-planning-handoff v0.6 -->\n## Quality Bar",
|
|
138
138
|
"## Planning Handoff",
|
|
139
139
|
"<!-- BEGIN skill-garden patch brainstorm-planning-readiness v0.6 -->\n8. Load `trellis-task-brief`",
|
|
140
|
-
"
|
|
140
|
+
"Only valid explicit preauthorization for the current final Brief, as defined by `trellis-task-brief`, permits same-turn start within unchanged scope.",
|
|
141
141
|
"The final Brief must cover Goal, Scope, Non-Goals, Key Decisions, Key Context, Acceptance, one-hop Next Step",
|
|
142
|
-
"
|
|
142
|
+
"Ordinary implementation intent expressed before the final artifacts and full Brief are shown authorizes planning only",
|
|
143
143
|
"unresolved items are `- [ ]`",
|
|
144
144
|
"Do not write bare placeholders"
|
|
145
145
|
]
|
|
@@ -161,9 +161,9 @@
|
|
|
161
161
|
"<!-- BEGIN skill-garden patch brainstorm-planning-handoff v0.6 -->\n## Quality Bar",
|
|
162
162
|
"## Planning Handoff",
|
|
163
163
|
"<!-- BEGIN skill-garden patch brainstorm-planning-readiness v0.6 -->\n8. Load `trellis-task-brief`",
|
|
164
|
-
"
|
|
164
|
+
"Only valid explicit preauthorization for the current final Brief, as defined by `trellis-task-brief`, permits same-turn start within unchanged scope.",
|
|
165
165
|
"The final Brief must cover Goal, Scope, Non-Goals, Key Decisions, Key Context, Acceptance, one-hop Next Step",
|
|
166
|
-
"
|
|
166
|
+
"Ordinary implementation intent expressed before the final artifacts and full Brief are shown authorizes planning only",
|
|
167
167
|
"unresolved items are `- [ ]`",
|
|
168
168
|
"Do not write bare placeholders"
|
|
169
169
|
]
|
|
@@ -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}")
|
package/enhancements/0.6/overrides/patches/skills/trellis-brainstorm/planning-handoff/content.md
CHANGED
|
@@ -9,15 +9,13 @@ Before declaring planning ready:
|
|
|
9
9
|
- Complex tasks have `design.md` and `implement.md`.
|
|
10
10
|
- Sub-agent-dispatch tasks have real curated entries in both `implement.jsonl` and `check.jsonl`; seed-only manifests are not ready.
|
|
11
11
|
- Planning artifacts are ready for the final Brief handoff.
|
|
12
|
-
- The latest full Brief has been presented to the user.
|
|
13
|
-
- In a subsequent message, the user explicitly approved that Brief for implementation.
|
|
14
12
|
|
|
15
13
|
Do not start implementation merely because the user originally asked for implementation.
|
|
16
14
|
|
|
17
15
|
## Planning Handoff
|
|
18
16
|
|
|
19
|
-
Once the Quality Bar is satisfied, load `trellis-task-brief`, refresh `brief.md` from the final planning artifacts, display the full Brief in chat,
|
|
17
|
+
Once the Quality Bar is satisfied, load `trellis-task-brief`, refresh `brief.md` from the final planning artifacts, and display the full Brief in chat. By default, end the current turn and wait for the user's planning review confirmation. Only valid explicit preauthorization for the current final Brief, as defined by `trellis-task-brief`, permits same-turn start within unchanged scope.
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
Ordinary implementation intent expressed before the final artifacts and full Brief are shown authorizes planning only; it cannot be reused as the final review confirmation.
|
|
22
20
|
|
|
23
21
|
For `## Open Questions`, use Markdown checkbox state rather than placeholder prose: unresolved items are `- [ ]`; resolved items move into requirements/decisions or are removed; when no open questions remain, remove the section or leave it empty. Do not write bare placeholders such as `- None`, `- TBD`, or `- 已确认` because historical bare list items require an explicit auto-loop semantic review.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
For every non-trivial task,
|
|
1
|
+
For every non-trivial task, follow `trellis-task-brief` for the final Brief review and its explicit preauthorization exception before implementation begins. Ordinary implementation or task-creation intent is not Brief approval.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
8. Load `trellis-task-brief`, refresh `brief.md` from the converged artifacts, display the full Brief
|
|
2
|
-
9.
|
|
1
|
+
8. Load `trellis-task-brief`, refresh `brief.md` from the converged artifacts, and display the full Brief. Follow its review gate and explicit preauthorization exception.
|
|
2
|
+
9. Run `task.py start` and begin implementation only after that review gate is satisfied. If the artifacts change materially after approval, refresh and repeat the Brief review.
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
Do not manufacture clarification questions when the request and repository evidence already resolve every decision. In that case, proceed directly to the final Brief handoff
|
|
1
|
+
Do not manufacture clarification questions when the request and repository evidence already resolve every decision. In that case, proceed directly to the final Brief handoff through `trellis-task-brief`.
|
|
2
2
|
|
|
3
|
-
The final Brief review is a required phase-transition gate, not a prohibited process question. Task-creation consent
|
|
3
|
+
The final Brief review is a required phase-transition gate, not a prohibited process question. Task-creation consent and ordinary implementation intent do not satisfy this gate; explicit preauthorization is evaluated only by `trellis-task-brief`.
|
|
@@ -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"}
|