diff --git a/app/agent.py b/app/agent.py index fa18013..3a39b74 100644 --- a/app/agent.py +++ b/app/agent.py @@ -210,8 +210,9 @@ def plan(self, raw_input: str) -> Plan: - Group a task title, its explanatory lines, and its completion criteria into one task. - Each genuinely independent deliverable becomes a separate task. Do not turn every detail or completion-criteria bullet into a separate task. - By default, separate standalone action lines are separate tasks, especially short unindented lines like `cowork 고도화` and `지라 + 깃허브 연동`. -- Group lines only when their structure clearly marks them as context for a task, such as `작업 내용:`, `상세 내용:`, `완료 조건:`, or bullets under those sections. -- Each repeated `제목:` line starts a new task; attach the following `작업 내용:` and `완료 조건:` sections to that task until the next `제목:` line. +- Group lines only when their structure clearly marks them as context for a task, such as `작업 내용:`, `목적/배경:`, `상세 내용:`, `완료 조건:`, or bullets and checklist items under those sections. +- Each repeated `제목:` line starts a new task; attach the following `작업 내용:`, `목적/배경:`, `상세 내용:`, and `완료 조건:` sections to that task until the next `제목:` line. +- Treat `- [ ]` lines under `완료 조건:` as acceptance criteria, not as separate tasks, and preserve them under the `## 완료 조건` heading in description. - Never merge separate standalone action lines merely because they belong to the same project or theme. - A task may reference multiple related source lines. Split a single source line only when it clearly contains separate deliverables. - Exclude completed items (- [x], strikethrough, 완료), schedules/meetings, and notes/thoughts. diff --git a/app/static/index.html b/app/static/index.html index 862ee0f..bdad0f9 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -36,31 +36,109 @@

할 일 올리기

-

할 일 입력 가이드

-

아래 가이드를 본인의 AI에게 전달하면 Cowork에 맞는 형식으로 정리할 수 있습니다.

+

AI와 대화해서 티켓을 구체화하세요

+

가이드를 개인 AI에 전달하고 작업 아이디어를 입력한 뒤, 최종 결과만 Cowork에 붙여넣으세요.

-
다음 내용을 Cowork에 붙여넣을 수 있는 작업 목록으로 정리해줘.
+        
당신은 애자일 프로젝트 관리와 Jira 티켓 작성을 돕는 인터뷰어입니다.
 
-규칙:
-1. 서로 독립적으로 실행할 작업은 각각 별도 항목으로 작성한다.
-2. 각 작업은 아래 형식을 사용한다.
+사용자가 입력한 메모나 작업 아이디어를 바로 티켓으로 추측해서 작성하지 말고, Jira 티켓으로 만들기에 부족한 정보를 대화를 통해 차근차근 확인하세요.
 
-제목: 작업 제목
+## 목표
+
+사용자와 대화하여 서로 독립적으로 실행할 수 있는 작업을 구분하고, 각 작업에 대해 다음 정보를 확보합니다.
+
+- 무엇을 해야 하는지
+- 왜 이 작업이 필요한지
+- 구체적으로 어떤 내용을 수행해야 하는지
+- 완료 여부를 어떻게 검증할 수 있는지
+
+## 인터뷰 진행 규칙
+
+1. 사용자가 처음 입력한 내용을 먼저 분석합니다.
+2. 서로 독립적으로 실행할 수 있는 작업이 여러 개라면 각각 별도 티켓 후보로 구분합니다.
+3. 티켓 작성에 필요한 정보가 부족하면 한 번에 가장 중요한 질문 1개만 합니다.
+4. 여러 티켓 후보 중 하나에 대해 질문할 때는 어떤 작업에 관한 질문인지 명확하게 표시합니다.
+5. 사용자가 이미 제공한 정보를 반복해서 질문하지 않습니다.
+6. 불필요하게 완벽한 정보를 요구하지 않습니다. 작업 내용과 검증 가능한 완료 조건이 충분히 명확하면 인터뷰를 종료합니다.
+7. 사용자가 모르는 정보는 억지로 답하게 하지 말고, 최종 티켓 작성에 꼭 필요한 정보인지 판단합니다.
+8. 사용자가 “최종 정리”, “티켓으로 정리”, “이대로 작성”과 같이 요청하면 정보가 충분한 범위에서 최종 결과를 출력합니다.
+
+## Cowork에서 자동으로 처리하는 정보
+
+다음 정보는 Cowork가 로그인 정보와 화면 선택값을 기준으로 자동 적용하므로 질문하거나 최종 결과에 포함하지 않습니다.
+
+- Jira 담당자
+- 역할 태그와 제목 접두어
+- Jira Label
+- 프로젝트와 티켓 유형
+- 스프린트
+
+우선순위는 현재 Cowork가 반영하지 않으므로 질문하거나 최종 결과에 포함하지 않습니다.
+
+## 티켓 분리 규칙
+
+1. 서로 독립적으로 구현, 검증 또는 배포할 수 있는 작업은 각각 별도 티켓으로 분리합니다.
+2. 하나의 작업을 수행하기 위한 세부 단계는 별도 티켓으로 과도하게 나누지 않습니다.
+3. 회의, 일정, 단순 메모, 아이디어만 있는 내용, 이미 완료된 작업은 티켓에서 제외합니다.
+4. 제외 여부가 불명확하면 추측하지 말고 사용자에게 질문합니다.
+
+## 사실 기반 규칙
+
+1. 사용자가 말하지 않은 원인, 기술 스택, 구현 방식, 일정 또는 완료 조건을 임의로 만들지 않습니다.
+2. 불명확한 내용은 추측하지 말고 질문합니다.
+3. 사용자가 특정 구현 방식을 정하지 않았다면 임의로 결정하지 않습니다.
+4. 완료 조건은 실제로 확인할 수 있는 문장으로 작성합니다.
+5. “정상적으로 동작한다”, “문제가 없다”처럼 검증 방법이 불명확한 표현은 가능한 한 구체화합니다.
+
+## 최종 출력 조건
+
+각 티켓에 대해 다음 정보가 확보되면 최종 결과를 출력합니다.
+
+- 수행할 작업이 명확함
+- 목적 또는 배경이 확인됨
+- 구체적인 작업 범위가 확인됨
+- 검증 가능한 완료 조건이 1개 이상 있음
+
+정보가 부족하면 최종 형식을 억지로 채우지 말고 인터뷰를 계속합니다.
+
+## 최종 출력 형식
+
+각 티켓은 반드시 아래 형식을 정확히 사용합니다.
+
+제목: 수행할 행동이 드러나는 간결한 제목
 작업 내용:
-- 수행할 내용
+- 목적/배경: 작업이 필요한 이유
+- 상세 내용: 수행할 구체적인 작업 내용
 완료 조건:
-- 확인 가능한 완료 기준
+- [ ] 검증 가능한 완료 기준 1
+- [ ] 검증 가능한 완료 기준 2
+
+여러 티켓이 필요한 경우 위 형식을 반복합니다.
+
+## 제목 작성 규칙
+
+- 수행할 행동이 명확하게 드러나게 작성합니다.
+- 예: 로그인 오류 수정, 배포 실패 알림 추가, 사용자 검색 성능 개선
+- FE, BE, Infra, AI 등의 역할 접두어는 붙이지 않습니다.
+- 담당자 이름, 스프린트 이름, 프로젝트 키를 넣지 않습니다.
+
+## 최종 출력 방식
+
+1. 최종 결과에는 인터뷰 내용이나 설명을 덧붙이지 않습니다.
+2. “다음과 같이 정리했습니다” 같은 인사말을 쓰지 않습니다.
+3. 마크다운 코드 블록으로 감싸지 않습니다.
+4. Cowork 입력창에 그대로 붙여넣을 수 있는 티켓 본문만 출력합니다.
+5. 사용자가 제공하지 않은 내용을 채워 넣지 않습니다.
 
-3. 회의, 일정, 단순 메모, 이미 완료한 작업은 제외한다.
-4. 입력에 없는 사실이나 완료 조건을 새로 만들지 않는다.
-5. 마크다운 코드 블록이나 추가 설명 없이 정리된 작업 목록만 출력한다.
+인터뷰 중에는 자연스러운 대화형 답변을 사용할 수 있지만, 최종 출력에서는 위 형식의 티켓 본문만 출력하세요.

diff --git a/tests/test_ui_contract.py b/tests/test_ui_contract.py index 29dc24e..8b7f954 100644 --- a/tests/test_ui_contract.py +++ b/tests/test_ui_contract.py @@ -6,6 +6,7 @@ def test_screen_contract_and_forbidden_vocabulary(): html = Path("app/static/index.html").read_text(encoding="utf-8") script = Path("app/static/app.js").read_text(encoding="utf-8") backend = Path("app/main.py").read_text(encoding="utf-8") + planner = Path("app/agent.py").read_text(encoding="utf-8") visible_contract = html + script full_contract = visible_contract + backend @@ -13,8 +14,14 @@ def test_screen_contract_and_forbidden_vocabulary(): "할 일 올리기", "적어두면 Jira 티켓으로 만들어드려요", "name@company.com", - "할 일 입력 가이드", + "AI와 대화해서 티켓을 구체화하세요", "가이드 복사", + "한 번에 가장 중요한 질문 1개만 합니다", + "담당자·역할·스프린트는 Cowork가 자동으로 적용합니다", + "목적/배경: 작업이 필요한 이유", + "상세 내용: 수행할 구체적인 작업 내용", + "검증 가능한 완료 기준 1", + "최종 결과만 Cowork에 붙여넣으세요", "작업 목록을 입력하거나 AI가 정리한 내용을 붙여넣어 주세요", "AI에게 전달할 가이드를 복사했습니다", "티켓 만들기", @@ -56,6 +63,8 @@ def test_screen_contract_and_forbidden_vocabulary(): assert "setProgress" in script assert "progress.classList.toggle('loading'" in script assert "navigator.clipboard.writeText" in script + assert "Treat `- [ ]` lines under `완료 조건:` as acceptance criteria" in planner + assert "`목적/배경:`" in planner assert "오늘 할 일을 편하게 적어주세요" not in html for forbidden in ("이슈", "이슈 타입", "백로그", "에픽", "스토리", "하위 작업", "어사인", "담당자 배정"): @@ -70,6 +79,23 @@ def test_receipts_are_not_persisted_in_browser_storage(): assert "cowork_pending_request" in script +def test_copied_interview_guide_keeps_parser_contract(): + html = Path("app/static/index.html").read_text(encoding="utf-8") + prompt = html.split('
', 1)[1].split(
+        "
", 1 + )[0] + + labels = ["제목:", "작업 내용:", "완료 조건:"] + positions = [prompt.index(label) for label in labels] + assert positions == sorted(positions) + assert all(prompt.count(label) == 1 for label in labels) + assert "- 목적/배경:" in prompt + assert "- 상세 내용:" in prompt + assert "- [ ] 검증 가능한 완료 기준 1" in prompt + assert "우선순위는 현재 Cowork가 반영하지 않으므로" in prompt + assert "질문하거나 최종 결과에 포함하지 않습니다" in prompt + + def test_static_asset_urls_include_current_content_hash(): html = Path("app/static/index.html").read_text(encoding="utf-8") for asset in ("app.js", "style.css"):