Skip to content

feat: new command system - #8

Open
Just-gomin wants to merge 1 commit into
mainfrom
feat/new-command-system
Open

feat: new command system#8
Just-gomin wants to merge 1 commit into
mainfrom
feat/new-command-system

Conversation

@Just-gomin

Copy link
Copy Markdown
Owner

New Command System

  • 기존 /mvp 명령에서 세분화된 명령 체계로 변경합니다.
  • 프로젝트 생성: /tinytree new [light/full] [아이디어]
  • 스레드 내 진행 상태 관리:
    • /tinytree [cancel | status | rename]

@Just-gomin Just-gomin self-assigned this Feb 11, 2026
@claude

claude Bot commented Feb 11, 2026

Copy link
Copy Markdown

PR 리뷰: feat/new-command-system

전반적으로 아키텍처 설계가 명확하고 책임 분리가 잘 되어 있습니다. 아래에 발견한 이슈와 개선 제안을 공유합니다.


🐛 버그 / 잠재적 문제

1. NewLightCommand / NewFullCommandfinally에서 세션 조기 삭제

src/slack/commands/new-light.command.ts

} finally {
  this.sessionStore.deleteByUserId(userId); // ← 문제
}

cancelProject()에서 SIGTERM → 5초 후 SIGKILL을 보내는 비동기 타이머가 있는데, finally가 먼저 실행되어 세션을 삭제해버리면 updateStatus(session.requestId, 'cancelled')가 세션을 찾지 못합니다. 취소 흐름에서 세션이 cancelled로 기록되지 않을 수 있습니다.

제안: 세션 삭제 전에 상태를 확인하거나, cancelled 상태 전환 후 별도 정리 단계를 두는 것을 고려해주세요.


2. ProjectService.cancelProject()processRef 참조 문제

src/project/project.service.ts

cancelProject(userId: string): boolean {
  const session = this.sessionStore.findByUserId(userId);
  if (!session?.processRef) return false;

  session.processRef.kill('SIGTERM');
  setTimeout(() => {
    if (!session.processRef?.killed) {
      session.processRef?.kill('SIGKILL');
    }
  }, 5000);
  ...
}

ProjectSession.processRef가 현재 ClaudeService.generateMVP()로 위임되고 있는데, processRef를 실제로 세션에 저장하는 코드가 이 PR에 없습니다. session.processRef는 항상 undefined이므로 cancelProject()는 항상 false를 반환합니다.

제안: ClaudeService.generateMVP() 내부에서 ChildProcess를 생성할 때 sessionStore.update(requestId, { processRef })를 호출하거나, 이 이슈를 명시적인 TODO로 남겨주세요.


3. ThreadStore — 메모리 누수 가능성

src/slack/utils/thread-store.ts

ThreadStoredelete()SlackMessageUtil 어디에서도 호출되지 않습니다. 프로젝트가 done/error/cancelled로 끝난 후에도 thread_ts가 메모리에 남습니다. GCP e2-micro 환경에서는 장기 운영 시 누수가 될 수 있습니다.

제안: NewLightCommand / NewFullCommandfinally에서 threadStore.delete(requestId)를 호출해주세요.


4. SlackServiceonModuleInitCommandRegistryService.onModuleInit 실행 순서 보장 없음

CommandRegistryService.onModuleInit()에서 this.slackService.getApp().command()를 호출하는데, SlackService.onModuleInit()app.start()가 먼저 완료되어야 합니다. NestJS의 OnModuleInit 실행 순서는 모듈 선언 순서에 따라 달라지므로 app.start() 전에 command() 등록이 시도될 수 있습니다.

현재 waitForReady()는 메시지 전송 시에만 사용되고, 커맨드 등록 시에는 사용되지 않습니다. Bolt.js에서 app.start() 이전의 app.command() 등록은 허용되지만, 의도된 동작인지 확인이 필요합니다.


⚠️ 코드 품질 개선 제안

5. NewLightCommand / NewFullCommand 코드 중복

두 파일은 mode: 'light' / mode: 'full' 문자열과 로그 메시지를 제외하면 완전히 동일합니다. handle() 로직을 공통 base class나 함수로 추출하면 중복을 제거할 수 있습니다.

// 예시
abstract class BaseNewCommand {
  abstract readonly mode: ProjectMode;
  abstract readonly startMessage: string;
  
  async handle(ctx: TinyTreeCommandContext): Promise<void> { ... }
}

6. RenameCommand — 미완성 상태로 병합됨

src/slack/commands/rename.command.ts

// Step 5 (ProjectService 구현) 이후 교체 예정
await this.slackMessage.postToChannel(
  ctx.channelId,
  `✏️ 이름 변경 기능은 아직 구현 중입니다. ...`,
);

사용자에게 구현 예정 메시지를 노출하는 미완성 핸들러가 포함되어 있습니다. 이 커맨드를 CommandRegistryService에 등록하지 않거나, unknown 처리로 라우팅하는 것을 고려해주세요.


7. sendUnknownGuidectx.subCommand.type 체크 로직 혼란

src/slack/commands/command-registry.service.ts

const text = ctx.subCommand.type === 'unknown' ? ctx.subCommand.raw : '';

이 메서드는 case 'unknown'에서만 호출되므로 ctx.subCommand.type은 항상 'unknown'입니다. 조건부 삼항 연산자 대신 ctx.subCommand.raw를 직접 사용하는 것이 더 명확합니다.


8. GlobalExceptionFilter — Slack 알림 제거

src/common/filters/global-exception.filter.ts에서 SlackService 의존성이 제거되어 전역 예외가 이제 로그에만 남고 사용자에게 알림이 가지 않습니다. 의도된 변경이라면 괜찮지만, 운영 중 오류 발생 시 사용자 피드백 채널이 없어지는 점을 감안해주세요.


🧪 테스트 커버리지

새로 추가된 파일 중 테스트가 없는 것들이 있습니다:

파일 테스트 여부
ProjectSessionStore
ProjectService
TinyTreeCommandHandler.parseSubCommand ❌ (경계 케이스: 빈 문자열, idea 없는 new light)
SlackMessageUtil
ThreadStore

CLAUDE.md에서 "단위 테스트 필수"로 명시되어 있으며, 특히 parseSubCommand의 파싱 로직과 ProjectSessionStore.isUserBusy 경계 케이스는 회귀 방지를 위해 테스트가 필요합니다.


✅ 잘 된 점

  • SubCommand 디스크리미네이티드 유니온 설계 — 타입 안전한 분기 처리 우수
  • isUserBusy() 체크로 동시 실행 방지 처리
  • /mvp 레거시 포워딩 패턴 + deprecated 안내 메시지 — 하위 호환성 고려 좋음
  • withRetry 활용으로 Slack API 호출 안정성 확보
  • waitForReady() 타임아웃 처리 포함

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant