quad/.claude/base/project-setup-plan.md
qwreey 9dd82136bd
qa: 8라운드 손 트레이싱 처리 — 결정 Q1~Q10 반영, M2 착수 게이트 0
발견 17건(H-107~H-123)의 사용자 결정을 base/ 전체에 반영. 7라운드 확정 중
뒤집힌 건 없고, 고친 건 전부 7라운드가 base/에 내려앉을 때 생긴 누락·충돌
(하루 차로 확정된 결정들이 서로를 못 본 자리)이다. 결정의 소스는
qa-request/pre-implementation-handtrace-round8-followup.md.

계약 변경 넷:
- Ref 콜백이 fn(value, ref) — 2번째가 곧 출처 Epoch. Effect가 Update(from)에
  넘길 유일한 통로였다(k(value)뿐이면 Update(nil) 크래시, 실측 재현).
- Observer fn이 세 자리 fn(targetState, self, emitFrom) + observer._state
  강참조. 옛 2-인자는 "self는 리시버" 계약과 정면 충돌해 무인자
  state:Observer()의 내부 콜백이 즉사했다.
- WeakSubscribe도 .Subscribed를 세운다. 안 그러면 Effect의 State dep 전량이
  조용히 침묵. 해제는 "건 경로로 푼다"(양방향 fail-fast).
- 예약 키 진단이 CheckReservedKeys<keyof<T>> — T를 통째로 넘기는 배선은
  실사용 T에서 아예 안 돈다(Source<T>가 *error-type*을 품어 유효한 Store
  전부에 스퓨리어스 에러).

사용자가 문항의 전제를 두 번 정정: Ref 콜백과 Observer 콜백은 이질적이라
애초에 통합 대상이 아니었고(Observer엔 자기 epoch가 없다), H-118은 소유권
문제가 아니라 gate-plan 5번의 문장이 틀린 것이었다(🟡🟢).

커밋 전 검증 — 감사 11라운드(44건, 0건으로 수렴) + /code-review high
7라운드(42건) = 86건. 감사가 0으로 수렴한 직후 code-review가 42건을 냈고,
그중 하나가 H-101의 "새 필드를 안 만든다"를 역전시켰다: getOffsetAt의
부수효과가 splice의 되감기 신호를 지우는 경로가 실재해, 부기 필드를
offsetCacheValidUpTo(캐시)와 offsetSetUpTo(:Set 완료) 둘로 분리했다.
"Set을 해줬느냐"와 "캐시가 유효하냐"를 한 값이 쥔 게 원인이었다.

그 외: splice 무효화 i-1, 명시 recompute 호출부 전부 재진입 게이트,
recompute 되감기 클램프, Store defaults isSource 검증, isModifier 가드를
Source 생성자로, 훅 슈가 nil 가드, pesde.lock 커밋 확정, :Single 3-인자.
2026-08-25 session/ 원문 공백은 2026-08-19 선례대로 재구성 없이 기록만.

doc-check ERROR 0 / WARN 기준선 유지.

Claude-Session: https://claude.ai/code/session_01F9zgJ4c4kDitAoQMm9qxKn
Co-authored-by: qwreey <me@qwreey.moe>
2026-08-26 17:18:30 +09:00

25 KiB

프로젝트 셋업 — pesde 워크스페이스, .luaurc, require 구조

상태: base — architecture.md의 "구현 착수: 소스 트리 구조 확정" 절이 정한 소스 트리를 실제로 pesde/luau CLI로 셋업해보고 검증한 결과. 그 절은 "무엇을 어디에 두는가"까지만 다루고 "그걸 실제로 어떻게 굴리는가"는 비워뒀는데, 이 문서가 그 나머지 — 패키지 매니저 조작, require 문법, 현재 환경에서 확인된 한계까지. [2026-08-19 신설, 같은 날 pesde 실제 설치·pesde install 실행으로 검증]

전제: 이 문서가 서술하는 건 M0/M1 스캐폴딩 단계에서 확인된 사실이지 M2 이후 실제 구현이 아님 — quad-base/src는 아직 Relate.luau/골격 New()/Debug 서브시스템뿐이고 quad-roblox/src는 비어 있음 (.claude/todos.md가 여전히 진행 상황의 소스). 여기 적힌 require/pesde 규칙은 실제 소스가 늘어나도 안 바뀔 구조적 사실이라 base로 승격했지만, "무엇이 구현됐는가"는 이 문서가 아니라 todos.md/ROADMAP.md를 볼 것.

왜 wally가 아니라 pesde인가

사용자 결정(2026-08-19): dev-dependency를 1급으로 지원하는 등 wally보다 툴링이 낫다는 판단. 상세 배경/재검토는 architecture.md의 "구현 착수: 소스 트리 구조 확정" 절 "패키징 방식" 문단이 소스 — 여기서 반복하지 않음. 요지만: 모노레포 모양(루트 통합 개발, 서브패키지마다 독립 게시) 자체는 안 바뀌고, pesde의 네이티브 workspace 기능이 그 모양에 그대로 들어맞는다.

pesde 워크스페이스 구조

전체 멤버 목록·최신 트리는 architecture.md의 "구현 착수: 소스 트리 구조 확정" 절이 소스 — 새 워크스페이스 멤버가 늘 때마다 그쪽만 갱신하면 되게 하기 위해 여기서 다시 나열/개수 세지 않는다(멤버 수가 실제로 2→3→4로 늘어난 이력에서 이 문서의 구식 트리가 갱신을 놓쳤던 게 계기). 아래는 그 구조가 실제로 동작하는 pesde 메커니즘 자체(문법/함정)만 다룬다 — 예시엔 최소 2-멤버 형태만 남겨두고 서술 부담을 줄임:

quad/
├── pesde.toml          # 워크스페이스 루트, private = true, workspace_members
├── mise.toml            # pesde/rojo/luau-lsp/selene 버전 핀
├── quad-base/
│   ├── pesde.toml       # name = "qwreey/quad_base"
│   └── selene.toml
└── quad-roblox/
    ├── pesde.toml       # name = "qwreey/quad_roblox", quad_base에 workspace 의존
    └── selene.toml
  • 루트 pesde.toml: private = true(게시 안 됨) + workspace_members (멤버 목록은 architecture.md가 소스). [target] environment = "roblox"도 필요(공식 workspace 가이드 예제가 루트에도 [target]을 요구함 — 이 세션엔 roblox 하나만 있어 실제로 검증 안 됨, 필요 여부/의미는 M5 이후 재확인 후보).
  • 서브패키지 pesde.toml: [target] environment = "roblox" + build_files = ["src"] + lib = "src/init.luau". quad-roblox[dependencies] quad_base = { workspace = "qwreey/quad_base", version = "^" }.
  • ⚠️ 패키지 이름은 a-z/0-9/_만 허용 — 하이픈 금지(pesde install 실측: qwreey/quad-base는 파싱 단계에서 바로 거부됨, 에러 메시지가 "did not match any variant of untagged enum DependencySpecifiers"로 나와서 원인 파악에 혼동을 줌 — 실제 원인은 이름 문자 제약이지 의존성 선언 문법이 아니었음). quad_base/quad_roblox로 확정 — 폴더 이름(quad-base/quad-roblox)은 architecture.md가 이미 확정해둔 것이라 그대로 두고, pesde.tomlname 필드만 언더스코어로 다르게 쓴다. 둘이 다르다는 걸 헷갈리지 말 것.
  • pesde add는 워크스페이스 멤버를 자동으로 못 찾는다 — 레지스트리 검색 전용 커맨드라 로컬 워크스페이스 형제를 이름으로 넘기면 "package not found"로 실패한다. [dependencies]workspace = "scope/name" 줄은 직접 손으로 쓸 것(위 표기 그대로 — 실제로 pesde install이 받아들이는 걸 확인함).
  • pesde install은 워크스페이스 루트에서 한 번만 돌리면 모든 워크스페이스 멤버가 스캔·링크됨(개수는 architecture.mdworkspace_members가 소스 — 새 멤버가 늘어도 이 동작은 안 바뀜).
  • [2026-08-19 후속, type-version-check 신설 때 실측] 의존하는 워크스페이스 멤버의 target이 자기 자신의 기본 target과 다르면 workspace = "..." 의존 선언에 target = "..."를 명시해야 한다. quad-types(기본 target roblox)가 type-version-check(자기 [target] environment = "luau")에 의존할 때, target 없이 { workspace = "qwreey/type_version_check", version = "^" }만 쓰면 pesde installno workspace member found with name qwreey/type_version_check and target roblox로 실패한다 — target = "luau"를 추가해야 해소됨.

툴체인 — rokit.toml에서 mise.toml로 전환 (2026-08-19)

원래는 initreq/vide(참고 레포)의 rokit.toml 선례를 따랐음(같은 날 pesde/rojo/luau-lsp 셋 다 /code/.local/bin에 직접 다운로드해 설치·핀과 버전 일치까지 검증). 같은 날 후속 세션에 mise.toml로 재전환 — 사용자 결정("요즘은 rokit 보단 mise로 까는듯 하네. 더 범용적이라 이걸 택하는듯"), 근거는 Word30210/roblox-project-example(initreq/roblox-project-example로 클론해 확인)의 mise.toml. rokit은 이 샌드박스에 아예 없어 한 번도 직접 검증 못 했던 반면, mise는 이 샌드박스 자체가 이미 luau 설치에 쓰고 있어서 mise install을 그 자리에서 실행해 진짜로 검증함 — pesde/rojo/luau-lsp/selene 넷 다 GitHub artifact attestation + SLSA provenance 검증까지 거쳐 설치됨(이전 세션이 curl로 직접 받던 방식보다 공급망 신뢰도가 높음), mise exec -- <tool> --version으로 버전 일치까지 확인. mise.toml의 tool 키는 백엔드 접두사가 붙는다 (github:owner/repo / aqua:owner/repo) — rokit.tomlowner/repo@ver 평문 표기와 형태가 다르니 그대로 베끼지 말 것.

darklua는 검토 후 채택 안 함 — 참고 레포는 .darklua.jsonconvert_require 룰로 경로 기반 require를 빌드 시점에 Rojo sourcemap 기준 script.Parent류로 변환하는데, 사용자 판단(2026-08-19): "Roblox 안에서도 이미 string require가 적용되긴 하고, @self@game이 먹는다. 같은 동작을 하지만 ./ 등으로 위치를 어떻게 두냐에 유의가 필요할 뿐" — 즉 실제 Roblox 엔진도 이 세션이 확인한 것과 같은 require-by-string 의미론(@self 등)을 그대로 지원하므로, darklua로 다른 형태로 변환할 필요성 자체가 낮다는 판단. quad-base는 엔진 무관이어야 하므로 애초에 Roblox 전용 변환의 적용 대상도 아님.

[2026-08-19 후속 확인 — 정확한 경계] darklua 바이너리(0.19.0)를 직접 설치해 참고 레포에서 실제로 darklua process를 돌려 정확히 어디까지 관여하는지 확인:

  • require("@self/X")/require("@game/...")convert_require가 아예 손을 안 댐unable to require resource: unknown source name '@self'로 경고만 찍고 원문 그대로 통과시킴. 즉 예약 alias는 런타임이 직접 처리한다고 전제하고 있고, 이 세션의 결론과 정합.
  • 커스텀 .luaurc alias(@pkg 등)는 다르다 — convert_require가 실제로 @pkg/assetsrequire(game:GetService('ReplicatedStorage'):WaitForChild('roblox_packages'):WaitForChild('assets')) 로 치환함(.luaurc의 alias 매핑 + Rojo sourcemap을 같이 읽어서 해석 — 대상 파일이 실제로 존재해야 성공, pesde install 전엔 실패). 즉 커스텀 alias 기반 require를 실제로 쓰려면 darklua(또는 동급 빌드 스텝) 없이는 배포 시점에 그 문자열이 그대로 남아 동작을 보장할 근거가 없다 — standalone luau CLI가 커스텀 alias를 거부하는 것과 같은 결(could not jump to alias), Roblox 엔진이 예약 alias 밖의 커스텀 alias까지 자체적으로 푼다는 근거는 없음.
  • 결론: quad는 커스텀 alias를 전혀 안 쓰고 상대경로+@self만 쓰므로 지금은 darklua가 불필요. 나중에 @pkg/quad_base류 축약 alias를 도입하고 싶어지면 그때는 darklua(혹은 동급 변환)가 실질적으로 필요해진다 — 그 시점에 이 절을 다시 열 것.

require 구조 — @self가 필수인 이유

[2026-08-19, 사용자 지적 + Luau RFC abstract-module-paths-and-init-dot-luau로 확인]

init.luau라는 파일은 require-by-string 상 자기가 든 폴더 자체를 가리키는 특수 취급을 받는다 — 그래서 그 파일 안에서 쓰는 상대 경로는 일반 파일과 기준점이 다르다:

  • init.luau 안에서 require("./X")/require("../X") — 이 폴더 자체가 "자기"이므로, 상대 경로는 이 폴더의 형제/조상을 가리킨다. 즉 quad-base/src/init.luau 안의 ./Debugquad-base/src/Debug가 아니라 quad-base/Debug를 가리킨다(존재하지 않으면 즉시 에러).
  • init.luau 안에서 그 폴더 안의 형제 파일에 접근하려면 @self/X를 써야 한다@self는 예약 alias로, 이 모듈(=이 폴더) 자신의 경로로 치환된다. quad-base/src/init.luauquad-base/src/Debug에 접근하려면 require("@self/Debug").
  • 일반 파일(init.luau가 아닌 *.luau)에서는 평범한 파일-상대 경로(.//../)면 충분 — @self가 전혀 필요 없다. 예: quad-base/src/Debug/init.luau(이것도 init.luau라 위 규칙이 적용됨) 안에서 형제 Relate.luau(quad-base/src/Relate.luau, Debug 폴더의 부모에 있음)를 가져오려면 — Debug/init.luau의 "자기"는 Debug 폴더이므로 그 부모(=quad-base/src)에 있는 Relate.luau는 형제 폴더 취급 → require("./Relate")(◯), require("../Relate")(✕, 한 단계 더 올라가 quad-base/Relate를 찾으려다 실패).

실측 근거: tbox(initreq/tbox, 다른 참고 레포)의 src/init.luaurequire("@self/types")/require("@self/schema/string") 패턴을 실제로 쓰고 있어 교차 확인됨. 이 세션에서 quad-base/src/init.luau가 처음에 require("./Debug")로 잘못 짜여 크로스파일 require가 전부 깨졌었고(런타임은 크래시, luau-analyze조용히 Unifiable<Error>로 새며 0 진단으로 통과 — 이것도 typing-limits.md의 "실측 방법 주의" 경고("luau-analyze가 진단 0건이어도 타입이 제대로 해소됐다는 뜻이 아닙니다")가 가리키는 것과 같은 종류의 함정, 다만 원인은 재귀 제네릭이 아니라 require 경로 오류라 그 문서 1번 항목과는 별개 사례), @self로 고치자 즉시 정상화됨(둘 다 clean).

체크리스트: 새 init.luau를 짤 때마다 "이 파일 안의 require가 같은 폴더 안의 형제를 가리키는가, 아니면 이 폴더의 형제/조상을 가리키는가"를 먼저 물을 것 — 전자면 @self/, 후자면 ./../.

워크스페이스 의존성은 심볼릭 링크로 연결된다 — CLI 테스트의 함정

[2026-08-19 실측] pesde install이 워크스페이스 멤버 간 의존성을 해소하는 방식은 심볼릭 링크다 — quad-roblox/roblox_packages/ 안에 실제로 이렇게 생긴다:

quad-roblox/roblox_packages/
├── quad_base.luau              # 얇은 링커: return require("./.pesde/qwreey+quad_base/0.0.0/quad_base/src")
└── .pesde/qwreey+quad_base/0.0.0/quad_base/
    ├── src   -> ../../../../../quad-base/src    (symlink)
    ├── test  -> ../../../../../quad-base/test    (symlink)
    ├── pesde.toml -> ...                          (symlink)
    └── pesde.lock -> ...                          (symlink)

⚠️ 문제: Luau의 standalone require-by-string 구현은 심볼릭 링크를 안 따라간다 — 의도된 설계다(Luau RFC 검색 결과: "보안 상의 이유로 symlink는 일반 파일처럼 취급되고 따라가지 않는다", 추후 .luaurc에 opt-in 토글이 추가될 수 있다고만 언급됨, 아직 없음). 직접 재현:

-- entry.luau, ./linked가 실제 폴더로의 symlink일 때
local v = require("./linked")
-- error requiring module "./linked": could not resolve child component "linked"

실무 영향: quad-roblox가 실제로 quad_base를 쓰게 되면(M5+), 표준 경로(require(".../roblox_packages/quad_base"))는 luau CLI로 직접 못 돌린다could not resolve child component로 즉시 깨짐.

[2026-08-19 후속 세션, 확인 완료] Rojo/Studio는 이 문제와 무관함 — rojo를 같은 방식으로 /code/.local/bin에 설치해 직접 검증. quad-roblox/ 아래 src+roblox_packages를 매핑하는 임시 project.json으로 rojo sourcemap을 돌려보니, symlink를 정확히 따라가 실제 파일까지 해소함을 확인:

{"name":"src","filePaths":["../quad-base/src/init.luau"],
 "children":[
   {"name":"Debug","filePaths":["../quad-base/src/Debug/init.luau"]},
   {"name":"Relate","filePaths":["../quad-base/src/Relate.luau"]}
 ]}

rojo build(실제 .rbxm 생성)도 같은 트리로 에러 없이 성공. 즉 Rojo는 fs::canonicalize류 평범한 파일시스템 API로 트리를 만들어서 symlink를 투명하게 통과하고, 위 함정은 Luau standalone CLI의 require-by-string 전용 문제로 확정 — Studio 배포 경로엔 영향 없음. Studio 자체(플러그인 연동)까지는 아직 미검증(HUMAN_TODO.md 1번, 계정 분리 대기)이지만, rojo build/sourcemap 레벨에서 이미 심볼릭 링크 순회가 확인됐으므로 Studio도 같은 파일시스템 계층을 쓰는 이상 다르게 동작할 이유가 없다.

우회가 필요한 범위는 M0/M1식 CLI 스파이크/mock 테스트로 좁혀짐: roblox_packages/를 거치지 말고 실제 형제 패키지 경로를 직접 가리킬 것 — 예: quad-roblox/src에서 검증용 스크립트를 짤 때 require("../../quad-base/src")처럼. 프로덕션 quad-roblox 소스 자체는 그대로 표준 pesde 경로(roblox_packages/quad_base)를 쓸 것 — Rojo/Studio가 실제로 소비하는 게 그 경로이고 위에서 확인했듯 문제없이 동작한다.

[2026-08-19 셋째 후속 세션] 이 함정이 이제 quad-base 자기 자신의 프로덕션 진입점에도 실제로 닥침quad-types 워크스페이스 패키지가 신설되며 quad-base/src/init.luaurequire("./roblox_packages/quad_types")를 쓰게 됐는데(quad-types-plan.md 참고), 이건 M0/M1 스파이크가 아니라 실제 출하 소스라 위 "우회는 스파이크에만"이라는 구분이 더 이상 깔끔하게 안 맞음. 이 세션이 실제로 쓴 해법: pesde install이 만든 심볼릭 링크를 로컬 CLI 테스트용으로만 실제 디렉토리 복사본으로 치환(find . -type l ... cp -r류, 저장소 파일은 안 건드리고 roblox_packages/.pesde/의 링크만 대상) — Rojo/Studio는 이미 symlink를 투명하게 처리하므로 배포 경로엔 아무 영향 없고, 순수 이 샌드박스의 luau/luau-analyze 실행을 가능하게 하는 로컬 조치다. [2026-08-26 정정, 8라운드 H-123] 이건 이미 스크립트로 정식화됐다 — 여기 한때 *"아직 반복 가능한 스크립트/mise task로 정식화하진 않음 — 매번 pesde install 후 수동으로 치환했음"*이라고 적혀 있었으나, 7라운드 H-78scripts/relink.sh + scripts/test.sh를 신설했고 둘 다 커밋돼 있다 (8라운드에서 실제로 실행해 확인). 지금 규칙은 하나다 — 테스트는 ./scripts/test.sh로 돌린다(그 스크립트가 relink.sh를 먼저 부른다). 그냥 luau로 돌리면 스모크가 죽고 luau-analyze는 모듈을 any로 떨어뜨려 조용히 통과한다("거짓 클린"). Luau의 .luaurc symlink opt-in 토글이 미래에 생기면 이 절 전체가 불필요해짐 — 그때 다시 볼 것.

[2026-08-19 같은 날 넷째 후속 세션] 의존 대상의 target에 따라 링크 디렉토리 이름이 달라진다quad-types(target roblox)가 type-version-check(target luau)에 의존하면, quad-base/quad-roblox가 쓰는 roblox_packages/와 달리 quad-types/src/init.luaurequire("./luau_packages/type_version_check")luau_packages/ 아래에서 링크를 찾는다 — pesde가 의존 대상 패키지 자신의 target 이름으로 디렉토리를 분리하기 때문(.gitignoreluau_packages/도 이미 포함돼 있어 별도 조치 불필요).

같은 워크어라운드가 2단 의존 체인에도 그대로 재적용됨 — type-version-check 신설로 quad-typestype-version-check가 추가되면서 quad-base/quad-robloxquad-typestype-version-check처럼 깊이 2인 워크스페이스 의존 그래프가 생겼는데, pesde install 후 같은 심볼릭 링크 치환을 반복 적용하는 것만으로 luau-analyze/luau 양쪽 다 문제없이 동작 확인됨 — 이 우회가 단일 깊이에 국한되지 않고 일반화됨이 실측으로 재확인됨.

.luaurc — alias는 여전히 편집기 전용

.luaurcaliases(@quad-base/@quad-roblox)는 런타임 require에서 여전히 안 먹는다architecture.md가 이미 이렇게 서술해뒀던 걸 이 세션에 직접 재확인: require("@quad-base/Debug")를 실행하면 could not jump to alias "quad-base/src"로 실패(별도 에러 메시지라 위 심볼릭 링크 문제와는 다른 원인 — alias 자체가 런타임 미지원이라는 뜻, @self예약 alias라 이 제약과 무관하게 항상 동작하는 것과 구분할 것). 그래서 alias는 편집기 자동완성/타입체크 용도로만 남기고, 실제 require는 위 규칙대로 상대경로 + @self.

selene 린터 — 패키지별 설정, CWD 상대 config 탐색 함정

[2026-08-19 신설] 사용자 결정으로 selene(참고 레포 initreq/roblox-project-examplescripts/selene.toml 그대로 채택)을 도입 — luau-analyze(타입체크)와 겹치지 않는 별도 축(정의되지 않은 변수, 사용 안 하는 변수, assert 메시지 누락 등 스타일/버그 패턴 린트)이라 같이 씀. quad-base/selene.toml/quad-roblox/selene.toml 각각 독립 배치(참고 레포도 패키지마다 독립 설정) — 아래 실측 이유 때문에 루트 단일 설정은 안 씀.

⚠️ selene--config 탐색은 파일 트리를 거슬러 올라가며 찾는 게 아니라 CWD 기준 고정 경로(./selene.toml)다. 루트에 selene.toml 하나만 두고 selene quad-base/를 저장소 루트에서 실행하면, 그 자리엔 selene.toml이 없어(패키지 폴더 안에 있으므로) Luau 문법 자체를 못 읽는 기본(Lua 5.1) std로 조용히 폴백type Quad = {...} 같은 평범한 타입 선언까지 전부 "unexpected token" 파싱 에러로 쏟아짐(30여 건, 전부 가짜). 처음엔 이게 "이 selene 빌드가 Luau 타입 문법을 아예 지원 못 한다"는 뜻인 줄 알았으나, std = "luau"가 든 selene.toml을 CWD에 두면 정확히 같은 파일이 0 에러로 통과함 — 즉 문제는 selene의 Luau 지원이 아니라 config 탐색 방식이었다.

규칙: 항상 패키지 디렉토리 안에서 실행할 것 — cd quad-base && selene .(참고 레포의 Justfile도 정확히 이 패턴으로 각 패키지를 순회함, refresh/clean 레시피). 저장소 루트에서 --config quad-base/selene.toml quad-base/처럼 명시적으로 경로를 지정해도 되지만, 그럴 거면 그냥 cd가 더 안전(설정 파일 지정을 빠뜨리기 쉬움).

pesde.lock — 커밋한다 (2026-08-26 확정)

[2026-08-19 실측, 최초 서술 정정] 처음엔 "워크스페이스 루트에 딱 하나만 생긴다"고 적었으나 틀렸음 — 실제로는 pesde install워크스페이스 멤버마다 각자의 pesde.lock도 같이 만든다(루트 pesde.lock 1개 + 멤버마다 1개씩, 개수는 architecture.mdworkspace_members를 따라간다 — 2026-08-19 이 세션 안에서만 2개 멤버(2+1개 lock)에서 4개 멤버(4+1개 lock)로 늘어난 전례가 있어 여기 숫자를 고정하지 않는다). 루트 것은 [workspace."qwreey/quad_base"]류 멤버 매핑만 담고, 멤버 것들은 각자의 실제 의존성 그래프를 담는다(quad_roblox의 lock엔 [graph."qwreey/quad_base@0.0.0 roblox"] + pkg_ref.ref_ty = "workspace"가 있음, quad_base는 의존성이 없어 메타데이터만).

이 세션의 잠정 권고는 셋 다 커밋: Cargo 생태계의 "라이브러리는 lockfile을 커밋하지 않는다" 관행이 여기 그대로 적용 안 되는 이유는, 이 lockfile들이 게시되는 대상이 아니기 때문 — 루트는 private = true, quad-base/quad-robloxpesde.tomlincludes = ["src/*"]뿐이라 pesde.lock은 애초에 게시물에 안 들어감(외부 소비자는 이 파일들을 절대 못 봄 — 자기 프로젝트에서 새로 resolve함). 그래서 "라이브러리 lockfile 딜레마" 자체가 성립하지 않고, 그냥 "이 모노레포를 체크아웃한 개발자/CI가 재현 가능한 빌드를 얻는가" 문제로 좁혀지는데 그건 커밋하는 쪽이 유리 — [2026-08-26 확정, 8라운드 H-123] 사용자 확정 — 전부 커밋한다. 여기 한때 *"이건 이 세션의 판단이고 최종 확정 아님, todos.md에 확인 필요 항목으로 반영"*이라고 적혀 있었으나 그 반영이 실제로는 어디에도 없었고 (question.md/todos.md/HUMAN_TODO.md grep 0건), 실태는 lockfile이 이미 전부 커밋돼 있어 위 잠정 권고와 일치했다. 현 실태 그대로 확정하고 열린 항목에서 뺀다.

확인 완료 / 아직 확인 안 된 것

확인 완료(이 세션, 실제 pesde/luau 실행 근거):

  • pesde 워크스페이스 설치가 당시 워크스페이스 멤버 전부(그때는 루트+2서브)에 대해 성공([2026-08-19 후속] 이후 quad-types/ type-version-check 추가로 멤버가 늘어난 뒤에도 같은 절차로 계속 성공 — 아래 후속 문단들 참고)
  • 패키지 이름 문자 제약(하이픈 금지)
  • workspace = "scope/name" 의존성 선언 문법
  • @selfinit.luau의 형제 파일 접근에 필수라는 것(런타임+ luau-analyze 양쪽)
  • .luaurc alias가 런타임에서 여전히 안 먹는다는 것(재확인)
  • 워크스페이스 의존성이 symlink로 연결되고, 그게 luau CLI의 require-by-string과 충돌한다는 것(직접 재현 + Luau RFC로 원인 확인)
  • [2026-08-19 후속 세션] Rojo(/code/.local/bin에 직접 설치, 7.7.0, 툴체인 핀과 일치)는 위 symlink 문제와 무관 — rojo sourcemap/rojo build 둘 다 roblox_packages의 symlink를 실제 파일까지 투명하게 따라감을 확인. 위 심볼릭 링크 함정은 Luau standalone CLI의 require-by-string 전용 문제로 범위가 좁혀짐
  • 덤 확인rojo가 PATH에 잡히자 luau-lsp(에디터)가 자동으로 rojo sourcemap default.project.json --output sourcemap.json --watch를 백그라운드로 띄움(루트 default.project.json 기준). 즉 지금 이 워크스페이스에서 에디터 타입 링킹이 실제로 살아있다는 뜻 — wally가 안고 있던 "설치된 패키지의 타입 정보 단절" 문제가 이 구성에선 재현 안 됨(architecture.md가 pesde 전환의 배경으로 들었던 문제 자체가 실제로 해소됐다는 간접 증거). 산출물 sourcemap.json은 재생성되는 빌드 산물이라 .gitignore에 추가
  • [2026-08-19 셋째 후속 세션] mise.tomlpesde/rojo/luau-lsp/ selene 넷 다 정확히 설치·버전 일치함을 mise install + mise exec로 직접 확인(attestation/provenance 검증까지 포함) — rokit은 끝내 한 번도 직접 검증 못 했던 것과 대비됨
  • selene--config 탐색이 파일 트리를 안 거슬러 올라가고 CWD 기준 고정 경로라는 것(위 "selene 린터" 절)

아직 확인 안 됨(다음에 도구/환경이 갖춰지면):

  • 루트 pesde.toml[target] 섹션이 실제로 의미가 있는지(지금은 workspace 가이드 예제를 그대로 따라 둔 것, 검증 안 됨)
  • Roblox Studio 자체(플러그인 연동)까지의 실제 동기화 — rojo build/sourcemap 레벨은 확인됐지만 HUMAN_TODO.md 1번(계정 분리)이 되기 전까진 Studio 실물로는 미확인
  • quad_base 설치 시 나온 "roblox_sync_config_generator 스크립트가 없으면 linking에 문제가 생길 수 있다"는 WARN의 실제 영향 범위 — 지금은 install 자체를 막지 않아서 방치, 실제 Rojo 동기화 단계에서 문제가 드러나면 그때 pesde 문서의 [target.scripts] 절을 찾아볼 것
  • pesde.lock 커밋 여부 최종 확정[2026-08-26 해소] 커밋하는 것으로 확정(위 절).