Records the approved approach for standing up memento-frontend: merge this template's history into it and keep the template as an `upstream` remote so later improvements can be merged in. Two decisions drive the rest of the design. The derived project tracks `upstream/main`, so this repository's `main` has to be fast-forwarded to `develop` first. And the derived project keeps the reference feature and the example routes, so upstream merges stay conflict-free. The design also covers one change to this repository that is not about memento: the supply-chain builder and verifier IDs currently hardcode the repository name in four files, which would make all four permanent conflict points for every derived project. Deriving them from the `name` in `package.json` reduces that surface to one line. This repository's own name does not change, so the emitted ID strings stay byte-identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
14 KiB
memento-frontend를 이 템플릿에서 파생시키기 — 설계서
- 작성일: 2026-09-20
- 대상 레포 (둘)
- 템플릿:
/home/donghyeon/workspace/desktop-server-git/clean-architecture-frontend-template(develop, 기준 커밋ec7f20e) - 파생:
/home/donghyeon/workspace/desktop-server-git/memento-frontend(main, 기준 커밋b35e17c)
- 템플릿:
- 원격: 둘 다 같은 Gitea (
https://git.learn.hyeonworks.com/donghyeon.kang/...) - 이 문서는 설계만 한다. 소스 수정·빌드·테스트 실행 없음.
0. 요약 (먼저 읽을 것)
| 항목 | 판정 |
|---|---|
| 반영 방식 | upstream 추적 부트스트랩. 템플릿 히스토리를 memento에 머지하고 upstream 원격으로 남겨 계속 받아온다 |
| 추적 브랜치 | upstream/main. 그러려면 템플릿의 main을 먼저 develop 지점까지 올려야 한다 |
develop → main |
fast-forward. main은 Initial commit 1개뿐이고 develop의 조상이라 머지 커밋이 안 생긴다 |
| memento 초기 상태 | 템플릿 그대로 유지 + 식별자만 리브랜딩. reference-feature와 /examples/*는 남긴다 |
| 템플릿 선수정 | builder/verifier ID를 package.json의 name에서 파생. 템플릿 자신의 산출물 문자열은 불변 |
| 템플릿에 memento 흔적 | 없다. 템플릿에 "memento"라는 문자열은 들어가지 않는다 |
| 최대 위험 | pnpm install 후 게이트가 memento에서도 통과하는지. 특히 verify:release 계열이 새 name으로 재계산된 ID를 일관되게 쓰는지 |
실행 순서는 되돌리기 어려운 것이 뒤에 오도록 잡았다: 템플릿 수정 → 게이트 → push → memento 머지 → 리브랜딩 → 게이트.
1. 확정된 결정 (사용자 승인 완료)
- 반영 = upstream 추적 부트스트랩. 1회성 스냅샷 복사안과 "동기화 스크립트만 만들기"안은 기각. 근거: 템플릿이 계속 개선되는 레포이므로, 파생 시점에 끊기면 개선분을 수동 diff로 옮겨야 한다.
- 추적 대상은
upstream/main. (develop이 아니다.) 따라서 템플릿의main을develop지점까지 올리는 것이 선행 작업이다. - memento 초기 상태는 "템플릿 그대로 + 리브랜딩만".
reference-feature와/examples/*를 남긴다. 근거: (a) 살아있는 참조 구현이 남아 팀이 패턴을 보고 따라갈 수 있다, (b) 삭제하면INVENTORY.md·테스트 레지스트리·e2e·a11y까지 연쇄로 손봐야 한다, (c) 삭제한 파일은 upstream 머지마다 충돌한다. - builder/verifier ID는 템플릿 쪽에서
package.json의name파생으로 바꾼다. memento에서 문자열만 교체하는 안은 기각. 근거: 그 경우 4개 파일이 영구 충돌 후보가 된다. - 앱 표시 이름은
Memento. ko/en 카탈로그 모두 동일 표기. - 템플릿
main의 origin push 승인됨.
2. Part A — 템플릿 선수정: 프로젝트 이름 파생
A.1 문제
레포 이름이 4개 파일에 하드코딩되어 있다.
| 파일 | 줄 | 값 |
|---|---|---|
scripts/lib/provider-evidence.ts |
12–13 | PROMOTION_VERIFIER_ID = "clean-architecture-frontend-template/promotion-verifier" |
scripts/lib/local-release-evidence.ts |
306–307 | LOCAL_EVIDENCE_VERIFIER_ID = "clean-architecture-frontend-template/local-evidence-verifier" |
scripts/generate-supply-chain.ts |
273 | builder: { id: "local:clean-architecture-frontend-template" } |
tests/unit/security-followup-fixture.ts |
38, 183, 499 | 위 두 ID의 리터럴 복사본 |
파생 프로젝트는 이 4개 파일을 고쳐야 하고, 그 순간 4개 파일 모두가 upstream 머지의 영구 충돌 지점이 된다.
A.2 해법: scripts/lib/supply-chain.ts에 PROJECT_NAME을 둔다
새 파일을 만들지 않는 것이 핵심이다. scripts/lib/supply-chain.ts는
- 세 소비자가 이미 전부 임포트하고 있고
(
provider-evidence.ts:5-8,local-release-evidence.ts:70,generate-supply-chain.ts:26) LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS(scripts/lib/release-candidate.ts:38-58)에 이미 등재되어 있다.
새 파일을 만들면 이렇게 된다:
tests/unit/security-followup.test.ts:79-92가local-release-evidence.ts의 모든 상대.tsimport를 뽑아LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS의 부분집합인지 검사한다. 새 파일을 임포트하면 이 테스트가 깨진다.- 목록을 늘리면
release-candidate.ts:71을 통해 릴리스 증적의 대상 파일 집합이 함께 바뀐다.
supply-chain.ts는 이미 임포트되어 있고 이미 목록에 있으므로 둘 다 발생하지 않는다.
A.3 변경 내용
scripts/lib/supply-chain.ts에 추가:
- 리포지토리 루트의
package.json에서name을 읽어PROJECT_NAME으로 노출한다. - 읽기는 모듈 로드 시점 1회.
node:fs의readFileSync를 쓴다 (선례:scripts/check-adapter-inventory.ts). 기존export const소비자들이 동기 상수를 기대하므로 비동기readFile로는 시그니처가 바뀐다. - 루트 해석은
new URL("../../package.json", import.meta.url)로 고정한다. 레포의 기존 관용구가 이것이다(scripts/run-vitest.ts:26,scripts/check-architecture.ts:74).import.meta.dirname은 이 레포에서 쓰인 적이 없으므로 도입하지 않는다.process.cwd()에 의존하면 다른 디렉터리에서 스크립트를 호출했을 때 값이 달라진다. name이 없거나 빈 문자열이면 던진다. 조용한 폴백은 증적(provenance)에 잘못된 identity를 박아 넣는다.
파생 상수:
| 상수 | 정의 | 템플릿에서의 값 |
|---|---|---|
PROMOTION_VERIFIER_ID |
`${PROJECT_NAME}/promotion-verifier` |
clean-architecture-frontend-template/promotion-verifier |
LOCAL_EVIDENCE_VERIFIER_ID |
`${PROJECT_NAME}/local-evidence-verifier` |
clean-architecture-frontend-template/local-evidence-verifier |
| supply-chain builder id | `local:${PROJECT_NAME}` |
local:clean-architecture-frontend-template |
템플릿의 package.json name은 안 바꾼다. 따라서 세 값 모두 현재와 문자 단위로 동일하다.
이 변경은 템플릿의 산출물을 바꾸지 않는다. 바뀌는 것은 값의 출처뿐이다.
A.4 테스트 픽스처
tests/unit/security-followup-fixture.ts의 리터럴 3곳(38, 183, 499)을 위 상수 import로 교체한다.
조사 결과 부정 케이스는 없다. 세 곳 모두 status: "PASS" 픽스처이고, 의도적으로 틀린
verifier ID를 넣어 거부를 확인하는 테스트는 tests/ 어디에도 없다. 따라서 세 곳 전부
상수로 바꿔도 테스트가 무력화되지 않는다.
참고로 불일치를 거부하는 코드는 scripts/lib/exact-promotion-bundle.ts:93과
scripts/lib/local-release-evidence.ts:785다. 나중에 부정 케이스를 추가한다면
그쪽은 의도적으로 틀린 리터럴을 써야 한다. 상수를 쓰면 자기 자신과 비교하게 된다.
A.5 Part A 완료 조건
corepack pnpm lint
corepack pnpm check:types
corepack pnpm test:all
corepack pnpm build
corepack pnpm verify:release
추가로, 이 변경이 산출물을 안 바꿨다는 것을 확인한다: 변경 전후로
artifacts/release/provenance.json의 predicate.runDetails.builder.id가 동일해야 한다.
3. Part B — 템플릿 develop → main
현재 상태:
main 91ae42a Initial commit (커밋 1개)
develop ec7f20e refactor: 프론트엔드 리펙토링 (커밋 234개)
main은 develop의 조상이다(git merge-base --is-ancestor main develop 통과). 따라서:
git checkout main
git merge --ff-only develop
git push origin main
--ff-only를 쓰는 이유: main이 고유 커밋을 하나도 갖고 있지 않아 --no-ff 머지 커밋은
아무 정보도 담지 못한다. --ff-only는 "조상이 아니면 실패"라는 안전장치 역할도 한다.
Part A의 커밋이 develop에 먼저 올라가야 하므로 Part A → Part B 순서를 지킨다.
(Part A가 develop에 커밋되어도 main은 여전히 조상이므로 ff는 유지된다.)
4. Part C — memento-frontend 부트스트랩
현재 상태: main에 커밋 2개 (bce7c4c Initial commit, b35e17c README.md 업데이트), 파일은 README.md 하나.
cd /home/donghyeon/workspace/desktop-server-git/memento-frontend
git remote add upstream https://git.learn.hyeonworks.com/donghyeon.kang/clean-architecture-frontend-template
git fetch upstream
git merge upstream/main --allow-unrelated-histories
--allow-unrelated-histories가 필요한 이유: 두 레포는 공통 조상이 없다.- 예상 충돌:
README.md1건(양쪽에 존재하는 유일한 경로). 템플릿 쪽을 채택한 뒤 Part D에서 교체한다. - 결과: memento의 커밋 2개가 first-parent 선상에 보존되고, 템플릿 234개가 머지로 들어온다.
이후 동기화:
git fetch upstream && git merge upstream/main
5. Part D — 리브랜딩
D.1 바꾸는 곳
| 파일 | 위치 | 현재 | 변경 후 |
|---|---|---|---|
package.json |
name |
clean-architecture-frontend-template |
memento-frontend |
index.html |
7 | Clean Architecture Frontend |
Memento |
src/presentation/i18n/catalog.ts |
6, 165 (common.appName) |
Frontend Skeleton |
Memento |
src/presentation/i18n/catalog.ts |
44, 203 (route.APP_HOME.title) |
Clean Architecture Frontend |
Memento |
src/presentation/pages/home-page.tsx |
43 | Clean Architecture Frontend |
Memento |
src/contracts/storage-keys.ts |
1 (APP_NAMESPACE) |
ca-frontend |
memento |
README.md |
전체 | 템플릿 설명 | memento 설명 + upstream 동기화 절차 |
package.json의 name 변경은 Part A 덕분에 자동으로 verifier/builder ID까지 memento 값으로 전파된다.
APP_NAMESPACE 변경 시 주의: 이 값은 storage-keys.ts:140에서 ${APP_NAMESPACE}:${scope}:v${schemaVersion}:${name} 형태로 브라우저 저장소 키를 만든다. 템플릿이 이미 쓰던 키가 남아 있는 브라우저에서는 기존 데이터가 고아가 된다. 신규 프로젝트라 실제 영향은 없지만, 키 형식을 검사하는 테스트가 있으면 같이 갱신해야 한다.
D.2 바꾸지 않는 곳 (의도적)
docs/superpowers/specs/*,docs/reviews/*,docs/architecture/*안의 템플릿 이름 이들은 템플릿에서 실제로 일어난 작업의 기록이다. 이름을 바꾸면 기록이 거짓이 되고, upstream 머지 충돌만 늘어난다. (해당 파일 7개)config/runtime/*.json의API_BASE_URLmemento-backend가 아직 빈 레포라 넣을 값이 없다.http://localhost:8080/유지.public/config.json의FEATURE_OVERRIDES["reference-feature"]결정 3에 따라reference-feature를 남기므로 그대로 둔다.
D.3 memento README.md
최소한 다음을 담는다. memento의 도메인이 아직 정해지지 않았으므로 제품 설명은 쓰지 않는다.
- 이 레포가
clean-architecture-frontend-template에서 파생되었다는 사실 - upstream 동기화 절차 (
git fetch upstream && git merge upstream/main) - 로컬 실행 절차 (템플릿 README에서 승계)
- 리브랜딩된 지점 목록 — 다음 사람이 upstream 충돌을 만났을 때 볼 곳
6. 검증
memento-frontend에서, Part D 완료 후:
corepack pnpm install --frozen-lockfile
corepack pnpm lint
corepack pnpm check:types
corepack pnpm check:architecture
corepack pnpm check:adapter-inventory
corepack pnpm test:all
corepack pnpm build
corepack pnpm verify:release
verify:release를 포함하는 이유: Part A가 손댄 경로가 정확히 이 게이트의 대상이고,
package.json의 name이 바뀐 상태에서 ID 파생이 일관되게 동작하는지는 여기서만 드러난다.
.nvmrc의 Node 24.14.0과 Corepack이 필요하다.
7. 비범위 (이번에 하지 않는 것)
- memento의 도메인 기능 설계·구현. 이번 작업의 산출물은 "빌드되고 게이트를 통과하는 빈 스캐폴드"까지다.
memento-backend연동. 해당 레포도 아직 비어 있다.reference-feature제거. 결정 3에 따라 남긴다. 나중에 제거할 때는 별도 작업으로 다룬다..gitea/workflows/quality-gates.yml의 CI 활성화 검증. 파일은 그대로 넘어가지만 Gitea Actions 러너 설정은 이 작업 범위 밖이다.- 템플릿의
develop브랜치 정리나refactor/platform-consumer-migration브랜치 처리.
8. 리스크
| 리스크 | 영향 | 완화 |
|---|---|---|
Part A에서 readFileSync 경로가 호출 컨텍스트에 따라 달라짐 |
증적에 잘못된 identity | new URL(..., import.meta.url) 기준 고정, process.cwd() 사용 금지 |
Part A가 local-release-evidence.ts에 새 import를 추가함 |
security-followup.test.ts:79-92 실패 |
이미 임포트 중인 supply-chain.ts만 쓴다. 새 파일을 만들지 않는다 |
memento에서 pnpm install 후 게이트 실패 |
부트스트랩 미완 | Part B push 전에 템플릿에서 전 게이트를 먼저 통과시킨다 |
--allow-unrelated-histories 머지에서 예상 밖 충돌 |
수작업 증가 | 충돌 파일이 README.md 외에 나오면 중단하고 보고 |
main ff 머지 실패 |
Part B 중단 | --ff-only가 알아서 실패시킨다. 그 경우 main에 예상 못한 커밋이 생긴 것이므로 재조사 |
9. 실행 순서 (되돌리기 어려운 것이 뒤)
- Part A — 템플릿
develop에서 ID 파생으로 변경, 게이트 통과, 커밋 - Part B —
develop→mainff 머지,origin/mainpush ← 첫 외부 반영 - Part C — memento에
upstream추가,upstream/main머지,README.md충돌 해소 - Part D — 리브랜딩
- 검증 — memento에서 전 게이트 실행
- memento 커밋 (push 여부는 별도 확인)