le-app-design v1.0.0:统一 App 设计工坊——真实集成并调用三上游的编排器。ui-ux-pro-max(MIT,search.py 设计系统/规范检索)+ frontend-design(Apache-2.0,Skill 工具装载的视觉 design lead)+ impeccable(Apache-2.0,CLI 检测器+playbook 审查);三机制保证真集成可验证:check_deps 硬依赖门(缺即终止)、log_call 证据账本(调用落 calls.jsonl 含装载 sha256)、evidence_report 验收门(三方各 ≥1 成功调用才 PASS);S0–S7 流水线含两个回合终止停点与轻量任务路由;E2E 已真跑(习惯打卡 App 双屏原型):7 条真实调用留账、双独立 sub-agent 评审 26/36 + detect 2 findings、修复 3×P1 后字阶清零、交互复测 4/4、验收门 PASS;上游三件补装 ~/.codex/skills,仓库 README 同步

This commit is contained in:
leroy
2026-11-07 16:18:01 +08:00
parent f7b7740829
commit 243bbf1db1
21 changed files with 1437 additions and 6 deletions
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env python3
"""le-app-design 证据账本:所有对上游 Skill 的真实调用经此包裹,落入 calls.jsonl。
用法:
执行型调用(CLI 类上游):
python3 log_call.py <dep> --run-dir <RUN> -- python3 <DEP>/scripts/search.py "…" --design-system
装载型调用(文档型上游 / playbook):
python3 log_call.py <dep> --run-dir <RUN> --load <DEP>/SKILL.md
dep ∈ {ui-ux-pro-max, frontend-design, impeccable, le-app-design(自检)}
--run-dir 缺省时按序: $LE_APP_DESIGN_RUN_DIR > <cwd>/le-app-design-runs/<UTC 时间戳>-run/
行为:
- exec: 运行命令,透传 stdout/stderr,透传退出码;成功失败都记账。
- load: 读文件记 sha256 与字节数(证明用的是原文而非转述),文件缺失记 FAIL 退出 1。
- 记录字段: ts / dep / kind / command / exit_code / duration_ms / stdout_tail /
stderr_tail / bytes / sha256 / note。stdout/stderr 仅保留尾部 4000 字符防账本膨胀。
仅 python3 标准库。
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import subprocess
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
TAIL = 4000
KNOWN_DEPS = {"ui-ux-pro-max", "frontend-design", "impeccable", "le-app-design"}
def default_run_dir() -> Path:
env = os.environ.get("LE_APP_DESIGN_RUN_DIR")
if env:
return Path(env).expanduser()
stamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%SZ")
return Path.cwd() / "le-app-design-runs" / f"{stamp}-run"
def append_record(run_dir: Path, record: dict) -> Path:
run_dir.mkdir(parents=True, exist_ok=True)
ledger = run_dir / "calls.jsonl"
with ledger.open("a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
return ledger
def confirm(dep: str, kind: str, status: str, ledger: Path, extra: str = "") -> None:
print(
f"[log_call] {dep} {kind} {status} → {ledger}"
+ (f" ({extra})" if extra else ""),
file=sys.stderr,
)
def main() -> int:
# 手工在第一个裸 "--" 处切分:argparse 的 REMAINDER 会把 -- 之前的选项误吞进命令
argv = sys.argv[1:]
cmd: list[str] = []
if "--" in argv:
split = argv.index("--")
argv, cmd = argv[:split], argv[split + 1:]
ap = argparse.ArgumentParser(description="le-app-design 调用账本")
ap.add_argument("dep", help="上游标识: ui-ux-pro-max | frontend-design | impeccable | le-app-design")
ap.add_argument("--run-dir", default=None, help="运行目录(缺省自动创建)")
ap.add_argument("--note", default="", help="阶段/用途备注,如 S2-design-system")
ap.add_argument("--load", metavar="PATH", default=None, help="装载型调用:记录读取的原文文件")
args = ap.parse_args(argv)
dep = args.dep
if dep not in KNOWN_DEPS:
print(f"[log_call] 未知依赖标识 {dep!r};允许: {sorted(KNOWN_DEPS)}", file=sys.stderr)
return 2
run_dir = Path(args.run_dir).expanduser() if args.run_dir else default_run_dir()
base = {"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"), "dep": dep, "note": args.note}
if args.load:
path = Path(args.load).expanduser()
rec = dict(base, kind="load", command=f"load:{path}")
try:
data = path.read_bytes()
except OSError as e:
rec.update(exit_code=1, stderr_tail=f"read failed: {e}"[:TAIL])
ledger = append_record(run_dir, rec)
confirm(dep, "load", "FAIL", ledger, str(e))
return 1
rec.update(
exit_code=0,
bytes=len(data),
sha256=hashlib.sha256(data).hexdigest(),
)
ledger = append_record(run_dir, rec)
confirm(dep, "load", "OK", ledger, f"{len(data)} bytes sha256:{rec['sha256'][:12]}…")
return 0
if not cmd:
print("[log_call] 需要 --load PATH 或 -- <命令…>", file=sys.stderr)
return 2
rec = dict(base, kind="exec", command=json.dumps(cmd, ensure_ascii=False))
t0 = time.monotonic()
try:
proc = subprocess.run(
cmd, capture_output=True, text=True, timeout=900,
env=dict(os.environ, PYTHONDONTWRITEBYTECODE="1"),
)
rc, out, err = proc.returncode, proc.stdout or "", proc.stderr or ""
except subprocess.TimeoutExpired as e:
rc, out, err = 124, (e.stdout or b"").decode(errors="replace") if isinstance(e.stdout, bytes) else (e.stdout or ""), f"timeout after {e.timeout}s"
except FileNotFoundError as e:
rc, out, err = 127, "", f"command not found: {e}"
rec.update(
exit_code=rc,
duration_ms=int((time.monotonic() - t0) * 1000),
stdout_tail=out[-TAIL:],
stderr_tail=err[-TAIL:],
)
ledger = append_record(run_dir, rec)
if out:
sys.stdout.write(out)
if not out.endswith("\n"):
sys.stdout.write("\n")
if err:
sys.stderr.write(err)
if not err.endswith("\n"):
sys.stderr.write("\n")
confirm(dep, "exec", f"exit={rc}", ledger, f"{rec['duration_ms']}ms")
return rc
if __name__ == "__main__":
sys.exit(main())