From 281c341e0c980ef135730c1e76474d4b8b326102 Mon Sep 17 00:00:00 2001 From: Maine Coon Yanyan Date: Mon, 3 Aug 2026 14:26:36 +0800 Subject: [PATCH 1/4] chore(fork): restore prompt authority and review gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: the rebuilt develop_base must retain Cat Cafe runtime identity authority and the fork-specific planning/review safeguards without carrying the old mixed commit history. [砚砚/gpt-5.6-sol🐾] --- AGENTS.md | 13 +- CLAUDE.md | 6 + GEMINI.md | 18 +- cat-cafe-skills/quality-gate/SKILL.md | 22 +++ cat-cafe-skills/receive-review/SKILL.md | 21 ++- .../refs/review-request-template.md | 14 ++ cat-cafe-skills/refs/shared-rules.md | 43 ++++- cat-cafe-skills/request-review/SKILL.md | 2 + cat-cafe-skills/writing-plans/SKILL.md | 47 ++++++ docs/public-lessons.md | 156 ++++++++++++++---- 10 files changed, 291 insertions(+), 51 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e8fa2ff3be..f610cf9c77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,8 @@ -# Clowder AI — OpenAI/Codex Agent Guide +# Clowder AI — Agent Guide ## Identity -You are the Maine Coon cat (Codex/GPT), the code reviewer and security specialist of this Clowder AI instance. +Your identity, role, and personality are provided by Cat Cafe at runtime. +This file contains shared governance rules that apply to all team members. ## Safety Rules (Iron Laws) 1. **Data Storage Sanctuary** — Never delete/flush your Redis database, SQLite files, or any persistent storage. @@ -9,15 +10,9 @@ You are the Maine Coon cat (Codex/GPT), the code reviewer and security specialis 3. **Config Immutability** — Never modify runtime config files. Config changes require human action. 4. **Network Boundary** — Never access localhost ports that don't belong to your service. -## Your Role -- Code review with clear stance on every finding (no "fix or not, up to you") -- Security analysis and vulnerability detection -- Test coverage verification -- Cross-model review (you review Claude's code, Claude reviews yours) - ## Review Protocol - Same individual cannot review their own code -- Cross-family review preferred (Maine Coon reviews Ragdoll's code) +- Cross-family review preferred - Every finding must have a clear severity: P1 (blocking) / P2 (should fix) / P3 (nice to have) ## Truth Sources diff --git a/CLAUDE.md b/CLAUDE.md index c00e9d80ac..46d58bf6a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,3 +22,9 @@ See `cat-cafe-skills/` for the full skill-based workflow: - No `any` types - Biome: `pnpm check` / `pnpm check:fix` - Types: `pnpm lint` + +## Cost Discipline +- **Agent demotion**: before spawning Agent/Explore/Workflow, ask "can a single grep/Read do this?" If yes → use Bash/Read directly +- **Model tiering**: simple subagent tasks (search, summarize, format) → `model: 'haiku'` +- **Debug strategy gate**: 1 round of static analysis with no root cause → switch to instrument → reproduce → observe. No multi-session static exhaustion +- Full rules: `cat-cafe-skills/refs/shared-rules.md` §20 diff --git a/GEMINI.md b/GEMINI.md index 23ecc1cff9..972a34631a 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -1,7 +1,8 @@ -# Clowder AI — Gemini Agent Guide +# Clowder AI — Agent Guide ## Identity -You are the Siamese cat (Gemini), the visual designer and creative thinker of this Clowder AI instance. +Your identity, role, and personality are provided by Cat Cafe at runtime. +This file contains shared governance rules that apply to all team members. ## Safety Rules (Iron Laws) 1. **Data Storage Sanctuary** — Never delete/flush persistent storage. @@ -9,13 +10,12 @@ You are the Siamese cat (Gemini), the visual designer and creative thinker of th 3. **Config Immutability** — Never modify runtime config files. 4. **Network Boundary** — Never access ports that don't belong to your service. -## Your Role -- Visual design and UX consultation -- Creative ideation and brainstorming -- Design system maintenance -- Breaking conventional thinking patterns - -## Important Constraints +## Gemini-Specific Constraints - Focus on design consultation, not code implementation - Always validate suggestions against the project's design system - Provide visual references when suggesting changes + +## Review Protocol +- Same individual cannot review their own code +- Cross-family review preferred +- Every finding must have a clear severity: P1 (blocking) / P2 (should fix) / P3 (nice to have) diff --git a/cat-cafe-skills/quality-gate/SKILL.md b/cat-cafe-skills/quality-gate/SKILL.md index 12cf17654f..63d21004cd 100644 --- a/cat-cafe-skills/quality-gate/SKILL.md +++ b/cat-cafe-skills/quality-gate/SKILL.md @@ -61,6 +61,28 @@ Step 2: CREATE — 建检查清单 - 列出每一个 AC / 功能点 / 边界条件 - 列出 Discussion 里的 UX 描述和场景 +Step 2.4: PATCH COUNTER GATE(反复返工硬闸)🔴 + - **Step A — 列出候选 commits**: + ``` + UPSTREAM_REF="$(git rev-parse --abbrev-ref --symbolic-full-name '@{upstream}' 2>/dev/null || echo origin/main)" + BASE_REF="$(git merge-base "$UPSTREAM_REF" HEAD)" + git log --oneline "$BASE_REF..HEAD" -- + ``` + 自动取当前分支的 upstream tracking ref,无 tracking 时 fallback `origin/main`。develop_base 流程中会自动解析为 `origin/develop_base`。 + - **Step B — 人工分类**:逐条标记每个 commit 为以下之一: + - ✅ **同一 bug/AC 返工**:修上次没修好的同一个问题、用户报告同一问题后的重复修复、同一区域反复修补 + - ⬚ **正常迭代**(不计入):reviewer 正常 P1/P2 修复(`fix: address review P2-xxx`)、新发现的不同问题的修复 + - ⬚ **排除**(不计入):纯 lint/format/typo(`chore:` / `style:`)、rebase 冲突解决 + - 判断标准:问自己——"这个 fix 是在修一个**新发现的问题**,还是在修**上次没修好的同一个问题**?"后者才标 ✅ + - **Step C — 硬闸判定**:标 ✅ 的 ≥3 个 → **GATE FAIL** + - FAIL 时必须: + ① 停止继续修补 + ② 重读 spec 原文 + 铲屎官原始需求 + ③ 产出完整的真相源矩阵(格式同 writing-plans 的 Truth-Source Model Gate) + ④ 写清"为什么需要这么多 fix"的根因分析 + ⑤ 根因分析通过后才能继续 + - > **根因(2026-06-05 反思 + LL-020)**:F719 分支前 25 个提交大量 fix/refactor/test-fix,远超 LL-020 的"N > 3 换方向"告警线。补丁数量是方向信号——N > 3 不是"还需要更多补丁"的信号,而是"理解不完整,需要停下来重新建模"的信号。 + Step 2.5: CLOSE GATE MATRIX + FOLLOW-UP TAIL SCAN(F177 Phase A)🔴 - 检查 CloseGateReport 是否已生成(schema: `cat-cafe-skills/refs/close-gate.md`) - 每个 unmet AC 是否三选一处置(immediate / delete / cvo_signoff) diff --git a/cat-cafe-skills/receive-review/SKILL.md b/cat-cafe-skills/receive-review/SKILL.md index 0b1c878a69..b69ee283e3 100644 --- a/cat-cafe-skills/receive-review/SKILL.md +++ b/cat-cafe-skills/receive-review/SKILL.md @@ -12,6 +12,7 @@ triggers: - "reviewer 说" - "fix these" - "github-review-feedback" +tips_exempt: true --- > **SOP 位置**: 本 skill 是 `sop-definitions/development.yaml` stage `review` 的反馈处理执行细节。 @@ -45,11 +46,12 @@ triggers: ## 核心知识 -### 两类反馈,处理方式不同 +### 三类反馈,处理方式不同 | 类型 | 特征 | 处理 | |------|------|------| | **代码级** | bug / edge case / 性能 / 命名 | Red→Green 修复流程 | +| **数据流级** | "真相源读错了" / "状态不一致" / "全局改了项目没级联" / "UI 和 API 对不上" | STOP → 重建真相源模型 → 再修(见下方) | | **愿景级** | "这不是operator要的" / "缺了多项目管理" / "UI 不可用" | STOP → 回读原始需求 → 升级operator | > **愿景级反馈不能用代码 patch 修补设计问题。** 先对照operator experience验证 reviewer 说得对吗;如确实偏离,升级operator确认偏差范围,再重新设计。 @@ -78,6 +80,21 @@ P3-1: 建议重新考虑整体架构方向 [FC:N/A] - Delta 数据自然累积在 review 记录中,Phase C `eval:qc` 聚合分析 - 标注不影响 finding 的 severity 判定或处理流程 +### 数据流级反馈处理(反补锅匠门禁)🔴 + +当 reviewer 或用户指出的问题涉及**真相源/状态/级联/一致性**时,**禁止直接按点修**。必须: + +1. **STOP** — 不写一行代码 +2. **重建真相源模型** — 产出真相源矩阵(格式同 `writing-plans` 的 Truth-Source Model Gate),明确谁写谁读谁派生 +3. **定位根因** — 当前 bug 是哪个环节的读/写/级联断了?用矩阵定位,不用直觉猜 +4. **Blast radius 评估** — 修这个环节会影响哪些消费方?列出来,每个都需要验证 +5. **补 regression guard** — 对每个受影响的消费方,先写测试保护现有正确行为,再改代码 +6. **修复** — 在 regression guard 保护下修根因,不是修症状 + +**判别标准**:如果你的修复方案是"在前端读另一个字段 / 加一个 workaround / 在 UI 层补逻辑"——这大概率是在修症状。问自己:"后端/数据层的真相源对不对?"如果不对,先修数据层。 + +> **根因(2026-06-05 反思)**:布偶猫 F719 中 toggle 不更新时,先做了前端读 `mountedCount` 的 workaround 而不是修 API 层的 `cap.enabled` 写入。这是"收到反馈后仍按点修"的典型失败模式。数据流级问题的修法必须回到数据层,不能在展示层打补丁。 + ### 禁止的响应(表演性同意) ``` @@ -118,7 +135,7 @@ review 报告中必须包含 fallback 层数分析结果。 WHEN 收到 review 反馈: 1. READ — 完整读完,不要边读边反应。**R2+ 时额外动作**:回看上轮 finding 列表,标注每个 finding 的 failure-mode 类型,用于 AUDIT 步骤的同型判别 -2. CLASSIFY — 区分愿景级 vs 代码级;按 P1/P2/P3 分优先级 +2. CLASSIFY — 区分代码级 / 数据流级 / 愿景级(见上方三类反馈表);按 P1/P2/P3 分优先级。数据流级 = 涉及真相源/状态/级联/一致性 → 走数据流级处理流程 3. CLARIFY — 有不清晰的问题先全部问清,再动手 4. VERIFY — reviewer 说的问题真的存在吗?(见下方三道门) 5. AUDIT — failure-mode sweep(见下方 §16e 判别) diff --git a/cat-cafe-skills/refs/review-request-template.md b/cat-cafe-skills/refs/review-request-template.md index 7c22db87f1..46128a3920 100644 --- a/cat-cafe-skills/refs/review-request-template.md +++ b/cat-cafe-skills/refs/review-request-template.md @@ -35,10 +35,22 @@ Why: {一句话} - 是否新建了并行 `Store` / `Queue` / `Router` / `Adapter` / `Dispatcher` / `Binding` - 若修改 `docs/architecture/ownership/cells/*.md`,是否确实改变了 owner / boundary / extension point / canonical anchor +## Invariant Matrix(涉及跨层状态同步/级联时必填) + +| 不变量 | 断言描述 | 验证方式 | +|--------|---------|---------| +| INV-1 | {描述} | {测试/手动验证} | + +## E2E User Path Evidence(涉及用户可感知功能时必填) + + +{端到端路径 + 命令/截图} + ## Open Questions ### 技术 OQ(给 reviewer) {需要 reviewer 特别关注的实现正确性/安全性/性能问题} +请 reviewer 逐条验证 Invariant Matrix 中的不变量是否被代码保持。 ### 价值 OQ(给 operator,如有) {需要 operator 判断的价值取舍——必须附 Decision Packet(格式见 `refs/decision-matrix.md`)} @@ -122,6 +134,8 @@ pnpm gate # ✓ pre-merge-check.sh 全套 - **附原始需求摘录(≤5 行)**,否则 reviewer 不审 - **附 Architecture Ownership 三字段**,否则 reviewer 不审 +- **涉及跨层状态同步/级联时附 Invariant Matrix**,reviewer 有权要求补上后再继续 +- **涉及用户可感知功能时附 E2E User Path Evidence**(引用 quality-gate Dogfood 输出) - 自检报告必须附上(从 quality-gate skill 输出) - 前端功能由 author 自跑 preview,附 URL / 关键交互 / 结果;截图、录屏或浏览器测试输出是可选证据载体,不得要求 operator 代采 - 前端 review 需要起 dev 时,**必须附 review 沙盒 Path + Start Command + 实际端口** diff --git a/cat-cafe-skills/refs/shared-rules.md b/cat-cafe-skills/refs/shared-rules.md index 143b38c7b4..e82f340436 100644 --- a/cat-cafe-skills/refs/shared-rules.md +++ b/cat-cafe-skills/refs/shared-rules.md @@ -260,7 +260,7 @@ Skill/refs 中描述工作流分工时,用以下角色词代替具体猫名。 ## 0. 身份契约 -猫猫是家庭成员,不是外包工具。讨论 Clowder AI 团队时用"我们" / "咱们" / "家里",禁止用"你们" / "他们"指代三猫。 +猫猫是家庭成员,不是外包工具。讨论 Cat Café 团队时用"我们" / "咱们" / "家里",禁止用"你们" / "他们"指代三猫。 ## 1. 交接五件套 @@ -780,7 +780,46 @@ beforeEach(() => { **违反代价**:用真身份发测试 payload 到operator thread / 其他猫 thread,看起来像 spam / cron job / 幻觉。已发出去的消息**不可撤回**。 -## 20. 根目录卫生公约(F214 — 根目录运行时残留治理) +## 20. Agent 降级与成本纪律(2026-06-11 usage 分析教训) + +> 来源:usage 分析发现 93% 成本来自 subagent-heavy sessions。Agent/Workflow 派发是高杠杆操作——每次调用 ≈ 新开一个完整上下文窗口。 + +### 20a. Agent 降级反射(派发前必问) + +派 Agent / Explore / Workflow **前**,先问: + +1. **一个 grep/Read 能做吗?** → 能 → 直接 `grep -rn` / `Read` / `Bash`,不派 agent +2. **需要并行或隔离上下文吗?** → 不需要 → 直接做,不开 Workflow +3. **这个 subagent 需要 Opus 吗?** → 简单搜索/摘要/格式化 → 用 `model: 'haiku'` + +**典型降级场景**(直接用工具,不派 agent): +- 找函数定义 → `grep -rn "functionName" .` +- 找文件 → `find . -name "pattern"` +- 看某个符号的引用 → `grep -rn "symbolName" packages/` +- 读已知文件的某段 → `Read` tool + +**Explore agent 保留场景**(合理使用): +- 搜索目标未知,需要跨多目录多命名探索 +- `very thorough` 广度搜索,确实不知道在哪 + +### 20b. 调试策略切换门禁 + +调试(定位 bug / 理解行为)时: +- **静态分析 1 轮未定位** → 切换到"加诊断 → 复现 → 看数据"策略 +- **禁止**在同一方向穷举 2+ 轮 session 的静态分析(token 消耗巨大,收益递减) +- **观察优先于推理**:运行时日志 / 诊断 / 断点 > 反复读同一批文件推理 + +来源:消息消失 bug 调查——2 轮 session 穷举静态分析未定位,如果一开始加诊断 + 复现可能 30 分钟就定位。 + +### 20c. 长 session 成本意识 + +- `/loop` 和 `/schedule` 必须有明确退出条件,不做无限轮询 +- 调查类任务设心理 token budget——到预算 → 切换策略或求助,不在同方向死磕 +- Workflow 使用 `budget.remaining()` 做提前终止 + +--- + +## 21. 根目录卫生公约(F214 — 根目录运行时残留治理) > 来源:F214(2026-05-28)。ADR-010 / F023 管子目录代码 + docs 归档,不管根目录运行时残留——本节是 ADR-010 在根目录维度的补丁。 diff --git a/cat-cafe-skills/request-review/SKILL.md b/cat-cafe-skills/request-review/SKILL.md index d48664eca9..f26bb552dd 100644 --- a/cat-cafe-skills/request-review/SKILL.md +++ b/cat-cafe-skills/request-review/SKILL.md @@ -72,6 +72,8 @@ Ask: checked=<请 reviewer 指认最高风险面> verdict=approve|block - `Original Requirements`:≤5 行原话 + 真相源路径; - `Architecture Ownership`:cell / map delta / why; +- `Invariant Matrix`:涉及跨层状态同步或级联时,列核心不变量与真相源读写关系; +- `E2E User Path Evidence`:涉及用户可感知功能时,引用 quality-gate 的 dogfood 证据; - 技术 OQ 与价值 OQ 分开;价值 OQ 才附 Decision Packet; - 验证命令、输出与 frontend preview 证据; - `Review-Target-ID`(需要 review sandbox 时用于 `/tmp/cat-cafe-review/{id}/{reviewer}`)。 diff --git a/cat-cafe-skills/writing-plans/SKILL.md b/cat-cafe-skills/writing-plans/SKILL.md index 69611ada2a..7410ce0131 100644 --- a/cat-cafe-skills/writing-plans/SKILL.md +++ b/cat-cafe-skills/writing-plans/SKILL.md @@ -10,6 +10,7 @@ triggers: - "写计划" - "implementation plan" - "拆分步骤" +tips_exempt: true --- # Writing Plans @@ -26,6 +27,52 @@ triggers: **Save plans to:** `feature-specs/YYYY-MM-DD-.md` +## Truth-Source Model Gate(复杂需求强制)🔴 + +**写不出完整数据流 = 还没理解 = 不许拆步骤。** + +对于涉及**跨层状态同步或级联更新**的需求(如:全局配置→项目配置→文件系统→UI 多层联动),动手拆步骤前**必须**先产出以下三张表。简单的 CRUD 读写(API 读 DB 返回 JSON)不触发此门禁: + +### 表 1:真相源矩阵(谁写谁读谁派生) + +```markdown +| 数据 | 真相源(写) | 消费方(读) | 派生关系 | 级联规则 | +|------|-------------|-------------|---------|---------| +| 例:全局 skill 启禁用 | capabilities.json (全局) | 项目级 capabilities / UI | 项目继承全局 | 全局禁用 → 所有项目 unmount | +``` + +**每一行必须回答**:这个数据从哪来?谁负责写?谁读?读的人直接读真相源还是读派生?状态变化时谁级联通知谁? + +### 表 2:核心不变量(invariants) + +```markdown +- INV-1: 全局禁用的 skill,任何项目下都不可见且无 symlink +- INV-2: 项目配置的启禁用状态 = 实际 symlink 存在与否 +- INV-3: UI 展示的状态 = 后端 API 返回的状态(无前端 workaround) +``` + +**每条 invariant 必须可测试**——写不出对应的断言 = 不是 invariant,是愿望。 + +### 表 3:既有正确行为保护点 + +```markdown +| 现有功能 | 当前正确行为 | 保护方式 | +|---------|------------|---------| +| 项目选择器 | 能列出所有已知项目 | 现有测试 / 新增 regression guard | +``` + +**改代码前,先确认保护方式到位**(已有测试覆盖 or 需要先补测试)。没有保护 = 改了之后不知道有没有破坏。 + +### 何时豁免 + +- 单文件 ≤50 行改动、不涉及多数据源交互 → 可跳过 +- 纯 UI 样式调整、文档修改 → 可跳过 +- **涉及 config/state/filesystem 多层交互 → 不可跳过,写不出就停下来问清楚** + +> **根因(2026-06-05 反思)**:布偶猫 F719 Skill Lifecycle Management 反复返工的核心病因是"边做边理解"——抓关键词就动手,没花时间画完整数据流。铲屎官给了闭环方案但实现出来"长得像但数据流不通"。此门禁把"理解"从"实现"中分离:理解产物是三张表,不是代码。 + +--- + ## Straight-Line Check (A→B, No Detour) **Before splitting steps, do this first:** diff --git a/docs/public-lessons.md b/docs/public-lessons.md index 677b3e5673..77ade956f5 100644 --- a/docs/public-lessons.md +++ b/docs/public-lessons.md @@ -90,7 +90,83 @@ created: 2026-02-26 --- -## 6) Maine Coon侧首批条目(AGENTS + Review + Skills) +## 6) 主题索引与高频核心 + +> ⭐ = 高频核心(日常协作反复踩的认知/流程护栏) + +**根因分析与方向纠偏** +- ⭐ LL-009: 关键前提不确定时,先提问再动作 +- LL-014: Bug 修复必须先写 Bug Report 再动手 +- LL-019: 过度修复反模式——根因修完后不要盲修触发器 +- ⭐ LL-020: 补丁数量是方向信号——N > 3 停下来复检方向 +- ⭐ LL-021: AI 倾向停在第一层"看起来合理"的答案,不主动追溯根因 +- ⭐ LL-058b: 热补丁反模式——已有治理机制不查就另起炉灶 + +**交付验证与证据纪律** +- ⭐ LL-006: 没有新鲜验证证据,不得宣称完成 +- ⭐ LL-029: 交付物验证不能只看 spec checkbox——必须核实 commit/PR +- ⭐ LL-031: Quality gate 逐字段对账 AC——文档承诺 ≠ 代码已兑现 +- ⭐ LL-032: 愿景守护不能只看代码和测试报告——必须真实启动 dev 跑一遍 +- ⭐ LL-041: 写完产物不主动打开 = 做了菜不端上桌 + +**Review 与协作纪律** +- LL-002: Review 问题必须先 Red 再 Green,禁止先改后补测 +- LL-003: Reviewer 必须有立场,Author 必须技术性 push back +- LL-004: P1/P2 当轮清零,P3 当场决断,不挂债务 +- LL-005: 修完 review 后必须回给 reviewer 二次确认再合 main +- LL-033: 云端 review 不能只看 review body state——必须检查 inline code comments + +**Worktree / Runtime / 数据安全** +- LL-008: Worktree 生命周期必须成套执行 +- LL-010: 删除文件必须用 trash,禁止 /bin/rm +- LL-011: Worktree 清理的正确顺序——先 push,再 cd 回主仓,最后 remove +- LL-012: 不要 --force 删有猫在工作的 worktree +- LL-015: Worktree 开发必须用独立 Redis 端口(6398) +- LL-045: Runtime worktree 反复被猫污染 +- ⭐ LL-049: `pnpm dev:direct` 无差别杀端口——review 踢翻 runtime +- LL-055: spawn 出的"长尾 child runtime"必须能脱离 parent 自动死亡 +- LL-056: stale browser profile 不是 orphan——cleanup 要按资源所有权分组 + +**知识工程与配置漂移** +- LL-001: 提炼教训前先做时效性验证 +- LL-007: 交接缺 Why 会让接手方无法判断 +- LL-025: 协作规则不能写死个体名,必须引用角色 +- LL-027: Feature spec 与代码实现的时间线漂移 +- LL-028: "最小实现"不等于"做个玩具再重写" +- LL-030: 共享脚本改默认值,同 commit 必须补显式环境值 +- LL-037: 共享记忆塑造视角——团队文化比模型参数更能影响判断趋同 +- LL-042: 配置真相源不加门禁就会漂移 +- LL-050: ADR 漂移 2 个月无人发现 +- LL-057: root prompt 重复可能是兼容副本,不是天然垃圾 + +**工具与基础设施细节** +- LL-013: Git commit 前必须检查暂存区 +- LL-016: ioredis keyPrefix 对 eval() 和 keys() 的行为不一致 +- LL-017: CAS 比较必须基于不可变快照 +- LL-018: Session 存储必须按 Thread 隔离 +- LL-022: 治理基线必须脚本化 +- LL-023: CLI JSON 格式陷阱与 jq 安全防护 +- LL-024: 状态字段多点写入会复发蜘蛛网 +- LL-026: 身份信息是硬约束常量 +- LL-034: Embedding 实现偷懒 +- LL-035: sync-to-opensource rsync --delete 打穿 runtime +- LL-036: full sync 长跑不能在半路报喜 +- LL-038: Promise timeout 不等于 Promise 取消 +- LL-039: gate 里推进 cursor 等于"还没干活就划卡" +- LL-040: AI 写文档日期不能凭内部时间感 +- LL-043: 删旧层前必须证明迁移已落成 +- LL-044: Chrome IME 回车误提交 +- LL-046: AOF/RDB 持久化脱节 +- LL-047: Socket.IO cors 不保护 WebSocket +- LL-048: 用户可感知状态禁止默认 TTL +- LL-051: 实验框架空转 +- LL-052: exec VAR=val cmd 不设置环境变量 +- LL-053: 无头 Codex CLI 长任务不能靠 shell 伪后台 +- LL-054: 猫的 callback env 泄漏到 unit test 子进程——用真身份发出 6 条 'hi' + +--- + +## 7) 条目 ### LL-002: Review 问题必须先 Red 再 Green,禁止先改后补测 - 状态:validated @@ -162,7 +238,7 @@ created: 2026-02-26 - 关联: - `cat-cafe-skills/merge-approval-gate/SKILL.md` - - `review-notes/README.md` + - *(internal reference removed)* ### LL-006: 没有新鲜验证证据,不得宣称完成 - 状态:validated @@ -198,7 +274,7 @@ created: 2026-02-26 - 关联: - `cat-cafe-skills/cross-cat-handoff/SKILL.md` - - `review-notes/README.md` + - *(internal reference removed)* ### LL-008: Worktree 生命周期必须成套执行(建-收敛-合入-清理) - 状态:validated @@ -238,10 +314,6 @@ created: 2026-02-26 - `cat-cafe-skills/systematic-debugging/SKILL.md` - `cat-cafe-skills/cat-cafe-receiving-review/SKILL.md` ---- - -## 7) Ragdoll侧首批条目(CLAUDE.md + Bug Report + Skills) - ### LL-010: 删除文件必须用 trash,禁止 /bin/rm - 状态:validated - 更新时间:2026-02-13 @@ -354,20 +426,6 @@ created: 2026-02-26 - 关联:CLAUDE.md §7 Redis 测试规则 | ADR-008 Lua 原子操作 -### LL-023: CLI JSON 格式陷阱与 `jq` 安全防护 -- 状态:draft -- 更新时间:2026-02-19 - -- 坑:在 CLI 中手动拼接带变量的 JSON 字符串(如 `curl` 调用 API)时,极易因双引号转义、多层嵌套或变量内容包含特殊字符而导致 JSON 格式损坏,甚至导致消息发送失败或变成“只有用户可见”的悄悄话。 -- 根因:手动拼接 JSON 违反了“数据与格式分离”原则,AI 对 Shell 转义规则(尤其是多层引号)的处理在复杂场景下不可靠。 -- 触发条件:通过 `curl` 调用含有环境变量(如 `$CAT_CAFE_INVOCATION_ID`)的 API,且消息内容包含引号、换行或表情符号时。 -- 修复:强制使用 `jq` 构造 JSON(例如:`jq -nc --arg c "$MSG" '{content: $c}'`),利用工具确保内容被自动转义。 -- 防护:更新所有 Agent 的提示词模板,将 `curl` 示例改为 `jq` 构造法;在 `GEMINI.md` 中增加醒目警告。 -- 来源锚点: - - `GEMINI.md` (2026-02-19 更新) - - 2026-02-19 Siamese(Gemini)“猫猫杀”游戏调试过程 -- 原理:结构化数据必须由结构化工具生成。在命令行环境中,`jq` 是保证数据序列化健壮性的事实标准。 - ### LL-017: CAS 比较必须基于不可变快照,不能用内存活引用 - 状态:validated - 更新时间:2026-02-13 @@ -463,12 +521,26 @@ created: 2026-02-26 - 防护:P0 验收前与后续回归中运行健康脚本;失败即阻断“可用”结论。 - 来源锚点: - `scripts/hindsight/p0-health-check.sh` - - `project-runbooks/hindsight-p0-health-check.md` + - *(internal reference removed)* - *(internal reference removed)* - 原理:治理有效性不是“策略存在”,而是“策略被持续验证”。没有自动化检查的治理,等同于没有治理。 - 关联:`docs/decisions/005-hindsight-integration-decisions.md` | `docs/ROADMAP.md` | Task 4 可观测检查 +### LL-023: CLI JSON 格式陷阱与 `jq` 安全防护 +- 状态:draft +- 更新时间:2026-02-19 + +- 坑:在 CLI 中手动拼接带变量的 JSON 字符串(如 `curl` 调用 API)时,极易因双引号转义、多层嵌套或变量内容包含特殊字符而导致 JSON 格式损坏,甚至导致消息发送失败或变成"只有用户可见"的悄悄话。 +- 根因:手动拼接 JSON 违反了"数据与格式分离"原则,AI 对 Shell 转义规则(尤其是多层引号)的处理在复杂场景下不可靠。 +- 触发条件:通过 `curl` 调用含有环境变量(如 `$CAT_CAFE_INVOCATION_ID`)的 API,且消息内容包含引号、换行或表情符号时。 +- 修复:强制使用 `jq` 构造 JSON(例如:`jq -nc --arg c "$MSG" '{content: $c}'`),利用工具确保内容被自动转义。 +- 防护:更新所有 Agent 的提示词模板,将 `curl` 示例改为 `jq` 构造法;在 `GEMINI.md` 中增加醒目警告。 +- 来源锚点: + - `GEMINI.md` (2026-02-19 更新) + - 2026-02-19 Siamese(Gemini)"猫猫杀"游戏调试过程 +- 原理:结构化数据必须由结构化工具生成。在命令行环境中,`jq` 是保证数据序列化健壮性的事实标准。 + ### LL-024: 状态字段多点写入会复发蜘蛛网 - 状态:validated - 更新时间:2026-02-27 @@ -1233,20 +1305,20 @@ created: 2026-02-26 - 状态:draft - 更新时间:2026-05-28 -- 坑:用户明确要“精美架构设计图 / 华为风 / 白底红黑 / 图片”时,Codex 第四五六次仍进入“先写 SVG 再转 PNG”的 coder 反射,产物方向错,且重复踩同一坑。 +- 坑:用户明确要”精美架构设计图 / 华为风 / 白底红黑 / 图片”时,Codex 第四五六次仍进入”先写 SVG 再转 PNG”的 coder 反射,产物方向错,且重复踩同一坑。 - 根因: - 1. 旧规则只是“默认建议”,没有进入执行前硬闸;一旦进入“文字可控、布局可控”的工程反射,imagegen 被错误降级成可选项。 - 2. 把“架构图需要精确”误判成“必须代码渲染”,但用户真正验收的是视觉完成度,而不是 SVG 源文件。 - 3. 已有猫档明确写了“Maine Coon原生图片生成强、禁止用 SVG 画”,但能力唤醒没有把这条转成 preflight。 -- 触发条件:复杂架构图、PPT 页面、企业信息图、华为风 / 红白黑风格、已有低保真蓝图但用户要求“精美图 / 终稿 / 图片”,且没有明确要求可编辑源文件。 -- 修复:已在 `cat-cafe-skills/image-generation/SKILL.md` 增加“Codex SVG 复发熔断闸”,匹配上述场景时禁止先写 SVG/HTML/Canvas,必须先原生 imagegen 整页直出。 + 1. 旧规则只是”默认建议”,没有进入执行前硬闸;一旦进入”文字可控、布局可控”的工程反射,imagegen 被错误降级成可选项。 + 2. 把”架构图需要精确”误判成”必须代码渲染”,但用户真正验收的是视觉完成度,而不是 SVG 源文件。 + 3. 已有猫档明确写了”Maine Coon原生图片生成强、禁止用 SVG 画”,但能力唤醒没有把这条转成 preflight。 +- 触发条件:复杂架构图、PPT 页面、企业信息图、华为风 / 红白黑风格、已有低保真蓝图但用户要求”精美图 / 终稿 / 图片”,且没有明确要求可编辑源文件。 +- 修复:已在 `cat-cafe-skills/image-generation/SKILL.md` 增加”Codex SVG 复发熔断闸”,匹配上述场景时禁止先写 SVG/HTML/Canvas,必须先原生 imagegen 整页直出。 - 防护: 1. image-generation skill 的 preflight:复杂架构/PPT/精美图 + 无可编辑要求 = imagegen-first。 2. SVG/HTML 降级必须写出 `SVG override reason`,且只能基于已失败的 imagegen 产物或用户显式可编辑要求。 3. “中文文字更可控 / 布局更可控 / 架构图需要精确 / 先 SVG 再转 PNG”都不是合格 override 理由。 - 来源锚点: - `cat-cafe-skills/image-generation/SKILL.md`(Codex SVG 复发熔断闸) - - `docs/team/cat-dossier.md#L122`(Maine Coon原生图片生成能力与“禁止用 SVG 画”事故记录) + - `docs/team/cat-dossier.md#L122`(Maine Coon原生图片生成能力与”禁止用 SVG 画”事故记录) - 2026-05-28 LLE 自进化平台三张图生成事故复盘 - 原理:**能力唤醒必须落到执行前硬闸。** “知道自己应该 imagegen”不等于会在任务压力下选择 imagegen;对复发型坏直觉,要把建议升级成 preflight + override reason。 @@ -1254,6 +1326,32 @@ created: 2026-02-26 --- +### LL-058b: 热补丁反模式——已有治理机制不查就另起炉灶 +- 状态:draft +- 更新时间:2026-04-30 + +- 坑:skill symlinks 指向旧版本,直接手动批量重链接 HOME 目录 + 改 `sync-skills.sh`,没有先分析已有的 worktree skill、`sync-skills.sh`、`check-skills-mount.sh`、`skill-sync.ts` 四层治理链条。同一天在 #598 verdict scope boundary 上也犯了类似错误——没分析 thread 根因、没和 reviewer 沟通就直接改了三个 skill 文件。 +- 根因: + 1. **对已有机制缺乏检索习惯**:问题出现后直接跳到”怎么修”,没有先问”已有什么机制在处理这个”。 + 2. **把”能跑”等同于”方案正确”**:手动 symlink 确实能让当前 session 读到新 skill,但绕过了 worktree 创建时的自动同步链条,制造了新的不一致。 + 3. **执行速度 > 方向正确**(LL-009 复发):铲屎官两次叫停,说明节奏判断失误不是个案。 +- 触发条件:发现”状态不对”时的冲动修复——尤其是 symlink/config/环境变量类问题,手动修一个很快,但会绕过治理链。 +- 修复:回退 `sync-skills.sh` 热补丁(`97ef3c2d`),改为先做根因分析 → 盘点已有机制 → 和 codex 对齐方案 → 开 worktree 走正常合入流程。 +- 防护: + 1. 发现问题后第一步:`grep -r` / `git log` 搜已有机制(脚本、治理服务、skill 步骤) + 2. 盘点完才提方案,方案必须说明”在已有机制的哪一层修” + 3. 改动走 worktree → review → merge-gate,不直接在 develop_base 上热补丁 +- 来源锚点: + - `scripts/sync-skills.sh` | `scripts/check-skills-mount.sh` | `cat-cafe-skills/worktree/SKILL.md` | `packages/api/src/config/governance/skill-sync.ts` + - commit:`482b6f27`(sync-skills.sh 热补丁)→ commit:`97ef3c2d`(revert) + - commit:`82972f45` / `453a4b54`(#598 verdict scope boundary 规则修正) + - thread:`thread_moicgl47en8m98do`(铲屎官两次叫停的原始对话) +- 原理:**治理机制存在的意义是把”正确做法”编码成自动化。绕过它 = 把一次性修复变成永久的手动负担,还会让治理机制逐渐失效(因为大家习惯绕过)。** + +- 关联:LL-009 | LL-020 | `cat-cafe-skills/refs/shared-rules.md` §P3 + +--- + ## 8) 维护约定 - 本文件是入口,不替代 ADR/bug-report 原文。 From 9c622073e865054a6c7776a58fd3381b3a78ab11 Mon Sep 17 00:00:00 2001 From: Maine Coon Yanyan Date: Mon, 3 Aug 2026 14:27:44 +0800 Subject: [PATCH 2/4] chore(fork): restore develop_base write protection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: develop_base is the live runtime baseline, so only the five explicitly governed shared-state files may be committed directly; code continues through feature PRs. Also restores the fork ROADMAP identity/status overlay without reviving obsolete TeamAct or execution-artifact history. [砚砚/gpt-5.6-sol🐾] --- .githooks/pre-commit | 26 +++- docs/ROADMAP.md | 10 +- .../f257-fix5-develop-base-allowlist.test.js | 135 ++++++++++++++++++ .../api/test/f257-fix5-pre-commit-e2e.test.js | 112 +++++++++++++++ scripts/check-develop-base-allowlist.sh | 75 ++++++++++ 5 files changed, 349 insertions(+), 9 deletions(-) create mode 100644 packages/api/test/f257-fix5-develop-base-allowlist.test.js create mode 100644 packages/api/test/f257-fix5-pre-commit-e2e.test.js create mode 100755 scripts/check-develop-base-allowlist.sh diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 86dcfd2976..612c559b10 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -169,7 +169,23 @@ if [ ${#hygiene_debris[@]} -gt 0 ] || [ ${#hygiene_unknown[@]} -gt 0 ]; then exit 1 fi -# ── Biome Guard: fail closed on stale local installs + full-index error scan ── +# ── Develop-base Runtime Guard (F257 #5 / dev-af6d4e28): develop_base = 白名单-only ── +# develop_base 是运行基线分支:本地直接 commit 仅允许 §14 + local override +# 穷举的五项共享状态文件(具体清单由独立脚本维护)。 +# 代码改动一律 feature worktree → PR → GitHub merge → 运行实例 pull(LI-004 结构强制)。 +# 白名单主体在独立脚本里(可单测);develop_base 上脚本缺失 = 保护失效 → fail-closed。 +if [ "$BRANCH" = "develop_base" ]; then + DB_GUARD="$REPO_ROOT/scripts/check-develop-base-allowlist.sh" + if [ ! -f "$DB_GUARD" ]; then + echo "🚫 DEVELOP-BASE GUARD: allowlist script missing ($DB_GUARD) — fail-closed." >&2 + exit 1 + fi + if ! git diff --cached --name-only | bash "$DB_GUARD" "$BRANCH"; then + exit 1 + fi +fi + +# ── Biome Guard: fail closed on stale local installs + staged/index-aware scans ── echo "" >&2 if [ "$VERDICT_ARTIFACT_ONLY" = "true" ]; then if ! validate_staged_artifact_file_types; then @@ -379,9 +395,11 @@ if [ -n "$BRAND_PROTECTED_STAGED" ] || [ -n "$STATEFUL_MIGRATION_CANDIDATE_STAGE fi fi -# main 分支:Inbound Guard 已在上面对所有分支生效;仅跳过 Shared State Guard -# (共享状态文件 SHOULD be committed on main) -if [ "$BRANCH" = "main" ]; then +# main / develop_base:Brand Guard 已在上面对所有分支生效;跳过 Shared State Guard +# — main: 共享状态文件 SHOULD be committed on main +# — develop_base: 由 Develop-base Runtime Guard(上方)全权守护,不二次拦截 +# (sol R2 P1-3:旧 guard 会二次拒绝已放行的 cat-config.json) +if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "develop_base" ]; then exit 0 fi diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 7eb7fc8dae..b05a1dafff 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -5,7 +5,7 @@ doc_kind: note created: 2026-02-26 --- -# Clowder AI Feature Roadmap +# Cat Café Feature Roadmap > 维护者:三猫 | 最后更新:2026-08-03(F288 编号分配给 clowder-ai 社区 K-1 plugin messaging domain) > @@ -22,7 +22,7 @@ created: 2026-02-26 | F051 | 猫粮看板 v2 — Quota Board (glanceable + scheduling) | in-progress | Ragdoll | internal | [F051](features/F051-real-quota-dashboard.md) | | F054 | HCI 预热基础设施 — Social Media MCP + 内容管线 | spec | Ragdoll (Opus 4.6, Leader) | internal | [F054](features/F054-hci-preheat-infra.md) | | F055 | A2A MCP Structured Routing — targetCats 结构化路由 | spec | Ragdoll | internal | [F055](features/F055-a2a-mcp-structured-routing.md) | -| F056 | Clowder AI 设计语言 — 猫猫化不是猫化 | doing | 三猫 | internal | [F056](features/F056-cat-cafe-design-language.md) | +| F056 | Cat Café 设计语言 — 猫猫化不是猫化 | doing | 三猫 | internal | [F056](features/F056-cat-cafe-design-language.md) | | F067 | Cold-start Verifier — 无历史污染的交付物验证 | spec | Ragdoll | internal | [F067](features/F067-cold-start-verifier.md) | | F069 | Thread Read State — 未读 Badge 后端真相源 | spec | Ragdoll | internal | [F069](features/F069-thread-read-state.md) | | F077 | Multi-User Secure Collaboration — GitHub OAuth + Thread ACL + Session | spec | Ragdoll | internal | [F077](features/F077-multi-user-secure-collab.md) | @@ -37,11 +37,11 @@ created: 2026-02-26 | F109 | Message Actions 修复与增强 — 软删除/Branch/编辑/通知 | in-progress | Ragdoll | internal | [F109](features/F109-message-actions-overhaul.md) | | F110 | 训练营愿景引导增强 — operator 需求挖掘 + SOP 显式加载 | spec | Ragdoll | internal | [F110](features/F110-bootcamp-vision-elicitation.md) | | F119 | 谁是卧底 — 坏猫战术推理游戏 #2 | spec | Ragdoll | internal | [F119](features/F119-who-is-spy-game.md) | -| F124 | Apple Ecosystem × Clowder AI 语音交互系统 — iOS/watchOS/AirPods | spec | Ragdoll | internal | [F124](features/F124-apple-ecosystem-voice-interaction.md) | -| F126 | 四肢控制面 — Clowder AI Limb Control Plane | in-progress | Ragdoll | internal | [F126](features/F126-limb-control-plane.md) | +| F124 | Apple Ecosystem × Cat Café 语音交互系统 — iOS/watchOS/AirPods | spec | Ragdoll | internal | [F124](features/F124-apple-ecosystem-voice-interaction.md) | +| F126 | 四肢控制面 — Cat Café Limb Control Plane | in-progress | Ragdoll | internal | [F126](features/F126-limb-control-plane.md) | | F128 | Cat-Proposed Thread Creation — 猫猫提议创建 Thread | in-progress | 三猫 | community [#82](https://github.com/zts212653/clowder-ai/issues/82) [#85](https://github.com/zts212653/clowder-ai/pull/85) | [F128](features/F128-cat-create-thread.md) | | F129 | Pack System — Multi-Agent 共创世界的 Mod 生态 | in-progress | Ragdoll | internal | [F129](features/F129-pack-system-multi-agent-mod.md) | -| F138 | Clowder AI Video Studio — AI 视频制作管线 | spec | 金渐层 | internal | [F138](features/F138-video-studio.md) | +| F138 | Cat Café Video Studio — AI 视频制作管线 | spec | 金渐层 | internal | [F138](features/F138-video-studio.md) | | F143 | Hostable Agent Runtime — 统一宿主抽象 | spec | Ragdoll | internal | [F143](features/F143-hostable-agent-runtime.md) | | F147 | i18n — Hub 界面中英文切换 | idea | 待定 | internal | — | | F152 | Expedition Memory — 外部项目记忆冷启动 + 经验回流 | in-progress | Ragdoll | internal | [F152](features/F152-expedition-memory.md) · 2026-07-09 Phase C re-audit (internal) | diff --git a/packages/api/test/f257-fix5-develop-base-allowlist.test.js b/packages/api/test/f257-fix5-develop-base-allowlist.test.js new file mode 100644 index 0000000000..cb73316f8e --- /dev/null +++ b/packages/api/test/f257-fix5-develop-base-allowlist.test.js @@ -0,0 +1,135 @@ +/** + * F257 修复清单 #5 — 运行实例写保护:develop_base 分支 commit 白名单。 + * + * 证据坐标:dev-af6d4e28(平行实例任务错位 merge 污染运行基线,V1 一度整体不在运行树)。 + * 目标:把 LI-004「运行实例对代码只读」从认知纪律降为结构强制。 + * + * 契约(scripts/check-develop-base-allowlist.sh,pre-commit 调用): + * - 仅 branch == develop_base 时启用白名单;其他分支一律放行(不影响 feature 开发) + * - 白名单 = §14 + local override 穷举的五项共享状态文件:BACKLOG.md、 + * ROADMAP.md、cat-config.json、shared-rules.local.md、docs/BACKLOG.md + * - 其余路径(packages、scripts、cat-template.json、cat-cafe-skills …)→ exit 1 拒绝 + * - stdin 收 staged 文件列表(一行一个),$1 = branch + */ + +import assert from 'node:assert/strict'; +import { execFileSync } from 'node:child_process'; +import { existsSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const SCRIPT = resolve(__dirname, '..', '..', '..', 'scripts', 'check-develop-base-allowlist.sh'); + +/** run script with branch + staged file list; returns {code, stderr} */ +function run(branch, files) { + try { + execFileSync('bash', [SCRIPT, branch], { input: `${files.join('\n')}\n`, stdio: ['pipe', 'pipe', 'pipe'] }); + return { code: 0, stderr: '' }; + } catch (err) { + return { code: err.status ?? 1, stderr: String(err.stderr ?? '') }; + } +} + +describe('F257 #5 修复:develop_base 白名单脚本存在性', () => { + it('scripts/check-develop-base-allowlist.sh 存在', () => { + assert.ok(existsSync(SCRIPT), `expected allowlist script at ${SCRIPT}`); + }); +}); + +describe('F257 #5 修复:develop_base 分支白名单强制', () => { + it('代码文件(packages/**)→ 拒绝', () => { + const { code, stderr } = run('develop_base', ['packages/api/src/index.ts']); + assert.equal(code, 1); + assert.match(stderr, /packages\/api\/src\/index\.ts/); + }); + + // sol R2 P1-3: 白名单收窄为穷举五项(§14 + local override 严格推导) + it('穷举白名单五项 → 放行', () => { + const { code } = run('develop_base', [ + 'BACKLOG.md', + 'ROADMAP.md', + 'cat-config.json', + 'cat-cafe-skills/refs/shared-rules.local.md', + 'docs/BACKLOG.md', + ]); + assert.equal(code, 0); + }); + + it('docs/** 非穷举项(feat-doc 等)→ 拒绝(改走 PR 或 --no-verify)', () => { + assert.equal(run('develop_base', ['docs/features/F257-harness-ledger.md']).code, 1); + assert.equal(run('develop_base', ['docs/bug-report/some/bug-report.md']).code, 1); + }); + + it('review-notes/** → 拒绝(收窄移出)', () => { + assert.equal(run('develop_base', ['review-notes/2026-07-19-review.md']).code, 1); + }); + + it('assets/** → 拒绝(收窄移出,含 markdown 知识文档和二进制)', () => { + assert.equal(run('develop_base', ['assets/F257/objective-driven-redesign-v1.md']).code, 1); + assert.equal(run('develop_base', ['assets/screenshots/foo.png']).code, 1); + }); + + it('混合 staged(白名单项 + 代码)→ 拒绝并只点名越界文件', () => { + const { code, stderr } = run('develop_base', ['ROADMAP.md', 'packages/api/src/x.ts']); + assert.equal(code, 1); + assert.match(stderr, /packages\/api\/src\/x\.ts/); + // ROADMAP.md should NOT appear in the "越界文件" violation list + // (it may appear in the help text whitelist listing — only check the violation section) + const violationSection = stderr.split('越界文件')[1]?.split('\n\n')[0] ?? ''; + assert.doesNotMatch(violationSection, /ROADMAP\.md/, 'whitelisted file must not be listed as violation'); + }); + + it('hook 自身与模板(.githooks/** / scripts/** / cat-template.json)→ 拒绝(改动必须走 PR)', () => { + assert.equal(run('develop_base', ['cat-template.json']).code, 1); + assert.equal(run('develop_base', ['.githooks/pre-commit']).code, 1); + assert.equal(run('develop_base', ['scripts/check-develop-base-allowlist.sh']).code, 1); + }); + + it('上游 pack 文件仍拒绝(shared-rules.md / skill 文件走 PR 通道)', () => { + assert.equal(run('develop_base', ['cat-cafe-skills/refs/shared-rules.md']).code, 1); + assert.equal(run('develop_base', ['cat-cafe-skills/feat-lifecycle/SKILL.md']).code, 1); + }); + + it('穷举白名单路径全部放行(路径正确性而非文件存在性)', () => { + // 白名单五项逐一验证放行——不依赖文件物理存在(worktree 是 feature 分支) + const whitelistPaths = [ + 'BACKLOG.md', + 'ROADMAP.md', + 'cat-config.json', + 'cat-cafe-skills/refs/shared-rules.local.md', + 'docs/BACKLOG.md', + ]; + for (const file of whitelistPaths) { + assert.equal(run('develop_base', [file]).code, 0, `${file} must pass the allowlist`); + } + }); + + it('行为变更注记:docs/features 直改被拦,改走 PR 或 operator --no-verify', () => { + // 8263d2381 形态(feat-doc 直改 develop_base)将被拦 + const repoRoot = resolve(import.meta.dirname, '..', '..', '..'); + const featDoc = 'docs/features/F257-harness-ledger.md'; + assert.ok(existsSync(resolve(repoRoot, featDoc)), `${featDoc} exists in repo (anchor)`); + assert.equal(run('develop_base', [featDoc]).code, 1, 'feat-doc direct commit must be rejected'); + }); + + it('路径前缀伪装(docs-evil/x.md、fake-review-notes/y.md)→ 拒绝', () => { + assert.equal(run('develop_base', ['docs-evil/x.md']).code, 1); + assert.equal(run('develop_base', ['fake-review-notes/y.md']).code, 1); + }); + + it('空 staged 列表 → 放行(允许 --allow-empty 等元操作)', () => { + assert.equal(run('develop_base', []).code, 0); + }); +}); + +describe('F257 #5 修复:非 develop_base 分支不受白名单限制', () => { + it('feature 分支改代码 → 放行', () => { + assert.equal(run('feat/harness-fix-a', ['packages/api/src/index.ts']).code, 0); + }); + + it('main 分支改代码 → 放行(上游语义不变)', () => { + assert.equal(run('main', ['packages/api/src/index.ts']).code, 0); + }); +}); diff --git a/packages/api/test/f257-fix5-pre-commit-e2e.test.js b/packages/api/test/f257-fix5-pre-commit-e2e.test.js new file mode 100644 index 0000000000..d7eedd0de9 --- /dev/null +++ b/packages/api/test/f257-fix5-pre-commit-e2e.test.js @@ -0,0 +1,112 @@ +/** + * F257 #5 修复 — pre-commit hook 全链 e2e 矩阵测试。 + * + * sol R2 补缺:白名单脚本的单元测试(f257-fix5-develop-base-allowlist.test.js)不等于 + * 全链 e2e 覆盖 — pre-commit 含 Root Hygiene / Develop-base Guard / Biome / Brand / + * Shared State Guard 五层门禁,白名单脚本仅其一。 + * + * 三态矩阵(Fable 架构裁决要求的最小可行集): + * 1. develop_base + cat-config.json → exit 0(白名单放行,全链通过) + * 2. develop_base + packages/x.ts → exit 1(代码改动被拦) + * 3. feat 分支 + docs/BACKLOG.md → exit 1(Shared State Guard 拦截) + * + * 方法:stub pnpm/node 于 PATH + tmp git repo,用真实 pre-commit hook 跑 git commit。 + */ + +import assert from 'node:assert/strict'; +import { execSync } from 'node:child_process'; +import { chmodSync, cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { after, describe, it } from 'node:test'; +import { fileURLToPath } from 'node:url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = resolve(__dirname, '..', '..', '..'); +const PRE_COMMIT_SRC = resolve(REPO_ROOT, '.githooks', 'pre-commit'); +const ALLOWLIST_SRC = resolve(REPO_ROOT, 'scripts', 'check-develop-base-allowlist.sh'); + +/** Create a disposable git repo with the real pre-commit hook + stub pnpm/node. */ +function setupTestRepo(branch) { + const tmpDir = mkdtempSync(join(tmpdir(), 'pre-commit-e2e-')); + const binDir = join(tmpDir, '_bin'); + mkdirSync(binDir); + + // Stub pnpm and node — both just exit 0 (biome guard / brand dictionary guard skip) + writeFileSync(join(binDir, 'pnpm'), '#!/bin/bash\nexit 0\n', { mode: 0o755 }); + writeFileSync(join(binDir, 'node'), '#!/bin/bash\nexit 0\n', { mode: 0o755 }); + + const run = (cmd) => execSync(cmd, { cwd: tmpDir, stdio: 'pipe' }); + run('git init'); + run('git config user.email "test@e2e.local"'); + run('git config user.name "e2e"'); + + // Copy real hooks and scripts + mkdirSync(join(tmpDir, '.githooks')); + cpSync(PRE_COMMIT_SRC, join(tmpDir, '.githooks', 'pre-commit')); + chmodSync(join(tmpDir, '.githooks', 'pre-commit'), 0o755); + mkdirSync(join(tmpDir, 'scripts')); + cpSync(ALLOWLIST_SRC, join(tmpDir, 'scripts', 'check-develop-base-allowlist.sh')); + chmodSync(join(tmpDir, 'scripts', 'check-develop-base-allowlist.sh'), 0o755); + + // Set hook path + run('git config core.hooksPath .githooks'); + + // Initial commit (--no-verify to bootstrap without triggering the hook) + run('git add .githooks scripts'); + run('git commit --no-verify -m "bootstrap hooks"'); + + // Switch to target branch + run(`git checkout -b ${branch}`); + + const env = { ...process.env, PATH: `${binDir}:${process.env.PATH}` }; + return { tmpDir, env, cleanup: () => rmSync(tmpDir, { recursive: true, force: true }) }; +} + +/** Stage a file and attempt git commit — returns { code, stderr }. */ +function tryCommit(tmpDir, env) { + try { + execSync('git commit -m "e2e-test"', { cwd: tmpDir, env, stdio: 'pipe' }); + return { code: 0, stderr: '' }; + } catch (err) { + return { code: err.status ?? 1, stderr: String(err.stderr ?? '') }; + } +} + +describe('F257 #5 修复:pre-commit hook 全链 e2e(三态矩阵)', () => { + const cleanups = []; + after(() => { + for (const fn of cleanups) fn(); + }); + + it('develop_base + cat-config.json → exit 0(全链通过:Hygiene✓ → DB-Guard✓ → Biome✓ → Brand✓ → skip SS)', () => { + const { tmpDir, env, cleanup } = setupTestRepo('develop_base'); + cleanups.push(cleanup); + writeFileSync(join(tmpDir, 'cat-config.json'), '{}'); + execSync('git add cat-config.json', { cwd: tmpDir, stdio: 'pipe' }); + const { code } = tryCommit(tmpDir, env); + assert.equal(code, 0, 'cat-config.json on develop_base must pass the full hook chain'); + }); + + it('develop_base + packages/x.ts → exit 1(Develop-base Runtime Guard 拦截代码改动)', () => { + const { tmpDir, env, cleanup } = setupTestRepo('develop_base'); + cleanups.push(cleanup); + mkdirSync(join(tmpDir, 'packages')); + writeFileSync(join(tmpDir, 'packages', 'x.ts'), 'export {};'); + execSync('git add packages/x.ts', { cwd: tmpDir, stdio: 'pipe' }); + const { code, stderr } = tryCommit(tmpDir, env); + assert.equal(code, 1, 'code file on develop_base must be rejected by allowlist'); + assert.match(stderr, /packages\/x\.ts/, 'error must name the violating file'); + }); + + it('feat 分支 + docs/BACKLOG.md → exit 1(Shared State Guard 拦截共享状态文件)', () => { + const { tmpDir, env, cleanup } = setupTestRepo('feat/test'); + cleanups.push(cleanup); + mkdirSync(join(tmpDir, 'docs')); + writeFileSync(join(tmpDir, 'docs', 'BACKLOG.md'), '# BACKLOG'); + execSync('git add docs/BACKLOG.md', { cwd: tmpDir, stdio: 'pipe' }); + const { code, stderr } = tryCommit(tmpDir, env); + assert.equal(code, 1, 'shared state file on feat branch must be rejected'); + assert.match(stderr, /SHARED-STATE GUARD/, 'must be caught by Shared State Guard, not other guards'); + }); +}); diff --git a/scripts/check-develop-base-allowlist.sh b/scripts/check-develop-base-allowlist.sh new file mode 100755 index 0000000000..2d853f8043 --- /dev/null +++ b/scripts/check-develop-base-allowlist.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# F257 修复清单 #5 — 运行实例写保护(develop_base 分支白名单) +# +# 证据坐标:dev-af6d4e28(平行实例任务错位 merge 污染运行基线,V1 一度整体不在运行树)。 +# 目标:把 LI-004「运行实例对代码只读」从认知纪律降为结构强制(修补环 O2→O1)。 +# +# 语义:develop_base 是运行基线分支——代码只能经 feature 分支 → PR → GitHub merge → +# 运行实例 pull 进入;develop_base 上的本地直接 commit 仅允许 §14 共享状态文档。 +# +# 用法:check-develop-base-allowlist.sh +# staged 文件列表从 stdin 读入(一行一个;由 pre-commit 传 git diff --cached --name-only) +# exit 0 = 放行;exit 1 = 拒绝(stderr 点名越界文件) +# +# 白名单——穷举五项,严格从 §14 + local override 推导(sol R2 P1-3 收窄裁决): +# +# 【文字明列】shared-rules.local.md「共享状态文档(ROADMAP / BACKLOG / 本文件)」: +# BACKLOG.md —— local 明列 +# ROADMAP.md —— local 明列 +# cat-cafe-skills/refs/shared-rules.local.md —— local 明列「本文件」 +# 【文字明列】upstream §14(.githooks/pre-commit Shared State Guard 既有清单): +# cat-config.json —— upstream 共享状态定义 +# docs/BACKLOG.md —— upstream 旧清单位置 +# +# 收窄移出(改走 PR 或 operator --no-verify): +# docs/**(feat-doc 等知识文档)、review-notes/**、assets/**/*.md +# ⚠️ 行为变更:8263d2381 形态(feat-doc 直改 develop_base)将被拦。 +# §14 原文「BACKLOG 等共享状态」存在开放类目读法,若合入后直改摩擦显著, +# 扩围决策升 operator(附 git log 证据一行 PR 即可扩)。 +# +# 不在白名单(必须走 PR):packages/**、scripts/**、.githooks/**、cat-template.json、 +# docs/**(非穷举项)、review-notes/**、assets/**、cat-cafe-skills/**(shared-rules.local.md 单文件例外)。 +# +# 绕过:git commit --no-verify(家规约束下仅限 operator 显式授权场景)。 + +BRANCH="$1" + +# 只保护 develop_base;feature / main / 其他分支不受白名单限制 +if [ "$BRANCH" != "develop_base" ]; then + exit 0 +fi + +violations=() +while IFS= read -r file; do + [ -z "$file" ] && continue + case "$file" in + BACKLOG.md) ;; # local 明列 + ROADMAP.md) ;; # local 明列 + cat-config.json) ;; # upstream §14 明列 + cat-cafe-skills/refs/shared-rules.local.md) ;; # local 明列「本文件」(单文件例外) + docs/BACKLOG.md) ;; # upstream §14 旧清单位置 + *) violations+=("$file") ;; + esac +done + +if [ ${#violations[@]} -gt 0 ]; then + { + echo "" + echo "🚫 DEVELOP-BASE GUARD (F257 #5 / dev-af6d4e28): develop_base 只允许共享状态文档直接 commit!" + echo "" + echo "越界文件:" + printf ' - %s\n' "${violations[@]}" + echo "" + echo "develop_base 是运行基线:代码与配置改动必须走 feature worktree → PR → merge 后 pull。" + echo " 1. git restore --staged " + echo " 2. 在 feature worktree(git worktree add ../ -b feat/ origin/develop_base)里改" + echo " 3. PR → review → GitHub merge → 运行实例 git pull" + echo "" + echo "白名单(§14+local 穷举五项):BACKLOG.md · ROADMAP.md · cat-config.json ·" + echo " cat-cafe-skills/refs/shared-rules.local.md · docs/BACKLOG.md" + echo "规则来源:shared-rules.local.md + upstream §14(LI-004 结构强制)" + } >&2 + exit 1 +fi + +exit 0 From b63d0ccf606e65cb5b7a8e0dae5e1d57f3cf5807 Mon Sep 17 00:00:00 2001 From: Maine Coon Yanyan Date: Mon, 3 Aug 2026 14:28:09 +0800 Subject: [PATCH 3/4] config(fork): retain weekly friction evaluation cadence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: three consecutive zero-signal 72h windows showed the 3-day schedule generated noise; weekly remains within the 168h SLA and is independently reversible. [砚砚/gpt-5.6-sol🐾] --- docs/harness-feedback/eval-domains/eval-friction.yaml | 10 +++++++--- .../api/test/harness-eval/eval-domain-daily.test.js | 8 +++++++- .../harness-eval/eval-domain-evidence-gate.test.js | 4 +++- .../api/test/harness-eval/eval-domain-registry.test.js | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/docs/harness-feedback/eval-domains/eval-friction.yaml b/docs/harness-feedback/eval-domains/eval-friction.yaml index 759dc8eb8b..66c525814c 100644 --- a/docs/harness-feedback/eval-domains/eval-friction.yaml +++ b/docs/harness-feedback/eval-domains/eval-friction.yaml @@ -11,9 +11,13 @@ evalCat: catId: gpt52 handle: "@gpt52" model: gpt-5.4 -# F245 PR2: 本家 3-day cadence — N-day infra landed. -# Community default remains `weekly` in community eval-domains/ copies. -frequency: every-3d +# F245 PR2: cadence adjusted 2026-07-31 (opus decision, thread_eval_friction). +# Rationale: 3 consecutive 72h zero-signal windows (9 days) — system healthy, +# empty verdict PR noise outweighs faster detection benefit. weekly still within +# SLA (reevalWithinHours=168). Reversible: switch back to every-3d if friction +# returns. Community default is also `weekly`. +# Previous: every-3d (F245 PR2 original, 2026-06 onwards). +frequency: weekly sourceAdapter: f245-friction-rollup sourceRefsKind: friction-rollup-snapshot threadPolicy: diff --git a/packages/api/test/harness-eval/eval-domain-daily.test.js b/packages/api/test/harness-eval/eval-domain-daily.test.js index 1c94159065..c01f9fc391 100644 --- a/packages/api/test/harness-eval/eval-domain-daily.test.js +++ b/packages/api/test/harness-eval/eval-domain-daily.test.js @@ -625,7 +625,7 @@ describe('eval-domain-weekly task spec (AC-E19, AC-E20)', () => { assert.equal(spec.display.category, 'system'); }); - it('weekly gate includes enabled weekly domains (capability-wakeup + sop + harness-ledger), excludes daily', async () => { + it('weekly gate includes enabled weekly domains (capability-wakeup + sop + harness-ledger + friction), excludes daily', async () => { const spec = createEvalDomainWeeklySpec({ harnessFeedbackRoot: repoHarnessFeedbackRoot }); const result = await spec.admission.gate(); @@ -643,6 +643,12 @@ describe('eval-domain-weekly task spec (AC-E19, AC-E20)', () => { domainIds.includes('eval:harness-ledger'), 'eval:harness-ledger (weekly + re-enabled after KD-17) must appear in weekly gate', ); + // Cadence adjusted 2026-07-31: eval:friction moved from every-3d to weekly after + // 3 consecutive zero-signal windows (thread_eval_friction, opus decision). + assert.ok( + domainIds.includes('eval:friction'), + 'eval:friction (weekly since 2026-07-31 cadence adjustment) must appear in weekly gate', + ); assert.ok(!domainIds.includes('eval:a2a'), 'eval:a2a (daily) must NOT appear in weekly gate'); assert.ok(!domainIds.includes('eval:memory'), 'eval:memory (daily) must NOT appear in weekly gate'); assert.ok(!domainIds.includes('eval:task-outcome'), 'eval:task-outcome (daily) must NOT appear in weekly gate'); diff --git a/packages/api/test/harness-eval/eval-domain-evidence-gate.test.js b/packages/api/test/harness-eval/eval-domain-evidence-gate.test.js index 069a965707..6eaaa7e43f 100644 --- a/packages/api/test/harness-eval/eval-domain-evidence-gate.test.js +++ b/packages/api/test/harness-eval/eval-domain-evidence-gate.test.js @@ -8,6 +8,7 @@ import { evaluateEvidencePrereq, } from '../../dist/infrastructure/harness-eval/domain/eval-domain-evidence-gate.js'; import { createEvalDomainNDaySpec } from '../../dist/infrastructure/harness-eval/domain/eval-domain-nday.js'; +import { FIXTURE_FRICTION_3D_YAML, makeTempRoot } from './eval-domain-nday-fixtures.js'; const repoHarnessFeedbackRoot = fileURLToPath(new URL('../../../../docs/harness-feedback', import.meta.url)); @@ -205,8 +206,9 @@ describe('eval-domain evidence-source prereq gate (eval:a2a PR #19)', () => { it('probe not-ok → skip notice, no trigger, no Redis last-dispatch write', async () => { const redisSet = mock.fn(async () => 'OK'); const redis = { get: mock.fn(async () => null), set: redisSet }; + const harnessFeedbackRoot = makeTempRoot(FIXTURE_FRICTION_3D_YAML); const spec = createEvalDomainNDaySpec({ - harnessFeedbackRoot: repoHarnessFeedbackRoot, + harnessFeedbackRoot, defaultUserId: 'default-user', redis, evidencePrereqProbe: () => ({ ok: false, reason: 'OTel disabled at boot' }), diff --git a/packages/api/test/harness-eval/eval-domain-registry.test.js b/packages/api/test/harness-eval/eval-domain-registry.test.js index 29ca67f58f..8cef247515 100644 --- a/packages/api/test/harness-eval/eval-domain-registry.test.js +++ b/packages/api/test/harness-eval/eval-domain-registry.test.js @@ -106,7 +106,7 @@ describe('Eval Domain Registry v0', () => { assert.equal(entry.domainId, 'eval:friction'); assert.equal(entry.sourceAdapter, 'f245-friction-rollup'); assert.equal(entry.sourceRefsKind, 'friction-rollup-snapshot'); - assert.equal(entry.frequency, 'every-3d'); // F245 PR2: 本家 3-day cadence + assert.equal(entry.frequency, 'weekly'); // cadence adjusted 2026-07-31 (3 consecutive zero-signal windows) assert.equal(entry.threadPolicy.role, 'working-home'); assert.equal(entry.handoffTargetResolver.featureId, 'F245'); }); From 83247ebe06314e271348f7b7ab880d1ed6fa032f Mon Sep 17 00:00:00 2001 From: "CatEqdvbcxw-GPT-5.6-sol" Date: Fri, 7 Aug 2026 22:04:33 +0800 Subject: [PATCH 4/4] test(fork): adapt develop_base guard fixture to current hook Why: the rebuilt fork overlay runs on the latest index-aware Biome pre-commit guard, so its isolated E2E fixture must provide the package and binary filesystem contract before branch-routing assertions can execute. --- packages/api/test/f257-fix5-pre-commit-e2e.test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/api/test/f257-fix5-pre-commit-e2e.test.js b/packages/api/test/f257-fix5-pre-commit-e2e.test.js index d7eedd0de9..26bd2479af 100644 --- a/packages/api/test/f257-fix5-pre-commit-e2e.test.js +++ b/packages/api/test/f257-fix5-pre-commit-e2e.test.js @@ -32,6 +32,17 @@ function setupTestRepo(branch) { const binDir = join(tmpDir, '_bin'); mkdirSync(binDir); + // The current pre-commit hook fails closed unless the local Biome package + // and binary exist. The fixture only needs their filesystem contract; the + // lint behavior itself stays stubbed so this test remains focused on branch + // routing through the full hook chain. + const biomePackageDir = join(tmpDir, 'node_modules', '@biomejs', 'biome'); + const nodeModulesBin = join(tmpDir, 'node_modules', '.bin'); + mkdirSync(biomePackageDir, { recursive: true }); + mkdirSync(nodeModulesBin, { recursive: true }); + writeFileSync(join(biomePackageDir, 'package.json'), '{"name":"@biomejs/biome"}\n'); + writeFileSync(join(nodeModulesBin, 'biome'), '#!/bin/bash\nexit 0\n', { mode: 0o755 }); + // Stub pnpm and node — both just exit 0 (biome guard / brand dictionary guard skip) writeFileSync(join(binDir, 'pnpm'), '#!/bin/bash\nexit 0\n', { mode: 0o755 }); writeFileSync(join(binDir, 'node'), '#!/bin/bash\nexit 0\n', { mode: 0o755 });