@haiyangbg/buildbeat 2.0.0-beta.2 → 2.0.0-beta.4
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 +42 -6
- package/SKILL.md +76 -2
- package/bin/buildbeat-v2.js +13 -1
- package/docs/CLI-PILOT-2026-08-23.md +1 -1
- package/docs/CLI.md +1 -1
- package/docs/EXECUTION-PLAN.md +2 -2
- package/docs/PHASE2-PILOT-PREFLIGHT-2026-08-25.md +2 -2
- package/docs/PHASE4-V1.20-PILOT-2026-08-25.md +2 -2
- package/docs/RELEASING.md +1 -1
- package/docs/V2-D2-DECISION-CARD.md +2 -2
- package/docs/V2-DECISIONS.md +2 -2
- package/docs/V2-ITERATION-01.md +13 -13
- package/docs/V2-ITERATION-06.md +2 -2
- package/docs/V2-ITERATION-08.md +62 -0
- package/docs/V2-PLAN.md +6 -6
- package/docs/V2-PROPOSAL.md +2 -2
- package/docs/V2.0.0-BETA.1-RELEASE-EVIDENCE-2026-08-28.md +1 -1
- package/docs/V2.0.0-BETA.2-RELEASE-EVIDENCE-2026-08-28.md +8 -0
- package/docs/V2.0.0-BETA.3-RELEASE-EVIDENCE-2026-09-01.md +8 -0
- package/docs/v2/M4-EXTERNAL-PILOT-2026-08-28.md +11 -11
- package/docs/v2/{M4-CHICKAI-PILOT-2026-08-28.md → M4-PILOT-APP-2026-08-28.md} +4 -4
- package/docs/v2/M4-SELFHOST-2026-08-28.md +1 -1
- package/docs/v2/RFC-0001-product-definition.md +2 -2
- package/docs/v2/SPEC-0001-events-v1.md +2 -2
- package/docs/v2/guide/00-how-to-talk.md +57 -0
- package/docs/v2/guide/01-quickstart.md +6 -0
- package/docs/v2/guide/02-workflow-guide.md +31 -0
- package/docs/v2/guide/04-adapter-guide.md +4 -0
- package/docs/v2/guide/05-worker-contract.md +10 -0
- package/docs/v2/guide/06-evidence-guide.md +10 -0
- package/docs/v2/guide/07-approval-guide.md +50 -0
- package/docs/v2/guide/10-recovery.md +25 -2
- package/docs/v2/guide/README.md +3 -0
- package/example/.buildbeat/manifest.json +1 -1
- package/lessons.md +12 -0
- package/package.json +1 -1
- package/src/v2/adapters/shell.js +87 -14
- package/src/v2/cli/run.js +602 -30
- package/src/v2/domain/event-registry.js +24 -0
- package/src/v2/engine/reducer.js +5 -0
- package/src/v2/engine/workflow.js +8 -1
- package/src/v2/evidence/collector.js +14 -3
- package/src/v2/observe/observe.js +9 -2
- package/src/v2/presets/release-readback.yaml +36 -0
- package/src/v2/presets/risk/release.yaml +21 -0
- package/src/v2/presets/software-delivery.yaml +5 -0
- package/src/v2/runtime/cache.js +124 -0
- package/src/v2/runtime/decisions.js +6 -4
- package/src/v2/runtime/env-contract.js +135 -0
- package/src/v2/runtime/envelope.js +183 -0
- package/src/v2/runtime/findings.js +158 -0
- package/src/v2/runtime/gc.js +182 -0
- package/src/v2/runtime/liveness.js +193 -0
- package/src/v2/runtime/metrics.js +8 -0
- package/src/v2/runtime/notify.js +223 -0
- package/src/v2/runtime/orchestrator.js +252 -24
- package/src/v2/runtime/overview.js +264 -0
- package/src/v2/runtime/repo-ref.js +38 -0
- package/src/v2/runtime/run-record.js +18 -3
- package/src/v2/workspace/workspace-manager.js +4 -1
- package/templates/v2/AGENTS.md +72 -0
- package/templates/v2//346/214/207/346/214/245/345/217/260.md +36 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
|
+
|
|
3
|
+
function outsideRepo(ref) {
|
|
4
|
+
return ref === ".." || ref.startsWith(`..${sep}`) || isAbsolute(ref);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Runtime operations use absolute paths, but ledger/run-record references are
|
|
8
|
+
// durable evidence and must not capture host-specific checkout locations.
|
|
9
|
+
// Keep those references repository-relative and fail closed if a caller tries
|
|
10
|
+
// to publish something outside the repository.
|
|
11
|
+
export function toRepoRef(repoRoot, targetPath) {
|
|
12
|
+
const root = resolve(repoRoot);
|
|
13
|
+
const target = isAbsolute(targetPath) ? resolve(targetPath) : resolve(root, targetPath);
|
|
14
|
+
const ref = relative(root, target);
|
|
15
|
+
if (outsideRepo(ref)) {
|
|
16
|
+
throw new Error("runtime reference is outside the repository");
|
|
17
|
+
}
|
|
18
|
+
return ref === "" ? "." : ref.split(sep).join("/");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Compatibility boundary: ledgers written before repo-relative references
|
|
22
|
+
// contain absolute paths. They remain readable, while all newly written
|
|
23
|
+
// events and compacted records use toRepoRef().
|
|
24
|
+
export function resolveRepoRef(repoRoot, ref) {
|
|
25
|
+
if (typeof ref !== "string" || ref === "") {
|
|
26
|
+
throw new Error("runtime reference must be a non-empty string");
|
|
27
|
+
}
|
|
28
|
+
const root = resolve(repoRoot);
|
|
29
|
+
const target = isAbsolute(ref) ? resolve(ref) : resolve(root, ref);
|
|
30
|
+
if (outsideRepo(relative(root, target))) {
|
|
31
|
+
throw new Error("runtime reference is outside the repository");
|
|
32
|
+
}
|
|
33
|
+
return target;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function normalizeRepoRef(repoRoot, ref) {
|
|
37
|
+
return toRepoRef(repoRoot, resolveRepoRef(repoRoot, ref));
|
|
38
|
+
}
|
|
@@ -8,6 +8,7 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
8
8
|
import { join, relative } from "node:path";
|
|
9
9
|
|
|
10
10
|
import { canonicalJson } from "../storage/event-ledger.js";
|
|
11
|
+
import { normalizeRepoRef } from "./repo-ref.js";
|
|
11
12
|
|
|
12
13
|
const KERNEL = { kind: "kernel", id: "orchestrator" };
|
|
13
14
|
|
|
@@ -22,6 +23,20 @@ export function writeRunRecord({ repoRoot, ledger, ts }) {
|
|
|
22
23
|
for (const [step, info] of Object.entries(state.steps)) {
|
|
23
24
|
attempts[step] = info.attempts;
|
|
24
25
|
}
|
|
26
|
+
const workspaces = Object.fromEntries(
|
|
27
|
+
Object.entries(state.workspaces).map(([id, workspace]) => [
|
|
28
|
+
id,
|
|
29
|
+
{
|
|
30
|
+
...workspace,
|
|
31
|
+
repo: normalizeRepoRef(repoRoot, workspace.repo),
|
|
32
|
+
worktreePath: normalizeRepoRef(repoRoot, workspace.worktreePath),
|
|
33
|
+
},
|
|
34
|
+
]),
|
|
35
|
+
);
|
|
36
|
+
const evidence = state.evidence.map((item) => ({
|
|
37
|
+
...item,
|
|
38
|
+
ref: normalizeRepoRef(repoRoot, item.ref),
|
|
39
|
+
}));
|
|
25
40
|
const record = {
|
|
26
41
|
run: first.run,
|
|
27
42
|
work: first.work,
|
|
@@ -31,11 +46,11 @@ export function writeRunRecord({ repoRoot, ledger, ts }) {
|
|
|
31
46
|
finishedAt: last.ts,
|
|
32
47
|
attempts,
|
|
33
48
|
budgets: state.budgets,
|
|
34
|
-
workspaces
|
|
35
|
-
evidence
|
|
49
|
+
workspaces,
|
|
50
|
+
evidence,
|
|
36
51
|
decisions: state.decisions,
|
|
37
52
|
approvals: state.approvals,
|
|
38
|
-
unverified:
|
|
53
|
+
unverified: evidence
|
|
39
54
|
.filter((item) => item.status === "unverified")
|
|
40
55
|
.map((item) => item.ref),
|
|
41
56
|
};
|
|
@@ -19,7 +19,10 @@ function git(cwd, args) {
|
|
|
19
19
|
return execFileSync("git", ["-C", cwd, ...args], {
|
|
20
20
|
encoding: "utf8",
|
|
21
21
|
stdio: ["ignore", "pipe", "pipe"],
|
|
22
|
-
|
|
22
|
+
// Preserve porcelain's fixed-width leading status columns. Trimming the
|
|
23
|
+
// first leading space turns " M file" into "M file" and makes the
|
|
24
|
+
// downstream slice(3) drop the first path character.
|
|
25
|
+
}).trimEnd();
|
|
23
26
|
} catch (error) {
|
|
24
27
|
const stderr = error.stderr ? String(error.stderr).trim() : error.message;
|
|
25
28
|
throw new WorkspaceError(`git ${args.join(" ")} failed: ${stderr}`);
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# AGENTS.md — <项目名> 工作区 · BuildBeat v2 协作契约
|
|
2
|
+
|
|
3
|
+
> 本文件走开放标准 `AGENTS.md`,被工作区下**任意会话**自动装载(Claude Code / Cursor / Codex / Gemini CLI / Aider / Zed 等均认)。目的:每个会话开工即知道「当前工作在哪 / 我是什么视角 / 读哪 / 写哪 / 该调哪条命令」,不靠人转述上下文。
|
|
4
|
+
> **层叠规则**(标准语义):会话从被编辑文件所在目录向上收集沿途所有 `AGENTS.md` 合并,**离得越近优先级越高**。本文件只写全局的(路由 / 协作规则 / 红线),各代码子仓的局部细节写进**该仓自己的 `AGENTS.md`**。
|
|
5
|
+
> 根目录 `CLAUDE.md` 只是一行指针(兼容只认该文件名的工具),内容单点在本文件。全栈总图见 `./ARCHITECTURE.md`,按需读。
|
|
6
|
+
> **本仓运行 BuildBeat v2**(运行时 `@haiyangbg/buildbeat@<版本>`,`buildbeat-v2` 由会话调用,人不必手敲)。若由 v1 迁来:`pm/NOW.md`、当期看板、`pm/status/*` 冻结只读,**禁止双写**。
|
|
7
|
+
|
|
8
|
+
## 0. v2 下工作怎么发生(一页流程)
|
|
9
|
+
|
|
10
|
+
1. **工作项**:每件事一个 `delivery/work/<WORK-ID>/`(`intent.md` 为什么做 + `plan.md` 怎么做,可选 `env-facts.md` 记踩出来的环境事实);被 digest 绑定接受(`buildbeat-v2 accept`)前只是草稿、不产生义务。
|
|
11
|
+
2. **代码工作跑 Run**:`buildbeat-v2 start --config <run-config.yaml> --attempt new` → 隔离 worktree 内 Build→Verify→Fix→Review 自动闭环 → **停在合并决定**。push、合并、部署永远是人批之后的人类动作。
|
|
12
|
+
3. **人怎么知道该做什么**:`buildbeat-v2 overview --repo .` 回答「每件事走到哪、下一步该谁」;`inbox` 只列等人批的 Run,每条后面附可复制的下一句命令;`status --run <RUN>` 回答「还在动吗、动了多久、卡没卡」。
|
|
13
|
+
4. **上线**:生产动作是人的;`release-readback` 预设 + `release` 风险预设把「做之前回读 → 人做 → 做之后回读 → 观察 → 人关窗」记成 L4 证据,任一步失败即停人批。
|
|
14
|
+
5. **observe 盯生产**:`buildbeat-v2 observe run --config .buildbeat/observe.yaml` 一次=一轮只读体检;异常分层(落账→只读诊断→intent 草稿入队 `delivery/observe/intents/`),草稿**绝不自动执行**,人用 `observe triage` 分诊。
|
|
15
|
+
6. **拍板台账**:平台级真实决策包一行进 `pm/decisions.md`;Run 级批准落各 Work 的 `decisions.jsonl`;finding 裁决落 `review-findings.jsonl`。契约在 `contracts/`。
|
|
16
|
+
7. **通知**:`.buildbeat/notify.yaml` 配一条通道(URL 只能来自环境变量),Run 停在人批 / 终态 / 疑似卡住会来找人。
|
|
17
|
+
8. **打扫**:终态 Run 留下的工作树用 `buildbeat-v2 gc --repo .` 清(默认只出计划)。
|
|
18
|
+
|
|
19
|
+
## 1. 工作包路由 —— Builder 端到端负责,会话按 AI 视角隔离
|
|
20
|
+
|
|
21
|
+
> 协作单元是需求/功能工作包(= v2 Work)。一个 Builder 对工作包的产品判断、实现、测试、合并与发布证据端到端负责;下表是可调用的 AI 专业视角和文件写边界,不是人类岗位或固定交接流水线。共享事实走 Git(`delivery/` 与 Run 台账)。
|
|
22
|
+
|
|
23
|
+
| AI 视角 | cwd | 可写(拥有) | 只读 | 开工先读 |
|
|
24
|
+
|---|---|---|---|---|
|
|
25
|
+
| **产品**(规格/编排) | 工作区根 | `delivery/**`、`pm/decisions.md`、根规划文档、`contracts/**` | 全仓 | `buildbeat-v2 overview --repo .` |
|
|
26
|
+
| **全栈**(实现,含运维) | `<代码仓>/` | `<代码仓>/**`(Run 内受 `allowedPaths` 机器约束) | `delivery/*`、契约 | 所属 Work 的 intent/plan + `run-config.yaml` |
|
|
27
|
+
| **测试**(契约验证·E2E) | 工作区根 | `tests/**`、独立核验报告(落所属 Work 目录) | 实现 + 规格 + 契约 | 所属 Work + 契约 |
|
|
28
|
+
|
|
29
|
+
> 🔴 **边界(按项目填写)**:<新地盘 / 老地盘 / 只读模块 / 不得借道写入的目录>。
|
|
30
|
+
> 🔴 **写者≠审者的机器化**:v2 Run 内置 fresh-context 只读 reviewer(快照强制,写入即失败落账);merge 门绑定 candidate + plan + 证据 digest,过期即 stale。
|
|
31
|
+
> **开工/收工护栏**:任意会话开工先 `bash scripts/bus-check.sh` + 各仓 `git pull`;收工前再跑 `bus-check --strict` 并保留 warning/unverified 边界。生产状态问 `observe status`,不猜。
|
|
32
|
+
|
|
33
|
+
## 1.5 UI 规范摘要(非 UI 项目可删)
|
|
34
|
+
|
|
35
|
+
- 延用既定设计语言与 token;每个可见界面处理加载 / 空 / 错误 / 移动端四态。
|
|
36
|
+
- **界面零元注释**:上线的可见界面不得出现给"做的人"看的文字;每次上线核查门必查。
|
|
37
|
+
- UI 交付的拍板对象必须含可渲染证据(真渲染入口 + 截图 digest);静态描述不构成拍板对象。
|
|
38
|
+
|
|
39
|
+
## 2. 协作规则(v2 版)
|
|
40
|
+
|
|
41
|
+
**① 唯一入口** —— 活动工作看 `delivery/`(`overview` / `inbox`);v1 遗留入口 `pm/NOW.md` 只读。
|
|
42
|
+
**② 契约落盘不喊话(双向)** —— 跨边界接口先改 `contracts/` 再动代码;收到协议声明独立核查再信。反向流:实现中发现契约不够用 → 不得就地消化,停下记契约缺口交产品域裁决。
|
|
43
|
+
**③ 交接靠 candidate hash + 台账** —— Run 停在合并决定时 candidate 已由 Git 回读固定;跨会话接力读 `delivery/work/<id>/` 即知全部事实,hash 不得编造。
|
|
44
|
+
**④ 护栏与不可逆动作** —— 开工 `bus-check`;部署/改契约/migration 等不可逆动作前再跑一次并走人批;exit 0 不消除 `warning/unverified`。
|
|
45
|
+
**⑤ 风险分轨** —— Risk Preset:`fast`(仅 merge 人批)/ `standard`(plan+merge,默认)/ `controlled`(intent+plan+merge+release)/ `release`(上线回读车道)。
|
|
46
|
+
**⑥ 核查门** —— Run 内 reviewer 只读、结构化 findings;`reviewTriage: required` 时 P0/P1 先过人分诊再派 fixer;review 每 Run 默认 2 轮封顶。**完成 = hash + 可核验证据**;标准轨最低 L3,上线必须 L4。`UNVERIFIED` 永不当作通过。
|
|
47
|
+
**⑦ 状态单点** —— 事实进 Run 证据与 Work 记录;进度看 `overview`,度量看 `metrics`(本地只读)。
|
|
48
|
+
**⑧ 视觉问题带图对比** —— 提 UI bug 必附『实现截图 ⟷ 设计稿截图』并排 + 标注差异点。
|
|
49
|
+
**⑨ 单点事实** —— 线上版本只信实查(`bus-check` / `observe status`);每个收敛后的真实决策包只在 `pm/decisions.md` 记一行;历史台账不回改。
|
|
50
|
+
**⑩ 真渲染拍板** —— 有 UI 的拍板对象必须是真渲染证据。
|
|
51
|
+
**⑪ 所有者可见命名进决策卡** —— 域名、服务名、环境名、自停时长、窗口时长等**所有者以后要看见或要念出来的名字与参数**,不由 worker 顺手定:进 intent 或门前决策卡(`BATCH_AT_GATE`),给推荐值和理由(用业务上听得懂的名字,不用内部术语)。
|
|
52
|
+
|
|
53
|
+
## 2.5 任务包与人批节奏
|
|
54
|
+
|
|
55
|
+
**任务包信封** —— 开工时从用户目标与活动 Work 明确 `objective / in_scope / terminal_condition`(即 Work 的 intent/plan)。默认一个工作包覆盖多个子项;每个会话同时只认领一个。只要仍有安全、可逆、在范围内且能推进目标的工作,会话就继续做,不因子产物完成交还接力棒。
|
|
56
|
+
|
|
57
|
+
**域回复格式** —— 面向用户收口、交接或回复明确检查点时,统一写「已做 → 未做 → 下一步」;已做/未做默认各总结成一句,不习惯性 1234 分条;只有事项性质差异大时才列举,每条仍是"结果+证据"。
|
|
58
|
+
|
|
59
|
+
**审批三级**:`STOP_NOW`(跨发布门 / 扩范围 / 改冻结契约 / 不可逆外部动作 / 接受风险);`BATCH_AT_GATE`(冻结前可逆取舍、默认值、阈值、可见命名——攒到门前一次批 2~5 个);`NO_APPROVAL`(能实查的事实、派生约束、文档归档、普通 P2)。
|
|
60
|
+
|
|
61
|
+
**人批预算** —— 每个工作包、每道门默认 1 个 `BATCH_AT_GATE` 请求;待批项必须带 findings 摘要与风险声明。
|
|
62
|
+
|
|
63
|
+
> **元原则:能实查的不问人** —— 查代码 / 配置 / 部署平台 / `overview` / `status` / `observe status` 能得到的事实,不拿去问用户、不信文档、不信上游转述。
|
|
64
|
+
|
|
65
|
+
## 3. 红线(每个会话受约束)
|
|
66
|
+
|
|
67
|
+
1. **凭据不入 git、不出本机**:文档只标位置不写值;本地 .env gitignore + 600;机器闸 = gitleaks pre-commit;v2 Worker 默认 env 白名单;通知 URL 只能来自环境变量。
|
|
68
|
+
2. **不 `git add -A`**:只 stage 当前工作包拥有的具体文件;各仓分别提交。
|
|
69
|
+
3. **不未授权部署**、不 force-push、不 `--amend` 已推送历史、不 `--no-verify`。Run 批准仅表示 merge-ready,合并/push/发布是其后的人类动作、逐项授权。
|
|
70
|
+
4. **每次部署完必更对应仓 `CHANGELOG.md`**;部署后 `observe run` 一轮。
|
|
71
|
+
5. **写者≠审者**:Run 内置只读 reviewer 机器强制;写者转述不构成证据。
|
|
72
|
+
6. **事实分层**:代码已确认事实 / 运行时待核事实 / 拟议需求 / 已实现行为,四类严格分开;未实查一律写「待核」。
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 指挥台 — Builder 怎么驱动 v2 工作(忘了怎么开场就看这页)
|
|
2
|
+
|
|
3
|
+
> 本仓运行 BuildBeat v2。每个 session 一开就自动读 `AGENTS.md`;活动工作在 `delivery/`。你基本只需说下面这几句话,命令由会话调、不用你手敲。
|
|
4
|
+
|
|
5
|
+
## 日常六句话
|
|
6
|
+
|
|
7
|
+
| 你想干什么 | 说什么 | 会话背后调什么 |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| 看现在到哪了、该谁动 | 「当前进度」/「待办是什么」 | `buildbeat-v2 overview --repo .`(每个 Work 的阶段 + 下一步)+ `observe status` |
|
|
10
|
+
| 看有什么等我批 | 「有什么要我拍板」 | `buildbeat-v2 inbox --repo .`(每条附可复制的下一句) |
|
|
11
|
+
| 立一件新事 | 「开个 Work:〔一句话目标〕」→ 看完说「接受」 | 建 `delivery/work/<ID>/intent.md + plan.md` → `accept`(digest 绑定) |
|
|
12
|
+
| 让它干活 | 「开工」/「再来一轮」 | `buildbeat-v2 start --config … --attempt new`(自动编号、自动作废旧等待,停在合并决定) |
|
|
13
|
+
| 它是不是卡了 | 「怎么样了」/「卡住了吗」 | `buildbeat-v2 status --repo . --run <RUN>`(耗时、历史中位数、最后输出、STALLED) |
|
|
14
|
+
| 拍板 | 「批准 / 拒绝〔RUN-ID〕」;分诊时「这条接受,那条不算」 | `approve` / `reject` / `findings adjudicate`;批准仅表示 merge-ready,合并/push/部署逐项另说 |
|
|
15
|
+
|
|
16
|
+
其他:生产报警看 `delivery/observe/intents/` 草稿 → 「fix_now / schedule / dismiss」;上线用 `release-readback` 预设开一个 Run,「我做完了」就是批准 `enter-apply-readback`;「打扫卫生」= `gc --repo .`(先看计划再 `--apply true`)。
|
|
17
|
+
|
|
18
|
+
## 视角开场白
|
|
19
|
+
|
|
20
|
+
| AI 视角 | 在哪开(cwd) | 你的一句话 |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| **产品** | 工作区根 | 「把当前目标收成一个 Work」/「验收」——定 intent/plan、维护决策收件箱、按决策包记 `pm/decisions.md` |
|
|
23
|
+
| **全栈** | `<代码仓>/` | 「〔WORK-ID〕该你了,开工」——授权范围内实现,Run 停在合并决定回人批 |
|
|
24
|
+
| **测试** | 工作区根 | 「验〔RUN-ID〕候选」——对精确 candidate 独立核验,报告落所属 Work 目录 |
|
|
25
|
+
|
|
26
|
+
## 域回复怎么写
|
|
27
|
+
|
|
28
|
+
面向你收口统一「已做 → 未做 → 下一步」,各一句;证据紧跟事项。下一步只写一条:下一棒是谁、需要你确认什么、或「我继续做,暂不交棒」。
|
|
29
|
+
|
|
30
|
+
## 按项目阶段的完整问法
|
|
31
|
+
|
|
32
|
+
未开始 → 立项定方案 → 准备执行 → 执行推进 → 验收合并 → 上线 → 完结换期复盘,每阶段说什么、会话做什么、你得到什么:见 BuildBeat 指南第 0 篇 `docs/v2/guide/00-how-to-talk.md`(Skill 装载目录下同路径)。
|
|
33
|
+
|
|
34
|
+
## 红线、写边界、审批三级、可见命名进决策卡
|
|
35
|
+
|
|
36
|
+
全在 `AGENTS.md`,本页不复制。
|