73 lines
4.1 KiB
Python
73 lines
4.1 KiB
Python
"""Static contract regressions; these do not prove LLM execution behavior."""
|
|
from pathlib import Path
|
|
import re
|
|
import unittest
|
|
|
|
HARNESS = Path(__file__).resolve().parents[2]
|
|
DISCOVERY = HARNESS / 'agents/spec/skills/gitea-discovery-review/SKILL.md'
|
|
IMPLEMENTATION = HARNESS / 'agents/spec/skills/gitea-implementation-spec-review/SKILL.md'
|
|
README = HARNESS / 'docs/discovery/README.md'
|
|
|
|
|
|
class PriorityReviewContracts(unittest.TestCase):
|
|
def test_scope_classification_and_optional_nonblocking(self):
|
|
text = README.read_text()
|
|
for value in ('핵심 요청:', '기존 호환성:', '선택적 확장:',
|
|
'명시적으로 채택되지 않은 선택적 확장은', 'blocking OQ로 승격하지 않는다'):
|
|
self.assertIn(value, text)
|
|
|
|
def test_all_oq_options_preserve_mandatory_contracts(self):
|
|
text = README.read_text()
|
|
oq_rules = text.split('## OQ(${open_questions}) 작성 방법', 1)[1].split('```markdown', 1)[0]
|
|
for value in ('추천안을 포함한 모든 선택지',
|
|
'원본 요청의 필수 범위, 확정된 결정과 기존 호환성 불변조건을 보존',
|
|
'미검증 전제를 확정된 사실로 취급하지 않는다',
|
|
'요구 철회나 기존 계약 변경',
|
|
'일반 구현 대안과 구분하고 변경 대상과 영향을 명시'):
|
|
with self.subTest(rule=value):
|
|
self.assertIn(value, oq_rules)
|
|
|
|
def test_compatibility_user_decisions_and_existing_oq_preserved(self):
|
|
text = DISCOVERY.read_text()
|
|
for value in ('기존 보안·호환성 제약은 선택적으로 낮추지',
|
|
'미선택 OQ를 임의 삭제·해결하지', '선택을 보존하고 사용자 판단',
|
|
'사용자 선택이 필요한 결정 공백과 구분'):
|
|
self.assertIn(value, text)
|
|
|
|
def test_experiment_verdict_boundary_and_parent_check(self):
|
|
text = DISCOVERY.read_text()
|
|
for value in ('시험한 방식의 실패:', '소스로 확인된 제약:', '요구 충족 불가능:',
|
|
'미검증 또는 판단 유보', '시험하지 않은 대안·확인 한계',
|
|
'원본 실험·공식 소스에 직접 대조', '일부 prototype 실패만으로'):
|
|
self.assertIn(value, text)
|
|
|
|
def test_document_delegate_inputs_and_fail_closed_result_adoption(self):
|
|
text = IMPLEMENTATION.read_text()
|
|
for value in ('byte parity', '실제 내용을 delegate context로 제공',
|
|
'대상 저장소 README/TEMPLATE를 작성 정책으로 대신 사용하지',
|
|
'산출물별 의무표:', 'pinned baseline 문서 및 전체 AC',
|
|
'이전 finding의 승인 근거와 최소 보완 요청',
|
|
'입력이 부족하면 main이 보완하거나 직접 검토',
|
|
'delegate의 PASS를 최종 판정으로 채택하지'):
|
|
self.assertIn(value, text)
|
|
|
|
def test_structure_semantics_and_preserved_contract_not_conflated(self):
|
|
text = IMPLEMENTATION.read_text()
|
|
for value in ('구조 적합성(', '의미 적합성(', 'exact section·line',
|
|
'구조 통과만으로 의미를 승인', '반복 기재 부재만으로 의미 누락',
|
|
'제품 저장소의 새 산출물이나 result schema 변경을 요구하지'):
|
|
self.assertIn(value, text)
|
|
|
|
def test_local_links_resolve_and_procedures_reference_new_rules(self):
|
|
for path in (DISCOVERY, IMPLEMENTATION, README):
|
|
text = path.read_text()
|
|
for link in re.findall(r'\]\(([^)]+)\)', text):
|
|
if not link.startswith(('http:', 'https:', '#')):
|
|
self.assertTrue((path.parent / link.split('#')[0]).exists(), link)
|
|
self.assertIn('8. 범위 분류·blocking 판단과 기술적 결론의 경계를 먼저 적용', DISCOVERY.read_text())
|
|
self.assertIn('11. 문서 검토 입력과 구조·의미 분리 절차', IMPLEMENTATION.read_text())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|