baseline: Clover 独立仓库首次基线提交
将 Clover 从上层产品包旧仓库中独立出来,建立专属版本控制。 当前状态=纵切片端到端已打通(登录→选品→出文出图→审核→下载包), M1文案质量去套路化已验收。此提交作为后续按核销清单逐条修复的基线。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
67
.claude/skills/clover-loop/SKILL.md
Normal file
67
.claude/skills/clover-loop/SKILL.md
Normal file
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: clover-loop
|
||||
version: 1.0.0
|
||||
description: |
|
||||
Clover 内容生产闭环开发 SKILL(改造自 ralph-loop)。
|
||||
Memory is Poison, Tests are Cure —— 但 Clover 的 Test 不止单元测试,还有北哥的眼睛。
|
||||
六阶段闭环:调研→约束→生成→验证→校准→闭环。质量过关=机器过滤+北哥抽检两阶段。
|
||||
triggers:
|
||||
- clover-loop
|
||||
- 内容闭环
|
||||
- 生产闭环
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
# Clover Loop SKILL
|
||||
|
||||
> 核心:机器层(machine_passed)只是进抽检队列,北哥点头(human_passed)才是真过关。
|
||||
|
||||
## 六阶段
|
||||
|
||||
### P0 调研(ralph-loop 没有,北哥"先调研")
|
||||
读架构方案v0.3 + 对应 PRD + 扒包落盘,确认本任务边界。
|
||||
不充分就进 P2 = 在错误的层做对的事(PSR-L 教训)。
|
||||
|
||||
### P1 约束
|
||||
冻结 task_spec:输入/输出/验证命令/期望exit_code/质量阈值。
|
||||
`python scripts/loop_state.py create <task_id>`
|
||||
|
||||
### P2 生成
|
||||
写代码 + 写验证脚本(必须含断言、返回exit_code)。
|
||||
`python scripts/loop_state.py advance`
|
||||
|
||||
### P3 验证(机器层)
|
||||
跑机器门禁:lint / test / 违禁词 / 五维≥90 / 去重。
|
||||
全过 → status=machine_passed → 进 P4;不过 → 记失败,回 P2。
|
||||
|
||||
### P4 校准(ralph-loop 没有,专治"分高但烂")
|
||||
机器过的内容样本,按维度比对北哥标准;同质化/跑偏单独标记。
|
||||
产出**抽检样本包**交北哥 → 北哥判断回写质量标准(喂飞轮)。
|
||||
|
||||
### P5 闭环
|
||||
- 北哥抽检通过 → status=human_passed → 归档 + 结晶
|
||||
- 北哥打回 / 验证不过 → 分析根因
|
||||
- 同层撞墙 **2 次** → 强制 Pivot(换层不换参),记 pivots[]
|
||||
- Pivot 后仍不过 / 北哥连续否决同方向 → status=blocked,停循环请人介入
|
||||
|
||||
## 状态机(.clover-loop-state.json)
|
||||
5 态:in_progress / machine_passed / human_passed / pivoted / blocked
|
||||
关键:machine_passed ≠ human_passed
|
||||
|
||||
## 命令
|
||||
| 命令 | 功能 |
|
||||
|------|------|
|
||||
| /clover-loop | 启动六阶段闭环 |
|
||||
| /clover-loop:status | 查当前状态 |
|
||||
| /clover-loop:pivot | 手动触发换层 |
|
||||
|
||||
## 铁律
|
||||
- 撞墙 2 次必 Pivot(对齐 CLAUDE.md 失败2次)
|
||||
- 北哥不耐烦/连续否决 = 路径选错层的信号,立即 Pivot 不调参
|
||||
- 软件测试过 ≠ 内容质量过
|
||||
133
.claude/skills/clover-loop/scripts/loop_state.py
Normal file
133
.claude/skills/clover-loop/scripts/loop_state.py
Normal file
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Clover Loop State Manager(改造自 ralph-loop state_manager)
|
||||
六阶段 + 5态 + 机器/人工双层验收 + Pivot 追踪。
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
STATE_FILE = ".clover-loop-state.json"
|
||||
PHASES = {"0": "调研", "1": "约束", "2": "生成", "3": "验证", "4": "校准", "5": "闭环"}
|
||||
|
||||
|
||||
def _path(work_dir=None):
|
||||
base = Path(work_dir) if work_dir else Path.cwd()
|
||||
return base / STATE_FILE
|
||||
|
||||
|
||||
def load(work_dir=None):
|
||||
p = _path(work_dir)
|
||||
return json.load(open(p, encoding="utf-8")) if p.exists() else None
|
||||
|
||||
|
||||
def save(state, work_dir=None):
|
||||
state["updated_at"] = datetime.now().isoformat()
|
||||
json.dump(state, open(_path(work_dir), "w", encoding="utf-8"),
|
||||
indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def create(task_id, work_dir=None):
|
||||
state = {
|
||||
"task_id": task_id,
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"updated_at": datetime.now().isoformat(),
|
||||
"current_phase": 0,
|
||||
"max_retries_per_layer": 2, # 单层撞墙2次→Pivot
|
||||
"machine_gate": {"lint": None, "test": None, "banned_word": None,
|
||||
"score": None, "dedup": None},
|
||||
"human_review": {"status": "pending", "sampled": 0,
|
||||
"approved": 0, "rejected": 0},
|
||||
"status": "in_progress", # in_progress/machine_passed/human_passed/pivoted/blocked
|
||||
"layer_attempts": {}, # {层名: 失败次数}
|
||||
"pivots": [],
|
||||
"logs": [],
|
||||
}
|
||||
save(state, work_dir)
|
||||
return state
|
||||
|
||||
|
||||
def advance(state, work_dir=None):
|
||||
cur = state["current_phase"]
|
||||
if cur < 5:
|
||||
state["current_phase"] = cur + 1
|
||||
save(state, work_dir)
|
||||
return state
|
||||
|
||||
|
||||
def machine_gate(state, key, result, work_dir=None):
|
||||
"""记录机器门禁单项结果。全过则置 machine_passed。"""
|
||||
state["machine_gate"][key] = result
|
||||
g = state["machine_gate"]
|
||||
passed = all(v in ("pass", True) or (isinstance(v, (int, float)) and v >= 90)
|
||||
for v in g.values() if v is not None)
|
||||
if passed and all(v is not None for v in g.values()):
|
||||
state["status"] = "machine_passed"
|
||||
save(state, work_dir)
|
||||
return state
|
||||
|
||||
|
||||
def record_fail(state, layer, error=None, work_dir=None):
|
||||
"""记录某层失败。同层2次→强制Pivot。"""
|
||||
state["layer_attempts"][layer] = state["layer_attempts"].get(layer, 0) + 1
|
||||
state["logs"].append({"ts": datetime.now().isoformat(),
|
||||
"layer": layer, "error": error})
|
||||
if state["layer_attempts"][layer] >= state["max_retries_per_layer"]:
|
||||
state["status"] = "pivoted"
|
||||
state["pivots"].append({"layer": layer, "ts": datetime.now().isoformat(),
|
||||
"reason": f"{layer}层撞墙{state['layer_attempts'][layer]}次"})
|
||||
save(state, work_dir)
|
||||
return state
|
||||
|
||||
|
||||
def human_review(state, action, work_dir=None):
|
||||
"""北哥抽检:approve/reject。approve累计达标→human_passed。"""
|
||||
hr = state["human_review"]
|
||||
hr["sampled"] += 1
|
||||
if action == "approve":
|
||||
hr["approved"] += 1
|
||||
hr["status"] = "approved"
|
||||
state["status"] = "human_passed"
|
||||
elif action == "reject":
|
||||
hr["rejected"] += 1
|
||||
hr["status"] = "rejected"
|
||||
state["status"] = "blocked" # 打回=停循环请人介入
|
||||
save(state, work_dir)
|
||||
return state
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("用法: loop_state.py <create|load|advance|gate|fail|review> [args]")
|
||||
print(" create <task_id> / advance / gate <key> <result>")
|
||||
print(" fail <layer> [error] / review <approve|reject>")
|
||||
sys.exit(1)
|
||||
cmd = sys.argv[1]
|
||||
if cmd == "create":
|
||||
print(json.dumps(create(sys.argv[2]), ensure_ascii=False, indent=2))
|
||||
elif cmd == "load":
|
||||
s = load()
|
||||
print(json.dumps(s, ensure_ascii=False, indent=2) if s else "无状态")
|
||||
elif cmd == "advance":
|
||||
s = load()
|
||||
if s:
|
||||
advance(s)
|
||||
print(f"进入 P{s['current_phase']} {PHASES[str(s['current_phase'])]}")
|
||||
elif cmd == "gate":
|
||||
s = load()
|
||||
if s:
|
||||
key, raw = sys.argv[2], sys.argv[3]
|
||||
val = float(raw) if raw.replace(".", "").isdigit() else raw
|
||||
machine_gate(s, key, val)
|
||||
print(f"门禁 {key}={val}, status={s['status']}")
|
||||
elif cmd == "fail":
|
||||
s = load()
|
||||
if s:
|
||||
record_fail(s, sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else None)
|
||||
print(f"{sys.argv[2]}层失败{s['layer_attempts'][sys.argv[2]]}次, status={s['status']}")
|
||||
elif cmd == "review":
|
||||
s = load()
|
||||
if s:
|
||||
human_review(s, sys.argv[2])
|
||||
print(f"抽检{sys.argv[2]}, status={s['status']}")
|
||||
Reference in New Issue
Block a user