fix: Plan/Dev/Reviewer subtask file path 일관화 (slug + indexing)#298
Conversation
…ck divergence) (T1) createPlan 응답의 `plan.slug` 를 우선 사용해 Plan chat 안내 파일명이 Dev chat / Reviewer / loadTaskFileTitles 와 같은 backend slug (collision counter 결과 포함) 를 사용하도록 통일. 외부 사용자 보고: 한글 title plan 에서 Plan chat 은 `plan` (frontend fallback), Dev chat 은 `plan-10` (backend collision) 로 파일명 mismatch 발생. `getPlanSlug` 가 이미 `plan.slug || slugifyPlanTitle(title)` 패턴이라 DB slug 없을 때만 frontend fallback. 직접 호출 미사용으로 `slugifyPlanTitle` import 제거. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ith Plan chat / loadTaskFileTitles) (T2) Dev chat handoff 의 file path 만 0-indexed (`plan-10-task-00.md`) 였던 회귀 수정. 다른 generator 3 곳 (PlanProposalCard / loadTaskFileTitles / reviewWorkflow) 은 모두 1-indexed. DB `idx` 컬럼 자체는 0-indexed 보존 (INV-SPC-3) — file path 변환 시점에서만 `+1`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…off (T3)
Plan chat 의 \`docs/plans/{{slug}}.md — 전체 계획서\` 와 대칭으로 Dev chat
prompt 에도 전체 계획서 path 명시. Developer 가 task 파일만 보는 게 아니라
전체 plan 도 read 할 수 있도록 진입점 제공.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
planFilePath.test.ts — Plan chat (PlanProposalCard 의 path 생성 contract) / Dev chat (approveAndStartImplementation) / Reviewer load (loadTaskFileTitles) / autoRecoverSubtasks 4 generator 가 같은 plan + subtasks input 에 대해 동일 path 생성하는지 cross-validate. - getPlanSlug 의 DB slug 우선 + null/undefined fallback + 한글-only title 의 literal "plan" fallback - approveAndStartImplementation prompt 안 1-indexed file path + 전체 계획서 path (T2 + T3) - loadTaskFileTitles 의 read 요청 path 가 1-indexed + DB slug - autoRecoverSubtasks regex 가 0-indexed + 1-indexed 양쪽 매칭 (INV-SPC-4) - 3 case cross-generator: 한글 title + "plan-10", 영문 title + 파생 slug, slug 미제공 시 fallback 13 신규 테스트. invoke / planApi / artifactApi mock. React 컴포넌트는 import 시 DOM 의존하므로 path 생성 contract 만 mirror. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T4 의 mock subtasks 가 `"pending"` 을 status 로 썼는데 실제 type 은 `"todo" | "approved" | "in_progress" | "done" | "abandoned"`. tsc 에러 수정. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request ensures cross-generator file path consistency for plans and subtasks by aligning slug resolution and switching to 1-based indexing for subtask file names. It also introduces a comprehensive test suite to prevent regressions. The review feedback points out a potential issue in the tests where an unawaited background promise inside approveAndStartImplementation could cause test flakiness, and suggests mocking the helper function to prevent it.
| import { getPlanSlug, slugifyPlanTitle } from "./helpers"; | ||
| import { approveAndStartImplementation } from "./implementWorkflow"; | ||
| import { autoRecoverSubtasks, loadTaskFileTitles } from "./planWorkflowService"; |
There was a problem hiding this comment.
approveAndStartImplementation 함수 내부에서 createArchitectDecisionArtifact가 await 없이 비동기(fire-and-forget)로 호출됩니다. 이로 인해 테스트가 종료된 후에도 백그라운드에서 listSubtasks나 createArtifact 등의 비동기 작업이 계속 실행될 수 있으며, 이는 다른 테스트 케이스의 모의 함수(mock) 상태를 오염시키거나 간헐적인 테스트 실패(flaky test)를 유발할 수 있습니다.
./helpers 모듈을 모의(mock)하여 createArchitectDecisionArtifact를 빈 함수로 대체하면 백그라운드 비동기 작업을 안전하게 차단하고 테스트의 일관성을 높일 수 있습니다.
vi.mock("./helpers", async (importOriginal) => {
const actual = await importOriginal<typeof import("./helpers")>();
return {
...actual,
createArchitectDecisionArtifact: vi.fn(async () => undefined),
};
});
import { getPlanSlug, slugifyPlanTitle } from "./helpers";
import { approveAndStartImplementation } from "./implementWorkflow";
import { autoRecoverSubtasks, loadTaskFileTitles } from "./planWorkflowService";…onsistency hotfix 매니페스트 4 곳 + Cargo.lock bump. CHANGELOG entry 추가. 핵심 fix (PR #298, merge bdcdbd9): - slug source = DB plan.slug (4 generator 일관) - file path 1-indexed (loadTaskFileTitles 와 일관) - Dev chat prompt 에 전체 계획서 path 추가 외부 사용자 메신저 보고 — Plan→Dev handoff 의 subtask 파일명 mismatch 회복. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…handle + test mock (#299) * fix(openai-compat): vllm base url /v1 suffix normalize (PR #297 review, T1 high) `VLLM_ENDPOINT=http://host:8000/v1` 형식 (vLLM 공식 docs 안내 패턴) 시 `format!("{}/v1/models", ...)` 가 `http://host:8000/v1/v1/models` 가 되어 discover_vllm() 이 실패하던 회귀. agent_detect.rs::probe_vllm 의 동일 정책 (`/v1` 끝나면 그대로, 아니면 append) 를 `normalize_vllm_base()` helper 로 추출해 두 호출부가 같은 정책을 공유하게 한다. URL 생성은 `format!("{}/models", base)` 로 통일. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(openai-compat): filter empty api key tokens (3 sites, PR #297 review, T2+T3+T4) `VLLM_API_KEY=""` 또는 `LMSTUDIO_API_KEY=""` 빈 문자열 시 현재 그대로 헤더에 추가되어 `Authorization: Bearer ` (token 없음) 가 송신되어 일부 vLLM / LM Studio 환경에서 401 을 반환하던 회귀. 3 호출부에 동일한 `.ok().filter(|t| !t.is_empty())` 패턴을 적용: - discover_vllm — 모델 목록 GET (T2) - stream_run_with_base — chat completion POST (T3) - stream_run_no_tools_with_base — fallback POST (T4) 빈 토큰은 헤더에서 완전히 누락 (로컬 비보호 인스턴스 호환). 보호된 인스턴스용 토큰이 있으면 그대로 송신. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(executor): Handle::try_current for vllm participant (PR #297 review, T5) `tokio::runtime::Handle::current()` 는 non-Tokio context 시 panic. spawn_blocking 안에서 호출되므로 일반적으로는 runtime handle 이 존재하지만, 호스트 환경 (예: test 또는 직접 호출) 에서 graceful fallback 이 필요. `try_current()` 후 `Err(_)` 시 명시적 AppError 로 변환 — commands/agents.rs:621 의 동일 패턴과 일관. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(plan-file-path): mock helpers to prevent fire-and-forget flakiness (PR #298 review, T6) `approveAndStartImplementation` 내부 `createArchitectDecisionArtifact(plan)` 는 fire-and-forget 으로 호출되어 test 안에서 비결정적 mock 호출 순서를 만들 수 있음. `vi.mock("./helpers", async (importOriginal) => ...)` 패턴으로 `createArchitectDecisionArtifact` 만 `vi.fn(async () => undefined)` 로 대체. 나머지 helper (getPlanSlug / slugifyPlanTitle / buildPlanContext / createAndLinkBranch …) 는 actual 구현 그대로 사용해 cross-generator 일관성 검증 의미를 보존. 13 test 모두 통과 (Test Files 1 passed, Tests 13 passed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: dghong <d9ng@outlook.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
외부 사용자 메신저 보고 회복 — 같은 plan 의 같은 subtask 가 Plan chat 안내 vs Dev chat handoff 에서 다른 파일명 으로 나오던 회귀 수정. Developer 가 Architect 가 작성한 파일을 못 찾고 hallucination 또는 작업 실패하던 문제.
Root cause 2 axis:
slugifyPlanTitle(title)(frontend fallback "plan") 사용. 나머지는 모두 DBplan.slug(backend collision counter 결과 "plan-10").s.idxraw). Plan chat / loadTaskFileTitles / Reviewer 는 1-indexed.Plan / 핸드오프
변경 요약
src/components/tunaflow/chat/PlanProposalCard.tsxslugifyPlanTitle(proposal.title)→getPlanSlug({ slug: plan.slug, title: proposal.title }). DB slug 우선, fallback 유지src/lib/workflow/implementWorkflow.tsString(s.idx).padStart(2, "0")→String(s.idx + 1).padStart(2, "0"). file path 만 1-indexed (DB idx 자체는 0-indexed 보존 — INV-SPC-3)src/lib/workflow/implementWorkflow.ts**전체 계획서**: \docs/plans/${slug}.md`` 추가. Plan chat 과 대칭src/lib/workflow/planFilePath.test.ts(신규, 13 tests)Verification
npx tsc --noEmit— PASS (no output)npx vitest run— 47 files / 491 tests pass (baseline 478 + T4 신규 13)cd src-tauri && cargo check— PASS (변경 0, 회귀 가드용 baseline 확인)회귀 가드 (grep 결과)
DO NOT 영역 (backend
slugify_title/find_unique_slug/ DB schema / Architect·Reviewer·Developer prompt template / loadTaskFileTitles·autoRecoverSubtasks·reviewWorkflow path 패턴) 모두 변경 0.Test Plan
CI 정책
Frontend 한정 (cross-platform 회귀 위험 0). PR 직후 admin merge.
🤖 Generated with Claude Code