4개 카테고리(필수/관리/보안/부가) 구조화 JSON 응답이 max_tokens=1024 를
넘으면 중간에 잘려 JSON 파싱이 실패하고 '추천을 가져오지 못했습니다' 로 떴음.
- callClaude 에 maxTokens 파라미터 추가, suggestChecklist 는 3072 사용
- parseSuggestions 파싱 실패/빈 결과 시 원본 일부를 warn 로그로 남겨 진단 가능
런타임 재현: 동일 입력으로 필수5·관리5·보안5·부가4(총 19개) 정상 반환 확인.
백엔드 build/lint 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
백엔드:
- SUGGEST_SYSTEM_PROMPT 를 정확히 4개 카테고리(필수/관리/보안/부가 기능)로 고정
(parseSuggestions 가 빈 카테고리는 자동 제외)
프론트(시안 'AI 추천 모달.html' 반영):
- 그라데이션 헤더(배지 아이콘 + '자동 생성' 뱃지 + 분석 요약 서브타이틀)
- 카테고리별 색상 아이콘 dot + 개수 pill + 전체 추가/해제 토글
- 항목을 체크 토글로 '선택' 후 푸터의 'N개 업무에 추가'로 일괄 반영(기존 즉시추가 → 선택→커밋 모델)
- 이미 체크리스트에 있는 항목은 잠금(added) 표시
- 푸터: 선택 개수 + 취소/추가 버튼(.ai-btn — 페이지 .btn 과 분리)
검증: 백엔드 build/lint 0·8 tests, 프론트 type-check/lint/build 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
AI 추천은 제목과 내용을 함께 분석하므로, 둘 중 하나라도 비면 해당 필드에
인라인 에러(빨강 테두리+메시지)를 표시하고 요청을 중단하도록 수정.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
기존: 제목 미입력 시 'AI 추천' 버튼 비활성화(canRequestAi)
변경: 제출 버튼과 동일 로직 — 버튼 상시 활성(분석 중 in-flight 만 disabled),
클릭 시 제목이 비어 있으면 titleError 로 인라인 안내(빨강 테두리+메시지) 후 중단.
사용 안 하게 된 canRequestAi computed 제거.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
borderless 제목 인풋에 has-error 시 빨간 테두리(+가로 패딩) 부여 —
에디터·담당자 필드와 일관. box-sizing:border-box 라 overflow 없음,
세로 패딩 유지로 레이아웃 점프 최소화.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 업무 내용(*)이 표기상 필수인데 검증이 없던 문제 → 필수로 강제
· 프론트: 인라인 검증(에디터 빨강 테두리 + '업무 내용을 입력해 주세요.'),
입력 시 즉시 해제
· 백엔드: CreateTaskDto.content 를 @ArrayNotEmpty 필수로(데이터 계약 일치)
- 제목 에러 표기 정리: 1.5rem 큰 placeholder 가 빨갛게 변하며 아래 메시지와
이중으로 강조되던 것을 제거 → 작은 인라인 메시지만 표시(차분하게)
검증: 백엔드 build/lint 0·8 tests, 프론트 type-check/lint/build 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
기존: 제목·담당자 미입력 시 '지시 보내기' 버튼이 비활성화(이유 안내 없음)
변경:
- 버튼 비활성화 해제(제출 중 중복 클릭 방지용 submitting 만 유지)
- 제출 시 validateForm 으로 검증 → 미입력 필드에 인라인 에러 표시
· 제목: 빨강 placeholder + '업무 제목을 입력해 주세요.'
· 담당자: combo 빨강 테두리 + '담당자를 1명 이상 지정해 주세요.'
- 해당 필드를 채우면 watch 로 에러 즉시 해제
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
기존: 체크리스트 아래에 인라인으로 펼쳐지던 AI 추천 패널
변경: Teleport 기반 중앙 모달(반투명 배경, 헤더 고정 + 본문 스크롤).
배경 클릭/× 로 닫기(분석 중에는 닫기 비활성). 추천 항목 추가 동작은 동일.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ProjectMemberService 의 notify payload 에 actorName 이 빠져 있어, 프론트
notification.store 가 actorName 부재 시 '누군가'로 폴백하던 문제.
세 member.* 알림(invited/role_changed/removed) payload 에 actorName 추가
(actingUserId 로 사용자명 조회하는 actorName 헬퍼 신설). 활동 피드는 actor
관계로 별도 해결되므로 영향 없음.
런타임 검증: 초대 시 수신자 알림 payload.actorName = 초대자 이름으로 채워짐.
백엔드 build/lint 0·8 tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
저장소 기능 제거 후 남은 '저장소' 표현을 프로젝트로 통일:
- 백엔드: activity/notification/task 주석·ApiResponse 문구, AI 시스템 프롬프트
(SUGGEST_SYSTEM_PROMPT 의 저장소→프로젝트, 무의미해진 'slug=영문이름' 지시 제거),
CONTEXT_REPO_LIMIT→CONTEXT_PROJECT_LIMIT(프로젝트 컨텍스트 한도)
- 프론트: 페이지/스토어 주석 및 TaskCreate/TaskDetail 의 사용자 노출 문구('저장소'→'프로젝트')
- 단, auth OAuth state 'store' 의미의 '저장소'(cookie-state/strategies)는 정당하여 유지
검증: 백엔드 build/lint 0·8 tests, 프론트 type-check/lint/build 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
복잡도를 줄이기 위해 저장소 연동 기능을 전부 제거하고 프로젝트를 단독 단위로 운영한다.
백엔드:
- modules/repo, modules/gitea 모듈 전체 삭제(엔티티/서비스/컨트롤러/DTO/역방향 동기화)
- app.module 에서 RepoModule 제거
- ProjectService: repoCount 집계·repoRepo·"미분류" 시스템 프로젝트(ensureUnassigned/
onApplicationBootstrap)·isSystem 게이트 전부 제거. ProjectResponse 에서 repoCount/isSystem 제거
- Project 엔티티 is_system 컬럼 제거(dev synchronize 가 DROP)
- ProjectModule forFeature 에서 Repo 제거
- Activity 타입 repo.linked/unlinked 제거
- env example 에서 Gitea/REPO_IMPORT 섹션 제거
프론트:
- ProjectReposPage, useRepo 삭제. ProjectTabs 의 '저장소' 탭·repoCount prop 제거
- 4개 페이지의 :repo-count 제거, 라우트 /projects/:id/repos 제거
- types/project·project.store·mock 에서 repoCount/isSystem·Repo 뷰 제거
- ProjectListPage 저장소수/시스템 배지·gitea CSS 제거, activity 의 repo.* 케이스 제거
- types/repo 는 공용 ApiMember 만 유지
- 잔여 '저장소'/Gitea 사용자 문구를 프로젝트로 정리
검증: 백엔드 build/lint 0·8 tests, 프론트 type-check/lint/build 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
세 겹의 제한을 모두 분당 1000회로 상향(테스트 중 429 회피):
- main.ts express-rate-limit: 15분 150회 → 1분 1000회
- 전역 ThrottlerModule: 분당 100회 → 1000회
- 라우트별 @Throttle(login/signup/resend/forgot/reset/ai): 3~20회 → 1000회
각 라우트 주석에 운영 권장값을 남겨 추후 복원/ env 화 용이.
운영 배포 전 적정값으로 되돌릴 것.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ProjectReposPage 가 .crumb .lnk(accent색)·.sep(여백) 를 로컬로 덮어써 다른 탭과
달라 보이던 것을 제거 → 전역 relay.css 의 crumb 스타일(text-2 링크·opacity 구분자)
을 따르도록 통일.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 필드 순서 변경: 표시 제목(name)을 최상단으로(기존 slug 가 최상단)
- 템플릿을 체크박스 → 기존 셀렉트 구조로 복원
(없음[빈 저장소] / {템플릿slug} 템플릿, 가용 시에만 옵션 노출)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 인증 링크(verify-email)의 리다이렉트 대상을 /login?verified → /email-verified?ok=1|0 으로 변경
- 신규 EmailVerifiedPage(/email-verified): 인증 완료/실패 표시 후 창 자동 닫기 시도.
브라우저가 사용자가 연 탭의 close 를 막을 수 있어 안내 문구 + 수동 로그인/가입 링크 병행
- SignupPage 인증 대기 화면에 '인증을 완료했어요 · 로그인하러 가기' 기본 버튼 추가
(다른 탭에서 인증을 마친 뒤 기존 화면에서 로그인 진입)
런타임 검증: 유효 토큰→/email-verified?ok=1·무효→ok=0, 인증 후 로그인 200, SPA 라우트 제공.
백엔드 build/lint 0, 프론트 type-check/lint/build 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 시안(비밀번호 찾기.html) 적용 — 좌측 브랜드 패널 + 우측 폼(로그인 화면과 동일 구조)
- 1단계 이메일 입력(뒤로가기 링크·재설정 링크 보내기) → 2단계 전송 완료
(메일 아이콘·수신 이메일 칩·스팸함 안내 박스·다시 보내기·로그인 복귀)
- 다시 보내기 시 동일 이메일 재요청 + '전송됨 ✓' 일시 피드백
- 시안의 '30분 유효'는 실제 백엔드 토큰 TTL(1시간)에 맞춰 표기
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 백엔드: 앱이 전역 프리픽스 /api 하위라 루트(/) 프로브가 404 → /api/health 프로브로 교정
- 공통: localhost 가 IPv6(::1)로 풀려 IPv4 전용 바인딩에 연결 실패하던 것을 127.0.0.1 고정
- 두 컨테이너가 정상 동작에도 unhealthy 로 표시되던 선존 버그(리팩터 무관)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 데이터 계층 신설: types/project·useProject·project.store (프로젝트 CRUD·페이지네이션)
- useTask/useMember/useActivity 경로를 /projects/:projectId/... 로 전환
- task·my-task·activity 타입/스토어 repoId/repoName → projectId/projectName
- 알림 store 라우팅·문구 프로젝트화, 활동 타입 repo.linked/unlinked·project.* 반영
- 라우팅 /projects/... 전면 교체, / → /projects 리다이렉트
- 페이지 rename Repo*→Project* (List/Create/Detail/Settings/Members/Activity)
- ProjectCreatePage: Gitea 저장소 폼 → 단순 프로젝트 생성 폼으로 재작성
- RepoHeader→ProjectHeader, RepoTabs→ProjectTabs, AppShell 네비 프로젝트화
- 죽은 파일 삭제: repo.store·useRepo·GiteaRepoBar
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
저장소=프로젝트 1:1 구조를 프로젝트 우선 + 프로젝트 1:N 저장소로 전환하는 리팩터의 기반.
기존 코드는 건드리지 않고 추가만 함(기존 저장소 기반 동작 유지).
- Project / ProjectMember 엔티티 (isSystem '미분류' 버킷, admin/member/owner)
- ProjectService: CRUD + 권한 헬퍼(assertProjectMember/Admin) + 부팅 시 '미분류' 시스템 프로젝트 보장
- ProjectMemberService: 초대/역할/제거 (활동·알림 배선은 Activity 이관 단계 TODO)
- /projects, /projects/:id/members 라우트, app.module 등록
- docs/project-refactor-plan.md (리팩터 SSOT)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
통짜 코드 전송 방식이 토큰 비효율·부정확해, 검색 기반(grep으로 좁힌 뒤 관련
스니펫만 판정) 방식으로 재설계하기 위해 일괄 제거. 추후 개선안으로 재작업 예정.
This reverts commit 85ae82f.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
업로드 API 가 기존 taskId 종속이라 막혀 있던 생성 화면 첨부를 2단계 제출로 해결.
선택한 파일을 메모리에 보유하다 업무 생성/수정 후 그 taskId 로 순차 업로드(베스트에포트,
실패 개수 안내). 수정 모드는 기존 첨부 표시·즉시 삭제 + 새 파일 업로드. 25MB 사전 필터.
백엔드 변경 없음(기존 업로드 엔드포인트 재사용).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- POST /ai/suggest-checklist — 저장소 표시제목/설명 + 업무 제목/내용을 Claude 에 보내
카테고리별(필수/추가/보안) 할 일 항목을 JSON 으로 받아 파싱(코드펜스/잡텍스트 방어, 빈 결과 502)
- AiSuggestController + SuggestChecklistDto + AiService.suggestChecklist/parseSuggestions
- 프론트: TaskCreatePage 액션바 "취소 / AI 추천 / 지시 보내기" + 체크리스트 추천 패널
(카테고리별 항목 클릭 추가·전체 추가·중복 스킵), useAi.suggestChecklist(silent)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 컨텍스트에 저장소 목록 추가(RepoService.findAll), slug 빼고 표시 제목만 노출
- AI 응답 마크다운 렌더링: shared/utils/markdown.ts (escapeHtml 후 신뢰 태그만 조립, 의존성 0, XSS 안전 — 굵게/목록/코드/제목/링크)
- 시스템 프롬프트: 간결·이모지/사과 남발 금지·과도한 존칭 지양·마크다운 사용·저장소는 표시제목으로 안내
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
listAssigned/listIssued 가 to-many 조인 + 페이지네이션 정렬에서 DB 컬럼명
'task.due_date' 를 사용해, TypeORM distinct 경로가 컬럼 메타데이터를 못 찾아
"Cannot read properties of undefined (reading 'databaseName')" 로 크래시.
정렬 키를 엔티티 속성명 'task.dueDate' 로 변경 — 내 업무 화면 + AI 컨텍스트 동시 복구.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 시스템 프롬프트를 업무/Relay 한정으로 재작성 — 무관한 주제는 정중히 거절
- 매 요청 시 사용자 컨텍스트 주입: 오늘 날짜(KST) + 담당 업무 최대 30건(제목/상태/마감/체크리스트) + 지시 업무 수
- TaskService.listAssigned/listIssued 활용(AiModule→TaskModule 단방향), 조회 실패해도 채팅 진행
- callClaude(messages, system)로 시그니처 변경(매 메시지 최신 컨텍스트)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- AiConversation·AiMessage 엔티티(user/conversation CASCADE), 제목 자동 생성
- 대화 스코프 API: GET/POST /ai/conversations, GET/:id, POST/:id/messages, DELETE/:id (전부 본인 소유 스코프 → IDOR 차단)
- 클라는 새 메시지 1건만 전송, 서버가 DB 이력으로 Claude 호출(무상태 /ai/chat 폐기)
- Claude 실패 시 사용자 메시지·빈 대화 롤백(고아 데이터 방지)
- 프론트: AgentChatPanel 채팅↔지난 대화 목록 뷰(열기/삭제), ai.store 재작성(목록·현재 대화)
- 다른 기기·재로그인·새로고침에도 기록 유지
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 백엔드 modules/ai: POST /ai/chat — Anthropic Messages API 프록시(키 서버 env 한정, 30s 타임아웃, throttle 20/분, 미설정 503/외부오류 502)
- ChatDto 검증(role enum, content 8000자, 메시지 50개 상한), 모델 CLAUDE_MODEL(기본 claude-sonnet-4-6)
- 프론트 AgentChatPanel: 우측 슬라이드 채팅(말풍선/타이핑/에러/새 대화), plain text 렌더로 XSS 안전
- AppShell 헤더에 AI 버튼 상시 노출 — 모든 화면에서 열림
- 대화 이력은 stores/ai.store(싱글톤)에 보관 → 화면 이동에도 유지, '새 대화'로만 초기화
- TaskDetailPage 의 기존 목업 에이전트 패널 전부 제거(보안 L1 해소)
- env.example 에 CLAUDE_MODEL 추가
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- RefreshSession(패밀리) 테이블 도입 — 한 로그인=1행(currentJti/prevJti/expiresAt)
- /auth/refresh 회전형으로 교체: 현재 jti→새 jti 회전, 옛 토큰 재제출 시 재사용 탐지→패밀리 폐기(401)
- 동시 갱신 오탐 방지: 직전 jti 15초 유예 + 프론트 useApi single-flight refresh
- 로그아웃=해당 세션만 폐기, revokeAllSessions(전 기기) 준비
- tokenVersion·jwt-refresh.strategy·JwtRefreshGuard 제거(refresh는 가드 없이 서비스 직접 처리)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- backend/.env.development.example: CLAUDE_API_KEY 추가(dev 실제와 키 셋 일치)
- backend/.env.production.example: 소셜 OAuth·APP_API_URL/FRONTEND_URL·CLAUDE_API_KEY 추가(운영 placeholder)
- frontend/.env.*.example: 고유 키 없음 + VITE_API_BASE_URL docker 주입 설명 헤더 추가
실제 시크릿/인프라 값은 미포함(전부 placeholder).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DTO 길이 바운드: 업무 content/assigneeIds/checklist ArrayMaxSize·요소 MaxLength, UpdateRepoDto.description MaxLength(300)
- 비밀번호 정책: 8~72자 + 영문·숫자 포함 강제, bcrypt rounds 10→12
- 그랜드페더링을 컷오프(2026-06-20) 이전 생성 계정으로 한정
- multer 업로드 오류를 400(VAL_001)로 매핑
- 운영 환경에서 CORS localhost:3000·Swagger /api-docs 노출 제외
(L1 에이전트 터미널 v-html 은 에이전트 작업 시 함께 처리 — 보류)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- User 엔티티 확장: passwordHash nullable, provider, emailVerified, 인증 토큰 해시/만료(select:false)
- 회원가입: 자동 로그인 폐지 → 미인증 계정 생성 + 인증 메일 발송(MailService 플레이스홀더, 백엔드 로그로 링크 출력)
- 이메일 인증: GET /auth/verify-email(해시 매칭+만료 검사) → 프론트로 리다이렉트, 재발송 엔드포인트
- 로그인 게이트: 미인증 로컬 403 차단, 소셜 전용 계정은 소셜 로그인 안내
- 소셜 OAuth: passport google/kakao 전략(키 미설정 시 자동 비활성), 이메일로 계정 매칭/생성
- 기존 로컬 계정 그랜드페더링(부팅 시 인증 완료 보정)
- 프론트: 가입 후 인증 메일 안내 화면, 로그인 소셜 버튼 실연동 + 인증 결과 배너
- env(APP_API_URL/FRONTEND_URL/GOOGLE_*/KAKAO_*) 문서화, 문서·계약 갱신
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- GET /repos/name-available?slug= 추가(:repoId 앞 선언, Relay slug 중복만 검사)
- 생성 폼: 형식 즉시 검사 + 350ms 디바운스 서버 조회로 상태별(빈칸/형식위반/확인중/사용가능/중복) 색·아이콘 표기
- 항상 떠 있던 정적 "사용 가능한 이름입니다" 표시 제거
- 조직 이니셜 소유자 아바타 제거
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RepoSyncService 의 @Cron 을 EVERY_HOUR→EVERY_10_MINUTES 로 변경.
Gitea 직접 변경(이름/공개범위/브랜치)의 Relay 반영 지연이 최대 1시간→10분으로 단축.
기동 직후 1회 동기화는 유지, 중복 실행 가드도 그대로.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
설정 탭/권한:
- findOne/update 응답에 현재 사용자 권한(canManage=admin, isOwner=소유자) 포함
(목록은 캐시·사용자 무관이라 미포함). RepoTabs 가 canManage 일 때만 설정 탭 노출,
비관리자의 설정 화면 직접 진입은 저장소 상세로 리다이렉트. 입력 비활성화 처리 제거.
- 저장소 삭제는 isOwner 일 때만(비소유자 admin 엔 비활성+안내).
설정 로직 버그/정리:
- update 가 이전 값과 비교해 실제 변경분만 활동/Gitea/캐시 반영
(무변경 저장 시 허위 'renamed'/'visibility_changed' 활동이 쌓이던 버그 수정).
- 위험구역의 미구현 'Gitea 저장소 이름 변경'(slug rename) 스텁 제거.
- 조직 owner 아바타(하드코딩 'M') 제거.
Gitea 역동기화(Gitea=SSOT):
- 동기화 매칭 시 slug(rename)·공개범위·기본 브랜치를 Gitea 기준으로 끌어옴
→ Gitea 에서 직접 바꿔도 Relay 가 따라가고 slugName 도 갱신돼 쓰기 경로 일치
(rename 후 옛 이름으로 쓰기 호출이 404 나던 desync 해소). 중복 매칭 가드 추가.
- 표시명/설명은 Gitea 단일 description 합본 구조라 Relay-SSOT 유지(외부 편집 시 깨짐 방지).
- GiteaService.updateRepo 에 default_branch 추가 + update 가 브랜치도 Gitea 에 푸시(양방향 일치).
검증: 백엔드 build/lint 0·20 tests, 프론트 type-check/lint/build 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>