@seanyao/roll 4.630.2 → 4.702.2
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/CHANGELOG.md +64 -0
- package/README.md +65 -56
- package/conventions/global/AGENTS.md +8 -7
- package/dist/roll.mjs +12909 -8532
- package/docs/INDEX.md +32 -0
- package/docs/architecture.md +444 -0
- package/docs/difftest-freeze-paradigm.md +113 -0
- package/docs/live-console.md +203 -0
- package/docs/manifesto.md +65 -0
- package/docs/migration/role-taxonomy-v4.md +60 -0
- package/docs/verification.md +83 -0
- package/guide/INDEX.md +86 -0
- package/guide/assets/layouts/cards-2.png +0 -0
- package/guide/assets/layouts/cards-3.png +0 -0
- package/guide/assets/layouts/cards-4.png +0 -0
- package/guide/assets/layouts/compare.png +0 -0
- package/guide/assets/layouts/highlight.png +0 -0
- package/guide/assets/layouts/pipeline.png +0 -0
- package/guide/assets/layouts/plain.png +0 -0
- package/guide/assets/layouts/quote.png +0 -0
- package/guide/assets/layouts/timeline.png +0 -0
- package/guide/en/acceptance-evidence.md +231 -0
- package/guide/en/ai-agents.md +185 -0
- package/guide/en/backlog-github-sync.md +108 -0
- package/guide/en/changelog.md +66 -0
- package/guide/en/configuration.md +112 -0
- package/guide/en/consistency.md +58 -0
- package/guide/en/conventions.md +113 -0
- package/guide/en/dream.md +121 -0
- package/guide/en/faq.md +855 -0
- package/guide/en/feedback.md +31 -0
- package/guide/en/getting-started.md +103 -0
- package/guide/en/installation.md +86 -0
- package/guide/en/legacy-onboarding.md +195 -0
- package/guide/en/loop-data-layout.md +256 -0
- package/guide/en/loop-driven-architecture.md +186 -0
- package/guide/en/loop.md +1324 -0
- package/guide/en/methodology.md +715 -0
- package/guide/en/migration-2.0.md +154 -0
- package/guide/en/overview.md +190 -0
- package/guide/en/pairing.md +151 -0
- package/guide/en/patterns/README.md +76 -0
- package/guide/en/patterns/graft-pattern.md +110 -0
- package/guide/en/patterns/replant-pattern.md +114 -0
- package/guide/en/patterns/seed-pattern.md +132 -0
- package/guide/en/peer.md +71 -0
- package/guide/en/pr-review.md +62 -0
- package/guide/en/practices/engineering-common-sense.md +395 -0
- package/guide/en/pricing.md +116 -0
- package/guide/en/project-setup.md +126 -0
- package/guide/en/roll-doc-audit.md +98 -0
- package/guide/en/skills.md +206 -0
- package/guide/en/test-isolation.md +51 -0
- package/guide/en/testing/quality-rubric.md +340 -0
- package/guide/en/testing.md +123 -0
- package/guide/en/tools.md +173 -0
- package/guide/skills.md +30 -0
- package/guide/zh/acceptance-evidence.md +194 -0
- package/guide/zh/ai-agents.md +170 -0
- package/guide/zh/backlog-github-sync.md +105 -0
- package/guide/zh/changelog.md +57 -0
- package/guide/zh/configuration.md +99 -0
- package/guide/zh/consistency.md +48 -0
- package/guide/zh/conventions.md +96 -0
- package/guide/zh/dream.md +97 -0
- package/guide/zh/faq.md +773 -0
- package/guide/zh/feedback.md +30 -0
- package/guide/zh/getting-started.md +96 -0
- package/guide/zh/installation.md +83 -0
- package/guide/zh/legacy-onboarding.md +192 -0
- package/guide/zh/loop-data-layout.md +236 -0
- package/guide/zh/loop-driven-architecture.md +186 -0
- package/guide/zh/loop.md +1124 -0
- package/guide/zh/methodology.md +702 -0
- package/guide/zh/migration-2.0.md +154 -0
- package/guide/zh/overview.md +186 -0
- package/guide/zh/pairing.md +117 -0
- package/guide/zh/patterns/README.md +74 -0
- package/guide/zh/patterns/graft-pattern.md +108 -0
- package/guide/zh/patterns/replant-pattern.md +112 -0
- package/guide/zh/patterns/seed-pattern.md +130 -0
- package/guide/zh/peer.md +63 -0
- package/guide/zh/pr-review.md +54 -0
- package/guide/zh/practices/engineering-common-sense.md +393 -0
- package/guide/zh/pricing.md +97 -0
- package/guide/zh/project-setup.md +114 -0
- package/guide/zh/roll-doc-audit.md +90 -0
- package/guide/zh/skills.md +191 -0
- package/guide/zh/test-isolation.md +46 -0
- package/guide/zh/testing/quality-rubric.md +284 -0
- package/guide/zh/testing.md +116 -0
- package/guide/zh/tools.md +173 -0
- package/package.json +4 -1
- package/skills/README.md +1 -0
- package/skills/roll-.qa/SKILL.md +1 -1
- package/skills/roll-.review/SKILL.md +1 -1
- package/skills/roll-build/SKILL.md +1 -1
- package/skills/roll-build/references/full-contract.md +16 -13
- package/skills/roll-design/SKILL.md +3 -3
- package/skills/roll-design/references/full-contract.md +17 -13
- package/skills/roll-fix/SKILL.md +1 -1
- package/skills/roll-fix/references/full-contract.md +13 -10
- package/skills/roll-peer/SKILL.md +1 -1
- package/skills/roll-prime/SKILL.md +77 -0
- package/skills/roll-prime/references/explorer-annex.md +39 -0
- package/skills/roll-prime/references/supervisor-prompt.md +165 -0
- package/skills/route-cases/skills.json +10 -0
- package/template/AGENTS.md +3 -1
package/docs/INDEX.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Documentation Index
|
|
2
|
+
|
|
3
|
+
> Auto-generated by roll-doc-audit on 2026-06-21. Edit individual docs, not this file.
|
|
4
|
+
|
|
5
|
+
## Index
|
|
6
|
+
|
|
7
|
+
| Path | Title | Category | Description |
|
|
8
|
+
|------|-------|----------|-------------|
|
|
9
|
+
| `docs/architecture.md` | 系统设计 / System Architecture | architecture | 产品定位、设计原则、系统架构、领域模型、行为合同、事实来源 (US-TRUTH)、结构化交付真相、唯一查询入口、存储裁定、消费者契约 |
|
|
10
|
+
| `docs/difftest-freeze-paradigm.md` | 快照冻结测试范式 | testing | CLI 命令输出用 Vitest 快照冻结的行为契约测试范式与可移植性陷阱 |
|
|
11
|
+
| `docs/live-console.md` | 实时控制台指南 / Live Console Guide | guide | CLI-first 实时可观测指南:`roll loop cycle watch` 用法、三流契约、静态导出 vs 实时服务、证据按构造 |
|
|
12
|
+
| `docs/manifesto.md` | Roll 宣言 | product | Roll 产品宣言与愿景 |
|
|
13
|
+
| `docs/migration/role-taxonomy-v4.md` | Role Taxonomy v4 Migration | migration | Breaking migration note for Supervisor / Designer / Builder / Evaluator taxonomy, designed profiles, and open-pool casting |
|
|
14
|
+
| `docs/verification.md` | 验收 / Verification | process | 故事验收标准与证据规范 |
|
|
15
|
+
|
|
16
|
+
## Coverage Summary
|
|
17
|
+
|
|
18
|
+
- Total docs indexed: 6
|
|
19
|
+
- By category: architecture (1) / guide (1) / migration (1) / testing (1) / product (1) / process (1)
|
|
20
|
+
|
|
21
|
+
## Key Architecture Chapters (US-TRUTH epic)
|
|
22
|
+
|
|
23
|
+
`docs/architecture.md` 以下章节由 US-TRUTH 系列落地,是交付真相的架构准则:
|
|
24
|
+
|
|
25
|
+
| Section | Topic | Key Principle |
|
|
26
|
+
|---------|-------|---------------|
|
|
27
|
+
| 事实来源 (US-TRUTH 系列) | 权威矩阵 + 终态事件 + 选择器 + 读侧适配器 | 读侧一律走结构化选择器,不各自解析 backlog/events/runs |
|
|
28
|
+
| 结构化交付真相 | `DeliveryRecord` / `deliveries.jsonl` | Backlog 状态格 = 派生显示;机器绝不 parse markdown |
|
|
29
|
+
| 生命周期与裁定正交 | `LifecycleState` ⊥ `TruthState` | 两个维度独立承载,不互斥、不塌缩 |
|
|
30
|
+
| 唯一查询入口 | `queryStoryDelivery()` | picker/reconcile/dossier/watch 唯一确定性查询函数 |
|
|
31
|
+
| 存储裁定 | 不上 SQLite | append-only JSONL + 原子写;SQLite 仅可作派生缓存 |
|
|
32
|
+
| 消费者契约 | 各消费者输入/输出/行为 | 新增消费者必须走 queryStoryDelivery,写本地解析即回归 |
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# 系统设计
|
|
2
|
+
|
|
3
|
+
## 产品定位
|
|
4
|
+
|
|
5
|
+
roll 是 AI coding agent 的**外层控制系统**(agent harness / reliability layer)。它不进入 agent 内部(不管理 token 窗口、不压缩对话、不干预单次推理),而是在外部建立一个闭环:设定目标 → 调度执行 → 感知结果 → 修正方向。
|
|
6
|
+
|
|
7
|
+
当前用户入口是 CLI-first:`roll init`、`roll supervisor`、`roll supervisor live`、`roll loop`、`roll status`、`roll loop cycle` 与按 Story 收口的 `roll attest` 验收 Review Page。浏览器/TUI 版 Supervisor Live Console 是下一阶段工作,不作为当前产品面承诺。安装方式:`npm install -g @seanyao/roll`。
|
|
8
|
+
|
|
9
|
+
## 为什么是分层闭环
|
|
10
|
+
|
|
11
|
+
一个可靠的控制器,必须是被控系统的一个**连贯模型**([specs/theory-foundation](specs/theory-foundation.md))。当同一个能力域被摊在多种异构载体上、靠文本缝合(stdout 解析、现生成脚本、散落的状态文件)时,协调开销本身就在吃掉控制带宽——**散,是不稳定与低效的根因**:那类"引号地狱 / 解析漂移 / 状态不一致"的故障,长的正是载体之间的缝。
|
|
12
|
+
|
|
13
|
+
所以 roll 是一台**分层的 TypeScript 控制器**:每个能力域一个家,层与层用类型化契约相连,反馈闭环作脊柱。缝消失,长在缝上的那类故障也就失去土壤。
|
|
14
|
+
|
|
15
|
+
## 设计五原则
|
|
16
|
+
|
|
17
|
+
1. **每个能力域一个家。** 把摊在四种载体上的同一个域(Orchestration / Observability / Evals / Guardrails / Context Engineering / Tool Use / Sandboxing),收敛成一个连贯的 owner/包。这是 roll 缺的那层"系统设计"。
|
|
18
|
+
2. **反馈闭环是脊柱,层是它的器官。** 别把能力域做成并列模块;按"结构核心 + 控制平面"接成一个闭环:核心作动(编排/执行/工具/上下文)→ 控制平面传感/评分/限幅(可观测/Evals/Guardrails)→ 反哺下一轮。
|
|
19
|
+
3. **TS 类型 = 层与层之间的契约。** 层与层用类型化接口相连,而不是 stdout 解析 / 现生成脚本——缝消失,长在缝上的那类故障失去土壤。
|
|
20
|
+
4. **守住黑盒边界(外层 harness)。** roll 不打开黑盒:token 级压缩、工具 schema 强制、单次 ReAct 委派给内层 agent。不必建模被控对象内部,靠反馈就能控制([specs/theory-foundation](specs/theory-foundation.md))。
|
|
21
|
+
5. **反馈要有 Goodhart 护栏。** 闭环一旦把度量当目标就会被钻空子。所以 Evals 信号**不自动激活**、只生成"待人确认"候选;人在环上(human-on-the-loop)作监督限幅;retry 设上限并转 replan(anti-windup);不对噪声反复 replan(deadband)。这是一条显性的设计纪律。
|
|
22
|
+
|
|
23
|
+
## 系统架构
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
spec 共享类型与事件合同(零依赖)
|
|
27
|
+
|
|
28
|
+
core 领域逻辑(纯函数,不碰 I/O,通过接口注入外部依赖)
|
|
29
|
+
BacklogStore · StoryPicker · AgentRouter
|
|
30
|
+
PRLifecycle · TCRPipeline · ReconcileEngine
|
|
31
|
+
CostTracker · PolicyEngine · EventBus
|
|
32
|
+
|
|
33
|
+
infra I/O 适配层
|
|
34
|
+
Config · Git · GitHub · launchd/cron · Tmux · ProcessManager
|
|
35
|
+
|
|
36
|
+
cli 命令入口(薄壳,解析参数 → 调 core → 格式化输出)
|
|
37
|
+
|
|
38
|
+
web 站点与静态展示(当前不是活体 Supervisor 控制台)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
依赖单向向上。下层不感知上层。
|
|
42
|
+
|
|
43
|
+
**技术选择**:TypeScript(类型安全)、vitest(自带进程沙箱)、commander + chalk(CLI)、simple-git + octokit(Git/GitHub)、handlebars(模板,替代 heredoc)、proper-fs-lock(替代手写锁)、React + shadcn/ui(前端)。
|
|
44
|
+
|
|
45
|
+
### 能力域归宿(每域一个家)
|
|
46
|
+
|
|
47
|
+
每个能力域在 6 包里的家:
|
|
48
|
+
|
|
49
|
+
| 能力域 | 家 |
|
|
50
|
+
|---|---|
|
|
51
|
+
| **Orchestration** 编排 | `core`(StoryPicker / TCRPipeline / ReconcileEngine / CycleOrchestrator)+ `infra`(launchd/cron 调度) |
|
|
52
|
+
| **Sandboxing** 执行隔离 | `infra`(Git worktree / ProcessManager / Tmux) |
|
|
53
|
+
| **Tool Use** 工具/多 agent | `core`(AgentRouter / AgentRegistry / CostTracker 的 usage 解析)+ `infra`(spawn / GitHub) |
|
|
54
|
+
| **Context Engineering** 上下文 | skill 桥接(独立仓)+ `.roll/` 文档纪律(是契约) |
|
|
55
|
+
| **Observability** 可观测 | `spec`(事件 schema)+ `core`(EventBus 写端 + 选择器)+ `cli`(CLI-first 观察面) |
|
|
56
|
+
| **Evals** 验证/评分 | `core`(Evals 六维 + 测试质量门) |
|
|
57
|
+
| **Guardrails** 治理 | `core`(PolicyEngine + Budget guardrails) |
|
|
58
|
+
|
|
59
|
+
> skills 不进 TS:仍是 markdown + shell,经桥接 spawn(它们是"灵魂/契约",归 `roll-skills` 独立仓)。roll 收敛的是**控制器代码**,不是 skill 内容。
|
|
60
|
+
|
|
61
|
+
## 领域模型
|
|
62
|
+
|
|
63
|
+
系统分为 8 个 Bounded Context。每个上下文内部一致,上下文之间通过共享 artifact 和事件流协作——没有中央调度器。
|
|
64
|
+
|
|
65
|
+
### BC1 · Backlog
|
|
66
|
+
|
|
67
|
+
管理项目意图。一个故事一棵层级树:Epic → Feature → Story。
|
|
68
|
+
|
|
69
|
+
**核心概念**:
|
|
70
|
+
- 故事有唯一 ID,处于四种状态之一:待办 / 进行中 / 完成 / 暂缓
|
|
71
|
+
- 故事之间有依赖边(`depends-on`)
|
|
72
|
+
- 状态翻转必须使用精确整行匹配(杜绝子串误伤)
|
|
73
|
+
|
|
74
|
+
**写规则**:多写并发使用乐观锁——读出全文哈希,修改后写前校验哈希未变,冲突即重试。写操作原子完成。
|
|
75
|
+
|
|
76
|
+
### BC2 · Loop 编排
|
|
77
|
+
|
|
78
|
+
这是系统的引擎。一个 Loop 是一个自治进程,按定时器唤醒,执行一个 Cycle。
|
|
79
|
+
|
|
80
|
+
**Cycle 生命周期**:选故事 → 路由 agent → 创建隔离工作区 → agent 执行(TCR 循环)→ 送交 PR → 等合并 → 对账 → 收尾。
|
|
81
|
+
|
|
82
|
+
**关键约束**:
|
|
83
|
+
- 一 Cycle 只做一个 Story。前一个未交付,不拿下一个。
|
|
84
|
+
- 进程可能被 SIGKILL。下次唤醒时,通过锁龄、心跳、PID 判断孤儿态,安全接管或重做。
|
|
85
|
+
- 心跳每 60 秒写入一次。超时无心跳 → 判定死亡并落终态。
|
|
86
|
+
- 退出时无条件写入终态。这是硬约束——trap 兜底。
|
|
87
|
+
- 连续失败达阈值 → 暂停并告警,等用户决策。不自动换 agent。不无限重试。
|
|
88
|
+
|
|
89
|
+
**Loop 类型**:
|
|
90
|
+
| 类型 | 职责 |
|
|
91
|
+
|------|------|
|
|
92
|
+
| main | 消费待办,执行完整的 pick→TCR→PR→对账 周期 |
|
|
93
|
+
| pr | 监控 open loop PR:CI 挂了自愈,绿了合并 |
|
|
94
|
+
| ci | 监控 CI 状态 |
|
|
95
|
+
| alert | 消费 ALERT 文件,推送到用户 |
|
|
96
|
+
|
|
97
|
+
### BC3 · Agent Scope / Role
|
|
98
|
+
|
|
99
|
+
管理可用的 AI agent 及其角色绑定。领域模型是递归的:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
Scope -> Role -> Binding -> Agent -> optional Model
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Scope**:`machine` / `project` / `story` / `skill` 等层级使用同一套形状。
|
|
106
|
+
Machine Scope 写在 `~/.roll/agents.yaml`,声明本机 Agent Pool、能力和机器级
|
|
107
|
+
`supervise`;Project Scope 写在 `.roll/agents.yaml`,绑定项目/Story 默认角色并可
|
|
108
|
+
`inherits: machine`。
|
|
109
|
+
|
|
110
|
+
**Role**:Canonical user-facing role model is **Supervisor / Designer / Builder / Evaluator**。
|
|
111
|
+
|
|
112
|
+
- Supervisor = control plane:接收 owner 意图、选卡、选执行剖面、观察 cycle、暂停或升级,但不替交付角色写实现或验收结论。
|
|
113
|
+
- Designer = design plane:把 idea/problem 收敛成设计、Story spec、evaluation contract,以及执行时的 `role-artifacts/designer/design-contract.md`。
|
|
114
|
+
- Builder = implementation plane:只按 Story spec 与 Designer contract 产出代码、文档和证据。
|
|
115
|
+
- Evaluator = verification plane:用 fresh session 对 Builder 交付、验收证据、score 和 gates 作独立判断。
|
|
116
|
+
|
|
117
|
+
角色值保留类型化 scope 形状:`supervise` / `design` / `execute` / `evaluate`。Story 交付通过 `story.design`、`story.execute`、`story.evaluate` 组合成执行剖面;角色之间只用 fresh sessions 和 artifact handoff 协作,不共享原始会话。
|
|
118
|
+
|
|
119
|
+
**Binding**:角色可以固定到一个 agent,也可以从候选池选择。选择策略是显式、可审计的
|
|
120
|
+
(例如 `first-available`、`least-recent`、`seeded-random`、`health-aware`),结果记录 source、trace、
|
|
121
|
+
candidates 和 skipped runtime health。
|
|
122
|
+
|
|
123
|
+
**公平候选池**:静态配置列出公平候选,不因历史 auth/VPN/account/network 事故永久排除
|
|
124
|
+
支持的 agent。Designer、Builder、Peer Reviewer 和 Evaluator 从同一个已安装 agent pool 里
|
|
125
|
+
选择;selector 可以按 capability、health、parser stability、recent failures、cost 和 story
|
|
126
|
+
risk 排序,也可以按 owner 明确策略偏好多样性,但默认不因 agent brand、是否也能监督、或是否
|
|
127
|
+
与其他角色同品牌而硬排除。`least-recent` 读 `runs.jsonl` 的近期使用记录公平轮换,避免反复落到
|
|
128
|
+
同一个 agent。`health-aware` 保留 pool 可见性,并按近期 auth block、timeout、parser failure、
|
|
129
|
+
no-TCR/gave-up、成功交付、成本档位和角色能力标签排序。运行时探活只影响当前 resolution:不可用
|
|
130
|
+
候选被记录为 skipped,静态池不被悄悄改写。`roll supervisor route --role <role> --story <id>
|
|
131
|
+
[--json]` 暴露 route trace:候选池、ranked score/reasons、warnings、skipped 候选及原因、策略、
|
|
132
|
+
近期使用输入、最终 agent 和 source 配置路径。
|
|
133
|
+
|
|
134
|
+
**反规则**:不因历史表现自动改写角色绑定。不做失败后的静默跨 agent 重试。指标可以*建议*
|
|
135
|
+
策略变更,但绝不绕过 human-on-the-loop。
|
|
136
|
+
|
|
137
|
+
### BC4 · 交付
|
|
138
|
+
|
|
139
|
+
每次交付是一个 Pull Request。一个 Story 至多同时有一个 open PR。
|
|
140
|
+
|
|
141
|
+
**生命周期**:开 PR → CI 通过 → 合并入 main → 删除分支。
|
|
142
|
+
|
|
143
|
+
**交付判定**:合并入 main 才算交付。PR 已开、CI 已绿、agent 声称完成都不算——事后对账,只认 main 上真实的 merge commit。
|
|
144
|
+
|
|
145
|
+
### BC5 · 演化
|
|
146
|
+
|
|
147
|
+
追踪一个 Story 的完整生长过程。每一次 TCR 微提交、每一次回退都可追溯。支持对比不同 agent 对同一 Story 的实现,支持回退到任意历史节点。
|
|
148
|
+
|
|
149
|
+
### BC6 · 策略
|
|
150
|
+
|
|
151
|
+
解析并执行 `.roll/policy.yaml` 中的人类意图。
|
|
152
|
+
|
|
153
|
+
**策略类型**:
|
|
154
|
+
- 自动合并:满足条件自动 merge PR
|
|
155
|
+
- 审查标记:特定文件或层级标记需人审查
|
|
156
|
+
- 安全限幅:连续失败 N 次 → 暂停并告警
|
|
157
|
+
- 角色绑定:覆盖 scoped role 到 agent/model 的解析
|
|
158
|
+
- 网络首检:任何需要网络的命令(`loop go/run`、agent 拉起、showcase、release 开 PR、update)把连通性(含代理)作为第一道检查。不通时跑配置的恢复钩子 `loop_safety.proxy_enable_cmd` 再复检:通了继续,仍不通就立刻停手并给出可操作的中英文原因——绝不带病前进、绝不空转、绝不静默降级。该钩子是用户自填的命令(roll 不内置任何代理工具);未配置即停手并告知。
|
|
159
|
+
- 探测目标默认是 `github.com:443`(海外路径)。若你的工作流只用国内可直连的服务(如 DeepSeek/Bailian),把探测指向你确实需要的主机:`loop_safety.probe_url: <host:port 或 URL>`(FIX-1025);这样 VPN 掉线也不会因一个工作流根本不需要的固定海外主机而误停。
|
|
160
|
+
- 完全跳过预检:`loop_safety.skip_network_check: true`(FIX-1025)——当你确认所配置的服务可直连、不希望被任何固定主机探测拦住时使用。
|
|
161
|
+
- English: the precheck defaults to `github.com:443`. For a domestic-only workflow, point it at a host you actually need via `loop_safety.probe_url`, or opt out entirely with `loop_safety.skip_network_check: true` — so a dropped VPN never halts loop/release when every configured provider is directly reachable.
|
|
162
|
+
- Warm session 复用:`loop_safety.session_reuse: true` 只表达复用意图;必须同时设置 `loop_safety.resume_scope: same-story` 才会在同一 story 重试时复用 codex session。缺省、非法值或未设置 `resume_scope` 都按 `off` 处理,跨卡复用保持禁用。
|
|
163
|
+
|
|
164
|
+
策略是规则源——它不直接执行动作,而是被其他上下文读取并遵循。
|
|
165
|
+
|
|
166
|
+
### BC7 · 可观测
|
|
167
|
+
|
|
168
|
+
不可变事件流是唯一的真相源。所有状态都从事件重建,无独立缓存。
|
|
169
|
+
|
|
170
|
+
#### 三流权威边界(Keystone 契约)
|
|
171
|
+
|
|
172
|
+
实时可观测收死在三条流的明确边界上——不新建第四条流:
|
|
173
|
+
|
|
174
|
+
| 流 | 权威级别 | 语义 |
|
|
175
|
+
|---|---------|------|
|
|
176
|
+
| `events.ndjson` | **唯一持久真相** | 全量结构化 `RollEvent`(原子追加);所有状态从这里重建。runner 写的事实(`cycle:phase/first_edit/tcr/stdout/end`、`pr:*`、`gate`、`attest`)跨 agent 通用、不可变。 |
|
|
177
|
+
| `ActivitySignal` | **投影模型** | 从 `RollEvent` 流派生(`cycleActivitySignalsFromEvents`),是按 tier/seg/summary 归一化的 UI 模型。所有下游渲染(watch 窗口、cycle ledger、未来 Supervisor Live Console)**只消费 `ActivitySignal`**——不做 per-agent 解析。`cycle-<id>.signals.jsonl` 持久化全量信号。 |
|
|
178
|
+
| `live.log` | **debug 附件** | Agent stdout 直通记录——不参与判定、不打分、不作为证据。可被截断、可缺失。仅供调试。 |
|
|
179
|
+
|
|
180
|
+
**单读选择器不变量**:`collectDossierState(cwd) → TruthSnapshot` 是读侧唯一数据归口。页面的 ~18 个面板(agent、On Deck、projects、casting、charter、skills 等)全部走这个快照——页面渲染路径不得绕过它直读文件或单独 collect。来自 US-OBS-016(读侧收口)和 FIX-376/377(幽灵项目/On Deck 计数)的教训:只要存在"第二条读取路径",漂移就是时间问题。`truth-adapter.ts`(在 `@roll/core`)是选择器的唯一入口。
|
|
181
|
+
|
|
182
|
+
**持久化文件**:
|
|
183
|
+
| 文件 | 内容 |
|
|
184
|
+
|------|------|
|
|
185
|
+
| `events.ndjson` | 全量事件(每行一个 JSON,原子追加) |
|
|
186
|
+
| `runs.jsonl` | 运行摘要(按 story+cycle_id 去重) |
|
|
187
|
+
| `heartbeat` | 活性心跳(idle 也写) |
|
|
188
|
+
| `cycle-<id>.signals.jsonl` | 每个 cycle 的标准 ActivitySignal 全量持久化 |
|
|
189
|
+
|
|
190
|
+
**事件类型**:`cycle:start/phase/tcr/end/terminal`、`warm-session:capture/resume-selected/resume-skipped`、`pr:open/merge`、`route:resolve`、`loop:heartbeat/fire/paused`、`policy:safety_pause`、`alert`、`peer:gate`、`attest:gate`、`ci:*`。
|
|
191
|
+
|
|
192
|
+
#### CLI-first 实时控制台(`roll loop cycle watch`)
|
|
193
|
+
|
|
194
|
+
主线是 CLI:`roll loop cycle watch [<id>] [--once] [--since <lines>] [--json]` 提供一个进行中 cycle 的**标准 ActivitySignal 流**。不传 id 时自动跟随当前 running cycle。
|
|
195
|
+
|
|
196
|
+
窗口显示:
|
|
197
|
+
- **顶部概要**:cycle id、story id、agent、outcome
|
|
198
|
+
- **信号行**(`●` 彩色圆点 + tier/seg/summary):lifecycle(开始/结束/超时回收)、TCR(每次 test/commit/revert)、gate(peer/attest 闸通过/失败)、stdout(agent 输出摘要)、工具调用(tool_use → tool_result)
|
|
199
|
+
- **证据指针**:cycle 结束或 `--once` 时输出 PR/diff/story 链接
|
|
200
|
+
|
|
201
|
+
信号来自 `events.ndjson` → `cycleActivitySignalsFromEvents()` 或已持久化的 `signals.jsonl`;消费 `tail -F` 跟随,不依赖 daemon。
|
|
202
|
+
|
|
203
|
+
对非当前 running cycle,`--once` 回放一帧后退出;`--json` 输出机器可读视图。
|
|
204
|
+
|
|
205
|
+
详见 [实时控制台指南](live-console.md)。
|
|
206
|
+
|
|
207
|
+
#### 静态导出 vs CLI-first 实时观察
|
|
208
|
+
|
|
209
|
+
- **实时 CLI**(`roll loop cycle watch`):直接跟随 `events.ndjson` 或 `signals.jsonl`,不经过外部进程——CLI 窗口在任何时候都是可用的一线视图。
|
|
210
|
+
- **状态摘要**(`roll status` / `roll status pulse` / `roll loop runs` / `roll loop cycle <id>`):从同一选择器读取 backlog、merge truth、cycle history、release readiness 和 story-scoped attest 覆盖率。
|
|
211
|
+
- **静态导出**(归档重建):按需把选择器结果渲染为 HTML archive,以 `file://` 打开。它是一次性快照,适合归档、CI artifact、历史修复和迁移对账;不是当前用户面的活体真相入口。
|
|
212
|
+
|
|
213
|
+
这些入口共享 `collectDossierState` / `cycleActivitySignalsFromEvents`,但当前产品承诺以 CLI-first 为准。
|
|
214
|
+
|
|
215
|
+
#### Supervisor Live Board
|
|
216
|
+
|
|
217
|
+
`roll supervisor live` 是当前已交付的 CLI-first 多角色 board:读取事件流生成 Supervisor pane 与 Designer / Builder / Evaluator role panes。默认模式输出一帧快照;`roll supervisor live --watch` 在交互式终端中原地重绘同一 view model,不追加重复帧,也不写任何 loop/backlog/release/evidence 状态。未来浏览器/TUI 面应复用同一 view model,并遵守以下边界:
|
|
218
|
+
|
|
219
|
+
- **依赖方向**:浏览器可观测只读消费 `spec` 事件 schema 与 `core` 读侧选择器;loop 不依赖浏览器进程。
|
|
220
|
+
- **只读隔离**:观察面不得写入 loop 状态,不得影响 Story delivery 的 TCR、CI、merge 或 attest 闸。
|
|
221
|
+
- **角色视图**:未来 board 展示 Supervisor、Designer、Builder、Evaluator、`supervise` / `design` / `execute` / `evaluate` 角色、scope/role/binding 解析、agent/model、runtime skipped candidates 和 story-scoped evidence;它不替代 evaluate 裁定或 owner 决策。
|
|
222
|
+
- **fail-loud**:浏览器面只能显示不可用 agent/model 和 skipped runtime facts,不能把替代执行包装成原请求 agent。
|
|
223
|
+
|
|
224
|
+
#### 远程就绪缝(design-constraint-only,未建)
|
|
225
|
+
|
|
226
|
+
以下为**设计约束**,写入架构是为了防止未来的"先建后设计"——当前**一条代码都没写**:
|
|
227
|
+
|
|
228
|
+
- **传输**:默认 `localhost-bind + no-auth` → 未来可切 `network-bind + bearer-token + relay`
|
|
229
|
+
- **通道分离**:READ 可观测通道 ⟂ 未来 WRITE/控制通道(独立端口 + 认证,不同安全域)
|
|
230
|
+
- **relay 未解**:异步路径借 GitHub 交会点绕过了 NAT(roll-meta repo 作异步 rendezvous);实时路径没有等价物——"bind 0.0.0.0 + token"只是暴露端口,不解决可达性。relay 是未来真问题,不是这个 sprint 的。
|
|
231
|
+
- **不杜撰 API**:未写服务发现、健康检查、连接恢复、reconnect backoff 等协议——留到真实建立时。
|
|
232
|
+
|
|
233
|
+
#### 异步远程(现有,互补)
|
|
234
|
+
|
|
235
|
+
实时控制台(CLI watch / future browser live)和 git-snapshot 异步远程(roll-meta + GitHub 作交会点)是**同一选择器、不同发射器**的关系:
|
|
236
|
+
|
|
237
|
+
- **异步路径**:`roll-meta` 私有 git 仓通过 `commitRollMetadataRepo` 提交 `.roll` 状态快照。远端 agent 读取 roll-meta + GitHub API 感知项目状态——不依赖实时连接。
|
|
238
|
+
- **实时路径**:当前产品只交付 `roll loop cycle watch`;未来浏览器实时面必须复用同一标准流,不能重新引入旧 daemon/frame surface。
|
|
239
|
+
- **共存**:二者彼此独立、并行不悖。实时路径不做异步远程做的事(跨 NAT 状态同步);异步路径不做实时路径的事(秒级活信号)。详见 `.roll/features/loop-observability/live-console-design.md` §2.3。
|
|
240
|
+
|
|
241
|
+
#### 证据按构造(US-OBS-031)
|
|
242
|
+
|
|
243
|
+
证据从 activity 流 + diff **自动起草**,不再是 builder 手动步骤:
|
|
244
|
+
|
|
245
|
+
- ac-map(AC→证据映射)从 cycle 活动流(改了哪些文件、跑了哪些命令、通过了哪些闸)和 git diff 自动生成骨架
|
|
246
|
+
- 验收 Review Page 由 `roll attest` 从 ac-map + 截图 + 测试输出自动渲染;legacy report alias 仅作迁移兼容
|
|
247
|
+
- 截图由 loop runner 的 headless Playwright 自动捕获(声明了 `deliverable_url` 的卡)
|
|
248
|
+
|
|
249
|
+
这是一个方向声明——US-OBS-031 的实际落地范围以它自己的 spec 为准。架构锚点是:**证据的素材源(activity stream + diff)已经在 BC7 中提供;证据生成路径不从外部另起。**
|
|
250
|
+
|
|
251
|
+
### BC8 · 成本
|
|
252
|
+
|
|
253
|
+
归集每个 Cycle 的实际消耗并设闸。
|
|
254
|
+
|
|
255
|
+
**记录内容**:`(agent, model, 输入 token, 输出 token, 预估成本, 回退次数, 含回退的有效成本)`。
|
|
256
|
+
|
|
257
|
+
**Budget guardrails**:项目/全局设日和周上限。逼近上限 → 自动降级到便宜模型或暂停并通知。便宜模型回退率高导致总成本反超 → 建议升级。
|
|
258
|
+
|
|
259
|
+
### BC8.5 · 运行模式(guided / autonomous)
|
|
260
|
+
|
|
261
|
+
Roll 的运行模式只有两个:`guided` 和 `autonomous`。它们不是两套 agent 配置,而是同一套 backlog、truth、route profile、execution profile、attest evidence、Evaluator 和 release gates 之上的两种触发方式。
|
|
262
|
+
|
|
263
|
+
- `guided`:owner 通过 `roll supervisor status/next/why` 理解状态和下一步,再显式运行 `roll loop go --cards <id>` 等命令。guided 模式不会静默启动长时间 Story 执行。
|
|
264
|
+
- `autonomous`:`roll loop on` 安装 scheduler;scheduler 可以在 pause、budget、route、evidence、Evaluator 和 release gates 内领取合格 Todo。`roll loop pause` / `roll loop off` 回到 guided;`roll loop resume` / `roll loop on` 显式切回 autonomous。
|
|
265
|
+
- 持久化来源只使用已有 loop/supervisor 状态:launchd plist、PAUSE/DORMANT marker、events/runs/backlog。不得新增独立 `mode.yaml` 之类的第二真相。
|
|
266
|
+
|
|
267
|
+
### BC9 · Supervisor 与执行剖面(v4)
|
|
268
|
+
|
|
269
|
+
v4 把"一张 Story 怎么交付"和"项目级怎么协调"分成两层。
|
|
270
|
+
|
|
271
|
+
**执行剖面 / Execution Profile**:一张 Story 的交付按风险/ROI 选最便宜够用的角色流水线,用户不必先想"团队形状":
|
|
272
|
+
|
|
273
|
+
- `standard` = Builder(低风险、范围局部、AC 清晰、证据风险低)
|
|
274
|
+
- `verified` = Builder -> Evaluator(用户可见 / 需视觉证据 / 历史证据薄弱——靠独立判断而非自评)
|
|
275
|
+
- `designed` = Designer -> Builder -> Evaluator(需求模糊 / 跨模块 / 触及 truth·release·路由·状态语义——风险是"做错事"而不仅是"证不出来")
|
|
276
|
+
|
|
277
|
+
剖面在 Cycle 开始时选一次并记入 `execution:profile` 事件。角色之间只通过 artifact(`role-artifacts/designer/design-contract.md` / `execute-evidence` / `eval-report.md` + `artifact-manifest.json`)交接,不共享原始会话;每个角色都是 fresh session。`evaluate` 不是单一 `pass/fail`——blocking review、score、attest 是三个分开的契约。evaluate→execute 的修复回合受硬熔断约束(最大轮数、重复 finding 签名、预算、超时),触界即升级。
|
|
278
|
+
|
|
279
|
+
**Supervisor**:项目级协调者,负责不属于某一张具体 Story 的工作——跨 Story/Epic 上下文、backlog 排序、风险分级、执行剖面建议、路由/Rig 建议、预算、并行、卡住的 cycle、重复失败、文件冲突、合并队列、发布就绪、truth coverage / 显式 release blockers、系统级用户交互("接下来做什么?""为什么卡住?")与 owner 升级。
|
|
280
|
+
|
|
281
|
+
Supervisor **绝不**:实现具体 Story、写 Story 的评估报告、覆盖 Evaluator 裁定、绕过 attest 闸、直接标记 Story 为 Done、用指标静默改写路由/策略。v4.0 的 Supervisor 是 observe/advise(`roll supervisor`):先用确定性 selector 把事实结构化,再(必要时)让 agent 措辞建议;历史 Done 缺少结构化 DeliveryRecord 只作为 truth coverage/backfill 提醒,发布是否阻塞以显式 release blockers / release consistency 为准;持久化策略变更一律需 owner 确认。安全并行调度(`max_parallel_cycles`、文件冲突串行化、合并队列/预算暂停)的决策逻辑已就位,活体并行交付留待 v4.1。
|
|
282
|
+
|
|
283
|
+
Backlog-clearing 模式下,Supervisor 的默认 scope 是所有 live 且非 Hold 的 `FIX-*`、`US-*`、`REFACTOR-*` 行;不是只扫缺陷修复。`IDEA-*` 只有被 owner 提升为 Story/Fix/Refactor 后才进入执行池。Supervisor 先对账 backlog、依赖、open PR、CI、Evaluator/Scorer、manual-merge gate、近期 cycle 终态、preserved worktree 和 `.roll` meta,再选择下一张卡。每张卡独立 cast Builder;执行剖面需要时独立 cast Designer、Evaluator/Scorer。`gave_up`、zero TCR、缺少 PR/CI/evaluator 证据、解析失败、auth/permission block、`[roll:manual-merge]` PR 或 `.roll` meta drift 都是停止继续调度并要求 owner/根因动作的信号。产品 repo 的 PR/CI/main truth 与 `.roll` meta truth 分开对账和提交。
|
|
284
|
+
|
|
285
|
+
#### Supervisor Backlog-Clearing Runbook
|
|
286
|
+
|
|
287
|
+
这是 Supervisor 的项目级操作契约,目标是清空当前 scope 内所有非 Hold 卡,而不是完成某一种卡型。
|
|
288
|
+
|
|
289
|
+
1. **Scope gate**:每轮开始先重读 live backlog,只纳入 `📋 Todo` / 可执行状态的 `FIX-*`、`US-*`、`REFACTOR-*`;排除 `🚫 Hold`、`✅ Done`、`IDEA-*`、已有 open PR 或 active cycle 的卡。
|
|
290
|
+
2. **Truth preflight**:启动下一张卡前必须确认上一轮没有未处理的 PR、红 CI、manual-merge gate、缺失 delivery record、缺失 evaluator/score、`.roll` meta dirty 或 preserved worktree。任一存在就先处理事实差异,不继续派新卡。
|
|
291
|
+
3. **One card, one cast**:每张卡 fresh 选择 Builder;`verified` 剖面必须 fresh 选择 Evaluator/Scorer,`designed` 剖面还必须 fresh 选择 Designer。Designer、Builder、Peer Reviewer、Evaluator 可以来自同一 agent pool,但不能共享同一会话;角色链必须写入可读摘要和结构化事件。
|
|
292
|
+
4. **Observe while running**:Supervisor 观察 cycle 心跳、TCR 数、builder stdout、peer/score 事件、PR/CI、attest gate 和 role summary;它只监督与分流,不在 Builder 会话里补实现,也不替 Evaluator 改 verdict。
|
|
293
|
+
5. **Failure triage before retry**:同一卡失败后先分类根因,再决定下一步。`gave_up`、zero TCR、auth/permission block、解析失败、缺报告、PR/CI 缺席、CI 红、路径/元数据误路由属于 supervisor-blocking,不允许盲目重跑;需要先建卡/修基础设施/换 agent/补权限/人工合并。实现缺口则保留 worktree 证据,换 fresh Builder 或 owner 指定 Builder 继续。
|
|
294
|
+
6. **Merge and metadata closeout**:一张卡只有在 PR merged to `main`、CI green、attest/report/role evidence 存在、backlog/spec 状态一致、`.roll` meta 已单独提交并推送后,才算可从 scope 移除。
|
|
295
|
+
7. **Continue condition**:只有当上一步 closeout 干净、没有 structural blocker、预算/并行/文件冲突闸允许时,Supervisor 才选择下一张卡。否则进入 guided pause,并给 owner 一个具体下一步命令或待确认动作。
|
|
296
|
+
|
|
297
|
+
这套 runbook 是 `roll supervisor next/why/live` 的产品标准:CLI 输出应能解释当前卡、当前 cast、为什么继续、为什么停止,以及下一步需要谁做什么。
|
|
298
|
+
|
|
299
|
+
#### Retired terms and breaking boundary
|
|
300
|
+
|
|
301
|
+
`Prime Agent` is a retired active term. `Planner` is a retired active term. `planned` is a retired execution profile, and `planner-contract.md` is a retired active artifact. Historical archives may preserve those words as immutable evidence, but active runtime docs, help, UI, tests, and skills use Supervisor / Designer / Builder / Evaluator.
|
|
302
|
+
|
|
303
|
+
This taxonomy cleanup is breaking by design. No alias, fallback, or dual-write path is introduced for removed inputs such as `execution_profiles.planned`, `roles.planner`, `execution_policy.mode: planned`, `default_profile: planned`, or active `planner-contract.md` consumption. Manual migration is expected.
|
|
304
|
+
|
|
305
|
+
> 命名:只用 **Supervisor / Designer / Builder / Evaluator / Agent Scope / Role / Binding / Agent / Model**。核心角色是 `supervise` / `design` / `execute` / `evaluate`。内部命令 `roll supervisor` 与 `Supervisor*` 代码标识符保留,因为它们已经匹配 canonical control-plane role。
|
|
306
|
+
|
|
307
|
+
### 上下文协作
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
人(写故事 / 定策略)
|
|
311
|
+
│ ┌──────────────┐
|
|
312
|
+
▼ │ BC6 策略 │
|
|
313
|
+
┌──────────┐ Backlog │ 规则 │
|
|
314
|
+
│ BC1 │◄──────────────────│ │
|
|
315
|
+
│ 意图管理 │ └──┬───┬───────┘
|
|
316
|
+
└────┬─────┘ │ │
|
|
317
|
+
│ Todo ▼ ▼
|
|
318
|
+
▼ ┌──────────────────┐
|
|
319
|
+
┌──────────┐ Route 请求 │ BC2 编排 │
|
|
320
|
+
│ BC3 路由 │◄───────────────│ pick→TCR→PR→对账 │──cycle:*/heartbeat──┐
|
|
321
|
+
└──────────┘──route:resolve─►│ │ │
|
|
322
|
+
└──┬───┬───────────┘ │
|
|
323
|
+
│ │ git/PR ▼
|
|
324
|
+
cost │ ▼ ┌──────────────────┐
|
|
325
|
+
▼ ┌──────────────────┐ │ BC4 交付 │
|
|
326
|
+
┌──────────┐ │ │ PR → CI → merge │
|
|
327
|
+
│ BC8 成本 │ │ └──────┬───────────┘
|
|
328
|
+
│ 记录 + 闸 │ │ │ merged
|
|
329
|
+
└──────────┘ │ ▼
|
|
330
|
+
│ main (真相)
|
|
331
|
+
│ │
|
|
332
|
+
全部事件 append ────────────────────────────────────────────► ┌──────────────────┐
|
|
333
|
+
│ BC7 可观测 │
|
|
334
|
+
ALERT ← loop 写 ← alert loop 推 → 人 │ 事件流 (唯一源) │
|
|
335
|
+
│ → BC5 + UI │
|
|
336
|
+
└──────────────────┘
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**协作模式**:策略被下游遵从(Conformist)、Backlog 和 git/PR 是共享真相(Shared Kernel)、路由结果写事件(Customer/Supplier)、对账层过滤假交付(Anti-Corruption)、事件追加(Published Language)。loops coordinate via shared artifacts——多 loop 独立、event-driven,互不直接调用。
|
|
340
|
+
|
|
341
|
+
## 行为合同
|
|
342
|
+
|
|
343
|
+
以下 12 条不变量定义了系统的可靠性边界。每条必须可测试(与 [specs/harness-principles](specs/harness-principles.md) 的 C1–C12 一一对应,那里有每条的 FIX 证据)。
|
|
344
|
+
|
|
345
|
+
| # | 不变量 |
|
|
346
|
+
|---|--------|
|
|
347
|
+
| I1 | 在跑 Cycle 每 ≤60s 写心跳。超 watchdog 阈值必回收并落终态。进程活性 ⟂ 业务健康。 |
|
|
348
|
+
| I2 | 任意时刻进程被 SIGKILL,下次重入检测孤儿态并安全接管。不依赖优雅退出。 |
|
|
349
|
+
| I3 | 同一 Story 至多一个 open PR。开 PR 前先查去重。 |
|
|
350
|
+
| I4 | Backlog 是愿望,main 是真相。每 Cycle 末对账——标了完成但未合并的自动退回。退出码 0 ≠ 已交付,CI 绿 ≠ 已交付。 |
|
|
351
|
+
| I5 | 一个坏 Story 不冻结其他工作。连败 N 次 → 永久暂缓。不靠手动干预无限重试。 |
|
|
352
|
+
| I6 | 连续失败 → 暂停 + 告警 + 通知,人决策。不自动跨 agent fallback。 |
|
|
353
|
+
| I7 | 路径即身份。所有运行态数据放在 `<project>/.roll/loop/`。不同项目并行互不污染,无共享可变状态。 |
|
|
354
|
+
| I8 | 状态从不可变事件流重建,无独立缓存。追加原子(tmp→rename)。退出无条件写终态。 |
|
|
355
|
+
| I9 | 多写并发用乐观锁。标记 Story 精确匹配,不用子串。 |
|
|
356
|
+
| I10 | 按可预测规则路由(任务层级/类型)。spawn 前秒级探活。同输入路由恒定。 |
|
|
357
|
+
| I11 | 每 Cycle 记录 `(agent, model, token, cost, 回退次数, 有效成本)`。逼近预算上限 → 降级或暂停并通知。有效成本含回退。 |
|
|
358
|
+
| I12 | 一 Cycle 一个 Story,全新上下文,TCR 每步 green-or-revert。0 个 TCR 提交 → 判定失败并告警。 |
|
|
359
|
+
|
|
360
|
+
## 事实来源(US-TRUTH 系列)
|
|
361
|
+
|
|
362
|
+
读侧三件套(dashboard / archive / status)不再各自解析 backlog/events/runs:
|
|
363
|
+
|
|
364
|
+
- **权威矩阵** `packages/spec/src/types/truth.ts`(`TRUTH_ANCHORS`):每个持久事实字段声明唯一权威源、唯一写者、派生视图、冲突仲裁与 unknown 判据。跨仓仲裁:`github_pr_merge > product_main > roll_meta`。
|
|
365
|
+
- **终态事件** `cycle:terminal`(schema v1,`TERMINAL_SCHEMA_EPOCH_SEC` 起强制):每字段要么有完整值,要么带枚举化缺失原因——静默 0/"—" 在结构上不可能。
|
|
366
|
+
- **选择器** `packages/core/src/truth/selectors.ts`:`deriveStoryTruth / deriveCycleTruth / deriveEvidenceTruth`,纯函数、闭合 reason code;输出 truth/warn/fail/unknown/grandfathered。
|
|
367
|
+
- **唯一读侧适配器** `packages/cli/src/lib/truth-adapter.ts`:dashboard 的周期分类、静态归档的 delivered 判定全部经它走选择器;**新增消费者必须走这里,再写一个本地解析就是本 epic 关掉的回归**。unknown 一律渲染为 `?`,绝不静默显示成功。
|
|
368
|
+
- **三聚合投影**:Story 判断 backlog 声明与 `main`/验收证据是否一致;Cycle 只认 TerminalOutcome 终态事实;Release 汇总发版闸 verdict 与有效 waiver。README / guide / site 只描述这些目标态语义。
|
|
369
|
+
- **claim vs truth**:backlog 的 `✅ Done` 是声明,不是事实源;`main` 合并、证据报告、终态事件、发版闸事件才是事实锚点。所有 UI 投影必须把声明和真相分开呈现。
|
|
370
|
+
- **静态归档首页**:归档重建 按需渲染 Story / Cycle / Release tiles 和真相条;未知事实显示 `?`,已知为零才显示 `0`。premature Done 会被标成 drift/fail,不会被当作已交付。它是 archive/repair renderer,不是当前活体真相入口。
|
|
371
|
+
- **影子审计**:只读漂移扫描作为 `roll release` 闸的内部模块运行,报告落 `.roll/reports/consistency/`。
|
|
372
|
+
- **发版闸**:`roll release` 是唯一发版命令,事务内置一致性闸;任一维 fail 拦截发版,没有豁免路径——修掉漂移才能发。历史 release:waiver 事件仅作存档,不再有写入者。一致性闸跑在**开 PR / 合并之前**(发布分支上 bump+changelog 已提交、未合并),漂移在落 `main` 前就被拦,绝不留"已合并但没打 tag"的半成品。`main` 受 PR 保护,发版给自己也开 PR,再用 GitHub 原生 auto-merge(`gh pr merge --auto --squash`)自驱合并:不依赖 `com.roll.pr.<slug>` 看护 lane,进程中断也由 GitHub 完成合并;等待期逐轮打印进度,CI 不调度时推空提交 nudge;仓库未开 "Allow auto-merge" 则诚实报错而非静默挂死。
|
|
373
|
+
- **变更点护栏** `packages/spec/src/types/truth-registry.ts`(`TRUTH_FIELD_REGISTRY`):落盘且被第二处读取的字段必须登记(绑锚点、记写者、derived-cache 必声明 rebuild);未登记字段 CI 红并指路登记——历史 v2 字段 grandfather 列单。局部变量不登记。
|
|
374
|
+
|
|
375
|
+
### 结构化交付真相 (`DeliveryRecord` / `deliveries.jsonl`)
|
|
376
|
+
|
|
377
|
+
Backlog 状态格(`✅ Done` / `🔨 In Progress` 等)是**给人看的派生显示**——机器**绝不** parse 它当真相。机器管理的交付生命周期真相是结构化 `DeliveryRecord` 投影,存储在 `.roll/loop/deliveries.jsonl`(可重建 JSONL 缓存)。
|
|
378
|
+
|
|
379
|
+
**`DeliveryRecord`**(`packages/spec/src/types/delivery.ts`):
|
|
380
|
+
- `storyId` / `cycleId` — 唯一定位一次交付
|
|
381
|
+
- `lifecycleState` — 机器派生的生命周期状态(见下一节)
|
|
382
|
+
- `prNumber` / `prUrl` / `mergedAt` / `mergeCommit` — PR 事实(`FactOr<T>`,缺失带枚举化原因,非静默零)
|
|
383
|
+
- `recordedAt` — 记录写入时间(epoch ms)
|
|
384
|
+
|
|
385
|
+
**事实来源**:
|
|
386
|
+
- `runs.jsonl` — cycle 意图、发布尝试、PR 字段、终态 outcome。
|
|
387
|
+
- first-parent `main`/`origin/main` git merge log — `done` 的权威信号;story-id 可出现在 merge subject 或 body。
|
|
388
|
+
- `deliveries.jsonl` — 从 runs + git 重建出的缓存,不是独立真相源;删掉后 `ensureDeliveriesFresh()` 会重建。
|
|
389
|
+
- `backlog.md` — 人可读声明与派生显示,不能作为机器交付真相。
|
|
390
|
+
|
|
391
|
+
**写入/重建规则**:Cycle 发 PR 时把 PR 字段写入 run 事实;交付投影由 `ensureDeliveriesFresh()` 幂等重建并覆盖 `deliveries.jsonl`,同一 story 的记录按投影规则 last-wins/merge-wins。PR 合并后的 `done` 以主干 merge 为准,而不是以 agent 自述或 backlog 翻牌为准。
|
|
392
|
+
|
|
393
|
+
**读取规则**:所有消费者(picker / reconcile / archive / watch)**一律**走 `queryStoryDelivery()`,不读 markdown 状态——见 [唯一查询入口](#唯一查询入口-querystorydelivery)。
|
|
394
|
+
|
|
395
|
+
### 生命周期与裁定正交
|
|
396
|
+
|
|
397
|
+
两个维度各自独立——绝不混:
|
|
398
|
+
|
|
399
|
+
| 维度 | 语义 | 值空间 | 来源 |
|
|
400
|
+
|------|------|--------|------|
|
|
401
|
+
| **LifecycleState**(生命周期) | 卡**在哪**(管道位置) | `todo` / `building` / `in_flight` / `ci_red` / `blocked` / `on_hold` / `done` / `failed` / `abandoned` | 机器从 `TerminalOutcome` + PR 状态**派生**(`lifecycleFromFacts()`),不手设 |
|
|
402
|
+
| **TruthState**(裁定) | claim 是否**对**(校验结果) | `truth` / `warn` / `fail` / `unknown` / `grandfathered` | 选择器 `deriveStoryTruth`/`deriveCycleTruth` 从权威锚点仲裁 |
|
|
403
|
+
|
|
404
|
+
一张卡可以同时处于 `in_flight`(生命周期:PR 已开)和 `warn`(裁定:backlog 行仍标记 `📋 Todo`,声明滞后)——两个字段独立承载,不互斥、不塌缩。`ci_red` 是 `in_flight` 的 PR 级子状态(CI 挂了但卡仍在飞——修→重推→还 `in_flight`)。
|
|
405
|
+
|
|
406
|
+
### 唯一查询入口 (`queryStoryDelivery`)
|
|
407
|
+
|
|
408
|
+
**`queryStoryDelivery(storyId, deliveries) → StoryDeliveryTruth`**(`packages/core/src/truth/query.ts`)是交付真相的**唯一确定性查询函数**。纯函数、零 I/O、零 markdown parse——给定 story ID 和所有 `DeliveryRecord`,返回一个序列化 verdict。
|
|
409
|
+
|
|
410
|
+
**消费者契约(硬约束)**:
|
|
411
|
+
- **picker**(选卡):跳过 `lifecycleState ∈ {in_flight, ci_red, done, blocked, on_hold}` 的卡
|
|
412
|
+
- **reconcile**(对账):比对 `StoryDeliveryTruth.delivered` 与 backlog 声明
|
|
413
|
+
- **archive**(静态归档):`lifecycleState` + `deliveringCycles` 渲染交付阶段
|
|
414
|
+
- **watch / dashboard**(监控):`TruthState` + 派生 backlog 状态格
|
|
415
|
+
|
|
416
|
+
**新增消费者必须走 `queryStoryDelivery`**——再写一个本地 markdown 解析、backlog 正则匹配、或 `runs.jsonl` 裸读,就是本 epic 关掉的回归。
|
|
417
|
+
|
|
418
|
+
**`deriveBacklogStatus(truth) → string`**:从 `StoryDeliveryTruth` 派生 backlog 显示字符串(如 `🔨 In Progress · PR#878`、`✅ Done · merged abc1234`)。backlog 状态格从此是**纯派生视图**——人可读但机器不认。`roll truth query <storyId>` CLI 命令直接调用 `queryStoryDelivery`,输出结构化 verdict。
|
|
419
|
+
|
|
420
|
+
### 存储裁定:不上 SQLite 当源
|
|
421
|
+
|
|
422
|
+
真实发生的 3-agent 会审(codex + kimi + pi,2026-06-20)一致否决 SQLite 作权威真相源。理由:
|
|
423
|
+
|
|
424
|
+
1. **毁 git-native** — SQLite 二进制不可 diff、不可 PR 评审、不可 `git revert` 单行回滚(I8)。
|
|
425
|
+
2. **毁 worktree 隔离** — Cycle worktree 各自操作同一个 SQLite 文件 → 需要 WAL 模式 + 文件锁 + 额外并发协议(I7)。
|
|
426
|
+
3. **毁可重建性** — 从事件流重建 SQLite 须维护 schema 迁移链;JSONL 按行追加,重建 = `cat events → filter → append`(I8)。
|
|
427
|
+
4. **过度工程** — "原子记全 卡↔PR↔🔨" justify 的是**事务性写入边界**(单 writer 一条复合 record 一次原子 append),不必是 DB。单行 JSON 远小于 `PIPE_BUF`(POSIX 保证原子性),一个 `O_APPEND write()` 就够了。
|
|
428
|
+
|
|
429
|
+
**当前方案**:`deliveries.jsonl` — append-only JSONL,复用已有原子写。SQLite **仅可作未来可重建的派生查询缓存**(每日从事件流重建),永不做真相源。
|
|
430
|
+
|
|
431
|
+
### 消费者契约总结
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
consumer input output / 行为
|
|
435
|
+
──────── ───── ─────────────
|
|
436
|
+
picker queryStoryDelivery() skip if in_flight/ci_red/done/blocked/on_hold
|
|
437
|
+
reconcile queryStoryDelivery() delivered? vs backlog claim → drift verdict
|
|
438
|
+
archive StoryDeliveryTruth lifecycle + deliveringCycles → phase UI
|
|
439
|
+
watch/dashboard StoryDeliveryTruth TruthState + derived backlog status → display
|
|
440
|
+
release gate queryStoryDelivery() all stories delivered? → gate pass/fail
|
|
441
|
+
shadow audit queryStoryDelivery() claim vs truth drift → .roll/reports/consistency/
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
`roll release consistency` 的 `truth-live` 维度是该契约的 CI/发版闸:它先运行 `ensureDeliveriesFresh()`,再用 `queryStoryDelivery()` 断言发布增量里的故事确实由结构化投影证明为 `done`,并校验 Done 行上的 PR ref 与投影一致。
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Difftest Freeze Paradigm
|
|
2
|
+
|
|
3
|
+
> How Roll locks a command's observable contract: a **frozen expectation
|
|
4
|
+
> snapshot**, captured once and compared byte-for-byte thereafter — no engine
|
|
5
|
+
> spawn, no live oracle.
|
|
6
|
+
>
|
|
7
|
+
> 范式:命令的可观察契约用冻结期望快照守护——抓一次基线,之后逐字节对拍。
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
A command's contract is its observable surface — args, stdout, side effects,
|
|
12
|
+
exit code, the files and events it writes. Tests must lock *that*, not the
|
|
13
|
+
implementation. The freeze paradigm captures the proven-correct output once and
|
|
14
|
+
asserts against it forever: a regression snapshot, with no external process in
|
|
15
|
+
the loop.
|
|
16
|
+
|
|
17
|
+
一段行为的契约是它的可观察面(入参 / stdout / 副作用 / 退出码 / 写的文件与事件)。
|
|
18
|
+
测试锁的是这个,不是实现。冻结范式抓一次正确输出、之后永久对拍——回归快照,测试期不起任何外部进程。
|
|
19
|
+
|
|
20
|
+
> History: this convention was established while porting Roll's engine to
|
|
21
|
+
> TypeScript — each function was first proven byte-equal to its bash/python
|
|
22
|
+
> predecessor, then that output was frozen so the predecessor could be retired.
|
|
23
|
+
> Those engines are now gone; the snapshots remain as the regression guard.
|
|
24
|
+
|
|
25
|
+
**Status (US-PORT-021 / 021b): the bash/python oracles are gone.** `bin/roll`
|
|
26
|
+
and the dead `lib/*.py` were deleted (US-PORT-021). The difftests that
|
|
27
|
+
still shelled an oracle — `migrate` · `offboard` · `test` · `update` ·
|
|
28
|
+
`changelog` · `loop-story` — are now frozen snapshots
|
|
29
|
+
(`toMatchSnapshot`) of the proven-correct TS output (US-PORT-021b). They scrub
|
|
30
|
+
per-run volatility (the temp dir incl. macOS `/private` prefix, git SHAs,
|
|
31
|
+
relative-time spans / cycle-id timestamps) so the snapshot is platform-stable;
|
|
32
|
+
**CI (Linux/UTC) is the cross-platform gate** that catches any locale/TZ drift a
|
|
33
|
+
+8/macOS box would miss. No difftest spawns `bin/roll` / `python3` / `jq` anymore.
|
|
34
|
+
|
|
35
|
+
## The mechanical recipe
|
|
36
|
+
|
|
37
|
+
For each `*.difftest.test.ts` that spawns a live oracle:
|
|
38
|
+
|
|
39
|
+
1. **Capture once.** With the oracle still present (tests green ⇒ TS == oracle),
|
|
40
|
+
run the TS side and capture its `{status, stdout, stderr}` (or structured
|
|
41
|
+
result) for every case. The captured value *is* the oracle value.
|
|
42
|
+
一次性捕获:测试还绿时 TS 输出即 oracle 输出,抓下来。
|
|
43
|
+
|
|
44
|
+
2. **Classify each output for portability — this is the load-bearing step:**
|
|
45
|
+
- **Deterministic & portable** (derived from fixed string inputs, fixed
|
|
46
|
+
`HOME`, fixed config content): freeze the exact literal.
|
|
47
|
+
确定且可移植:原样固化为字面量。
|
|
48
|
+
- **Volatile** (embeds a random `mktemp` path, a `realpath`'d tmp dir whose
|
|
49
|
+
prefix differs across machines — macOS `/private/tmp` vs Linux `/tmp` — a
|
|
50
|
+
timestamp, or a live pid): **do not freeze the raw bytes.** Either
|
|
51
|
+
(a) feed the pure function a *fixed* input string so the output becomes
|
|
52
|
+
deterministic, or (b) assert a stable substring / structural pattern
|
|
53
|
+
(e.g. `/^slug-[0-9a-f]{6}$/`).
|
|
54
|
+
易变(随机路径/realpath 前缀跨机不同/时间戳/pid):改喂固定输入,
|
|
55
|
+
或断言稳定子串/结构,**绝不冻结原始字节**——否则本机绿、CI 红。
|
|
56
|
+
|
|
57
|
+
3. **Delete the oracle spawn.** Remove the `bashX` / `pyX` helper, the
|
|
58
|
+
`sed -n '/.../p' bin/roll` extraction, the `source lib/*.sh`, the transcribed
|
|
59
|
+
bash snippet. Assert `expect(ts).toBe(FROZEN)` instead.
|
|
60
|
+
删掉 spawn helper,断言改对拍冻结值。
|
|
61
|
+
|
|
62
|
+
4. **Verify green & record divergences.** Run the file's vitest. Note any
|
|
63
|
+
intentional v3-vs-v2 divergence (e.g. a whitelisted behavior change) in a
|
|
64
|
+
header comment, same as US-PORT-005 did for `changelog --help`.
|
|
65
|
+
|
|
66
|
+
## Already oracle-free (no conversion needed)
|
|
67
|
+
|
|
68
|
+
Tests that drive a **fabricated binary on `PATH`** (a fake `gh`, fake `tmux`,
|
|
69
|
+
fake `launchctl`/`crontab`) never spawned the v2 engine — they assert argv shape
|
|
70
|
+
and parse canned output. They already satisfy the US-PORT-009e gate and stay
|
|
71
|
+
as-is.
|
|
72
|
+
用 PATH 上假二进制(假 gh/tmux/launchctl)的测试本就不起旧引擎,无需转换。
|
|
73
|
+
|
|
74
|
+
## Freezing form: literal `toBe` vs inline snapshot
|
|
75
|
+
|
|
76
|
+
Both are frozen expectations with **zero engine spawn** — pick by output shape:
|
|
77
|
+
|
|
78
|
+
- **Scalar / small structured values** (a count, a sorted JSON object, a one-line
|
|
79
|
+
string): freeze a literal with `expect(ts).toBe(FROZEN)` / `toEqual(FROZEN)`.
|
|
80
|
+
Used by the spec/infra/core batches (009a/009b).
|
|
81
|
+
- **Multi-line, ANSI-coloured, or CJK CLI render output** (`roll status`,
|
|
82
|
+
`agent list`, `backlog`, `doctor`, …): freeze with vitest
|
|
83
|
+
`expect(ts).toMatchInlineSnapshot()`, captured once via `vitest -u`. The
|
|
84
|
+
snapshot lives in the test file (visible, reviewable), auto-escapes ANSI/CJK,
|
|
85
|
+
and is byte-exact — hand-transcribing such literals is error-prone and a single
|
|
86
|
+
wrong byte reds CI. Used by the CLI read-only batch (009c).
|
|
87
|
+
|
|
88
|
+
Inline snapshots are keyed by **call site**, so a parametrized `for` loop whose
|
|
89
|
+
iterations produce *different* values cannot share one — **unroll** such loops
|
|
90
|
+
into explicit `it` blocks (one snapshot each). Loops whose iterations all yield
|
|
91
|
+
the *same* value may stay.
|
|
92
|
+
|
|
93
|
+
### Scrubbing volatile substrings before the snapshot
|
|
94
|
+
|
|
95
|
+
When the otherwise-deterministic output embeds a volatile fragment (a tmp path, a
|
|
96
|
+
`basename(cwd)`, a uid, `uname -srm`, the package.json version), do **not** freeze
|
|
97
|
+
the raw bytes (step 2). Instead scrub the known fragment to a placeholder *before*
|
|
98
|
+
the snapshot, e.g. `out.split(home).join("<HOME>")`, `…/${uid}/… → /<UID>/`,
|
|
99
|
+
`/- OS: .*/ → "- OS: <OS>"`. The test already knows the fabricated path/uid, so
|
|
100
|
+
the scrub is exact. For an inherently host-specific appendix (for example an
|
|
101
|
+
issue body Environment block), assert the deterministic prefix + structural
|
|
102
|
+
markers and replace the whole appendix with `<ENV>`.
|
|
103
|
+
|
|
104
|
+
## The portability trap (why step 2 matters)
|
|
105
|
+
|
|
106
|
+
A slug like `basename-<md5(path)>` looks freezable, but `md5` of a `realpath`'d
|
|
107
|
+
temp dir is **not portable**: macOS resolves `/tmp` → `/private/tmp`, Linux CI
|
|
108
|
+
does not, so a frozen md5 goes green locally and red in CI. For pure functions,
|
|
109
|
+
pass a fixed path *string* (no I/O, no realpath) so the md5 is of a constant. For
|
|
110
|
+
I/O-bound functions that must touch a real dir, assert the structure
|
|
111
|
+
(`basename` slug + 6 hex), not the exact md5.
|
|
112
|
+
教训同 difftest TZ 假绿:冻结 realpath 的 md5 会本机绿 CI 红。纯函数喂固定字符串,
|
|
113
|
+
I/O 函数断言结构。
|