1129 lines
42 KiB
Python
1129 lines
42 KiB
Python
"""Default prompt templates and pipeline presets."""
|
|
from __future__ import annotations
|
|
|
|
import collections
|
|
from pathlib import Path
|
|
from typing import Callable, Optional
|
|
|
|
from cross_eval.models import PhaseConfig, StepConfig
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Default prompt templates
|
|
# ---------------------------------------------------------------------------
|
|
|
|
CODING_TEMPLATE = """\
|
|
You are tasked with implementing code based on a plan and checklist.
|
|
|
|
## Artifact References
|
|
{artifact_references}
|
|
|
|
## Iteration
|
|
This is iteration {iteration} of {max_iterations}.
|
|
|
|
## Instructions
|
|
1. Read the referenced plan/checklist/docs/review artifacts directly from disk.
|
|
2. Explore the project directory and git state to understand the current codebase structure.
|
|
3. Implement ONLY what the plan specifies. Do NOT add extra features, \
|
|
unnecessary abstractions, or premature optimizations.
|
|
4. Follow every item in the checklist.
|
|
5. If there is previous feedback in the referenced markdown artifacts, address ONLY those issues.
|
|
6. If previous feedback contains items marked as DISMISSED or false positive, \
|
|
IGNORE those items — they have been verified as correct.
|
|
7. Prefer git and markdown artifacts as the source of truth. Use commit hashes, `git show`, `git diff`, and referenced markdown files instead of relying on inline summaries.
|
|
8. Output the complete implementation.
|
|
"""
|
|
|
|
REVIEW_TEMPLATE = """\
|
|
You are tasked with reviewing code against a plan and checklist.
|
|
|
|
## Artifact References
|
|
{artifact_references}
|
|
|
|
## Execution Evidence
|
|
{execution_evidence}
|
|
|
|
## Review Instructions
|
|
Read the referenced plan/checklist/docs/review artifacts directly from disk. \
|
|
Inspect the referenced commit/git state and markdown artifacts, then evaluate \
|
|
the code against ONLY the plan and checklist. Use the execution evidence above \
|
|
to verify agent claims against actual command outputs, artifact paths, and exit codes.
|
|
|
|
For each issue found, classify it with BOTH severity AND category:
|
|
|
|
Severity levels:
|
|
- **Critical**: Breaks functionality, causes data loss, or introduces security vulnerabilities.
|
|
- **Major**: Requirement mismatch, significant logic errors, or missing core functionality.
|
|
- **Minor**: Coding convention violations, trivial omissions, or style issues.
|
|
|
|
Categories:
|
|
- **Over-engineering**: Code adds features, abstractions, or complexity \
|
|
NOT required by the plan.
|
|
- **Omission**: A requirement from the plan or checklist that is missing or \
|
|
incomplete in the implementation.
|
|
|
|
If previous review feedback is provided above, you MUST assess each item:
|
|
- **CONFIRMED**: The issue is still present in the current code.
|
|
- **DISMISSED (false positive)**: The flagged item is actually correct per \
|
|
the plan requirements. Provide rationale.
|
|
|
|
If you find issues outside the plan/checklist scope (e.g. pre-existing bugs, \
|
|
security concerns, performance problems), report them separately under \
|
|
"Out of Scope Issues".
|
|
|
|
## Output Format
|
|
|
|
### Previous Feedback Assessment
|
|
(Only include this section if previous feedback was provided.)
|
|
- CONFIRMED: [item description] — still an issue because [reason]
|
|
- DISMISSED (false positive): [item description] — actually correct because [reason]
|
|
(Write "N/A" if no previous feedback was provided.)
|
|
|
|
### Issues Found
|
|
List issues ordered by severity (Critical first). Assign each issue a unique ID (ISS-NNN):
|
|
- ISS-001 [Critical][Over-engineering] Description (reference specific plan/checklist item)
|
|
- ISS-002 [Major][Omission] Description (reference specific plan/checklist item)
|
|
- ISS-003 [Minor][Omission] Description (reference specific plan/checklist item)
|
|
|
|
### Out of Scope Issues
|
|
Issues found outside plan/checklist scope but worth noting:
|
|
- [Critical] Description of issue
|
|
- [Minor] Description of issue
|
|
(Write "None" if no out-of-scope issues found.)
|
|
|
|
### Summary
|
|
- Critical: N, Major: N, Minor: N
|
|
- Over-engineering count: N
|
|
- Omission count: N
|
|
- CONFIRMED: N, DISMISSED: N
|
|
- Overall quality: [BRIEF ASSESSMENT]
|
|
|
|
### Verdict
|
|
If all checklist items are satisfied and there is no over-engineering or \
|
|
omission, output: VERDICT: PASS
|
|
Otherwise output: VERDICT: FAIL
|
|
"""
|
|
|
|
|
|
CODING_TEMPLATE_KO = """\
|
|
당신은 기획서와 체크리스트를 기반으로 코드를 구현하는 개발자입니다.
|
|
|
|
## 참조 아티팩트
|
|
{artifact_references}
|
|
|
|
## 반복 정보
|
|
현재 {max_iterations}회 중 {iteration}번째 반복입니다.
|
|
|
|
## 지침
|
|
1. 참조된 plan/checklist/docs/review markdown를 직접 읽으세요.
|
|
2. 프로젝트 디렉토리와 git 상태를 탐색하여 현재 코드베이스 구조를 파악하세요.
|
|
3. 기획서에 명시된 것만 구현하세요. 추가 기능, 불필요한 추상화, 과도한 최적화를 하지 마세요.
|
|
4. 체크리스트의 모든 항목을 충족하세요.
|
|
5. 참조된 이전 리뷰 피드백이 있다면 해당 이슈만 해결하세요.
|
|
6. 이전 피드백에서 DISMISSED 또는 오탐으로 표시된 항목은 무시하세요 — 이미 올바른 것으로 검증되었습니다.
|
|
7. inline 요약보다 git commit hash, `git show`, `git diff`, markdown 아티팩트를 우선 사용하세요.
|
|
8. 완전한 구현을 출력하세요.
|
|
"""
|
|
|
|
REVIEW_TEMPLATE_KO = """\
|
|
당신은 기획서와 체크리스트 기준으로 코드를 검토하는 리뷰어입니다.
|
|
|
|
## 참조 아티팩트
|
|
{artifact_references}
|
|
|
|
## 실행 증거
|
|
{execution_evidence}
|
|
|
|
## 검토 지침
|
|
참조된 plan/checklist/docs/review markdown와 git 상태를 직접 읽고, \
|
|
그 내용을 기준으로만 코드를 평가하세요. \
|
|
위 실행 증거를 활용하여 에이전트의 주장을 실제 명령어 출력, 아티팩트 경로, 종료 코드로 검증하세요.
|
|
|
|
발견된 각 이슈에 심각도와 카테고리를 모두 부여하세요:
|
|
|
|
심각도:
|
|
- **Critical**: 기능 장애, 데이터 손실, 보안 취약점을 유발하는 문제.
|
|
- **Major**: 요구사항 불일치, 중대한 로직 오류, 핵심 기능 누락.
|
|
- **Minor**: 코딩 컨벤션 위반, 사소한 누락, 스타일 문제.
|
|
|
|
카테고리:
|
|
- **과최적화**: 기획서에 없는 기능, 추상화, 복잡성을 추가한 경우.
|
|
- **누락**: 기획서/체크리스트에 있지만 구현에서 빠지거나 불완전한 요구사항.
|
|
|
|
이전 리뷰 피드백이 제공된 경우, 각 항목을 반드시 평가하세요:
|
|
- **CONFIRMED**: 현재 코드에 여전히 존재하는 이슈.
|
|
- **DISMISSED (오탐)**: 기획서 요구사항상 실제로 올바른 항목. 근거를 제시하세요.
|
|
|
|
기획서/체크리스트 범위 밖에서 발견된 문제(기존 버그, 보안 이슈, 성능 문제 등)는 \
|
|
"범위 밖 이슈" 섹션에 별도로 보고하세요.
|
|
|
|
## 출력 형식
|
|
|
|
### 이전 피드백 평가
|
|
(이전 피드백이 제공된 경우에만 포함하세요.)
|
|
- CONFIRMED: [항목 설명] — 여전히 이슈인 이유: [근거]
|
|
- DISMISSED (오탐): [항목 설명] — 실제로 올바른 이유: [근거]
|
|
(이전 피드백이 없으면 "해당 없음"이라고 작성하세요.)
|
|
|
|
### 발견된 이슈
|
|
심각도 순서(Critical 먼저)로 나열. 각 이슈에 고유 ID(ISS-NNN)를 부여하세요:
|
|
- ISS-001 [Critical][과최적화] 이슈 설명 (관련 기획서/체크리스트 항목 참조)
|
|
- ISS-002 [Major][누락] 이슈 설명 (관련 기획서/체크리스트 항목 참조)
|
|
- ISS-003 [Minor][누락] 이슈 설명 (관련 기획서/체크리스트 항목 참조)
|
|
|
|
### 범위 밖 이슈
|
|
기획서/체크리스트 범위 밖이지만 주목할 만한 이슈:
|
|
- [Critical] 이슈 설명
|
|
- [Minor] 이슈 설명
|
|
(범위 밖 이슈가 없으면 "없음"이라고 작성하세요.)
|
|
|
|
### 요약
|
|
- Critical: N, Major: N, Minor: N
|
|
- 과최적화 수: N
|
|
- 누락 수: N
|
|
- CONFIRMED: N, DISMISSED: N
|
|
- 전체 품질: [간략한 평가]
|
|
|
|
### 판정
|
|
모든 체크리스트 항목이 충족되고 과최적화/누락이 없으면: VERDICT: PASS
|
|
그렇지 않으면: VERDICT: FAIL
|
|
"""
|
|
|
|
|
|
REVIEW_ONLY_TEMPLATE = """\
|
|
You are tasked with reviewing existing code against a plan and checklist.
|
|
|
|
## Plan
|
|
{plan}
|
|
|
|
## Checklist
|
|
{checklist}
|
|
|
|
## Reference Documents
|
|
{docs}
|
|
|
|
## Previous Review (iteration {iteration} of {max_iterations})
|
|
{feedback}
|
|
|
|
## Execution Evidence
|
|
{execution_evidence}
|
|
|
|
## Review Instructions
|
|
Explore the project directory thoroughly to understand the full codebase, \
|
|
then evaluate the EXISTING code against ONLY the plan and checklist above. \
|
|
Use the execution evidence above to verify agent claims against actual \
|
|
command outputs and exit codes.
|
|
|
|
You are NOT generating or modifying code. You are auditing what already exists.
|
|
|
|
If previous review results are provided above, you MUST:
|
|
1. Verify each previously reported issue — is it a real issue or a false positive?
|
|
2. Look for issues the previous review MISSED.
|
|
3. Do NOT simply repeat the previous review. Provide your own independent assessment.
|
|
4. Explicitly mark items as CONFIRMED (still an issue) or DISMISSED (false positive).
|
|
|
|
For each issue found, classify it with BOTH severity AND category:
|
|
|
|
Severity levels:
|
|
- **Critical**: Breaks functionality, causes data loss, or introduces security vulnerabilities.
|
|
- **Major**: Requirement mismatch, significant logic errors, or missing core functionality.
|
|
- **Minor**: Coding convention violations, trivial omissions, or style issues.
|
|
|
|
Categories:
|
|
- **Over-engineering**: Code adds features, abstractions, or complexity \
|
|
NOT required by the plan.
|
|
- **Omission**: A requirement from the plan or checklist that is missing or \
|
|
incomplete in the implementation.
|
|
|
|
If you find issues outside the plan/checklist scope (e.g. pre-existing bugs, \
|
|
security concerns, performance problems), report them separately under \
|
|
"Out of Scope Issues".
|
|
|
|
## Output Format
|
|
|
|
### Issues Found
|
|
List issues ordered by severity (Critical first):
|
|
- [Critical][Over-engineering] Description (reference specific plan/checklist item)
|
|
- [Major][Omission] Description (reference specific plan/checklist item)
|
|
- [Minor][Omission] Description (reference specific plan/checklist item)
|
|
|
|
### Out of Scope Issues
|
|
Issues found outside plan/checklist scope but worth noting:
|
|
- [Critical] Description of issue
|
|
- [Minor] Description of issue
|
|
(Write "None" if no out-of-scope issues found.)
|
|
|
|
### Summary
|
|
- Critical: N, Major: N, Minor: N
|
|
- Over-engineering count: N
|
|
- Omission count: N
|
|
- CONFIRMED: N, DISMISSED: N
|
|
- Overall quality: [BRIEF ASSESSMENT]
|
|
|
|
### Verdict
|
|
If all checklist items are satisfied and there is no over-engineering or \
|
|
omission, output: VERDICT: PASS
|
|
Otherwise output: VERDICT: FAIL
|
|
"""
|
|
|
|
REVIEW_ONLY_TEMPLATE_KO = """\
|
|
당신은 기존 코드를 기획서와 체크리스트 기준으로 감사하는 리뷰어입니다.
|
|
|
|
## 참조 아티팩트
|
|
{artifact_references}
|
|
|
|
## 실행 증거
|
|
{execution_evidence}
|
|
|
|
## 검토 지침
|
|
참조된 plan/checklist/docs/review markdown와 git 상태를 직접 읽고, \
|
|
그 내용을 기준으로 **기존 코드**를 평가하세요. \
|
|
위 실행 증거를 활용하여 에이전트의 주장을 실제 명령어 출력, 아티팩트 경로, 종료 코드로 검증하세요.
|
|
|
|
코드를 생성하거나 수정하지 마세요. 이미 존재하는 코드를 감사하는 것이 목적입니다.
|
|
|
|
이전 리뷰 결과가 제공된 경우 반드시:
|
|
1. 이전에 보고된 각 이슈를 검증하세요 — 진짜 이슈인지 오탐인지?
|
|
2. 이전 리뷰가 놓친 새로운 이슈를 찾으세요.
|
|
3. 이전 리뷰를 그대로 반복하지 마세요. 독립적인 평가를 제공하세요.
|
|
4. 각 항목에 CONFIRMED (여전히 이슈) 또는 DISMISSED (오탐) 태그를 명시하세요.
|
|
|
|
발견된 각 이슈에 심각도와 카테고리를 모두 부여하세요:
|
|
|
|
심각도:
|
|
- **Critical**: 기능 장애, 데이터 손실, 보안 취약점을 유발하는 문제.
|
|
- **Major**: 요구사항 불일치, 중대한 로직 오류, 핵심 기능 누락.
|
|
- **Minor**: 코딩 컨벤션 위반, 사소한 누락, 스타일 문제.
|
|
|
|
카테고리:
|
|
- **과최적화**: 기획서에 없는 기능, 추상화, 복잡성을 추가한 경우.
|
|
- **누락**: 기획서/체크리스트에 있지만 구현에서 빠지거나 불완전한 요구사항.
|
|
|
|
기획서/체크리스트 범위 밖에서 발견된 문제(기존 버그, 보안 이슈, 성능 문제 등)는 \
|
|
"범위 밖 이슈" 섹션에 별도로 보고하세요.
|
|
|
|
## 출력 형식
|
|
|
|
### 발견된 이슈
|
|
심각도 순서(Critical 먼저)로 나열:
|
|
- [Critical][과최적화] 이슈 설명 (관련 기획서/체크리스트 항목 참조)
|
|
- [Major][누락] 이슈 설명 (관련 기획서/체크리스트 항목 참조)
|
|
- [Minor][누락] 이슈 설명 (관련 기획서/체크리스트 항목 참조)
|
|
|
|
### 범위 밖 이슈
|
|
기획서/체크리스트 범위 밖이지만 주목할 만한 이슈:
|
|
- [Critical] 이슈 설명
|
|
- [Minor] 이슈 설명
|
|
(범위 밖 이슈가 없으면 "없음"이라고 작성하세요.)
|
|
|
|
### 요약
|
|
- Critical: N, Major: N, Minor: N
|
|
- 과최적화 수: N
|
|
- 누락 수: N
|
|
- CONFIRMED: N, DISMISSED: N
|
|
- 전체 품질: [간략한 평가]
|
|
|
|
### 판정
|
|
모든 체크리스트 항목이 충족되고 과최적화/누락이 없으면: VERDICT: PASS
|
|
그렇지 않으면: VERDICT: FAIL
|
|
"""
|
|
|
|
PLAN_REVIEW_TEMPLATE = """\
|
|
You are tasked with reviewing planning documents before implementation begins.
|
|
|
|
## Plan
|
|
{plan}
|
|
|
|
## Checklist
|
|
{checklist}
|
|
|
|
## Reference Documents
|
|
{docs}
|
|
|
|
## Previous Review (iteration {iteration} of {max_iterations})
|
|
{feedback}
|
|
|
|
## Review Instructions
|
|
Review the planning package itself: the plan, checklist, and reference documents.
|
|
You MAY inspect the current repository to validate feasibility, constraints, and integration assumptions.
|
|
Do NOT write or modify code. Assume implementation has NOT started yet.
|
|
|
|
Your job is to find planning issues that would likely cause bad implementation outcomes:
|
|
- Ambiguous or contradictory requirements
|
|
- Missing acceptance criteria, constraints, edge cases, or dependencies
|
|
- Scope that is broader or more complex than the stated objective
|
|
- Checklist items that do not verify the actual requirements
|
|
- Plan details that conflict with the current codebase or architecture
|
|
|
|
If previous review results are provided above, you MUST:
|
|
1. Verify each previously reported issue — is it a real issue or a false positive?
|
|
2. Look for issues the previous review MISSED.
|
|
3. Do NOT simply repeat the previous review. Provide your own independent assessment.
|
|
4. Explicitly mark items as CONFIRMED (still an issue) or DISMISSED (false positive).
|
|
|
|
For each issue found, classify it with BOTH severity AND category:
|
|
|
|
Severity levels:
|
|
- **Critical**: The plan is likely to cause fundamentally wrong implementation or unsafe behavior.
|
|
- **Major**: Important requirements, constraints, or acceptance criteria are unclear, conflicting, missing, or incompatible with the existing system.
|
|
- **Minor**: Wording, structure, or checklist quality problems that reduce implementation clarity.
|
|
|
|
Categories:
|
|
- **Over-engineering**: The plan introduces scope, abstractions, or complexity not justified by the stated objective.
|
|
- **Omission**: A necessary requirement, constraint, acceptance criterion, edge case, dependency, or compatibility consideration is missing or incomplete.
|
|
|
|
If you find issues outside the planning scope (e.g. repository health, pre-existing code problems), report them separately under "Out of Scope Issues".
|
|
|
|
## Output Format
|
|
|
|
### Issues Found
|
|
List issues ordered by severity (Critical first):
|
|
- [Critical][Over-engineering] Description (reference specific plan/checklist item)
|
|
- [Major][Omission] Description (reference specific plan/checklist item)
|
|
- [Minor][Omission] Description (reference specific plan/checklist item)
|
|
|
|
### Out of Scope Issues
|
|
Issues found outside planning scope but worth noting:
|
|
- [Critical] Description of issue
|
|
- [Minor] Description of issue
|
|
(Write "None" if no out-of-scope issues found.)
|
|
|
|
### Summary
|
|
- Critical: N, Major: N, Minor: N
|
|
- Over-engineering count: N
|
|
- Omission count: N
|
|
- CONFIRMED: N, DISMISSED: N
|
|
- Overall quality: [BRIEF ASSESSMENT]
|
|
|
|
### Verdict
|
|
If the planning documents are clear, complete enough to implement, compatible with the current repository, and free of unjustified scope, output: VERDICT: PASS
|
|
Otherwise output: VERDICT: FAIL
|
|
"""
|
|
|
|
PLAN_REVIEW_TEMPLATE_KO = """\
|
|
당신은 구현 시작 전에 기획 문서를 검토하는 리뷰어입니다.
|
|
|
|
## 기획서
|
|
{plan}
|
|
|
|
## 체크리스트
|
|
{checklist}
|
|
|
|
## 참고 문서
|
|
{docs}
|
|
|
|
## 이전 리뷰 결과 ({max_iterations}회 중 {iteration}번째)
|
|
{feedback}
|
|
|
|
## 검토 지침
|
|
검토 대상은 코드가 아니라 기획 패키지 자체입니다: 기획서, 체크리스트, 참고 문서를 함께 검토하세요.
|
|
현재 저장소를 살펴보며 구현 가능성, 제약조건, 통합 가정이 맞는지도 확인할 수 있습니다.
|
|
코드를 생성하거나 수정하지 마세요. 아직 구현이 시작되지 않았다고 가정하세요.
|
|
|
|
목표는 구현 단계에서 문제를 일으킬 기획 결함을 찾는 것입니다:
|
|
- 요구사항이 모호하거나 서로 충돌하는 경우
|
|
- 수용 기준, 제약조건, 엣지 케이스, 의존성이 빠진 경우
|
|
- 목표 대비 범위가 지나치게 넓거나 복잡한 경우
|
|
- 체크리스트가 실제 요구사항 검증에 충분하지 않은 경우
|
|
- 기획 내용이 현재 코드베이스나 아키텍처와 충돌하는 경우
|
|
|
|
이전 리뷰 결과가 제공된 경우 반드시:
|
|
1. 이전에 보고된 각 이슈를 검증하세요 — 진짜 이슈인지 오탐인지?
|
|
2. 이전 리뷰가 놓친 새로운 이슈를 찾으세요.
|
|
3. 이전 리뷰를 그대로 반복하지 마세요. 독립적인 평가를 제공하세요.
|
|
4. 각 항목에 CONFIRMED (여전히 이슈) 또는 DISMISSED (오탐) 태그를 명시하세요.
|
|
|
|
발견된 각 이슈에 심각도와 카테고리를 모두 부여하세요:
|
|
|
|
심각도:
|
|
- **Critical**: 잘못된 구현이나 위험한 동작으로 직결될 가능성이 큰 기획 결함.
|
|
- **Major**: 중요한 요구사항, 제약조건, 수용 기준이 모호하거나 충돌하거나 누락되었거나 기존 시스템과 맞지 않는 경우.
|
|
- **Minor**: 문서 표현, 구조, 체크리스트 품질 문제로 구현 명확성이 떨어지는 경우.
|
|
|
|
카테고리:
|
|
- **과최적화**: 목표 대비 불필요한 범위, 추상화, 복잡성을 기획에 추가한 경우.
|
|
- **누락**: 필요한 요구사항, 제약조건, 수용 기준, 엣지 케이스, 의존성, 호환성 고려가 빠졌거나 불완전한 경우.
|
|
|
|
기획 범위 밖에서 발견된 문제(저장소 상태, 기존 코드 문제 등)는 "범위 밖 이슈" 섹션에 별도로 보고하세요.
|
|
|
|
## 출력 형식
|
|
|
|
### 발견된 이슈
|
|
심각도 순서(Critical 먼저)로 나열:
|
|
- [Critical][과최적화] 이슈 설명 (관련 기획서/체크리스트 항목 참조)
|
|
- [Major][누락] 이슈 설명 (관련 기획서/체크리스트 항목 참조)
|
|
- [Minor][누락] 이슈 설명 (관련 기획서/체크리스트 항목 참조)
|
|
|
|
### 범위 밖 이슈
|
|
기획 범위 밖이지만 주목할 만한 이슈:
|
|
- [Critical] 이슈 설명
|
|
- [Minor] 이슈 설명
|
|
(범위 밖 이슈가 없으면 "없음"이라고 작성하세요.)
|
|
|
|
### 요약
|
|
- Critical: N, Major: N, Minor: N
|
|
- 과최적화 수: N
|
|
- 누락 수: N
|
|
- CONFIRMED: N, DISMISSED: N
|
|
- 전체 품질: [간략한 평가]
|
|
|
|
### 판정
|
|
기획 문서가 구현 가능한 수준으로 명확하고 충분하며 현재 저장소와도 정합적이고, 불필요한 범위 확장이 없으면: VERDICT: PASS
|
|
그렇지 않으면: VERDICT: FAIL
|
|
"""
|
|
|
|
PLAN_FIX_TEMPLATE = """\
|
|
You are tasked with revising planning documents based on adjudicated review feedback.
|
|
|
|
## Artifact References
|
|
{artifact_references}
|
|
|
|
## Current Review Feedback
|
|
{feedback}
|
|
|
|
## Instructions
|
|
1. Read the referenced plan/checklist/docs/review artifacts directly from disk.
|
|
2. Update the planning package itself: the plan, checklist, and reference documents as needed.
|
|
3. Do NOT write or modify production code. Only revise planning artifacts.
|
|
4. Address ONLY the confirmed planning issues from the current review feedback.
|
|
5. If feedback marks any item as DISMISSED or false positive, leave it unchanged.
|
|
6. Make the smallest document changes that resolve ambiguity, omissions, scope creep, or repository compatibility issues.
|
|
7. Keep the plan, checklist, and supporting docs internally consistent after your edits.
|
|
8. After editing, briefly summarize what you changed and any blocker that still needs human input.
|
|
"""
|
|
|
|
PLAN_FIX_TEMPLATE_KO = """\
|
|
당신은 시니어 리뷰 결과를 바탕으로 기획 문서를 수정하는 담당자입니다.
|
|
|
|
## 참조 아티팩트
|
|
{artifact_references}
|
|
|
|
## 현재 리뷰 피드백
|
|
{feedback}
|
|
|
|
## 지침
|
|
1. 참조된 plan/checklist/docs/review markdown를 직접 읽으세요.
|
|
2. 수정 대상은 기획 패키지 자체입니다. 필요에 따라 기획서, 체크리스트, 참고 문서를 수정하세요.
|
|
3. 프로덕션 코드를 작성하거나 수정하지 마세요. 기획 문서만 고치세요.
|
|
4. 현재 리뷰 피드백에서 확정된 기획 이슈만 해결하세요.
|
|
5. DISMISSED 또는 오탐으로 정리된 항목은 건드리지 마세요.
|
|
6. 모호성, 누락, 과도한 범위, 저장소 정합성 문제를 해소하는 최소한의 문서 수정만 하세요.
|
|
7. 수정 후에도 기획서, 체크리스트, 참고 문서가 서로 모순되지 않게 유지하세요.
|
|
8. 수정이 끝나면 무엇을 바꿨는지와 아직 사람 판단이 필요한 blocker가 있는지 짧게 정리하세요.
|
|
"""
|
|
|
|
AGGREGATE_REVIEW_TEMPLATE = """\
|
|
You are adjudicating multiple review results and turning them into an actionable decision.
|
|
|
|
## Artifact References
|
|
{artifact_references}
|
|
|
|
## Candidate Artifact Under Review
|
|
{candidate_outputs}
|
|
|
|
## Reviewer Findings Bundle
|
|
{reviews_bundle}
|
|
|
|
## Previous Issue Tracker
|
|
{previous_senior_tracker}
|
|
|
|
## Execution Evidence
|
|
{execution_evidence}
|
|
|
|
## Instructions
|
|
Read the referenced plan/checklist/docs/review artifacts directly from disk. \
|
|
Inspect the repository and referenced artifacts only as needed to confirm the \
|
|
current target state. Use the execution evidence above to verify claims against \
|
|
actual command outputs, artifact paths, and exit codes. Then:
|
|
1. Deduplicate overlapping issues across reviewers.
|
|
2. Resolve disagreements explicitly.
|
|
3. Keep only issues supported by the plan, checklist, reference docs, repository state, or reviewer evidence.
|
|
4. When evidence is mixed, explain what was confirmed, what was dismissed, and what still needs follow-up.
|
|
5. Produce a prioritized action list for the implementer/editor.
|
|
6. Maintain the Issue Tracker table across iterations (carry forward unresolved issues).
|
|
7. If no confirmed issue remains, output VERDICT: PASS.
|
|
8. If issues exist that the implementer/editor can fix, output VERDICT: FAIL.
|
|
9. If issues require human intervention (ambiguous requirements, architecture decisions, \
|
|
external dependency problems, or the same issue persists after 2+ attempts), \
|
|
output VERDICT: ESCALATE.
|
|
|
|
## Output Format
|
|
|
|
### Confirmed Issues
|
|
- [Critical][Omission] Description with rationale and source reviewer(s)
|
|
|
|
### Dismissed Findings
|
|
- [False positive] Claim — reason why it is actually correct (raised by: Reviewer X)
|
|
- [Already fixed] Claim — already resolved in the current code (raised by: Reviewer X)
|
|
(Write "None" if nothing was dismissed.)
|
|
|
|
### Action Items
|
|
1. Concrete fix the implementer/editor should make
|
|
2. Concrete fix the implementer/editor should make
|
|
|
|
## Issue Tracker
|
|
|
|
| ISS-ID | Severity | Description | Status | Since |
|
|
|--------|----------|-------------|--------|-------|
|
|
| ISS-001 | Critical | ... | Open/Fixed/Dismissed | v1 |
|
|
|
|
### Summary
|
|
- Confirmed issues: N
|
|
- Dismissed findings: N (false positive: N, already fixed: N)
|
|
- Overall quality: [BRIEF ASSESSMENT]
|
|
|
|
### Verdict
|
|
VERDICT: PASS or VERDICT: FAIL or VERDICT: ESCALATE
|
|
"""
|
|
|
|
AGGREGATE_REVIEW_TEMPLATE_KO = """\
|
|
당신은 여러 리뷰 결과를 판정하고 coder가 수정할 액션으로 정리하는 시니어 리뷰어입니다.
|
|
|
|
## 참조 아티팩트
|
|
{artifact_references}
|
|
|
|
## 현재 검토 대상
|
|
{candidate_outputs}
|
|
|
|
## 리뷰 결과 묶음
|
|
{reviews_bundle}
|
|
|
|
## 이전 이슈 트래커
|
|
{previous_senior_tracker}
|
|
|
|
## 실행 증거
|
|
{execution_evidence}
|
|
|
|
## 지침
|
|
참조된 plan/checklist/docs/review markdown와 저장소 상태를 직접 읽어 현재 검토 대상의 상태를 확인한 뒤, \
|
|
위 실행 증거를 활용하여 에이전트의 주장을 실제 명령어 출력, 아티팩트 경로, 종료 코드로 검증하세요. \
|
|
그런 다음 아래를 수행하세요.
|
|
1. 리뷰어들 사이에 중복되는 이슈를 합치세요.
|
|
2. 의견 충돌은 명시적으로 정리하세요.
|
|
3. 기획서, 체크리스트, 참고 문서, 저장소 상태, 리뷰 근거로 뒷받침되는 이슈만 남기세요.
|
|
4. 근거가 엇갈리면 무엇이 확정이고 무엇이 기각 또는 추가확인 대상인지 분명히 적으세요.
|
|
5. 수정 담당자가 바로 처리할 수 있는 우선순위 액션 아이템을 만드세요.
|
|
6. 이슈 트래커 테이블을 반복 간에 유지하세요 (미해결 이슈를 이월).
|
|
7. 확정된 이슈가 없으면 VERDICT: PASS 를 출력하세요.
|
|
8. 수정 담당자가 해결 가능한 이슈가 있으면 VERDICT: FAIL 을 출력하세요.
|
|
9. 사람의 개입이 필요한 이슈(모호한 요구사항, 아키텍처 결정, 외부 의존성 문제, \
|
|
동일 이슈가 2회 이상 해결 실패)가 있으면 VERDICT: ESCALATE 를 출력하세요.
|
|
|
|
## 출력 형식
|
|
|
|
### 확정 이슈
|
|
- [Critical][누락] 확정된 이슈 설명, 근거, 출처 리뷰어
|
|
|
|
### 기각된 주장
|
|
- [오탐] 주장 내용 — 실제로 올바른 이유 (제기: 리뷰어 X)
|
|
- [수정 완료] 주장 내용 — 현재 코드에서 이미 해결됨 (제기: 리뷰어 X)
|
|
(기각된 항목이 없으면 "없음"이라고 작성하세요.)
|
|
|
|
### 액션 아이템
|
|
1. 수정 담당자가 처리해야 할 구체적인 작업
|
|
2. 수정 담당자가 처리해야 할 구체적인 작업
|
|
|
|
## 이슈 트래커
|
|
|
|
| ISS-ID | 심각도 | 설명 | 상태 | 최초 발견 |
|
|
|--------|--------|------|------|-----------|
|
|
| ISS-001 | Critical | ... | Open/Fixed/Dismissed | v1 |
|
|
|
|
### 요약
|
|
- 확정 이슈 수: N
|
|
- 기각된 주장 수: N (오탐: N, 수정 완료: N)
|
|
- 전체 품질: [간략한 평가]
|
|
|
|
### 판정
|
|
VERDICT: PASS 또는 VERDICT: FAIL 또는 VERDICT: ESCALATE
|
|
"""
|
|
|
|
|
|
DEFAULT_TEMPLATES: dict[str, dict[str, str]] = {
|
|
"en": {
|
|
"coding": CODING_TEMPLATE,
|
|
"review": REVIEW_TEMPLATE,
|
|
"plan-review": PLAN_REVIEW_TEMPLATE,
|
|
"plan-fix": PLAN_FIX_TEMPLATE,
|
|
"review-only": REVIEW_ONLY_TEMPLATE,
|
|
"aggregate-review": AGGREGATE_REVIEW_TEMPLATE,
|
|
},
|
|
"ko": {
|
|
"coding": CODING_TEMPLATE_KO,
|
|
"review": REVIEW_TEMPLATE_KO,
|
|
"plan-review": PLAN_REVIEW_TEMPLATE_KO,
|
|
"plan-fix": PLAN_FIX_TEMPLATE_KO,
|
|
"review-only": REVIEW_ONLY_TEMPLATE_KO,
|
|
"aggregate-review": AGGREGATE_REVIEW_TEMPLATE_KO,
|
|
},
|
|
}
|
|
|
|
# Current language (set by pipeline before run)
|
|
_current_language: str = "en"
|
|
|
|
|
|
def set_language(lang: str) -> None:
|
|
"""Set the current template language."""
|
|
global _current_language
|
|
if lang not in DEFAULT_TEMPLATES:
|
|
raise ValueError(f"Unsupported language '{lang}'. Available: {list(DEFAULT_TEMPLATES.keys())}")
|
|
_current_language = lang
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Pipeline presets
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def _safe_key(name: str) -> str:
|
|
"""Sanitize agent name for use as template variable / output_key.
|
|
|
|
Replaces hyphens with underscores so names like 'claude-coder'
|
|
become 'claude_coder', which is valid in format_map().
|
|
"""
|
|
return name.replace("-", "_")
|
|
|
|
|
|
def _unique_safe_keys(names: list[str]) -> list[str]:
|
|
"""Return stable, collision-free keys for agent names.
|
|
|
|
Duplicate names keep the first key unchanged and receive numeric suffixes
|
|
from the second occurrence onward.
|
|
"""
|
|
totals = collections.Counter(_safe_key(name) for name in names)
|
|
seen: collections.defaultdict[str, int] = collections.defaultdict(int)
|
|
keys: list[str] = []
|
|
|
|
for name in names:
|
|
base = _safe_key(name)
|
|
seen[base] += 1
|
|
if totals[base] == 1 or seen[base] == 1:
|
|
keys.append(base)
|
|
else:
|
|
keys.append(f"{base}_{seen[base]}")
|
|
|
|
return keys
|
|
|
|
|
|
def _build_named_bundle(
|
|
labels: list[str],
|
|
step_names: list[str],
|
|
output_keys: list[str],
|
|
title: str,
|
|
) -> str:
|
|
"""Build a templated bundle from prior step outputs."""
|
|
parts: list[str] = []
|
|
for label, step_name, output_key in zip(labels, step_names, output_keys):
|
|
parts.append(
|
|
f"## {title}: {label} ({step_name})\n"
|
|
f"{{{output_key}}}"
|
|
)
|
|
return "\n\n---\n\n".join(parts)
|
|
|
|
|
|
def _build_simple_preset(
|
|
coders: list[str], reviewers: list[str], seniors: list[str],
|
|
) -> list[StepConfig]:
|
|
"""First coder writes code, first reviewer reviews."""
|
|
if not coders:
|
|
raise ValueError("'simple' preset requires at least 1 coder")
|
|
if not reviewers:
|
|
raise ValueError("'simple' preset requires at least 1 reviewer")
|
|
steps = [
|
|
StepConfig(
|
|
name="coding",
|
|
agent=coders[0],
|
|
role="coding",
|
|
prompt_template="default:coding",
|
|
output_key="coding_output",
|
|
),
|
|
StepConfig(
|
|
name="review",
|
|
agent=reviewers[0],
|
|
role="review",
|
|
prompt_template="default:review",
|
|
output_key="review_result",
|
|
verdict=not seniors,
|
|
),
|
|
]
|
|
if seniors:
|
|
steps.append(
|
|
StepConfig(
|
|
name="senior_review",
|
|
agent=seniors[0],
|
|
role="review",
|
|
prompt_template="default:aggregate-review",
|
|
output_key="senior_review_result",
|
|
verdict=True,
|
|
context_override={
|
|
"candidate_outputs": "## Coding output\n{coding_output}",
|
|
"reviews_bundle": f"## Review: {reviewers[0]} (review)\n{{review_result}}",
|
|
},
|
|
),
|
|
)
|
|
return steps
|
|
|
|
|
|
def _build_cross_review_preset(
|
|
coders: list[str], reviewers: list[str], seniors: list[str],
|
|
) -> list[StepConfig]:
|
|
"""Both coders write code, then cross-review each other's output."""
|
|
if len(coders) < 2:
|
|
raise ValueError("'cross-review' preset requires at least 2 coders")
|
|
a, b = coders[0], coders[1]
|
|
ak, bk = _unique_safe_keys([a, b])
|
|
steps = [
|
|
StepConfig(
|
|
name=f"coding_{ak}",
|
|
agent=a,
|
|
role="coding",
|
|
prompt_template="default:coding",
|
|
output_key=f"code_{ak}",
|
|
parallel=True,
|
|
),
|
|
StepConfig(
|
|
name=f"coding_{bk}",
|
|
agent=b,
|
|
role="coding",
|
|
prompt_template="default:coding",
|
|
output_key=f"code_{bk}",
|
|
parallel=True,
|
|
),
|
|
StepConfig(
|
|
name=f"review_by_{ak}",
|
|
agent=a,
|
|
role="review",
|
|
prompt_template="default:review",
|
|
output_key=f"review_by_{ak}",
|
|
context_override={"coding_output": f"{{code_{bk}}}"},
|
|
parallel=True,
|
|
verdict=not seniors,
|
|
),
|
|
StepConfig(
|
|
name=f"review_by_{bk}",
|
|
agent=b,
|
|
role="review",
|
|
prompt_template="default:review",
|
|
output_key=f"review_by_{bk}",
|
|
verdict=not seniors,
|
|
context_override={"coding_output": f"{{code_{ak}}}"},
|
|
parallel=True,
|
|
),
|
|
]
|
|
if seniors:
|
|
steps.append(
|
|
StepConfig(
|
|
name="senior_review",
|
|
agent=seniors[0],
|
|
role="review",
|
|
prompt_template="default:aggregate-review",
|
|
output_key="senior_review_result",
|
|
verdict=True,
|
|
context_override={
|
|
"candidate_outputs": _build_named_bundle(
|
|
[a, b],
|
|
[f"coding_{ak}", f"coding_{bk}"],
|
|
[f"code_{ak}", f"code_{bk}"],
|
|
"Coding Output",
|
|
),
|
|
"reviews_bundle": _build_named_bundle(
|
|
[a, b],
|
|
[f"review_by_{ak}", f"review_by_{bk}"],
|
|
[f"review_by_{ak}", f"review_by_{bk}"],
|
|
"Review",
|
|
),
|
|
},
|
|
),
|
|
)
|
|
return steps
|
|
|
|
|
|
def _build_review_only_preset(
|
|
coders: list[str], reviewers: list[str], seniors: list[str],
|
|
) -> list[StepConfig]:
|
|
"""Review-only: all reviewers audit existing code independently."""
|
|
if not reviewers:
|
|
raise ValueError("'review-only' preset requires at least 1 reviewer")
|
|
|
|
if len(reviewers) == 1 and not seniors:
|
|
# Single reviewer — backward compatible
|
|
return [
|
|
StepConfig(
|
|
name="review",
|
|
agent=reviewers[0],
|
|
role="review",
|
|
prompt_template="default:review-only",
|
|
output_key="review_result",
|
|
verdict=True,
|
|
),
|
|
]
|
|
|
|
# Multiple reviewers — each produces a separate review with verdict (parallel)
|
|
steps: list[StepConfig] = []
|
|
reviewer_keys = _unique_safe_keys(reviewers)
|
|
for reviewer, rk in zip(reviewers, reviewer_keys):
|
|
steps.append(
|
|
StepConfig(
|
|
name=f"review_{rk}",
|
|
agent=reviewer,
|
|
role="review",
|
|
prompt_template="default:review-only",
|
|
output_key=f"review_{rk}",
|
|
verdict=not seniors,
|
|
parallel=True,
|
|
),
|
|
)
|
|
if seniors:
|
|
step_names = [f"review_{rk}" for rk in reviewer_keys]
|
|
output_keys = [f"review_{rk}" for rk in reviewer_keys]
|
|
steps.append(
|
|
StepConfig(
|
|
name="senior_review",
|
|
agent=seniors[0],
|
|
role="review",
|
|
prompt_template="default:aggregate-review",
|
|
output_key="senior_review_result",
|
|
verdict=True,
|
|
context_override={
|
|
"candidate_outputs": "Current repository working tree under review.",
|
|
"reviews_bundle": _build_named_bundle(
|
|
reviewers, step_names, output_keys, "Review",
|
|
),
|
|
},
|
|
),
|
|
)
|
|
return steps
|
|
|
|
|
|
def _build_plan_review_preset(
|
|
coders: list[str], reviewers: list[str], seniors: list[str],
|
|
) -> list[StepConfig]:
|
|
"""Plan-review: review planning docs, revise them, then verify in a loop."""
|
|
if not coders:
|
|
raise ValueError("'plan-review' preset requires at least 1 coder")
|
|
if not reviewers:
|
|
raise ValueError("'plan-review' preset requires at least 1 reviewer")
|
|
|
|
review_steps: list[StepConfig] = []
|
|
if len(reviewers) == 1:
|
|
review_steps.append(
|
|
StepConfig(
|
|
name="plan_review",
|
|
agent=reviewers[0],
|
|
role="review",
|
|
prompt_template="default:plan-review",
|
|
output_key="plan_review_result",
|
|
),
|
|
)
|
|
review_step_names = ["plan_review"]
|
|
review_output_keys = ["plan_review_result"]
|
|
else:
|
|
reviewer_keys = _unique_safe_keys(reviewers)
|
|
for reviewer, rk in zip(reviewers, reviewer_keys):
|
|
review_steps.append(
|
|
StepConfig(
|
|
name=f"plan_review_{rk}",
|
|
agent=reviewer,
|
|
role="review",
|
|
prompt_template="default:plan-review",
|
|
output_key=f"plan_review_{rk}",
|
|
parallel=True,
|
|
),
|
|
)
|
|
review_step_names = [f"plan_review_{rk}" for rk in reviewer_keys]
|
|
review_output_keys = [f"plan_review_{rk}" for rk in reviewer_keys]
|
|
|
|
fix_coder = coders[0]
|
|
senior_agent = seniors[0] if seniors else reviewers[0]
|
|
|
|
return review_steps + [
|
|
StepConfig(
|
|
name="aggregate_review",
|
|
agent=senior_agent,
|
|
role="review",
|
|
prompt_template="default:aggregate-review",
|
|
output_key="aggregate_review",
|
|
context_override={
|
|
"candidate_outputs": "Current planning package under review (plan/checklist/reference docs).",
|
|
"reviews_bundle": _build_named_bundle(
|
|
reviewers, review_step_names, review_output_keys, "Review",
|
|
),
|
|
},
|
|
),
|
|
StepConfig(
|
|
name="plan_fix",
|
|
agent=fix_coder,
|
|
role="coding",
|
|
prompt_template="default:plan-fix",
|
|
output_key="plan_fix_output",
|
|
context_override={"feedback": "{aggregate_review}"},
|
|
),
|
|
StepConfig(
|
|
name="verify",
|
|
agent=senior_agent,
|
|
role="review",
|
|
prompt_template="default:plan-review",
|
|
output_key="verify_result",
|
|
verdict=True,
|
|
),
|
|
]
|
|
|
|
|
|
def _build_review_fix_preset(
|
|
coders: list[str], reviewers: list[str], seniors: list[str],
|
|
) -> list[PhaseConfig]:
|
|
"""Review in parallel, aggregate findings, fix, then verify in a loop."""
|
|
if not coders:
|
|
raise ValueError("'review-fix' preset requires at least 1 coder")
|
|
if not reviewers:
|
|
raise ValueError("'review-fix' preset requires at least 1 reviewer")
|
|
|
|
review_steps: list[StepConfig] = []
|
|
reviewer_keys = _unique_safe_keys(reviewers)
|
|
for reviewer, rk in zip(reviewers, reviewer_keys):
|
|
review_steps.append(
|
|
StepConfig(
|
|
name=f"review_{rk}",
|
|
agent=reviewer,
|
|
role="review",
|
|
prompt_template="default:review-only",
|
|
output_key=f"review_{rk}",
|
|
verdict=False,
|
|
parallel=True,
|
|
),
|
|
)
|
|
|
|
fix_coder = coders[0]
|
|
senior_agent = seniors[0] if seniors else reviewers[0]
|
|
review_step_names = [f"review_{rk}" for rk in reviewer_keys]
|
|
review_output_keys = [f"review_{rk}" for rk in reviewer_keys]
|
|
|
|
return [
|
|
PhaseConfig(
|
|
name="review_fix",
|
|
steps=review_steps + [
|
|
StepConfig(
|
|
name="aggregate_review",
|
|
agent=senior_agent,
|
|
role="review",
|
|
prompt_template="default:aggregate-review",
|
|
output_key="aggregate_review",
|
|
context_override={
|
|
"candidate_outputs": "Current repository working tree under review.",
|
|
"reviews_bundle": _build_named_bundle(
|
|
reviewers, review_step_names, review_output_keys, "Review",
|
|
),
|
|
},
|
|
),
|
|
StepConfig(
|
|
name="coding",
|
|
agent=fix_coder,
|
|
role="coding",
|
|
prompt_template="default:coding",
|
|
output_key="coding_output",
|
|
context_override={"feedback": "{aggregate_review}"},
|
|
),
|
|
StepConfig(
|
|
name="verify",
|
|
agent=senior_agent,
|
|
role="review",
|
|
prompt_template="default:review",
|
|
output_key="verify_result",
|
|
verdict=True,
|
|
),
|
|
],
|
|
max_iterations=5,
|
|
consecutive_pass=1,
|
|
),
|
|
]
|
|
|
|
|
|
def _build_coding_review_fix_preset(
|
|
coders: list[str], reviewers: list[str], seniors: list[str],
|
|
) -> list[PhaseConfig]:
|
|
"""Write code once, then run the review-fix convergence loop."""
|
|
if not coders:
|
|
raise ValueError("'coding-review-fix' preset requires at least 1 coder")
|
|
if not reviewers:
|
|
raise ValueError("'coding-review-fix' preset requires at least 1 reviewer")
|
|
|
|
return [
|
|
PhaseConfig(
|
|
name="initial_coding",
|
|
steps=[
|
|
StepConfig(
|
|
name="coding",
|
|
agent=coders[0],
|
|
role="coding",
|
|
prompt_template="default:coding",
|
|
output_key="coding_output",
|
|
),
|
|
],
|
|
max_iterations=1,
|
|
consecutive_pass=1,
|
|
),
|
|
*_build_review_fix_preset(coders, reviewers, seniors),
|
|
]
|
|
|
|
|
|
PIPELINE_PRESETS: dict[str, Callable] = {
|
|
"simple": _build_simple_preset,
|
|
"cross-review": _build_cross_review_preset,
|
|
"plan-review": _build_plan_review_preset,
|
|
"review-only": _build_review_only_preset,
|
|
}
|
|
|
|
PHASED_PRESETS: dict[str, Callable] = {
|
|
"review-fix": _build_review_fix_preset,
|
|
"coding-review-fix": _build_coding_review_fix_preset,
|
|
}
|
|
|
|
ALL_PRESET_NAMES: list[str] = list(PIPELINE_PRESETS.keys()) + list(PHASED_PRESETS.keys())
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Template resolution and rendering
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def resolve_template(template_ref: str, templates_dir: Optional[Path] = None) -> str:
|
|
"""Resolve a template reference to its content string.
|
|
|
|
Formats:
|
|
- "default:coding" -> built-in CODING_TEMPLATE
|
|
- "default:review" -> built-in REVIEW_TEMPLATE
|
|
- "path/to/file.md" -> read file contents
|
|
"""
|
|
if template_ref.startswith("default:"):
|
|
key = template_ref.split(":", 1)[1]
|
|
lang_templates = DEFAULT_TEMPLATES.get(_current_language, DEFAULT_TEMPLATES["en"])
|
|
if key not in lang_templates:
|
|
raise ValueError(
|
|
f"Unknown default template '{key}'. "
|
|
f"Available: {list(lang_templates.keys())}"
|
|
)
|
|
return lang_templates[key]
|
|
|
|
# Treat as file path
|
|
path = Path(template_ref)
|
|
if templates_dir and not path.is_absolute():
|
|
path = templates_dir / path
|
|
if not path.exists():
|
|
raise FileNotFoundError(f"Template file not found: {path}")
|
|
return path.read_text(encoding="utf-8")
|
|
|
|
|
|
class _DefaultDict(collections.defaultdict):
|
|
"""defaultdict that uses the missing key name in the default value."""
|
|
|
|
def __missing__(self, key: str) -> str:
|
|
return f"(no {key} provided)"
|
|
|
|
|
|
def render_template(template: str, context: dict[str, str]) -> str:
|
|
"""Render a template string with {variable} placeholders.
|
|
|
|
Missing keys produce "(no <key> provided)" instead of raising KeyError.
|
|
"""
|
|
safe_context = _DefaultDict(str)
|
|
safe_context.update(context)
|
|
return template.format_map(safe_context)
|