chore: 이전 세션이 남긴 변경을 커밋한다
이번 파이프라인 작업과 무관하게 작업 트리에 남아 있던 것을 그대로 올린다. 사용자가 「전부 커밋」으로 정했고, 이번 작업과 섞이지 않게 커밋만 나눴다. 대부분은 clean-architecture-backend-template 의 그림 정본 재배치다 — final/assets/diagrams/<이름>/ 에 있던 것이 CLAUDE.md 가 적은 배치인 final/assets/<이름>/ 로 옮겨졌고 .techviz/<이름>/ 이 함께 들어왔다. 삽입 줄의 대부분(3.15M)이 그 .techviz context.json 이다. 그 밖에 ca-tmpl·document-haness 의 정리, .claude/agents/ 열한 개, writing-practitioner-guides 스킬, .playwright-mcp 세션 산출물, scripts/check-ssot-facts.py 와 그 시험이 들어 있다. 이 커밋의 내용은 내가 만든 것이 아니라 이전 세션이 남긴 것이고 검증하지 않았다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2109f726fe
commit
ab59130196
+44
-4
@@ -15,7 +15,7 @@
|
||||
**새로 만드는 것이 아니라 같은 규약을 옮기는 것이다.**
|
||||
|
||||
python3 scripts/run-ledger.py open <원장> --project P --record R
|
||||
python3 scripts/run-ledger.py begin <원장> --stage S3 --runby subagent
|
||||
python3 scripts/run-ledger.py begin <원장> --stage S3 # runBy 는 계약값을 쓴다
|
||||
python3 scripts/run-ledger.py gate <원장> --stage S3 --cmd "..." --exit 0
|
||||
python3 scripts/run-ledger.py end <원장> --stage S3 --status DONE --echo "..." --output ...
|
||||
python3 scripts/run-ledger.py rider <원장> --id R11 --why "..." --seconds 1070
|
||||
@@ -28,6 +28,7 @@ import datetime
|
||||
import fcntl
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
@@ -37,6 +38,7 @@ TEMPLATE = os.path.join(
|
||||
STAGES = ["S1", "S2", "S3", "S4", "S5", "S6", "S7"]
|
||||
UNSKIPPABLE = {"S3", "S5", "S6"}
|
||||
OPEN_STATES = {"RUNNING"}
|
||||
REVISION_FIELD = "skillRevision"
|
||||
|
||||
|
||||
class Contract(Exception):
|
||||
@@ -47,6 +49,36 @@ def _now() -> str:
|
||||
return datetime.datetime.now().astimezone().isoformat(timespec="seconds")
|
||||
|
||||
|
||||
def _git(*args: str) -> str | None:
|
||||
"""저장소에 묻는다. 실패는 빈 문자열이 아니라 `None` 이다."""
|
||||
try:
|
||||
p = subprocess.run(["git", "-C", ROOT, *args], capture_output=True,
|
||||
encoding="utf-8", errors="replace", timeout=30)
|
||||
except (OSError, subprocess.SubprocessError):
|
||||
return None
|
||||
return p.stdout if p.returncode == 0 else None
|
||||
|
||||
|
||||
def _skill_revision(skill: str) -> str | None:
|
||||
"""지금 이 스킬의 글자를 담고 있는 커밋.
|
||||
|
||||
영수증(`skillEcho`)은 그때 SKILL.md 에 있던 문장인데, 스킬은 나중에 고쳐진다. 그러면
|
||||
검사기가 「위조」와 「그 뒤에 고쳐졌다」를 가르려고 이력을 훑어야 한다. 그 시점 커밋을
|
||||
여기서 적어 두면 훑지 않고 그 커밋 하나만 본다.
|
||||
|
||||
작업 트리가 그 커밋과 다르면 `None` 이다 — 모르는 리비전을 지어내지 않는다.
|
||||
그런 원장은 검사기에서 이력 훑기로 떨어지고, 그것이 맞는 결과다.
|
||||
"""
|
||||
if not skill:
|
||||
return None
|
||||
rel = f".agents/skills/{skill}"
|
||||
dirty = _git("status", "--porcelain", "--", rel)
|
||||
if dirty is None or dirty.strip():
|
||||
return None
|
||||
out = _git("log", "-n", "1", "--format=%H", "--", rel)
|
||||
return (out or "").strip() or None
|
||||
|
||||
|
||||
class Ledger:
|
||||
"""flock 을 잡고 읽어서, 임시 파일에 완성한 뒤 원자적으로 바꾼다.
|
||||
|
||||
@@ -146,7 +178,8 @@ def cmd_open(args) -> int:
|
||||
"sessions": [{"session": args.session, "openedAt": _now()}],
|
||||
})
|
||||
for st in led.data["stages"]:
|
||||
st.update({"startedAt": None, "finishedAt": None, "elapsedSeconds": 0})
|
||||
st.update({"startedAt": None, "finishedAt": None, "elapsedSeconds": 0,
|
||||
REVISION_FIELD: _skill_revision(str(st.get("skill") or ""))})
|
||||
led.save()
|
||||
print(f"런을 열었다: {args.ledger} (runId={led.data['runId']})")
|
||||
return 0
|
||||
@@ -175,7 +208,11 @@ def cmd_begin(args) -> int:
|
||||
})
|
||||
print(f"{args.stage} 이(가) 열린 채였다 — 그 구간을 닫고 잇는다")
|
||||
st["status"] = "RUNNING"
|
||||
st["runBy"] = args.runby
|
||||
# `--runby` 를 안 주면 **틀이 적어 둔 계약값을 그대로 둔다.** 여기서 기본값으로
|
||||
# 덮어쓰면 단계마다 배정된 관리 에이전트 이름이 매번 지워지고, 원장은 다시
|
||||
# 「서브에이전트가 돌렸다」까지만 말하게 된다 — 누가 돌렸는지는 안 남는다
|
||||
if args.runby:
|
||||
st["runBy"] = args.runby
|
||||
st["startedAt"] = _now()
|
||||
# 이어받을 때마다 세대를 올리고 주인을 적는다. `bin/task.py` 의 attempt 와 같은 자리다 —
|
||||
# 앞 세션이 아직 살아 있어도 낮은 세대의 쓰기는 이 단계에 못 들어온다.
|
||||
@@ -223,6 +260,9 @@ def cmd_end(args) -> int:
|
||||
st["finishedBy"] = args.session or None
|
||||
if args.echo:
|
||||
st["skillEcho"] = args.echo
|
||||
# 영수증을 적는 자리에서 그 영수증이 어느 판의 스킬에서 나왔는지도 적는다.
|
||||
# 여는 시각이 아니라 읽은 시각이라 이쪽이 더 맞다
|
||||
st[REVISION_FIELD] = _skill_revision(str(st.get("skill") or ""))
|
||||
if args.why:
|
||||
st["skipReason"] = args.why
|
||||
for key, values in (("inputs", args.input), ("outputs", args.output)):
|
||||
@@ -293,7 +333,7 @@ def main() -> int:
|
||||
p.add_argument("--force", action="store_true"); p.set_defaults(fn=cmd_open)
|
||||
|
||||
p = sub.add_parser("begin"); p.add_argument("ledger")
|
||||
p.add_argument("--stage", required=True); p.add_argument("--runby", default="subagent")
|
||||
p.add_argument("--stage", required=True); p.add_argument("--runby", default=None)
|
||||
p.add_argument("--session", default=""); p.set_defaults(fn=cmd_begin)
|
||||
|
||||
p = sub.add_parser("gate"); p.add_argument("ledger")
|
||||
|
||||
Reference in New Issue
Block a user