feat(826-2): M01 기계 = 한 대 한 파일 · M01 저장 길 줄이기

- 기계.json 한 파일 → 폴더 기계/(EQ 키마다 파일 + _머리.json · 차례 = 키 차례) · 옮긴 빌더 build_기계폴더_826_2 (655 대 머리 · 줄 · 차례 대조 뒤 옛 파일 지움)
- 읽기 도우미 master_machine_files — cm.load · Store_Cache · Store.read 가 옛 「기계.json」 한 표 꼴로 지어 줌(API · 화면 · 색인 그대로) · 쓰기 = 바뀐 대 파일만
- 기계.json 직접 열던 곳 도우미로: M02_San_Basis_Index · M02_Logic_Standard 지문 · build_용도 · master_keys.adopt · _틀 · _갱신절차
- 저장 몸 M01_MasterData_Store_Save — 전 = 기억 · 후 = 고친 파일만 얕은 사본 · 틀은 고친 줄만 · 조합 · 일위대가 문서는 로직 줄이 빠질 때만 · 재료 찾기 기억 이어받음 · 저장 뒤 쓴 파일만 기억에 갈아 끼움
- 마스터 사본 시험 22 개 = 기계 폴더도 복사 · 821_2 · 815_2 글 대조 = 대 파일 하나 대조(옛 한 파일 글 대조 낡음)
- 새 시험 test_826_2 — 폴더 = 한 표 · 한 대 한 파일 · 새 · 지움 · 409 · 옛 길과 같은 오류 스무 견본

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-12 19:45:00 +09:00
co-authored by Claude Opus 5.5
parent 5fb7b6c16b
commit c7cbef86a0
694 changed files with 40486 additions and 39831 deletions
+41 -8
View File
@@ -57,10 +57,12 @@ def _now(folder: Path) -> dict[str, tuple]:
def _stamps(folder: Path) -> dict[str, tuple]:
"""첫 층 JSON 자국 — {이름: (시각, 크기)} · 밑줄 파일은 `cm.load` 가 안 읽으므로 뺌."""
out = {}
for path in sorted(folder.glob("*.json")):
if not path.name.startswith("_"):
stat = path.stat()
out[path.name] = (stat.st_mtime_ns, stat.st_size)
for name in cm.mmf.names(folder):
if name == cm.mmf.NAME and cm.mmf.here(folder): # 기계 폴더(826-2) — 대마다 자국
out[name] = cm.mmf.stamp(folder)
elif not name.startswith("_"):
stat = (folder / name).stat()
out[name] = (stat.st_mtime_ns, stat.st_size)
return out
@@ -127,6 +129,29 @@ def stale(names, marks=()) -> None:
held.clear()
def adopt(folder: Path, files: dict[str, dict], whole, names, marks=()) -> None:
"""저장 뒤 — 쓴 파일(`names`)만 기억에 갈아 끼움(다시 읽지 않음 · `files` · `whole` = 저장한 뒤
모습). 흔들린 자리는 `stale` 처럼 적어 둠. 그 사이 다른 파일도 바뀌었으면 `stale` 로."""
with _FRESH:
now = _stamps(folder)
rest = [n for n in now if n not in names]
if (
_INDEX.get("폴더") != folder
or set(now) != set(_READ) | set(names)
or any(_READ[n][0] != now[n] for n in rest)
):
return stale(names, marks)
for name in names:
was = _READ.get(name, (None, {}))[1]
_READ[name] = (now[name], files[name])
_BLOCK.pop(name, None)
if was.get("그룹") != "로직":
_MARKED.setdefault(name, set()).update(marks)
_INDEX.update(자국=now, files={n: _READ[n][1] for n in now}, whole=whole)
if (held := _STEADY.get()) is not None:
held.clear()
def loaded(folder: Path) -> tuple[dict[str, dict], object]:
"""(파일 묶음, 색인) — **읽기 전용**. 고칠 것이면 `cm.load` 로 새로 읽을 것."""
with _FRESH:
@@ -138,6 +163,9 @@ def loaded(folder: Path) -> tuple[dict[str, dict], object]:
del _READ[gone]
for name, stamp in now.items():
if _READ.get(name, (None,))[0] != stamp:
if name == cm.mmf.NAME and cm.mmf.here(folder): # 바뀐 대만 다시 읽음
_READ[name] = (stamp, cm.mmf.table(folder, shared=True)[0])
continue
raw = (folder / name).read_bytes()
_READ[name] = (stamp, json.loads(raw, parse_float=Decimal, parse_int=Decimal))
if _INDEX.get("자국") != now:
@@ -260,20 +288,25 @@ def _labor_files() -> list[tuple[str, Path]]:
]
def labor_errors(whole) -> list[str]:
def labor_errors(whole, keys=None) -> list[str]:
"""일위대가 · 사전조합 문서가 담은 로직 마스터에 없음(39-1d · 50-3) —
로직 지우기를 막음 · 어디서 쓰는지 알림 · 참조 줄(로직 없음)은 건너뜀. 전부터 깨진 것은
`save` 가 고치기 전 검사와 견주어 뺌."""
`save` 가 고치기 전 검사와 견주어 뺌. `keys` = 그 로직 키만 봄(빈 묶음이면 파일을 안 엶)."""
out = []
if keys is not None and not keys:
return out
for where, path in _labor_files():
try:
doc = json.loads(path.read_bytes())
raw = path.read_bytes()
if keys is not None and not any(k.encode() in raw for k in keys):
continue
doc = json.loads(raw)
except (OSError, ValueError):
continue
name = (doc.get("일위대가번") if isinstance(doc, dict) else None) or path.stem
for one in (doc.get("담은로직") if isinstance(doc, dict) else None) or []:
key = str(one.get("로직") or "") if isinstance(one, dict) else ""
if not key:
if not key or (keys is not None and key not in keys):
continue
try:
whole.logic(key)