From 32a87d8f62df27c5333d5850eda74ed27e016be2 Mon Sep 17 00:00:00 2001 From: ttipo Date: Sun, 21 Jun 2026 01:57:06 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20=EC=9E=94=EC=A1=B4=20'=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=EC=86=8C'=20=EC=A3=BC=EC=84=9D=C2=B7=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=EB=A5=BC=20'=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8'?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 저장소 기능 제거 후 남은 '저장소' 표현을 프로젝트로 통일: - 백엔드: 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) --- .../modules/activity/entities/activity.entity.ts | 2 +- backend/src/modules/ai/ai.module.ts | 2 +- backend/src/modules/ai/ai.service.ts | 15 +++++++++------ .../notification/entities/notification.entity.ts | 8 ++++---- backend/src/modules/task/task.controller.ts | 5 ++++- backend/src/modules/task/task.service.ts | 6 +++--- frontend/src/composables/useMember.ts | 2 +- frontend/src/pages/relay/LoginPage.vue | 2 +- frontend/src/pages/relay/ProjectDetailPage.vue | 10 +++++----- frontend/src/pages/relay/ProjectListPage.vue | 2 +- frontend/src/pages/relay/SignupPage.vue | 2 +- frontend/src/pages/relay/TaskCreatePage.vue | 12 ++++++------ frontend/src/pages/relay/TaskDetailPage.vue | 4 ++-- frontend/src/stores/activity.store.ts | 4 ++-- frontend/src/stores/member.store.ts | 2 +- 15 files changed, 42 insertions(+), 36 deletions(-) diff --git a/backend/src/modules/activity/entities/activity.entity.ts b/backend/src/modules/activity/entities/activity.entity.ts index f9c59f5..2a32fab 100644 --- a/backend/src/modules/activity/entities/activity.entity.ts +++ b/backend/src/modules/activity/entities/activity.entity.ts @@ -45,7 +45,7 @@ export class Activity { @JoinColumn({ name: 'project_id' }) project!: Relation; - // 업무 활동이면 업무 순번(seq) — 업무 상세 활동 탭 필터용. 저장소 활동이면 null + // 업무 활동이면 업무 순번(seq) — 업무 상세 활동 탭 필터용. 프로젝트 활동이면 null // (Task FK 대신 seq 를 저장해 업무 삭제와 무관하게 활동을 유지) @Index() @Column({ name: 'task_seq', type: 'int', nullable: true }) diff --git a/backend/src/modules/ai/ai.module.ts b/backend/src/modules/ai/ai.module.ts index 28e03c4..9077339 100644 --- a/backend/src/modules/ai/ai.module.ts +++ b/backend/src/modules/ai/ai.module.ts @@ -9,7 +9,7 @@ import { AiConversation } from './entities/ai-conversation.entity'; import { AiMessage } from './entities/ai-message.entity'; // AI 모듈 — Claude(Anthropic) 채팅 프록시 + 대화/메시지 영속. -// Task/ProjectModule 을 가져와 사용자 업무·저장소·프로젝트 컨텍스트를 시스템 프롬프트에 주입한다. +// Task/ProjectModule 을 가져와 사용자 업무·프로젝트 컨텍스트를 시스템 프롬프트에 주입한다. @Module({ imports: [ TypeOrmModule.forFeature([AiConversation, AiMessage]), diff --git a/backend/src/modules/ai/ai.service.ts b/backend/src/modules/ai/ai.service.ts index f7114f0..1b52cd3 100644 --- a/backend/src/modules/ai/ai.service.ts +++ b/backend/src/modules/ai/ai.service.ts @@ -48,8 +48,8 @@ const MAX_TOKENS = 1024; const TITLE_MAX = 60; // 컨텍스트로 주입할 담당 업무 최대 건수(토큰 절약) const CONTEXT_TASK_LIMIT = 30; -// 컨텍스트로 주입할 저장소 최대 개수 -const CONTEXT_REPO_LIMIT = 30; +// 컨텍스트로 주입할 프로젝트 최대 개수 +const CONTEXT_PROJECT_LIMIT = 30; // 주제 한정(가드레일) + 데이터 근거 지시 const SYSTEM_PROMPT = [ @@ -64,7 +64,7 @@ const SYSTEM_PROMPT = [ '- 목록이나 강조가 필요하면 마크다운(굵게 **텍스트**, 목록 - 또는 1.)을 사용하세요. 표·복잡한 서식은 피하세요.', "- 아래 '사용자 데이터' 에 근거해 답하고, 데이터에 없는 내용은 추측하지 말고 모른다고 하세요.", "- 마감/일정은 제공된 '오늘 날짜' 를 기준으로 계산하세요.", - '- 저장소는 영문 내부 이름이 아니라 한글 표시 제목으로 설명하세요.', + '- 프로젝트는 한글 표시 제목으로 설명하세요.', ].join('\n'); // 업무 상태 → 한국어 라벨(컨텍스트 표기용) @@ -79,13 +79,13 @@ const STATUS_LABEL: Record = { // 할 일 추천용 시스템 프롬프트 — JSON 으로만 응답하도록 강제 const SUGGEST_SYSTEM_PROMPT = [ '당신은 소프트웨어/업무 기획 보조자입니다.', - "주어진 저장소와 업무 정보를 바탕으로, 이 업무(기능)를 완성하기 위해 필요한 '할 일(체크리스트)' 항목을 제안합니다.", + "주어진 프로젝트와 업무 정보를 바탕으로, 이 업무(기능)를 완성하기 위해 필요한 '할 일(체크리스트)' 항목을 제안합니다.", '', '[규칙]', '- 반드시 아래 JSON 형식으로만 응답하세요. 설명·인사·마크다운 코드펜스 없이 순수 JSON 만 출력합니다.', '- 카테고리는 "필수 기능", "추가 기능", "보안 기능" 을 기본으로 하되 업무 성격에 맞게 가감하세요.', '- 각 항목은 한국어로 간결한 한 문장(체크리스트 항목)으로 작성합니다.', - '- 업무·저장소 맥락에 구체적으로 맞추고 일반론은 피하세요. 카테고리당 3~6개가 적당합니다.', + '- 업무·프로젝트 맥락에 구체적으로 맞추고 일반론은 피하세요. 카테고리당 3~6개가 적당합니다.', '', '[형식]', '{"groups":[{"category":"필수 기능","items":["...","..."]},{"category":"보안 기능","items":["..."]}]}', @@ -367,7 +367,10 @@ export class AiService { CONTEXT_TASK_LIMIT, ); const issued = await this.taskService.listIssued(userId, 1, 1); - const projects = await this.projectService.findAll(1, CONTEXT_REPO_LIMIT); + const projects = await this.projectService.findAll( + 1, + CONTEXT_PROJECT_LIMIT, + ); const lines = [`오늘 날짜: ${today} (KST)`, '']; diff --git a/backend/src/modules/notification/entities/notification.entity.ts b/backend/src/modules/notification/entities/notification.entity.ts index 24b5ba6..194b227 100644 --- a/backend/src/modules/notification/entities/notification.entity.ts +++ b/backend/src/modules/notification/entities/notification.entity.ts @@ -9,7 +9,7 @@ import { } from 'typeorm'; import { User } from '../../user/entities/user.entity'; -// 알림 타입 — 수신자 관점의 이벤트(활동 피드와 별개: 활동은 저장소 전체 로그, 알림은 개인 수신함) +// 알림 타입 — 수신자 관점의 이벤트(활동 피드와 별개: 활동은 프로젝트 전체 로그, 알림은 개인 수신함) export type NotificationType = | 'task.assigned' // 나에게 업무가 지시됨 | 'task.unassigned' // 업무 담당에서 제외됨 @@ -20,9 +20,9 @@ export type NotificationType = | 'task.due_changed' // 내가 담당한 업무의 마감이 변경됨 | 'task.removed' // 내 관련 업무가 삭제됨 | 'task.commented' // 내 관련 업무에 댓글/답글이 달림 - | 'member.invited' // 저장소에 멤버로 초대됨 - | 'member.role_changed' // 저장소 내 내 역할이 변경됨 - | 'member.removed'; // 저장소에서 제외됨 + | 'member.invited' // 프로젝트에 멤버로 초대됨 + | 'member.role_changed' // 프로젝트 내 내 역할이 변경됨 + | 'member.removed'; // 프로젝트에서 제외됨 // 알림 — 사용자별 수신함. 표시 문구/아이콘은 프론트가 type+payload 로 조립한다. @Entity('notifications') diff --git a/backend/src/modules/task/task.controller.ts b/backend/src/modules/task/task.controller.ts index 6e9397f..2de1b57 100644 --- a/backend/src/modules/task/task.controller.ts +++ b/backend/src/modules/task/task.controller.ts @@ -61,7 +61,10 @@ export class TaskController { summary: '업무 목록 (세그먼트/검색 + 페이지네이션 + 세그먼트 카운트)', }) @ApiResponse({ status: 200, description: '목록 조회 성공' }) - @ApiResponse({ status: 404, description: '저장소를 찾을 수 없음 (RES_001)' }) + @ApiResponse({ + status: 404, + description: '프로젝트를 찾을 수 없음 (RES_001)', + }) list( @Param('projectId') projectId: string, @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, diff --git a/backend/src/modules/task/task.service.ts b/backend/src/modules/task/task.service.ts index 08aeace..b85cb92 100644 --- a/backend/src/modules/task/task.service.ts +++ b/backend/src/modules/task/task.service.ts @@ -312,7 +312,7 @@ export class TaskService { dto.assigneeIds, ); - // 저장소 내 다음 순번 채번 + // 프로젝트 내 다음 순번 채번 const last = await this.taskRepo.findOne({ where: { project: { id: project.id } }, order: { seq: 'DESC' }, @@ -598,7 +598,7 @@ export class TaskService { taskSeq, }); - // 알림 — 지시자/담당자에게 '업무 삭제'(업무가 사라지므로 프론트는 저장소로 이동) + // 알림 — 지시자/담당자에게 '업무 삭제'(업무가 사라지므로 프론트는 프로젝트로 이동) await this.notification.notify({ recipientIds: removalRecipients, actorId: actingUserId, @@ -1065,7 +1065,7 @@ export class TaskService { } } - // projectId 으로 저장소 로딩, 없으면 404 + // projectId 으로 프로젝트 로딩, 없으면 404 private async getProjectOrThrow(projectId: string): Promise { const project = await this.projectRepo.findOne({ where: { id: projectId }, diff --git a/frontend/src/composables/useMember.ts b/frontend/src/composables/useMember.ts index 80268e0..0eba0e8 100644 --- a/frontend/src/composables/useMember.ts +++ b/frontend/src/composables/useMember.ts @@ -11,7 +11,7 @@ import type { Paginated } from '@/types/pagination' export function useMember() { const api = useApi() - // 멤버 목록 (멤버 또는 공개 저장소) — 백엔드가 페이지네이션 { items, total, ... } 반환 + // 멤버 목록 (멤버 또는 공개 프로젝트) — 백엔드가 페이지네이션 { items, total, ... } 반환 async function list( projectId: string, page = 1, diff --git a/frontend/src/pages/relay/LoginPage.vue b/frontend/src/pages/relay/LoginPage.vue index 95094b7..4b37e34 100644 --- a/frontend/src/pages/relay/LoginPage.vue +++ b/frontend/src/pages/relay/LoginPage.vue @@ -29,7 +29,7 @@ const verifyBanner = computed<{ ok: boolean; text: string } | null>(() => { return null }) -// 로그인 처리 — 성공 시 항상 저장소 목록으로 진입(이전 화면/딥링크로 복귀하지 않음) +// 로그인 처리 — 성공 시 항상 프로젝트 목록으로 진입(이전 화면/딥링크로 복귀하지 않음) async function onLogin() { if (submitting.value) return submitting.value = true diff --git a/frontend/src/pages/relay/ProjectDetailPage.vue b/frontend/src/pages/relay/ProjectDetailPage.vue index 73096a5..2d2e8c9 100644 --- a/frontend/src/pages/relay/ProjectDetailPage.vue +++ b/frontend/src/pages/relay/ProjectDetailPage.vue @@ -19,7 +19,7 @@ const projectStore = useProjectStore() const taskStore = useTaskStore() const { tasks, counts, hasMore, loadingMore, loading } = storeToRefs(taskStore) -// 저장소 헤더/진행률 — API 연동 +// 프로젝트 헤더/진행률 — API 연동 const repo = ref(null) async function loadProject() { try { @@ -56,7 +56,7 @@ async function loadMoreTasks() { } } -// 저장소 전환 시 헤더 + 목록 리셋 로드 +// 프로젝트 전환 시 헤더 + 목록 리셋 로드 watch( projectId, () => { @@ -66,7 +66,7 @@ watch( { immediate: true }, ) -// 세그먼트 변경 시 목록만 리셋 로드(저장소 재조회 불필요) +// 세그먼트 변경 시 목록만 리셋 로드(프로젝트 재조회 불필요) watch(segment, loadTasks) // 검색어는 디바운스 후 리셋 로드(연속 입력 시 호출 폭주 방지) @@ -116,7 +116,7 @@ const STATUS_LABEL: Record = { {{ repo.name }} - + = { - +
color: var(--text-2); } -/* 저장소 행 */ +/* 프로젝트 행 */ .repo-row { display: flex; align-items: center; diff --git a/frontend/src/pages/relay/SignupPage.vue b/frontend/src/pages/relay/SignupPage.vue index 32cf44f..887da74 100644 --- a/frontend/src/pages/relay/SignupPage.vue +++ b/frontend/src/pages/relay/SignupPage.vue @@ -103,7 +103,7 @@ const points = [ 팀에 합류하고
업무 지시를 시작하세요
- 계정을 만들면 저장소에 초대되어, 받은 업무를 확인하고 직접 업무를 지시할 수 있습니다. + 계정을 만들면 프로젝트에 초대되어, 받은 업무를 확인하고 직접 업무를 지시할 수 있습니다.
(null) async function loadProject() { try { @@ -163,7 +163,7 @@ const aiSuggestions = ref(null) const aiError = ref(null) const canRequestAi = computed(() => !aiSuggesting.value && title.value.trim().length > 0) -// 저장소 설명 + 제목/내용을 분석해 카테고리별 할 일 추천을 받는다 +// 프로젝트 설명 + 제목/내용을 분석해 카테고리별 할 일 추천을 받는다 async function requestAiSuggestions() { if (!canRequestAi.value) return aiSuggesting.value = true @@ -326,14 +326,14 @@ function goBack() { to="/projects" class="lnk" > - 저장소 + 프로젝트 / - {{ repo?.name ?? '저장소' }} + {{ repo?.name ?? '프로젝트' }} / {{ isEdit ? '수정' : '새 업무' }} @@ -353,7 +353,7 @@ function goBack() { class="btn ai" type="button" :disabled="!canRequestAi" - title="저장소·업무 내용을 분석해 할 일을 추천합니다" + title="프로젝트·업무 내용을 분석해 할 일을 추천합니다" @click="requestAiSuggestions" > - 저장소와 업무 내용을 분석하고 있어요… + 프로젝트와 업무 내용을 분석하고 있어요…
- 저장소 + 프로젝트 / { const days = ref([]) const loading = ref(false) const activityApi = useActivity() - // 저장소 활동 조회 → 날짜 그룹으로 묶기(목록은 최신순이라 같은 날은 연속) + // 프로젝트 활동 조회 → 날짜 그룹으로 묶기(목록은 최신순이라 같은 날은 연속) async function fetch(projectId: string): Promise { loading.value = true try { diff --git a/frontend/src/stores/member.store.ts b/frontend/src/stores/member.store.ts index 3387fbe..6a86362 100644 --- a/frontend/src/stores/member.store.ts +++ b/frontend/src/stores/member.store.ts @@ -27,7 +27,7 @@ function toMemberView(api: ApiProjectMember, currentUserId: string | null): Memb } } -// 프로젝트 멤버 스토어 — 현재 저장소의 멤버 목록 상태 + 초대/역할변경/제거 액션 +// 프로젝트 멤버 스토어 — 현재 프로젝트의 멤버 목록 상태 + 초대/역할변경/제거 액션 export const useMemberStore = defineStore('member', () => { const members = ref([]) const loading = ref(false)