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:
@@ -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())
|
||||
Reference in New Issue
Block a user