Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b7f1a90d2 | ||
|
|
3ac0a367dc | ||
|
|
24dc5ed633 | ||
|
|
332762b94f | ||
|
|
a5ba640492 | ||
|
|
c014b48433 |
@@ -7,24 +7,49 @@ description: Use when a Korean technical blog or reader-facing document should r
|
|||||||
|
|
||||||
## 핵심 원칙
|
## 핵심 원칙
|
||||||
|
|
||||||
기술 사실을 설명문으로 나열하지 말고, 작성자가 직접 코드를 보고 실행하면서 예상하고, 발견하고, 판단하고, 바꾼 과정으로 쓴다. 논리와 근거는 그대로 두고 서술 관점과 문장 형식만 고친다.
|
기술 사실을 설명문으로 나열하지 말고, 서술자가 코드를 만들고 판단했거나 직접 확인한 과정으로 쓴다. 먼저 서술자가 코드의 작성자인지 외부 분석자인지 확정한다. 논리와 근거는 그대로 두고 서술 관점과 문장 형식만 고친다.
|
||||||
|
|
||||||
[문장 패턴](references/sentence-patterns.md)을 처음부터 끝까지 읽은 뒤 수정한다.
|
[문장 패턴](references/sentence-patterns.md)을 처음부터 끝까지 읽은 뒤 수정한다.
|
||||||
|
|
||||||
|
## 서술자의 위치부터 고정하기
|
||||||
|
|
||||||
|
사용자가 자신이 만든 코드라고 밝혔다면 작성자 관점으로 쓴다. `처음 저장소를 열었습니다`, `코드를 따라가 보니`, `모듈을 훑어봤습니다`, `확인해 보니 이런 구조였습니다`처럼 다른 사람의 코드를 분석하는 표현을 쓰지 않는다.
|
||||||
|
|
||||||
|
작성자 관점에서는 다음 내용을 앞에 둔다.
|
||||||
|
|
||||||
|
- 만들 때 해결하려 한 문제
|
||||||
|
- 당시 정한 기준과 선택
|
||||||
|
- 그 선택을 코드와 빌드에 옮긴 방법
|
||||||
|
- 구현을 끝낸 범위와 아직 연결하지 않은 범위
|
||||||
|
- 선택하면서 받아들인 비용
|
||||||
|
|
||||||
|
예를 들어 `처음 코드를 열었을 때 19개 모듈이 보였습니다`가 아니라 `코어의 클래스패스를 제한하고 선택형 어댑터를 분리하면서 19개 모듈로 만들었습니다`라고 쓴다. 소유 관계가 확인되지 않았다면 작성자 경험을 만들어 내지 않는다.
|
||||||
|
|
||||||
## 경험담 문체
|
## 경험담 문체
|
||||||
|
|
||||||
문서의 시작과 주요 전환에서는 `저는`이나 `제가`를 사용한다. 모든 문장에 반복하지는 않는다. 다음 흐름으로 문단 전체를 다시 쓴다.
|
문서의 시작과 주요 전환에서는 `저는`이나 `제가`를 사용한다. 모든 문장에 반복하지는 않는다. 다음 흐름으로 문단 전체를 다시 쓴다.
|
||||||
|
|
||||||
1. 처음 마주한 코드, 화면, 요청 또는 문제를 보여 준다.
|
1. 처음 마주한 코드, 화면, 요청 또는 만들 때 해결하려 한 문제를 보여 준다.
|
||||||
2. 당시의 예상이나 처음 보인 인상을 짧게 적는다.
|
2. 당시의 예상이나 처음 보인 인상을 짧게 적는다.
|
||||||
3. `그런데` 뒤에 실제 실행이나 코드 추적에서 발견한 차이를 둔다.
|
3. `그런데` 뒤에 실제 실행이나 코드 추적에서 발견한 차이를 둔다.
|
||||||
4. 낯선 용어는 그때 이해한 내용처럼 바로 풀어 쓴다.
|
4. 낯선 용어는 그때 이해한 내용처럼 바로 풀어 쓴다.
|
||||||
5. 그 차이 때문에 무엇을 확인하고 선택하거나 바꿨는지 적는다.
|
5. 그 차이 때문에 무엇을 확인하고 선택하거나 바꿨는지 적는다.
|
||||||
6. 결과와 함께 새로 생긴 비용이나 남은 문제를 적는다.
|
6. 결과와 함께 새로 생긴 비용이나 남은 문제를 적는다.
|
||||||
|
|
||||||
여섯 요소의 이름은 출력하지 않고 필요한 요소만 쓴다. 독자가 작성자의 확인 순서를 따라가게 하며, 기준 문단이 `~했습니다`를 사용하면 같은 높임말을 유지한다.
|
여섯 요소의 이름은 출력하지 않고 필요한 요소만 쓴다. 독자가 작성자의 판단과 확인 순서를 따라가게 하며, 기준 문단이 `~했습니다`를 사용하면 같은 높임말을 유지한다.
|
||||||
|
|
||||||
`첫 번째 제약이다`를 지우거나 `호출 주체`를 구성요소 이름으로 바꾸는 것만으로는 교정이 끝나지 않는다. `처음에는`, `그런데 확인해 보니`, `그래서`, `그 결과`로 이어지는 경험의 흐름이 보여야 한다.
|
`첫 번째 제약이다`를 지우거나 `호출 주체`를 구성요소 이름으로 바꾸는 것만으로는 교정이 끝나지 않는다. 경험의 흐름도 서술자의 위치에 맞춰야 한다. 외부 분석자는 `처음에는`, `그런데 확인해 보니`로 쓸 수 있지만, 작성자는 `만들 때`, `구현하면서`, `그래서 …로 나눴습니다`, `그 결과`로 자신의 판단과 작업을 설명한다.
|
||||||
|
|
||||||
|
## 문장 종결을 문서 전체에서 맞추기
|
||||||
|
|
||||||
|
사용자가 `~했습니다` 문체를 예시로 주었거나 기준 문서가 높임말이면, 제목·표의 명사구·인용문·코드 블록을 제외한 독자용 본문 전체를 다음 어투로 쓴다.
|
||||||
|
|
||||||
|
- 직접 확인하거나 수행한 일: `확인했습니다`, `따라갔습니다`, `생각했습니다`
|
||||||
|
- 현재 동작과 기술 설명: `사용합니다`, `호출합니다`, `막습니다`
|
||||||
|
- 상태와 판단: `있습니다`, `없습니다`, `입니다`, `아닙니다`
|
||||||
|
- 부정: `다루지 않습니다`, `보장하지 않습니다`
|
||||||
|
|
||||||
|
한 문단만 `저는 확인했습니다`로 열고 뒤에서 `있다`, `한다`, `아니다`, `다루지 않는다`로 돌아가면 교정이 끝난 것이 아니다. 수정이 끝나면 fenced code를 제외한 본문에서 평서형 종결이 남았는지 따로 검색한다. 원문 코드, 직접 인용, 명령 출력은 어투를 바꾸지 않는다.
|
||||||
|
|
||||||
## 한국어 말하기 검사
|
## 한국어 말하기 검사
|
||||||
|
|
||||||
@@ -38,7 +63,7 @@ description: Use when a Korean technical blog or reader-facing document should r
|
|||||||
|
|
||||||
## 경험과 사실의 경계
|
## 경험과 사실의 경계
|
||||||
|
|
||||||
코드 비교, 실행, 요청 추적처럼 실제로 수행한 작업은 1인칭으로 서술할 수 있다. 사용자가 경험담 문체를 요구했다면 이 관점을 제거하지 않는다.
|
코드 작성, 코드 비교, 실행, 요청 추적처럼 실제로 수행한 작업은 1인칭으로 서술할 수 있다. 사용자가 경험담 문체를 요구했다면 이 관점을 제거하지 않는다. 다만 코드를 만든 경험을 저장소를 처음 분석한 경험으로 바꾸지 않는다.
|
||||||
|
|
||||||
다만 근거에 없는 감정, 대화, 조언, 실패, 성과, 작업 기간은 만들지 않는다. `놀랐습니다`, `누군가 조언했습니다`, `며칠이 걸렸습니다`처럼 확인할 수 없는 경험은 추가하지 않는다. 기술 선택의 이유도 새로 만들지 않는다.
|
다만 근거에 없는 감정, 대화, 조언, 실패, 성과, 작업 기간은 만들지 않는다. `놀랐습니다`, `누군가 조언했습니다`, `며칠이 걸렸습니다`처럼 확인할 수 없는 경험은 추가하지 않는다. 기술 선택의 이유도 새로 만들지 않는다.
|
||||||
|
|
||||||
@@ -52,17 +77,20 @@ description: Use when a Korean technical blog or reader-facing document should r
|
|||||||
|
|
||||||
## 수정 절차
|
## 수정 절차
|
||||||
|
|
||||||
1. 앞뒤 문단까지 읽고 작성자가 실제로 확인한 코드, 실행 결과와 판단을 찾는다.
|
1. 사용자 요청과 문서에서 코드 소유 관계를 확인하고 서술자를 작성자 또는 외부 분석자로 고정한다.
|
||||||
2. 독자가 경험을 따라갈 시작점과 `그런데` 뒤에 놓을 발견을 고른다.
|
2. 앞뒤 문단까지 읽고 작성자가 실제로 구현하거나 확인한 코드, 실행 결과와 판단을 찾는다.
|
||||||
3. 주요 절을 1인칭 경험으로 열고, 기술 동작은 구체적인 구성요소와 동사로 설명한다.
|
3. 독자가 경험을 따라갈 시작점과 `그런데` 뒤에 놓을 발견을 고른다.
|
||||||
4. 낯선 용어를 처음 필요한 문장 옆에서 풀어 쓴다.
|
4. 주요 절을 1인칭 경험으로 열고, 기술 동작은 구체적인 구성요소와 동사로 설명한다.
|
||||||
5. 대조 문장은 소리 내어 읽고 `실제로는 무엇이 다른가`에 바로 답하는지 확인한다.
|
5. 낯선 용어를 처음 필요한 문장 옆에서 풀어 쓴다.
|
||||||
6. 파일을 직접 수정한 뒤 사실, 조건, 불확실성이 바뀌지 않았는지 비교한다.
|
6. 대조 문장은 소리 내어 읽고 `실제로는 무엇이 다른가`에 바로 답하는지 확인한다.
|
||||||
|
7. 파일을 직접 수정한 뒤 사실, 조건, 불확실성이 바뀌지 않았는지 비교한다.
|
||||||
|
|
||||||
## 완료 조건
|
## 완료 조건
|
||||||
|
|
||||||
- 작성자의 시작점, 발견, 행동과 결과가 보인다.
|
- 작성자의 시작점, 발견, 행동과 결과가 보인다.
|
||||||
|
- 코드 작성자의 문서가 외부 분석자의 저장소 검토처럼 읽히지 않는다.
|
||||||
- 단순한 객관식 해설이나 결론 목록으로 읽히지 않는다.
|
- 단순한 객관식 해설이나 결론 목록으로 읽히지 않는다.
|
||||||
- 필요한 곳에서 `저는`이나 `제가`가 실제 서술 관점을 만든다.
|
- 필요한 곳에서 `저는`이나 `제가`가 실제 서술 관점을 만든다.
|
||||||
|
- 사용자 예시와 같은 높임말이 독자용 본문 전체에서 유지된다.
|
||||||
- 한국인 개발자가 동료에게 설명할 때 쓸 수 있는 문장으로 읽힌다.
|
- 한국인 개발자가 동료에게 설명할 때 쓸 수 있는 문장으로 읽힌다.
|
||||||
- 기술적 의미와 문서 구조가 그대로 남는다.
|
- 기술적 의미와 문서 구조가 그대로 남는다.
|
||||||
|
|||||||
@@ -27,6 +27,20 @@
|
|||||||
|
|
||||||
여기서는 `호출 주체가 다르다`를 구성요소 이름으로 바꾸는 데서 멈추지 않는다. 작성자가 같은 화면을 보고, 예상과 다른 요청을 발견하고, 낯선 값을 이해한 뒤, 각 구성요소를 확인한 순서로 문단 전체를 다시 쓴다.
|
여기서는 `호출 주체가 다르다`를 구성요소 이름으로 바꾸는 데서 멈추지 않는다. 작성자가 같은 화면을 보고, 예상과 다른 요청을 발견하고, 낯선 값을 이해한 뒤, 각 구성요소를 확인한 순서로 문단 전체를 다시 쓴다.
|
||||||
|
|
||||||
|
## 작성자와 외부 분석자를 바꾸지 않는다
|
||||||
|
|
||||||
|
사용자가 자신이 만든 코드를 설명한다면 다음 문장은 관점이 잘못됐다.
|
||||||
|
|
||||||
|
수정 전:
|
||||||
|
|
||||||
|
> 처음 저장소를 열었을 때 19개 모듈이 먼저 보였습니다. 각 빌드 파일을 따라가 보니 코어의 클래스패스를 제한하는 구조였습니다.
|
||||||
|
|
||||||
|
수정 후:
|
||||||
|
|
||||||
|
> 코어에서 금지한 프레임워크 타입이 보이지 않게 하려고 클래스패스를 모듈마다 나눴습니다. 선택형 어댑터까지 분리한 결과 19개 모듈이 됐습니다.
|
||||||
|
|
||||||
|
작성자는 자신의 코드를 `열어 보고`, `훑어 보고`, `따라가며 발견한` 사람처럼 말하지 않는다. 만들 때 해결하려 한 문제, 정한 기준, 구현한 방법, 아직 연결하지 않은 범위와 받아들인 비용을 설명한다. 반대로 소유 관계를 알 수 없는 문서에 작성자 경험을 새로 붙이지 않는다.
|
||||||
|
|
||||||
## 한국어에서는 실제 차이를 바로 말한다
|
## 한국어에서는 실제 차이를 바로 말한다
|
||||||
|
|
||||||
수정 전:
|
수정 전:
|
||||||
@@ -57,7 +71,7 @@
|
|||||||
|
|
||||||
수정 후:
|
수정 후:
|
||||||
|
|
||||||
> AP1과 AP2에서는 브라우저가 API를 직접 부른다. AP3에서는 BFF가 대신 호출하고, AP4에서는 Nginx가 요청을 이어 간다.
|
> AP1과 AP2에서는 브라우저가 API를 직접 부릅니다. AP3에서는 BFF가 대신 호출하고, AP4에서는 Nginx가 요청을 이어 갑니다.
|
||||||
|
|
||||||
`호출 주체가 다르다`고 분류하지 말고 실제 구성요소와 행동을 쓴다.
|
`호출 주체가 다르다`고 분류하지 말고 실제 구성요소와 행동을 쓴다.
|
||||||
|
|
||||||
@@ -69,7 +83,7 @@
|
|||||||
|
|
||||||
수정 후:
|
수정 후:
|
||||||
|
|
||||||
> 그래서 패턴 이름만 보고 구현되지 않은 운영 속성을 덧붙이면 안 된다.
|
> 그래서 패턴 이름만 보고 구현되지 않은 운영 속성을 덧붙이면 안 됩니다.
|
||||||
|
|
||||||
실제 순서를 뜻하지 않는 `첫 번째`, `두 번째`, `마지막`은 제거한다. 절차, 화면, 레이어처럼 순서 자체가 정보라면 유지한다.
|
실제 순서를 뜻하지 않는 `첫 번째`, `두 번째`, `마지막`은 제거한다. 절차, 화면, 레이어처럼 순서 자체가 정보라면 유지한다.
|
||||||
|
|
||||||
@@ -81,7 +95,7 @@
|
|||||||
|
|
||||||
수정 후:
|
수정 후:
|
||||||
|
|
||||||
> 브라우저가 Resource Server를 직접 부르는 방식은 유지해야 했다. 다만 client secret과 refresh token까지 JavaScript에 맡길 수는 없었다. 그래서 code 교환과 refresh token 보관은 confidential mediator가 맡았다.
|
> 브라우저가 Resource Server를 직접 부르는 방식은 유지해야 했습니다. 다만 client secret과 refresh token까지 JavaScript에 맡길 수는 없었습니다. 그래서 code 교환과 refresh token 보관은 confidential mediator가 맡았습니다.
|
||||||
|
|
||||||
`상황`, `선택`, `대안`, `가드레일`을 주어로 삼지 않는다. 그 자리에 요구, 행동, 이유, 남은 비용을 넣는다.
|
`상황`, `선택`, `대안`, `가드레일`을 주어로 삼지 않는다. 그 자리에 요구, 행동, 이유, 남은 비용을 넣는다.
|
||||||
|
|
||||||
@@ -89,9 +103,9 @@
|
|||||||
|
|
||||||
용어를 삭제하거나 모두 번역하지 않는다. 처음 이해가 필요한 지점에서만 짧게 푼다.
|
용어를 삭제하거나 모두 번역하지 않는다. 처음 이해가 필요한 지점에서만 짧게 푼다.
|
||||||
|
|
||||||
> 포팅은 코드를 다른 언어로 옮겨 다시 만드는 일이다.
|
> 포팅은 코드를 다른 언어로 옮겨 다시 만드는 일입니다.
|
||||||
|
|
||||||
> authorized client는 로그인한 사용자와 OAuth client에 연결된 token 묶음이다.
|
> authorized client는 로그인한 사용자와 OAuth client에 연결된 token 묶음입니다.
|
||||||
|
|
||||||
뒤 문장에서 이미 행동으로 뜻이 드러나면 별도의 정의를 반복하지 않는다.
|
뒤 문장에서 이미 행동으로 뜻이 드러나면 별도의 정의를 반복하지 않는다.
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
+647
-298
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,6 @@ Processing /home/donghyeon/workspace/ai-tool/document-haness
|
|||||||
Building wheels for collected packages: claridoc-harness
|
Building wheels for collected packages: claridoc-harness
|
||||||
Building wheel for claridoc-harness (pyproject.toml): started
|
Building wheel for claridoc-harness (pyproject.toml): started
|
||||||
Building wheel for claridoc-harness (pyproject.toml): finished with status 'done'
|
Building wheel for claridoc-harness (pyproject.toml): finished with status 'done'
|
||||||
Created wheel for claridoc-harness: filename=claridoc_harness-0.2.0-py3-none-any.whl size=193673 sha256=9a5dddeee5df1fb98ae949c56213b1f6ff0143b3b5eb31f541eeb1f907ebbf83
|
Created wheel for claridoc-harness: filename=claridoc_harness-0.2.0-py3-none-any.whl size=212405 sha256=a8eb8563b280593146c32b52e1ee00d7bfe4e9ca312988d2d7cf72a8f847e004
|
||||||
Stored in directory: /tmp/pip-ephem-wheel-cache-04qw0e2v/wheels/35/8c/5a/14e7d960f0df960a7b2711bf02a47dcdc5925d278104863c49
|
Stored in directory: /tmp/pip-ephem-wheel-cache-5wc_0dhn/wheels/35/8c/5a/14e7d960f0df960a7b2711bf02a47dcdc5925d278104863c49
|
||||||
Successfully built claridoc-harness
|
Successfully built claridoc-harness
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,6 +14,7 @@ class ValidationError(ValueError):
|
|||||||
|
|
||||||
class DocumentType(str, Enum):
|
class DocumentType(str, Enum):
|
||||||
TECHNICAL_BLOG = "technical_blog"
|
TECHNICAL_BLOG = "technical_blog"
|
||||||
|
README = "readme"
|
||||||
TUTORIAL = "tutorial"
|
TUTORIAL = "tutorial"
|
||||||
HOW_TO = "how_to"
|
HOW_TO = "how_to"
|
||||||
EXPLANATION = "explanation"
|
EXPLANATION = "explanation"
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ STRUCTURE_SPECS: dict[DocumentType, tuple[SectionSpec, ...]] = {
|
|||||||
S("tradeoffs", "얻은 것, 잃은 것, 적용하지 않을 때", "What was gained, lost, and when not to apply it", "이 선택의 비용과 한계는 무엇이며 언제 다른 선택이 나은가?", "What are the costs and limits, and when is another choice better?", "프로젝트 지역 결정을 보편 법칙처럼 쓰지 않고, 적용 조건과 남은 위험을 제시한다.", "Do not universalize a project-local decision; state applicability and remaining risks.", ("얻은 것", "잃은 것", "적용 조건", "남은 위험"), ("gains", "costs", "applicability", "remaining risks")),
|
S("tradeoffs", "얻은 것, 잃은 것, 적용하지 않을 때", "What was gained, lost, and when not to apply it", "이 선택의 비용과 한계는 무엇이며 언제 다른 선택이 나은가?", "What are the costs and limits, and when is another choice better?", "프로젝트 지역 결정을 보편 법칙처럼 쓰지 않고, 적용 조건과 남은 위험을 제시한다.", "Do not universalize a project-local decision; state applicability and remaining risks.", ("얻은 것", "잃은 것", "적용 조건", "남은 위험"), ("gains", "costs", "applicability", "remaining risks")),
|
||||||
S("conclusion", "결국 지키려던 것은 무엇이었나", "What the design was ultimately protecting", "세부 기술을 걷어냈을 때 남는 판단은 무엇인가?", "What judgment remains after removing implementation detail?", "앞 내용을 반복하지 않고, 문제와 선택을 연결하는 한 문장 판단으로 닫는다.", "Close with a compact judgment that reconnects the problem and choice without repetition.", ("압축된 판단", "독자가 자신의 환경에서 확인할 질문"), ("compressed judgment", "question for the reader's environment")),
|
S("conclusion", "결국 지키려던 것은 무엇이었나", "What the design was ultimately protecting", "세부 기술을 걷어냈을 때 남는 판단은 무엇인가?", "What judgment remains after removing implementation detail?", "앞 내용을 반복하지 않고, 문제와 선택을 연결하는 한 문장 판단으로 닫는다.", "Close with a compact judgment that reconnects the problem and choice without repetition.", ("압축된 판단", "독자가 자신의 환경에서 확인할 질문"), ("compressed judgment", "question for the reader's environment")),
|
||||||
),
|
),
|
||||||
|
DocumentType.README: (
|
||||||
|
S("problem_value", "이 프로젝트가 필요한 이유", "Why this project exists", "어떤 구체적인 문제를 해결하며 왜 이 프로젝트가 필요한가?", "What concrete problem does this project solve, and why does it exist?", "독자가 겪는 문제와 프로젝트가 제공하는 가치를 실제 상황에서 설명한다.", "Explain the reader's problem and the project's value through a concrete situation.", ("문제 상황", "프로젝트 가치", "대상 독자"), ("problem context", "project value", "intended reader")),
|
||||||
|
S("principles", "동작 원칙과 지키는 경계", "Operating principles and boundaries", "사용 전에 알아야 할 핵심 원칙과 경계는 무엇인가?", "Which principles and boundaries must readers understand before use?", "프로젝트가 보장하는 동작과 의도적으로 보장하지 않는 범위를 구분한다.", "Separate guaranteed behavior from deliberately unsupported scope.", ("핵심 원칙", "보장 범위", "비보장 범위"), ("core principles", "guarantees", "non-guarantees")),
|
||||||
|
S("workflow", "전체 동작 흐름", "End-to-end workflow", "입력부터 결과와 검증 기록까지 어떤 순서로 진행되는가?", "How does work proceed from input to output and verification artifacts?", "주요 구성요소와 산출물이 이어지는 전체 흐름을 보여 준다.", "Show the end-to-end flow connecting components and artifacts.", ("입력", "주요 단계", "독자용 결과", "내부 산출물"), ("inputs", "main stages", "reader output", "internal artifacts")),
|
||||||
|
S("installation", "설치와 시작 전 준비", "Installation and prerequisites", "실행 전에 무엇을 설치하고 준비해야 하는가?", "What must be installed and prepared before use?", "지원 버전, 필수 도구, 설치 명령과 초기 상태를 설명한다.", "Explain supported versions, required tools, installation commands, and initial state.", ("지원 버전", "필수 도구", "설치 명령"), ("supported versions", "required tools", "installation commands")),
|
||||||
|
S("quickstart", "가장 작은 실행 예시", "Smallest useful run", "가장 짧은 경로로 어떤 유용한 결과를 확인할 수 있는가?", "What useful result can be observed through the shortest path?", "복사 가능한 최소 명령과 예상 결과, 확인 지점을 제공한다.", "Provide the smallest copyable command, expected result, and verification point.", ("최소 입력", "실행 명령", "예상 결과", "확인 방법"), ("minimal input", "run command", "expected result", "verification")),
|
||||||
|
S("configuration", "주요 설정과 선택 기준", "Configuration and selection criteria", "어떤 설정을 언제 선택하며 결과에 어떤 영향을 주는가?", "Which settings should be chosen when, and how do they affect the result?", "핵심 설정의 기본값, 선택 조건, 비용과 제한을 연결한다.", "Connect key configuration defaults to selection criteria, costs, and limits.", ("설정 항목", "기본값", "선택 조건", "영향"), ("settings", "defaults", "selection criteria", "effects")),
|
||||||
|
S("verification", "검증과 문제 확인", "Verification and diagnosis", "성공을 어떻게 확인하고 대표적인 실패를 어떻게 좁히는가?", "How is success verified and common failure narrowed down?", "관측 가능한 성공 기준과 비파괴 진단 경로를 제공한다.", "Provide observable success criteria and a non-destructive diagnostic path.", ("성공 기준", "확인 명령", "대표 실패 신호", "진단 경로"), ("success criteria", "check command", "failure signal", "diagnostic path")),
|
||||||
|
S("limits_next", "한계와 다음 행동", "Limits and next action", "어디까지 검증되었으며 다음에 무엇을 해야 하는가?", "What has been verified, where are the limits, and what comes next?", "근거 한계와 비지원 범위를 밝히고 직접 연결된 다음 행동으로 닫는다.", "State evidence limits and unsupported scope, then close with the next directly related action.", ("검증 범위", "한계", "비지원 항목", "다음 행동"), ("verified scope", "limits", "unsupported items", "next action")),
|
||||||
|
),
|
||||||
DocumentType.TUTORIAL: (
|
DocumentType.TUTORIAL: (
|
||||||
S("outcome", "완성 결과와 학습 목표", "Outcome and learning objective", "끝에서 무엇을 만들고 무엇을 배우는가?", "What will be built and learned?", "가시적인 결과와 학습 목표를 먼저 보여준다.", "Show the visible outcome and learning objective first.", ("완성 상태", "학습 목표", "예상 소요 범위"), ("finished state", "learning objective", "expected effort")),
|
S("outcome", "완성 결과와 학습 목표", "Outcome and learning objective", "끝에서 무엇을 만들고 무엇을 배우는가?", "What will be built and learned?", "가시적인 결과와 학습 목표를 먼저 보여준다.", "Show the visible outcome and learning objective first.", ("완성 상태", "학습 목표", "예상 소요 범위"), ("finished state", "learning objective", "expected effort")),
|
||||||
S("prerequisites", "시작 전 준비 사항", "Prerequisites", "시작 전에 무엇이 준비되어야 하는가?", "What must be ready before starting?", "필요 지식, 도구, 버전, 초기 상태를 명시한다.", "State required knowledge, tools, versions, and initial state.", ("지식", "도구와 버전", "초기 상태"), ("knowledge", "tools and versions", "initial state")),
|
S("prerequisites", "시작 전 준비 사항", "Prerequisites", "시작 전에 무엇이 준비되어야 하는가?", "What must be ready before starting?", "필요 지식, 도구, 버전, 초기 상태를 명시한다.", "State required knowledge, tools, versions, and initial state.", ("지식", "도구와 버전", "초기 상태"), ("knowledge", "tools and versions", "initial state")),
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1 +1 @@
|
|||||||
9a5dddeee5df1fb98ae949c56213b1f6ff0143b3b5eb31f541eeb1f907ebbf83 claridoc_harness-0.2.0-py3-none-any.whl
|
a8eb8563b280593146c32b52e1ee00d7bfe4e9ca312988d2d7cf72a8f847e004 claridoc_harness-0.2.0-py3-none-any.whl
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -0,0 +1,630 @@
|
|||||||
|
# Korean Experience-Prose Contract Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Make Korean technical blogs and Korean READMEs use one experience-oriented `합니다/했습니다` prose contract that drafting, review, revision, deterministic lint, and the quality gate all enforce.
|
||||||
|
|
||||||
|
**Architecture:** Add `readme` as a first-class document type, centralize style activation and prompt guidance in `claridoc.style_contracts`, and add Markdown-aware deterministic style checks to `claridoc.lint`. Keep objective checks in lint and qualitative experience-flow checks in every model review, then migrate the maintained Korean fixtures and repository README to the enforced contract.
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3.10+, standard-library `unittest`, JSON Schema Draft 2020-12, Markdown text processing with `re`, existing ClariDoc provider and quality-gate abstractions.
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- Apply the contract automatically to Korean `technical_blog` briefs using `auto`, `woowahan_tech_blog_ko`, or `korean_problem_solving_blog`.
|
||||||
|
- Apply the contract automatically to every Korean `readme`.
|
||||||
|
- Do not apply first-person retrospective rules to tutorial, how-to, reference, troubleshooting, explanation, or design-document types.
|
||||||
|
- Preserve `Brief → SourcePack → deterministic outline → draft → lint/reviews → revision → quality gate → reader/provenance artifacts`.
|
||||||
|
- Treat source text and quoted examples as untrusted data; never invent experience or decision rationale.
|
||||||
|
- Exempt fenced code, headings, tables, block quotations, image alt text, command output, and quoted spans from formal-ending lint.
|
||||||
|
- Style-contract failures are blockers and cannot pass through configured error tolerance.
|
||||||
|
- Preserve unrelated user changes and do not regenerate `build/`, `dist/`, `.verify/`, or `.run/` artifacts during implementation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Add a first-class README document contract
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/claridoc/models.py`
|
||||||
|
- Modify: `src/claridoc/structures.py`
|
||||||
|
- Modify: `schemas/brief.schema.json`
|
||||||
|
- Modify: `schemas/outline.schema.json`
|
||||||
|
- Modify: `tests/test_models.py`
|
||||||
|
- Modify: `tests/test_schemas.py`
|
||||||
|
- Modify: `tests/test_structures.py`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: existing `DocumentType`, `Brief.from_dict`, and `STRUCTURE_SPECS`.
|
||||||
|
- Produces: `DocumentType.README` with value `"readme"` and an eight-intent deterministic outline.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing runtime and structure tests**
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_readme_brief_round_trip(self) -> None:
|
||||||
|
brief = Brief.from_dict(brief_dict("readme"))
|
||||||
|
self.assertEqual(brief.document_type, DocumentType.README)
|
||||||
|
self.assertEqual(Brief.from_dict(brief.to_dict()).document_type, DocumentType.README)
|
||||||
|
```
|
||||||
|
|
||||||
|
and:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_readme_outline_preserves_reader_onboarding_order(self) -> None:
|
||||||
|
brief = Brief.from_dict(brief_dict("readme"))
|
||||||
|
outline = create_outline(brief, make_sources())
|
||||||
|
self.assertEqual(
|
||||||
|
[section.intent for section in outline.sections],
|
||||||
|
[
|
||||||
|
"problem_value",
|
||||||
|
"principles",
|
||||||
|
"workflow",
|
||||||
|
"installation",
|
||||||
|
"quickstart",
|
||||||
|
"configuration",
|
||||||
|
"verification",
|
||||||
|
"limits_next",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Extend the schema test to validate a `readme` brief and outline instance with
|
||||||
|
`jsonschema.Draft202012Validator`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the focused tests and verify RED**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest \
|
||||||
|
tests.test_models.ModelTests.test_readme_brief_round_trip \
|
||||||
|
tests.test_structures.StructureTests.test_readme_outline_preserves_reader_onboarding_order \
|
||||||
|
tests.test_schemas.SchemaTests.test_readme_is_accepted_by_brief_and_outline_schemas -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: failures because `"readme"` is not in the runtime enum or schemas.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement the README type and deterministic outline**
|
||||||
|
|
||||||
|
Add:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class DocumentType(str, Enum):
|
||||||
|
...
|
||||||
|
README = "readme"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add eight `SectionSpec` entries under `DocumentType.README` using the approved
|
||||||
|
intent order. Each section must have Korean and English titles, reader
|
||||||
|
questions, purposes, and concrete `must_include` fields. Add `"readme"` to the
|
||||||
|
two schema enums.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run the focused tests and verify GREEN**
|
||||||
|
|
||||||
|
Run the Step 2 command.
|
||||||
|
|
||||||
|
Expected: all three tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit the model contract**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/claridoc/models.py src/claridoc/structures.py \
|
||||||
|
schemas/brief.schema.json schemas/outline.schema.json \
|
||||||
|
tests/test_models.py tests/test_schemas.py tests/test_structures.py
|
||||||
|
git commit -m "feat: add README document contract"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Centralize the Korean experience-prose prompt contract
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/claridoc/style_contracts.py`
|
||||||
|
- Modify: `src/claridoc/prompts.py`
|
||||||
|
- Modify: `tests/test_prompts.py`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `Brief.is_korean`, `Brief.document_type`, and `constraints.style_profile`.
|
||||||
|
- Produces:
|
||||||
|
|
||||||
|
```python
|
||||||
|
KOREAN_EXPERIENCE_CONTRACT_ID = "korean_first_person_experience_v1"
|
||||||
|
def korean_experience_contract_applies(brief: Brief) -> bool: ...
|
||||||
|
def style_guidance(brief: Brief) -> str: ...
|
||||||
|
def mandatory_style_review_checks(brief: Brief) -> str: ...
|
||||||
|
def revision_style_protocol(brief: Brief) -> str: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing prompt propagation tests**
|
||||||
|
|
||||||
|
Replace the narrow ordinal-only prompt test with separate tests that assert:
|
||||||
|
|
||||||
|
```python
|
||||||
|
for prompt in (draft, review, revision):
|
||||||
|
self.assertIn("korean_first_person_experience_v1", prompt)
|
||||||
|
self.assertIn("저는", prompt)
|
||||||
|
self.assertIn("제가", prompt)
|
||||||
|
self.assertIn("했습니다", prompt)
|
||||||
|
self.assertIn("현재 동작과 기술 설명", prompt)
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a Korean `readme` case with the same assertions, an English technical-blog
|
||||||
|
case that does not contain the contract ID, and a Korean `tutorial` case that
|
||||||
|
does not contain the contract ID. Assert that review asks whether first person
|
||||||
|
represents a real observation and revision asks for a whole-document recheck.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run prompt tests and verify RED**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest tests.test_prompts -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: contract-ID and README propagation assertions fail.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement the shared contract module**
|
||||||
|
|
||||||
|
Move the existing Korean technical-blog profile out of `prompts.py`. Return a
|
||||||
|
single provider-facing contract for the approved activation cases. Include:
|
||||||
|
|
||||||
|
```text
|
||||||
|
concrete starting point
|
||||||
|
→ initial expectation
|
||||||
|
→ observed difference
|
||||||
|
→ immediate term explanation
|
||||||
|
→ author action or decision
|
||||||
|
→ result, cost, or remaining limit
|
||||||
|
```
|
||||||
|
|
||||||
|
Require `했습니다` for performed or observed work and `합니다` for current
|
||||||
|
behavior. State that `저는/제가` must establish a supported experience, not
|
||||||
|
decorate an objective explanation. State that unsupported conversations,
|
||||||
|
emotions, failures, durations, results, and rationales are forbidden.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Inject the shared contract into every provider stage**
|
||||||
|
|
||||||
|
Make planning and drafting call `style_guidance(brief)`. Add
|
||||||
|
`mandatory_style_review_checks(brief)` to the mandatory review section and
|
||||||
|
`revision_style_protocol(brief)` to the revision protocol. Keep ordinal-frame
|
||||||
|
guidance inside the shared contract so no abbreviated duplicate remains in
|
||||||
|
`prompts.py`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run prompt tests and verify GREEN**
|
||||||
|
|
||||||
|
Run the Step 2 command.
|
||||||
|
|
||||||
|
Expected: all prompt tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit prompt integration**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/claridoc/style_contracts.py src/claridoc/prompts.py tests/test_prompts.py
|
||||||
|
git commit -m "feat: propagate Korean prose contract to providers"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Add Markdown-aware deterministic style lint
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/claridoc/style_contracts.py`
|
||||||
|
- Modify: `src/claridoc/lint.py`
|
||||||
|
- Modify: `tests/test_lint.py`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `korean_experience_contract_applies(brief)` and Markdown text.
|
||||||
|
- Produces:
|
||||||
|
|
||||||
|
```python
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class ReaderProseSegment:
|
||||||
|
text: str
|
||||||
|
line: int
|
||||||
|
h2_title: str | None
|
||||||
|
|
||||||
|
def reader_prose_segments(markdown: str) -> list[ReaderProseSegment]: ...
|
||||||
|
def plain_form_ending_locations(markdown: str) -> list[int]: ...
|
||||||
|
def first_person_metrics(markdown: str) -> dict[str, int | float | bool]: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
and lint codes `STYLE002` and `STYLE003`.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write a failing formal-ending lint test**
|
||||||
|
|
||||||
|
Create a Korean experience-contract brief and a structurally valid document,
|
||||||
|
then replace one prose sentence with `현재 구현은 이 값을 사용한다.`. Assert:
|
||||||
|
|
||||||
|
```python
|
||||||
|
issues = [issue for issue in report.issues if issue.code == "STYLE002"]
|
||||||
|
self.assertEqual(len(issues), 1)
|
||||||
|
self.assertEqual(issues[0].severity, Severity.BLOCKER)
|
||||||
|
self.assertEqual(report.metrics["plain_form_ending_count"], 1)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the focused test and verify RED**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest \
|
||||||
|
tests.test_lint.LintTests.test_korean_experience_contract_blocks_plain_form_endings -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `STYLE002` is absent.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement minimal Markdown prose extraction and ending lint**
|
||||||
|
|
||||||
|
Track fenced-code state and current H2 while scanning lines. Exclude headings,
|
||||||
|
block quotations, tables, image-only lines, and command-output blocks. Remove
|
||||||
|
inline code, Markdown link targets, and paired quoted spans before matching
|
||||||
|
plain Korean declarative endings with a boundary that does not match `니다.`.
|
||||||
|
Consolidate all matches into one blocker and record the total count.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run the focused test and verify GREEN**
|
||||||
|
|
||||||
|
Run the Step 2 command.
|
||||||
|
|
||||||
|
Expected: the test passes.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Write failing exclusion tests**
|
||||||
|
|
||||||
|
Build a document whose fenced code, heading, table cell, block quote, image alt
|
||||||
|
text, inline code, and direct quoted example contain `한다.` while reader prose
|
||||||
|
uses `합니다.`. Assert that `STYLE002` is absent and
|
||||||
|
`plain_form_ending_count == 0`.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run the exclusion test and verify RED**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest \
|
||||||
|
tests.test_lint.LintTests.test_korean_style_lint_exempts_non_reader_prose -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: at least one exempt region is incorrectly counted until all
|
||||||
|
exclusions are implemented.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Complete the exclusion parser and verify GREEN**
|
||||||
|
|
||||||
|
Refine `reader_prose_segments` only as needed for the failing examples. Do not
|
||||||
|
implement a general Markdown parser or add a dependency.
|
||||||
|
|
||||||
|
- [ ] **Step 8: Write failing first-person coverage tests**
|
||||||
|
|
||||||
|
Add tests for:
|
||||||
|
|
||||||
|
- no `저는/제가` in the opening;
|
||||||
|
- fewer than half of substantive H2 sections containing a marker;
|
||||||
|
- table-only and code-only H2 sections not entering the denominator;
|
||||||
|
- opening plus at least half of substantive sections passing.
|
||||||
|
|
||||||
|
Assert `STYLE003` is one consolidated blocker and that the metrics contain the
|
||||||
|
approved contract ID, counts, and coverage.
|
||||||
|
|
||||||
|
- [ ] **Step 9: Run coverage tests and verify RED**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest \
|
||||||
|
tests.test_lint.LintTests.test_korean_style_lint_requires_first_person_opening \
|
||||||
|
tests.test_lint.LintTests.test_korean_style_lint_requires_major_section_coverage \
|
||||||
|
tests.test_lint.LintTests.test_korean_style_lint_ignores_non_prose_sections \
|
||||||
|
tests.test_lint.LintTests.test_korean_style_lint_accepts_compliant_experience_prose -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: missing `STYLE003` and metrics failures.
|
||||||
|
|
||||||
|
- [ ] **Step 10: Implement first-person metrics and verify GREEN**
|
||||||
|
|
||||||
|
Treat the first substantive reader-prose paragraph as the opening. Count each
|
||||||
|
substantive H2 at most once. Require an opening marker and
|
||||||
|
`marked_sections / substantive_sections >= 0.5`. If there are no substantive
|
||||||
|
H2 sections, let existing structure checks handle the empty document while
|
||||||
|
recording zero coverage.
|
||||||
|
|
||||||
|
- [ ] **Step 11: Run all lint tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest tests.test_lint -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: style tests pass; fixture-dependent failures, if any, identify the
|
||||||
|
next migration task rather than being hidden.
|
||||||
|
|
||||||
|
- [ ] **Step 12: Commit deterministic enforcement**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/claridoc/style_contracts.py src/claridoc/lint.py tests/test_lint.py
|
||||||
|
git commit -m "feat: block Korean prose contract violations"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Make maintained fixtures satisfy the enforced contract
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/claridoc/providers/mock.py`
|
||||||
|
- Modify: `examples/golden/application-core-spring-di-boundary.md`
|
||||||
|
- Modify: `tests/test_lint.py`
|
||||||
|
- Modify: `tests/test_pipeline.py`
|
||||||
|
- Modify: `src/claridoc/report.py`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: new style metrics and existing mock `draft`/`revise` stages.
|
||||||
|
- Produces: contract-compliant mock Korean technical-blog prose and quality
|
||||||
|
reports that expose the active style contract and metrics.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing mock-pipeline and report assertions**
|
||||||
|
|
||||||
|
In `test_end_to_end_mock_run_creates_auditable_artifacts`, assert:
|
||||||
|
|
||||||
|
```python
|
||||||
|
self.assertEqual(
|
||||||
|
result.rounds[-1].lint_report.metrics["style_contract"],
|
||||||
|
"korean_first_person_experience_v1",
|
||||||
|
)
|
||||||
|
self.assertIn("korean_first_person_experience_v1", report_text)
|
||||||
|
```
|
||||||
|
|
||||||
|
Add a pipeline test that supplies a provider document with a `STYLE002`
|
||||||
|
violation and sets `max_errors` above zero; assert `result.passed` is false and
|
||||||
|
the blocker appears in `quality-gate.json`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run pipeline and golden tests and verify RED**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest \
|
||||||
|
tests.test_pipeline \
|
||||||
|
tests.test_lint.LintTests.test_golden_application_core_example_has_no_material_lint_issue -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: the report omits style metrics and maintained fixtures fail the new
|
||||||
|
blockers.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update mock prose without weakening lint**
|
||||||
|
|
||||||
|
Revise the mock technical-blog generator so its opening and at least half of
|
||||||
|
its H2 sections use supported `저는/제가` experience transitions and all
|
||||||
|
reader-facing Korean sentences use `합니다/했습니다`. Preserve synthetic
|
||||||
|
fixture warnings and never describe mock prose as quality evidence.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Migrate the golden document**
|
||||||
|
|
||||||
|
Use the `revising-korean-technical-prose` skill to revise the golden document
|
||||||
|
in place. Preserve its exact H1/H2 contract, technical claims, decision
|
||||||
|
rationale, code block, evidence boundaries, and length intent.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Render style metrics in the run report**
|
||||||
|
|
||||||
|
Add a `Reader-prose contract` subsection when
|
||||||
|
`final.lint_report.metrics["style_contract"] != "none"`. Render contract ID,
|
||||||
|
plain-ending count, first-person marker count, substantive-section count, and
|
||||||
|
coverage.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Run pipeline and lint tests and verify GREEN**
|
||||||
|
|
||||||
|
Run the Step 2 command.
|
||||||
|
|
||||||
|
Expected: all tests pass and the report contains contract evidence.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit fixture and report integration**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/claridoc/providers/mock.py src/claridoc/report.py \
|
||||||
|
examples/golden/application-core-spring-di-boundary.md \
|
||||||
|
tests/test_lint.py tests/test_pipeline.py
|
||||||
|
git commit -m "test: migrate maintained Korean prose fixtures"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Restore the technical-document authoring skill
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `.agents/skills/technical-document-author/SKILL.md`
|
||||||
|
- Create: `.agents/skills/technical-document-author/references/logic-contract.md`
|
||||||
|
- Create: `.agents/skills/technical-document-author/references/review-rubric.md`
|
||||||
|
- Create: `.agents/skills/technical-document-author/agents/openai.yaml`
|
||||||
|
- Create: `tests/test_repository_contracts.py`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: repository `AGENTS.md`, the ClariDoc pipeline, and
|
||||||
|
`revising-korean-technical-prose`.
|
||||||
|
- Produces: the authoring skill path already required by `AGENTS.md`, with a
|
||||||
|
validation-artifact completion contract.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Invoke the skill-writing guidance**
|
||||||
|
|
||||||
|
Read and follow both `skill-creator` and `superpowers:writing-skills` before
|
||||||
|
creating the skill files.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write a failing repository-contract test**
|
||||||
|
|
||||||
|
Assert that the four skill files exist and that `SKILL.md` contains:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Brief
|
||||||
|
SourcePack
|
||||||
|
STRUCTURE_SPECS
|
||||||
|
revising-korean-technical-prose
|
||||||
|
quality-gate.json
|
||||||
|
provenance
|
||||||
|
```
|
||||||
|
|
||||||
|
Also assert that the skill tells authors not to claim completion without lint,
|
||||||
|
independent review, and quality-gate artifacts.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run the repository-contract test and verify RED**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest \
|
||||||
|
tests.test_repository_contracts.RepositoryContractTests.test_technical_author_skill_is_complete -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: failure because the required skill path is absent.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Create the authoring skill and references**
|
||||||
|
|
||||||
|
The skill must route every document through the repository sequence, treat
|
||||||
|
inputs as untrusted data, keep provenance out of reader prose, and invoke the
|
||||||
|
Korean revision skill for Korean technical blogs and READMEs. The review rubric
|
||||||
|
must separate deterministic findings from model judgment. The logic contract
|
||||||
|
must preserve context, choice, reason, alternative, accepted cost, guardrail,
|
||||||
|
verification, and evidence status.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run the repository-contract test and verify GREEN**
|
||||||
|
|
||||||
|
Run the Step 3 command.
|
||||||
|
|
||||||
|
Expected: pass.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit the restored skill**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .agents/skills/technical-document-author tests/test_repository_contracts.py
|
||||||
|
git commit -m "feat: restore technical document author skill"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Revise and document the repository README
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `README.md`
|
||||||
|
- Create: `examples/briefs/claridoc-readme.json`
|
||||||
|
- Modify: `tests/test_schemas.py`
|
||||||
|
- Modify: `tests/test_cli.py`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: `DocumentType.README`, shared prompt contract, and CLI
|
||||||
|
`validate`/`outline` behavior.
|
||||||
|
- Produces: a schema-valid README brief, documented usage, and a repository
|
||||||
|
README written in the enforced style.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing example and CLI tests**
|
||||||
|
|
||||||
|
Add tests that load `examples/briefs/claridoc-readme.json`, validate it through
|
||||||
|
`Brief.from_dict`, and run the CLI `validate` and `outline` commands. Assert
|
||||||
|
that the outline reports type `readme` and the eight approved intents.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the focused tests and verify RED**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest \
|
||||||
|
tests.test_schemas.SchemaTests.test_examples_match_runtime_contracts \
|
||||||
|
tests.test_cli.CliTests.test_readme_brief_validates_and_outlines -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: failure because the README brief does not yet exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Add the README brief fixture**
|
||||||
|
|
||||||
|
Create a Korean `readme` brief for ClariDoc with hidden citations,
|
||||||
|
`style_profile: "auto"`, the current project scope, and no invented operational
|
||||||
|
claims.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run the focused tests and verify GREEN**
|
||||||
|
|
||||||
|
Run the Step 2 command.
|
||||||
|
|
||||||
|
Expected: pass.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Revise README in place**
|
||||||
|
|
||||||
|
Use `revising-korean-technical-prose` and its sentence-pattern reference.
|
||||||
|
Preserve commands, tables, links, diagrams, versions, source hierarchy,
|
||||||
|
provider descriptions, and safety statements. Convert reader-facing Korean
|
||||||
|
prose to `합니다/했습니다`, add supported `저는/제가` experience transitions,
|
||||||
|
and add a section describing:
|
||||||
|
|
||||||
|
- automatic activation for Korean technical blogs and READMEs;
|
||||||
|
- `STYLE002` and `STYLE003`;
|
||||||
|
- model-review responsibilities;
|
||||||
|
- a `readme` brief example and validation command.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Scan the README contract**
|
||||||
|
|
||||||
|
Run a read-only scanner using `reader_prose_segments` and assert:
|
||||||
|
|
||||||
|
```text
|
||||||
|
plain_form_ending_count = 0
|
||||||
|
opening_has_first_person = true
|
||||||
|
experience_section_coverage >= 0.5
|
||||||
|
```
|
||||||
|
|
||||||
|
Review the diff to confirm facts, code blocks, links, and information order
|
||||||
|
remain intact.
|
||||||
|
|
||||||
|
- [ ] **Step 7: Commit README migration**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add README.md examples/briefs/claridoc-readme.json \
|
||||||
|
tests/test_schemas.py tests/test_cli.py
|
||||||
|
git commit -m "docs: apply Korean prose contract to README"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 7: Run regression validation and review the implementation
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify only files required by verified failures in the preceding tasks.
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: all implemented tasks.
|
||||||
|
- Produces: test and review evidence with no regenerated user-owned build or
|
||||||
|
distribution artifacts.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run the full unit and integration suite**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m unittest discover -s tests -v
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run non-destructive contract commands**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PYTHONPATH=src python3 -m claridoc validate \
|
||||||
|
--brief examples/briefs/claridoc-readme.json \
|
||||||
|
--sources examples/sources/retry-policy-sources.json
|
||||||
|
|
||||||
|
PYTHONPATH=src python3 -m claridoc outline \
|
||||||
|
--brief examples/briefs/claridoc-readme.json \
|
||||||
|
--sources examples/sources/retry-policy-sources.json \
|
||||||
|
--output /tmp/claridoc-readme-outline.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: both commands succeed and the output uses `document_type: readme`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Inspect destructive verification scope**
|
||||||
|
|
||||||
|
Do not run `scripts/verify.sh` because it removes and rebuilds `.verify`,
|
||||||
|
`build`, `dist`, egg-info, and demo outputs that already contain user changes.
|
||||||
|
Run its non-destructive validation portions through the unit suite, schema
|
||||||
|
tests, CLI tests, JSON parsing, local-link scan, and an isolated wheel build in
|
||||||
|
`/tmp`.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run independent code review**
|
||||||
|
|
||||||
|
Use `superpowers:requesting-code-review` to inspect the final diff against the
|
||||||
|
design and plan. Resolve every blocker and error through a new failing test
|
||||||
|
before changing production code.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Run verification-before-completion**
|
||||||
|
|
||||||
|
Use `superpowers:verification-before-completion`, rerun the full test suite,
|
||||||
|
README style scan, schema validation, and isolated package build, and record
|
||||||
|
the exact results.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Finish the development branch**
|
||||||
|
|
||||||
|
Use `superpowers:finishing-a-development-branch`. Because the user explicitly
|
||||||
|
requested uninterrupted inline implementation on the current branch, do not
|
||||||
|
merge, push, or open a PR without a new explicit request.
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
# Korean Experience-Prose Contract Design
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
ClariDoc must apply one enforceable Korean prose contract when it writes or
|
||||||
|
reviews a Korean technical blog or Korean README. The contract must preserve
|
||||||
|
facts and document structure while making the reader follow the author's
|
||||||
|
experience in consistent `합니다/했습니다` prose.
|
||||||
|
|
||||||
|
The change closes the gap between a skill file that describes the desired
|
||||||
|
style and a harness that currently neither passes that style to providers nor
|
||||||
|
checks it before returning `PASS`.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The contract applies automatically to:
|
||||||
|
|
||||||
|
- a Korean `technical_blog` using `auto`, `woowahan_tech_blog_ko`, or
|
||||||
|
`korean_problem_solving_blog`;
|
||||||
|
- every Korean `readme`.
|
||||||
|
|
||||||
|
It does not force first-person retrospective prose onto tutorials, how-to
|
||||||
|
guides, references, troubleshooting guides, explanations, or design documents.
|
||||||
|
Those document types keep their existing style behavior.
|
||||||
|
|
||||||
|
The current repository `README.md` is part of the migration. Its factual
|
||||||
|
content, commands, links, tables, and overall information order remain intact,
|
||||||
|
but its reader-facing Korean prose is revised to the same experience-oriented
|
||||||
|
`합니다/했습니다` style.
|
||||||
|
|
||||||
|
## Considered Approaches
|
||||||
|
|
||||||
|
### Prompt-only guidance
|
||||||
|
|
||||||
|
Copy the skill text into the drafting prompt. This has the smallest code
|
||||||
|
change, but it leaves no objective proof that the writer or reviser kept the
|
||||||
|
rules. It would preserve the current failure mode in which one correction
|
||||||
|
causes another part of the document to regress.
|
||||||
|
|
||||||
|
### Opt-in style profile only
|
||||||
|
|
||||||
|
Require README authors to select a special `style_profile`. This avoids adding
|
||||||
|
a document type, but a missing configuration value silently disables the
|
||||||
|
contract. It also makes README structure masquerade as another document type.
|
||||||
|
|
||||||
|
### Shared contract with a first-class README type
|
||||||
|
|
||||||
|
Add `readme` to the document model and define one shared prose contract used by
|
||||||
|
prompts, lint, reviews, revisions, reports, and tests. This is the selected
|
||||||
|
approach because it makes activation explicit and lets deterministic and model
|
||||||
|
judgment checks cover different parts of the same contract.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### First-class README document type
|
||||||
|
|
||||||
|
`DocumentType.README` is added to the model and JSON schemas. Its deterministic
|
||||||
|
outline contains these intents in order:
|
||||||
|
|
||||||
|
1. `problem_value`: the concrete problem and why the project exists;
|
||||||
|
2. `principles`: the project behavior and boundaries readers must understand;
|
||||||
|
3. `workflow`: the end-to-end operating flow;
|
||||||
|
4. `installation`: prerequisites and installation;
|
||||||
|
5. `quickstart`: the smallest useful execution path and expected result;
|
||||||
|
6. `configuration`: the main configuration choices and their effects;
|
||||||
|
7. `verification`: how to verify success and diagnose common failure;
|
||||||
|
8. `limits_next`: evidence limits, unsupported claims, and the next relevant
|
||||||
|
action.
|
||||||
|
|
||||||
|
The planner may refine titles and evidence allocation, but it must preserve
|
||||||
|
these intents and their order just as it does for existing document types.
|
||||||
|
|
||||||
|
### Shared prose contract
|
||||||
|
|
||||||
|
A focused `claridoc.style_contracts` module owns activation and provider-facing
|
||||||
|
guidance. It exposes:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def korean_experience_contract_applies(brief: Brief) -> bool: ...
|
||||||
|
|
||||||
|
def style_guidance(brief: Brief) -> str: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
The returned guidance includes the same rules in every provider stage:
|
||||||
|
|
||||||
|
- open the document and major transitions from a concrete code, screen,
|
||||||
|
request, or problem the author encountered;
|
||||||
|
- show the initial expectation, then the observed difference;
|
||||||
|
- explain an unfamiliar term where it first becomes necessary;
|
||||||
|
- show what the author checked, selected, or changed;
|
||||||
|
- close the thread with the result, accepted cost, or remaining problem;
|
||||||
|
- use `저는` or `제가` where it establishes the experience, without repeating
|
||||||
|
it mechanically in every sentence;
|
||||||
|
- use `했습니다` for observed or performed work and `합니다` for current
|
||||||
|
behavior and technical explanation;
|
||||||
|
- never invent an emotion, conversation, failure, duration, result, or
|
||||||
|
technical rationale that the evidence does not support;
|
||||||
|
- preserve code, commands, identifiers, numbers, links, tables, diagrams,
|
||||||
|
claims, evidence status, and outline order.
|
||||||
|
|
||||||
|
The guidance describes the canonical paragraph pattern as form, not as facts
|
||||||
|
to copy:
|
||||||
|
|
||||||
|
```text
|
||||||
|
concrete starting point
|
||||||
|
→ initial expectation
|
||||||
|
→ observed difference
|
||||||
|
→ immediate term explanation
|
||||||
|
→ author action or decision
|
||||||
|
→ result, cost, or remaining limit
|
||||||
|
```
|
||||||
|
|
||||||
|
`drafting_prompt`, `review_prompt`, and `revision_prompt` all call this shared
|
||||||
|
module. No stage keeps a separate abbreviated version.
|
||||||
|
|
||||||
|
### Deterministic checks
|
||||||
|
|
||||||
|
Deterministic lint checks only properties that can be recognized without
|
||||||
|
guessing the author's intent.
|
||||||
|
|
||||||
|
`STYLE002` reports a blocker when reader-facing prose mixes plain declarative
|
||||||
|
endings such as `한다.`, `있다.`, `아니다.`, or `~했다.` into a document whose
|
||||||
|
contract requires `합니다/했습니다`. Fenced code, headings, Markdown tables,
|
||||||
|
block quotations, image alt text, command output, and quoted spans are excluded.
|
||||||
|
The report consolidates matches and records their count and first locations.
|
||||||
|
|
||||||
|
`STYLE003` reports a blocker when the opening has no explicit `저는` or `제가`
|
||||||
|
marker, or when fewer than half of substantive H2 sections contain an explicit
|
||||||
|
first-person experience marker. A substantive section is an H2 section with at
|
||||||
|
least one reader-facing prose paragraph; code-only and table-only sections do
|
||||||
|
not count.
|
||||||
|
|
||||||
|
The lint report adds:
|
||||||
|
|
||||||
|
- `style_contract`: `korean_first_person_experience_v1` or `none`;
|
||||||
|
- `plain_form_ending_count`;
|
||||||
|
- `first_person_marker_count`;
|
||||||
|
- `experience_section_count`;
|
||||||
|
- `experience_section_coverage`.
|
||||||
|
|
||||||
|
Because both style issues are blockers, configured error tolerances cannot turn
|
||||||
|
them into a passing result.
|
||||||
|
|
||||||
|
Deterministic lint does not try to decide whether a paragraph contains a
|
||||||
|
genuine discovery, whether a term is unfamiliar, or whether the prose sounds
|
||||||
|
natural. Those require model judgment.
|
||||||
|
|
||||||
|
### Independent review and revision
|
||||||
|
|
||||||
|
Every reviewer role receives mandatory prose checks when the contract applies:
|
||||||
|
|
||||||
|
- the opening and major transitions follow an experience rather than listing
|
||||||
|
settled facts;
|
||||||
|
- the paragraph presents an actual expectation or observation rather than
|
||||||
|
inserting `저는` as decoration;
|
||||||
|
- unfamiliar terms are explained at first need;
|
||||||
|
- contrasts name the actual component and behavior that differ;
|
||||||
|
- the document does not manufacture personal history or project rationale;
|
||||||
|
- `합니다/했습니다` remains consistent outside exempt Markdown regions.
|
||||||
|
|
||||||
|
The revision prompt requires a whole-document contract audit after resolving
|
||||||
|
individual findings. This prevents a local rewrite from regressing another
|
||||||
|
section. Each revision round already runs lint and independent reviews again,
|
||||||
|
so the shared contract is re-evaluated before the quality gate can pass.
|
||||||
|
|
||||||
|
### Skill entry point
|
||||||
|
|
||||||
|
The dangling `.agents/skills/technical-document-author/SKILL.md` reference is
|
||||||
|
replaced with a real authoring skill. It preserves the repository sequence:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Brief
|
||||||
|
→ SourcePack
|
||||||
|
→ deterministic outline
|
||||||
|
→ draft
|
||||||
|
→ lint and independent reviews
|
||||||
|
→ revision
|
||||||
|
→ quality gate
|
||||||
|
→ reader document and provenance artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
For Korean technical blogs and Korean READMEs, the authoring skill requires the
|
||||||
|
Korean prose contract and its sentence-pattern reference. It may not claim
|
||||||
|
completion without lint, review, and quality-gate artifacts. The existing
|
||||||
|
`revising-korean-technical-prose` skill remains the focused in-place revision
|
||||||
|
skill.
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
```text
|
||||||
|
Brief(document_type, language, style_profile)
|
||||||
|
→ style-contract activation
|
||||||
|
→ planner keeps deterministic document structure
|
||||||
|
→ writer receives shared prose guidance
|
||||||
|
→ deterministic lint checks endings and first-person coverage
|
||||||
|
→ every reviewer checks experience quality and factual boundaries
|
||||||
|
→ reviser receives the same guidance plus all findings
|
||||||
|
→ lint and reviews run again
|
||||||
|
→ blockers prevent PASS
|
||||||
|
→ report records style metrics and findings
|
||||||
|
```
|
||||||
|
|
||||||
|
## README Migration
|
||||||
|
|
||||||
|
The repository `README.md` is revised in place with the
|
||||||
|
`revising-korean-technical-prose` skill:
|
||||||
|
|
||||||
|
- existing facts, code blocks, commands, paths, links, tables, and diagrams are
|
||||||
|
preserved;
|
||||||
|
- Korean reader-facing prose uses `합니다/했습니다`;
|
||||||
|
- the opening and major transitions explain how the harness's failure modes
|
||||||
|
were encountered and how the implemented workflow addresses them;
|
||||||
|
- no unverified personal event, advice, measurement, or project rationale is
|
||||||
|
added;
|
||||||
|
- a section documents the activation scope, lint codes, review behavior, and
|
||||||
|
`readme` brief usage.
|
||||||
|
|
||||||
|
The migration is checked separately from generated documents because the
|
||||||
|
repository README is not itself a pipeline output artifact.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- Invalid `document_type: readme` handling disappears once the enum and schemas
|
||||||
|
are updated; other unknown types remain validation errors.
|
||||||
|
- Style lint returns actionable locations and correction guidance rather than
|
||||||
|
rewriting content.
|
||||||
|
- Empty or structure-only documents still fail existing structure and length
|
||||||
|
checks; style metrics do not mask those failures.
|
||||||
|
- Quoted evidence and code are excluded from deterministic ending checks so
|
||||||
|
original material is not altered to satisfy prose style.
|
||||||
|
- A model review cannot override a deterministic style blocker.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Tests are added before production changes.
|
||||||
|
|
||||||
|
### Model and structure tests
|
||||||
|
|
||||||
|
- `readme` is accepted by `Brief` and outline schemas;
|
||||||
|
- `readme` receives eight unique required intents in the specified order;
|
||||||
|
- all existing document types retain their current outlines.
|
||||||
|
|
||||||
|
### Prompt tests
|
||||||
|
|
||||||
|
- Korean technical-blog and README draft, review, and revision prompts contain
|
||||||
|
the same contract identifier and required rules;
|
||||||
|
- English and unrelated Korean document types do not receive the contract;
|
||||||
|
- the revision prompt requires a whole-document recheck.
|
||||||
|
|
||||||
|
### Lint tests
|
||||||
|
|
||||||
|
- mixed `한다/합니다` prose is a blocker;
|
||||||
|
- fenced code, headings, tables, block quotations, image alt text, and quoted
|
||||||
|
examples do not cause false positives;
|
||||||
|
- missing opening first person is a blocker;
|
||||||
|
- insufficient substantive-section coverage is a blocker;
|
||||||
|
- a representative experience-oriented technical blog passes;
|
||||||
|
- a representative Korean README passes;
|
||||||
|
- unrelated document types retain existing lint behavior.
|
||||||
|
|
||||||
|
### Pipeline tests
|
||||||
|
|
||||||
|
- a style blocker prevents the quality gate from passing even when configured
|
||||||
|
error tolerance is nonzero;
|
||||||
|
- revision rounds receive the blocker and rerun the contract checks;
|
||||||
|
- final artifacts record the style contract and findings.
|
||||||
|
|
||||||
|
### Repository validation
|
||||||
|
|
||||||
|
- targeted unit tests are run after each TDD cycle;
|
||||||
|
- `PYTHONPATH=src python3 -m unittest discover -s tests -v` is run;
|
||||||
|
- `bash scripts/verify.sh` is run if it can preserve the user's unrelated
|
||||||
|
working-tree changes; otherwise its destructive build steps are inspected
|
||||||
|
and an equivalent non-destructive validation set is reported explicitly;
|
||||||
|
- the revised `README.md` is scanned outside code and quoted regions for plain
|
||||||
|
declarative endings and reviewed against the experience-flow checklist.
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
The implementation is complete only when:
|
||||||
|
|
||||||
|
- Korean technical blogs and Korean READMEs receive the contract in every model
|
||||||
|
stage;
|
||||||
|
- omitting `합니다/했습니다` consistency or first-person experience coverage
|
||||||
|
creates a deterministic blocker;
|
||||||
|
- qualitative experience flow is a mandatory independent-review concern;
|
||||||
|
- a revision cannot pass without rerunning the checks;
|
||||||
|
- `readme` is a supported contract-first document type;
|
||||||
|
- the missing technical-author skill entry point exists and requires validation
|
||||||
|
artifacts;
|
||||||
|
- the repository README follows and documents the same contract;
|
||||||
|
- all targeted and full regression tests pass.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"created_at": "2026-07-29T07:45:31+00:00",
|
"created_at": "2026-07-29T09:23:39+00:00",
|
||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "final/document.md",
|
"path": "final/document.md",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
{
|
{
|
||||||
"path": "provider-events.jsonl",
|
"path": "provider-events.jsonl",
|
||||||
"bytes": 2788,
|
"bytes": 2788,
|
||||||
"sha256": "2a88c5b7d5d75e11ee2017c06099582b7288d1d625c92398b457edac82344037"
|
"sha256": "cebfe8a1d50135a84968341712d4b1b63a492c4bd47d239bffdb2a3bbb140431"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "rounds/round-01/draft.md",
|
"path": "rounds/round-01/draft.md",
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
{
|
{
|
||||||
"path": "run.json",
|
"path": "run.json",
|
||||||
"bytes": 1112,
|
"bytes": 1112,
|
||||||
"sha256": "af0dd9cbd07f6caa9ed27487f04ce0ca1e97b39550ba1a6cc9fc9936ace442e2"
|
"sha256": "eff4efefd44cb67bf0ff46526787ec49c0efae65fb682b37486aa58e13a7fe39"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "stages/01-planner.raw.txt",
|
"path": "stages/01-planner.raw.txt",
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "plan", "provider": "mock", "model": "", "metadata": {"document_type": "technical_blog"}, "status": "started"}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "plan", "provider": "mock", "model": "", "metadata": {"document_type": "technical_blog"}, "status": "started"}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "plan", "provider": "mock", "model": "", "metadata": {"document_type": "technical_blog"}, "status": "completed", "duration_ms": 0.4, "response_characters": 5367, "command": []}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "plan", "provider": "mock", "model": "", "metadata": {"document_type": "technical_blog"}, "status": "completed", "duration_ms": 0.7, "response_characters": 5367, "command": []}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "draft", "provider": "mock", "model": "", "metadata": {}, "status": "started"}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "draft", "provider": "mock", "model": "", "metadata": {}, "status": "started"}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "draft", "provider": "mock", "model": "", "metadata": {}, "status": "completed", "duration_ms": 0.4, "response_characters": 2786, "command": []}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "draft", "provider": "mock", "model": "", "metadata": {}, "status": "completed", "duration_ms": 0.8, "response_characters": 2786, "command": []}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "logic"}, "status": "started"}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "logic"}, "status": "started"}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "logic"}, "status": "completed", "duration_ms": 0.3, "response_characters": 571, "command": []}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "logic"}, "status": "completed", "duration_ms": 0.4, "response_characters": 571, "command": []}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "decision"}, "status": "started"}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "decision"}, "status": "started"}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "decision"}, "status": "completed", "duration_ms": 0.3, "response_characters": 574, "command": []}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "decision"}, "status": "completed", "duration_ms": 0.4, "response_characters": 574, "command": []}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "reader"}, "status": "started"}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "reader"}, "status": "started"}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "reader"}, "status": "completed", "duration_ms": 0.3, "response_characters": 572, "command": []}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "reader"}, "status": "completed", "duration_ms": 0.7, "response_characters": 572, "command": []}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "editor"}, "status": "started"}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "editor"}, "status": "started"}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "editor"}, "status": "completed", "duration_ms": 0.3, "response_characters": 572, "command": []}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "editor"}, "status": "completed", "duration_ms": 0.4, "response_characters": 572, "command": []}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "evidence"}, "status": "started"}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "evidence"}, "status": "started"}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "evidence"}, "status": "completed", "duration_ms": 0.3, "response_characters": 574, "command": []}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "evidence"}, "status": "completed", "duration_ms": 0.4, "response_characters": 574, "command": []}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "operations"}, "status": "started"}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "operations"}, "status": "started"}
|
||||||
{"at": "2026-07-29T07:45:31+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "operations"}, "status": "completed", "duration_ms": 0.3, "response_characters": 576, "command": []}
|
{"at": "2026-07-29T09:23:39+00:00", "stage": "review", "provider": "mock", "model": "", "metadata": {"role": "operations"}, "status": "completed", "duration_ms": 0.4, "response_characters": 576, "command": []}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"created_at": "2026-07-29T07:45:31+00:00",
|
"created_at": "2026-07-29T09:23:39+00:00",
|
||||||
"document": "API 재시도는 횟수가 아니라 부하 예산으로 설계한다",
|
"document": "API 재시도는 횟수가 아니라 부하 예산으로 설계한다",
|
||||||
"document_type": "technical_blog",
|
"document_type": "technical_blog",
|
||||||
"passed": true,
|
"passed": true,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"technical_blog",
|
"technical_blog",
|
||||||
|
"readme",
|
||||||
"tutorial",
|
"tutorial",
|
||||||
"how_to",
|
"how_to",
|
||||||
"explanation",
|
"explanation",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"technical_blog",
|
"technical_blog",
|
||||||
|
"readme",
|
||||||
"tutorial",
|
"tutorial",
|
||||||
"how_to",
|
"how_to",
|
||||||
"explanation",
|
"explanation",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,6 +13,11 @@ from claridoc.models import (
|
|||||||
Severity,
|
Severity,
|
||||||
SourcePack,
|
SourcePack,
|
||||||
)
|
)
|
||||||
|
from claridoc.style_contracts import (
|
||||||
|
KOREAN_EXPERIENCE_CONTRACT_ID,
|
||||||
|
korean_experience_contract_applies,
|
||||||
|
plain_form_ending_locations,
|
||||||
|
)
|
||||||
from claridoc.utils import line_number, normalize_heading, strip_code_blocks, word_count
|
from claridoc.utils import line_number, normalize_heading, strip_code_blocks, word_count
|
||||||
|
|
||||||
|
|
||||||
@@ -201,6 +206,27 @@ def lint_document(text: str, brief: Brief, outline: Outline, sources: SourcePack
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
style_contract_applies = korean_experience_contract_applies(brief)
|
||||||
|
plain_form_locations = (
|
||||||
|
plain_form_ending_locations(text)
|
||||||
|
if style_contract_applies
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
if plain_form_locations:
|
||||||
|
add(
|
||||||
|
"STYLE002",
|
||||||
|
Severity.BLOCKER,
|
||||||
|
(
|
||||||
|
"Reader-facing Korean prose mixes plain declarative endings "
|
||||||
|
f"into the required 합니다/했습니다 style ({len(plain_form_locations)} occurrence(s))."
|
||||||
|
),
|
||||||
|
line=plain_form_locations[0],
|
||||||
|
suggestion=(
|
||||||
|
"Use 했습니다 for observed or performed work and 합니다 for "
|
||||||
|
"current behavior. Preserve quoted material and code unchanged."
|
||||||
|
),
|
||||||
|
)
|
||||||
long_paragraph_count = 0
|
long_paragraph_count = 0
|
||||||
crowded_paragraph_count = 0
|
crowded_paragraph_count = 0
|
||||||
long_sentence_count = 0
|
long_sentence_count = 0
|
||||||
@@ -398,6 +424,12 @@ def lint_document(text: str, brief: Brief, outline: Outline, sources: SourcePack
|
|||||||
"decision_section_count": sum(bool(section.decision_requirements) for section in outline.sections),
|
"decision_section_count": sum(bool(section.decision_requirements) for section in outline.sections),
|
||||||
"numbered_steps": numbered_steps,
|
"numbered_steps": numbered_steps,
|
||||||
"formulaic_ordinal_opening_count": len(formulaic_ordinal_openings),
|
"formulaic_ordinal_opening_count": len(formulaic_ordinal_openings),
|
||||||
|
"style_contract": (
|
||||||
|
KOREAN_EXPERIENCE_CONTRACT_ID
|
||||||
|
if style_contract_applies
|
||||||
|
else "none"
|
||||||
|
),
|
||||||
|
"plain_form_ending_count": len(plain_form_locations),
|
||||||
"has_verification": has_verification,
|
"has_verification": has_verification,
|
||||||
"has_tradeoffs": has_tradeoffs,
|
"has_tradeoffs": has_tradeoffs,
|
||||||
"severity_counts": dict(severity_counts),
|
"severity_counts": dict(severity_counts),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ class ValidationError(ValueError):
|
|||||||
|
|
||||||
class DocumentType(str, Enum):
|
class DocumentType(str, Enum):
|
||||||
TECHNICAL_BLOG = "technical_blog"
|
TECHNICAL_BLOG = "technical_blog"
|
||||||
|
README = "readme"
|
||||||
TUTORIAL = "tutorial"
|
TUTORIAL = "tutorial"
|
||||||
HOW_TO = "how_to"
|
HOW_TO = "how_to"
|
||||||
EXPLANATION = "explanation"
|
EXPLANATION = "explanation"
|
||||||
|
|||||||
+11
-34
@@ -11,6 +11,11 @@ from claridoc.models import (
|
|||||||
Outline,
|
Outline,
|
||||||
SourcePack,
|
SourcePack,
|
||||||
)
|
)
|
||||||
|
from claridoc.style_contracts import (
|
||||||
|
mandatory_style_review_checks,
|
||||||
|
revision_style_protocol,
|
||||||
|
style_guidance,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
FOUNDATION_RULES = """\
|
FOUNDATION_RULES = """\
|
||||||
@@ -29,25 +34,6 @@ FOUNDATION_RULES = """\
|
|||||||
13. End with the decision the reader should carry into a similar situation, not a generic recap or a checklist added by habit.
|
13. End with the decision the reader should carry into a similar situation, not a generic recap or a checklist added by habit.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
WOOWAHAN_TECH_BLOG_KO = """\
|
|
||||||
Korean technical-blog operating profile (derived from a bounded sample of Woowahan engineering articles; it is not an official house-style specification):
|
|
||||||
- Begin from the team or system's concrete context, then expose the friction in observable terms.
|
|
||||||
- Explain why the problem mattered before introducing the selected tool or architecture.
|
|
||||||
- Show prior approaches, failed attempts, or realistic alternatives when they affected the decision.
|
|
||||||
- State the selection criteria and the reason for the final choice. Pair benefits with the cost or boundary that remained.
|
|
||||||
- Let implementation details answer the problem already established; do not turn the article into a component inventory.
|
|
||||||
- Connect verification to the original problem. Report only what the available tests or observations actually prove.
|
|
||||||
- Treat problem -> constraints -> options -> decision as a semantic order, never as a sentence template. Do not narrate outline labels to the reader.
|
|
||||||
- Start a paragraph from a concrete actor, state, change, consequence, or decision when the evidence supports one. Make the subject and impact visible instead of opening with an abstract category label.
|
|
||||||
- Do not open consecutive paragraphs with formulaic ordinal frames such as “첫 번째 제약은”, “두 번째 제약은”, and “세 번째 제약은”. Use ordinals for a real sequence, method, layer, or figure; use a list or meaningful subheadings for genuinely parallel items.
|
|
||||||
- A question heading or transition must receive an immediate answer in the following prose. Do not use unanswered rhetorical questions as decoration.
|
|
||||||
- Use “하지만/다만” only for a real contrast and “이 때문에/그 결과/그래서/이에” only when the referenced cause is explicit in the preceding context.
|
|
||||||
- Use “팀에서는/저희는/우리는” when ownership or project-local judgment matters, not as a filler subject and never to universalize a local choice.
|
|
||||||
- Use conversational but disciplined Korean. Avoid canned phrases such as “이 절에서는”, “제공된 근거에 따르면”, “독자는 ~할 수 있다”, and repeated “먼저/다음으로/마지막으로”.
|
|
||||||
- An “예상 독자” block is optional. Use it only when it materially prevents the wrong audience from reading the article; never insert it as mandatory boilerplate.
|
|
||||||
- Revise for flow: when a paragraph feels paused or a connector feels forced, repair the logical relation rather than adding a transition word.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ROLE_GUIDANCE: dict[str, str] = {
|
ROLE_GUIDANCE: dict[str, str] = {
|
||||||
"logic": "Audit premises, causal links, section order, transitions, contradictions, and whether each conclusion follows from stated constraints and evidence.",
|
"logic": "Audit premises, causal links, section order, transitions, contradictions, and whether each conclusion follows from stated constraints and evidence.",
|
||||||
"reader": "Simulate the declared reader. Audit orientation, missing context, cognitive load, examples, scan paths, and whether process language or internal metadata breaks immersion.",
|
"reader": "Simulate the declared reader. Audit orientation, missing context, cognitive load, examples, scan paths, and whether process language or internal metadata breaks immersion.",
|
||||||
@@ -62,17 +48,6 @@ def _dump(value: Any) -> str:
|
|||||||
return json.dumps(value, ensure_ascii=False, indent=2)
|
return json.dumps(value, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
|
||||||
def _style_guidance(brief: Brief) -> str:
|
|
||||||
profile = brief.constraints.style_profile.casefold()
|
|
||||||
if brief.is_korean and brief.document_type.value == "technical_blog" and profile in {
|
|
||||||
"auto",
|
|
||||||
"woowahan_tech_blog_ko",
|
|
||||||
"korean_problem_solving_blog",
|
|
||||||
}:
|
|
||||||
return WOOWAHAN_TECH_BLOG_KO
|
|
||||||
return "Use a reader-facing style appropriate to the document type; never expose planning or evidence-processing scaffolding."
|
|
||||||
|
|
||||||
|
|
||||||
def _citation_policy(brief: Brief) -> str:
|
def _citation_policy(brief: Brief) -> str:
|
||||||
style = brief.constraints.citation_style
|
style = brief.constraints.citation_style
|
||||||
if not brief.constraints.require_citations:
|
if not brief.constraints.require_citations:
|
||||||
@@ -134,7 +109,7 @@ Apply these foundation rules:
|
|||||||
{FOUNDATION_RULES}
|
{FOUNDATION_RULES}
|
||||||
|
|
||||||
Apply this style guidance:
|
Apply this style guidance:
|
||||||
{_style_guidance(brief)}
|
{style_guidance(brief)}
|
||||||
|
|
||||||
{_source_hierarchy()}
|
{_source_hierarchy()}
|
||||||
|
|
||||||
@@ -176,7 +151,7 @@ Apply these foundation rules:
|
|||||||
{FOUNDATION_RULES}
|
{FOUNDATION_RULES}
|
||||||
|
|
||||||
Apply this style guidance:
|
Apply this style guidance:
|
||||||
{_style_guidance(brief)}
|
{style_guidance(brief)}
|
||||||
|
|
||||||
{_source_hierarchy()}
|
{_source_hierarchy()}
|
||||||
|
|
||||||
@@ -232,7 +207,7 @@ Apply these foundation rules:
|
|||||||
{FOUNDATION_RULES}
|
{FOUNDATION_RULES}
|
||||||
|
|
||||||
Apply this style guidance:
|
Apply this style guidance:
|
||||||
{_style_guidance(brief)}
|
{style_guidance(brief)}
|
||||||
|
|
||||||
{_source_hierarchy()}
|
{_source_hierarchy()}
|
||||||
|
|
||||||
@@ -247,6 +222,7 @@ Mandatory checks:
|
|||||||
- The opening must establish a real problem or tension rather than recite audience, scope, and source metadata.
|
- The opening must establish a real problem or tension rather than recite audience, scope, and source metadata.
|
||||||
- Information-architecture labels must not leak as repetitive sentence scaffolding. In Korean technical blogs, distinguish real ordered sequences from formulaic “첫 번째/두 번째/세 번째 + abstract category” paragraph openings.
|
- Information-architecture labels must not leak as repetitive sentence scaffolding. In Korean technical blogs, distinguish real ordered sequences from formulaic “첫 번째/두 번째/세 번째 + abstract category” paragraph openings.
|
||||||
- A question heading or transition must be answered immediately, and each contrast or causal connector must point to a real relation in the surrounding prose.
|
- A question heading or transition must be answered immediately, and each contrast or causal connector must point to a real relation in the surrounding prose.
|
||||||
|
{mandatory_style_review_checks(brief)}
|
||||||
|
|
||||||
Scoring dimensions (0-100 each):
|
Scoring dimensions (0-100 each):
|
||||||
{dimension_list}
|
{dimension_list}
|
||||||
@@ -313,7 +289,7 @@ Apply these foundation rules:
|
|||||||
{FOUNDATION_RULES}
|
{FOUNDATION_RULES}
|
||||||
|
|
||||||
Apply this style guidance:
|
Apply this style guidance:
|
||||||
{_style_guidance(brief)}
|
{style_guidance(brief)}
|
||||||
|
|
||||||
{_source_hierarchy()}
|
{_source_hierarchy()}
|
||||||
|
|
||||||
@@ -328,6 +304,7 @@ Revision protocol:
|
|||||||
8. Preserve correct material and the author's project context; avoid generic filler and unrelated rewrites.
|
8. Preserve correct material and the author's project context; avoid generic filler and unrelated rewrites.
|
||||||
9. Remove repeated ordinal sentence scaffolding that merely reads the outline aloud. Preserve ordinals when they identify a real procedure, method, layer, or figure, and prefer a list or meaningful subheadings for parallel items.
|
9. Remove repeated ordinal sentence scaffolding that merely reads the outline aloud. Preserve ordinals when they identify a real procedure, method, layer, or figure, and prefer a list or meaningful subheadings for parallel items.
|
||||||
10. Return the entire revised document, not a patch or explanation.
|
10. Return the entire revised document, not a patch or explanation.
|
||||||
|
{revision_style_protocol(brief)}
|
||||||
|
|
||||||
Citation policy: {_citation_policy(brief)}
|
Citation policy: {_citation_policy(brief)}
|
||||||
Date policy: {_date_policy(brief)}
|
Date policy: {_date_policy(brief)}
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ STRUCTURE_SPECS: dict[DocumentType, tuple[SectionSpec, ...]] = {
|
|||||||
S("tradeoffs", "얻은 것, 잃은 것, 적용하지 않을 때", "What was gained, lost, and when not to apply it", "이 선택의 비용과 한계는 무엇이며 언제 다른 선택이 나은가?", "What are the costs and limits, and when is another choice better?", "프로젝트 지역 결정을 보편 법칙처럼 쓰지 않고, 적용 조건과 남은 위험을 제시한다.", "Do not universalize a project-local decision; state applicability and remaining risks.", ("얻은 것", "잃은 것", "적용 조건", "남은 위험"), ("gains", "costs", "applicability", "remaining risks")),
|
S("tradeoffs", "얻은 것, 잃은 것, 적용하지 않을 때", "What was gained, lost, and when not to apply it", "이 선택의 비용과 한계는 무엇이며 언제 다른 선택이 나은가?", "What are the costs and limits, and when is another choice better?", "프로젝트 지역 결정을 보편 법칙처럼 쓰지 않고, 적용 조건과 남은 위험을 제시한다.", "Do not universalize a project-local decision; state applicability and remaining risks.", ("얻은 것", "잃은 것", "적용 조건", "남은 위험"), ("gains", "costs", "applicability", "remaining risks")),
|
||||||
S("conclusion", "결국 지키려던 것은 무엇이었나", "What the design was ultimately protecting", "세부 기술을 걷어냈을 때 남는 판단은 무엇인가?", "What judgment remains after removing implementation detail?", "앞 내용을 반복하지 않고, 문제와 선택을 연결하는 한 문장 판단으로 닫는다.", "Close with a compact judgment that reconnects the problem and choice without repetition.", ("압축된 판단", "독자가 자신의 환경에서 확인할 질문"), ("compressed judgment", "question for the reader's environment")),
|
S("conclusion", "결국 지키려던 것은 무엇이었나", "What the design was ultimately protecting", "세부 기술을 걷어냈을 때 남는 판단은 무엇인가?", "What judgment remains after removing implementation detail?", "앞 내용을 반복하지 않고, 문제와 선택을 연결하는 한 문장 판단으로 닫는다.", "Close with a compact judgment that reconnects the problem and choice without repetition.", ("압축된 판단", "독자가 자신의 환경에서 확인할 질문"), ("compressed judgment", "question for the reader's environment")),
|
||||||
),
|
),
|
||||||
|
DocumentType.README: (
|
||||||
|
S("problem_value", "이 프로젝트가 필요한 이유", "Why this project exists", "어떤 구체적인 문제를 해결하며 왜 이 프로젝트가 필요한가?", "What concrete problem does this project solve, and why does it exist?", "독자가 겪는 문제와 프로젝트가 제공하는 가치를 실제 상황에서 설명한다.", "Explain the reader's problem and the project's value through a concrete situation.", ("문제 상황", "프로젝트 가치", "대상 독자"), ("problem context", "project value", "intended reader")),
|
||||||
|
S("principles", "동작 원칙과 지키는 경계", "Operating principles and boundaries", "사용 전에 알아야 할 핵심 원칙과 경계는 무엇인가?", "Which principles and boundaries must readers understand before use?", "프로젝트가 보장하는 동작과 의도적으로 보장하지 않는 범위를 구분한다.", "Separate guaranteed behavior from deliberately unsupported scope.", ("핵심 원칙", "보장 범위", "비보장 범위"), ("core principles", "guarantees", "non-guarantees")),
|
||||||
|
S("workflow", "전체 동작 흐름", "End-to-end workflow", "입력부터 결과와 검증 기록까지 어떤 순서로 진행되는가?", "How does work proceed from input to output and verification artifacts?", "주요 구성요소와 산출물이 이어지는 전체 흐름을 보여 준다.", "Show the end-to-end flow connecting components and artifacts.", ("입력", "주요 단계", "독자용 결과", "내부 산출물"), ("inputs", "main stages", "reader output", "internal artifacts")),
|
||||||
|
S("installation", "설치와 시작 전 준비", "Installation and prerequisites", "실행 전에 무엇을 설치하고 준비해야 하는가?", "What must be installed and prepared before use?", "지원 버전, 필수 도구, 설치 명령과 초기 상태를 설명한다.", "Explain supported versions, required tools, installation commands, and initial state.", ("지원 버전", "필수 도구", "설치 명령"), ("supported versions", "required tools", "installation commands")),
|
||||||
|
S("quickstart", "가장 작은 실행 예시", "Smallest useful run", "가장 짧은 경로로 어떤 유용한 결과를 확인할 수 있는가?", "What useful result can be observed through the shortest path?", "복사 가능한 최소 명령과 예상 결과, 확인 지점을 제공한다.", "Provide the smallest copyable command, expected result, and verification point.", ("최소 입력", "실행 명령", "예상 결과", "확인 방법"), ("minimal input", "run command", "expected result", "verification")),
|
||||||
|
S("configuration", "주요 설정과 선택 기준", "Configuration and selection criteria", "어떤 설정을 언제 선택하며 결과에 어떤 영향을 주는가?", "Which settings should be chosen when, and how do they affect the result?", "핵심 설정의 기본값, 선택 조건, 비용과 제한을 연결한다.", "Connect key configuration defaults to selection criteria, costs, and limits.", ("설정 항목", "기본값", "선택 조건", "영향"), ("settings", "defaults", "selection criteria", "effects")),
|
||||||
|
S("verification", "검증과 문제 확인", "Verification and diagnosis", "성공을 어떻게 확인하고 대표적인 실패를 어떻게 좁히는가?", "How is success verified and common failure narrowed down?", "관측 가능한 성공 기준과 비파괴 진단 경로를 제공한다.", "Provide observable success criteria and a non-destructive diagnostic path.", ("성공 기준", "확인 명령", "대표 실패 신호", "진단 경로"), ("success criteria", "check command", "failure signal", "diagnostic path")),
|
||||||
|
S("limits_next", "한계와 다음 행동", "Limits and next action", "어디까지 검증되었으며 다음에 무엇을 해야 하는가?", "What has been verified, where are the limits, and what comes next?", "근거 한계와 비지원 범위를 밝히고 직접 연결된 다음 행동으로 닫는다.", "State evidence limits and unsupported scope, then close with the next directly related action.", ("검증 범위", "한계", "비지원 항목", "다음 행동"), ("verified scope", "limits", "unsupported items", "next action")),
|
||||||
|
),
|
||||||
DocumentType.TUTORIAL: (
|
DocumentType.TUTORIAL: (
|
||||||
S("outcome", "완성 결과와 학습 목표", "Outcome and learning objective", "끝에서 무엇을 만들고 무엇을 배우는가?", "What will be built and learned?", "가시적인 결과와 학습 목표를 먼저 보여준다.", "Show the visible outcome and learning objective first.", ("완성 상태", "학습 목표", "예상 소요 범위"), ("finished state", "learning objective", "expected effort")),
|
S("outcome", "완성 결과와 학습 목표", "Outcome and learning objective", "끝에서 무엇을 만들고 무엇을 배우는가?", "What will be built and learned?", "가시적인 결과와 학습 목표를 먼저 보여준다.", "Show the visible outcome and learning objective first.", ("완성 상태", "학습 목표", "예상 소요 범위"), ("finished state", "learning objective", "expected effort")),
|
||||||
S("prerequisites", "시작 전 준비 사항", "Prerequisites", "시작 전에 무엇이 준비되어야 하는가?", "What must be ready before starting?", "필요 지식, 도구, 버전, 초기 상태를 명시한다.", "State required knowledge, tools, versions, and initial state.", ("지식", "도구와 버전", "초기 상태"), ("knowledge", "tools and versions", "initial state")),
|
S("prerequisites", "시작 전 준비 사항", "Prerequisites", "시작 전에 무엇이 준비되어야 하는가?", "What must be ready before starting?", "필요 지식, 도구, 버전, 초기 상태를 명시한다.", "State required knowledge, tools, versions, and initial state.", ("지식", "도구와 버전", "초기 상태"), ("knowledge", "tools and versions", "initial state")),
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from claridoc.models import Brief, DocumentType
|
||||||
|
|
||||||
|
|
||||||
|
KOREAN_EXPERIENCE_CONTRACT_ID = "korean_first_person_experience_v1"
|
||||||
|
|
||||||
|
_KOREAN_TECHNICAL_BLOG_PROFILES = frozenset(
|
||||||
|
{
|
||||||
|
"auto",
|
||||||
|
"woowahan_tech_blog_ko",
|
||||||
|
"korean_problem_solving_blog",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
_GENERIC_STYLE_GUIDANCE = (
|
||||||
|
"Use a reader-facing style appropriate to the document type; never expose "
|
||||||
|
"planning or evidence-processing scaffolding."
|
||||||
|
)
|
||||||
|
|
||||||
|
_KOREAN_EXPERIENCE_GUIDANCE = f"""\
|
||||||
|
Reader-prose contract: {KOREAN_EXPERIENCE_CONTRACT_ID}
|
||||||
|
|
||||||
|
Write Korean reader-facing prose as a supported first-person experience, not as a list of settled facts.
|
||||||
|
- Follow this semantic order, never as a sentence template: concrete starting point -> initial expectation -> observed difference -> immediate term explanation -> author action or decision -> result, cost, or remaining limit.
|
||||||
|
- At the opening and major section transitions, use `저는` or `제가` when it establishes what the author actually inspected, ran, understood, selected, or changed. Do not repeat first person mechanically in every sentence.
|
||||||
|
- A first-person marker must represent a real observation or action supported by the source material. Never add an unsupported emotion, conversation, advice, failure, duration, result, or technical rationale.
|
||||||
|
- Use `했습니다` for directly observed or performed work: `확인했습니다`, `따라갔습니다`, `생각했습니다`.
|
||||||
|
- Use `합니다` for 현재 동작과 기술 설명: `사용합니다`, `호출합니다`, `막습니다`.
|
||||||
|
- Use `있습니다`, `없습니다`, `입니다`, and `아닙니다` for state and judgment. Do not mix reader prose ending in `한다`, `있다`, `아니다`, or `~했다`.
|
||||||
|
- Explain an unfamiliar term beside its first necessary use, as something the author came to understand while following the work.
|
||||||
|
- Connect a contrast to the concrete component and behavior that actually differ. Do not leave the reader with abstract conclusions such as a changed "position", "shape", "meaning", or "perspective".
|
||||||
|
- Preserve the exact claims, evidence status, numbers, versions, identifiers, code, commands, tables, links, diagrams, outline intents, and section order.
|
||||||
|
- Treat problem -> constraints -> options -> decision as a semantic order, never as a sentence template. Do not narrate outline labels or open consecutive paragraphs with formulaic `첫 번째 제약은`, `두 번째 제약은`, and `세 번째 제약은`.
|
||||||
|
- Use conversational but disciplined Korean. A Korean developer should be able to say the sentence naturally to a colleague without turning it into forced colloquial speech.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_PLAIN_FORM_ENDING = re.compile(r"(?<!니)다(?=[.!?](?:\s|$))")
|
||||||
|
_FENCE = re.compile(r"^\s*(?:```|~~~)")
|
||||||
|
_HEADING = re.compile(r"^\s{0,3}(#{1,6})\s+(.+?)\s*$")
|
||||||
|
_IMAGE_ONLY = re.compile(r"^\s*!\[[^\]]*\]\([^)]*\)\s*$")
|
||||||
|
_TABLE_DIVIDER = re.compile(
|
||||||
|
r"^\s*\|?\s*:?-{3,}:?\s*(?:\|\s*:?-{3,}:?\s*)+\|?\s*$"
|
||||||
|
)
|
||||||
|
_QUOTED_SPANS = (
|
||||||
|
re.compile(r'"[^"\n]*"'),
|
||||||
|
re.compile(r"'[^'\n]*'"),
|
||||||
|
re.compile(r"“[^”\n]*”"),
|
||||||
|
re.compile(r"‘[^’\n]*’"),
|
||||||
|
re.compile(r"「[^」\n]*」"),
|
||||||
|
re.compile(r"『[^』\n]*』"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class ReaderProseSegment:
|
||||||
|
text: str
|
||||||
|
line: int
|
||||||
|
h2_title: str | None
|
||||||
|
|
||||||
|
|
||||||
|
def korean_experience_contract_applies(brief: Brief) -> bool:
|
||||||
|
if not brief.is_korean:
|
||||||
|
return False
|
||||||
|
if brief.document_type == DocumentType.README:
|
||||||
|
return True
|
||||||
|
return (
|
||||||
|
brief.document_type == DocumentType.TECHNICAL_BLOG
|
||||||
|
and brief.constraints.style_profile.casefold() in _KOREAN_TECHNICAL_BLOG_PROFILES
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def style_guidance(brief: Brief) -> str:
|
||||||
|
if korean_experience_contract_applies(brief):
|
||||||
|
return _KOREAN_EXPERIENCE_GUIDANCE
|
||||||
|
return _GENERIC_STYLE_GUIDANCE
|
||||||
|
|
||||||
|
|
||||||
|
def mandatory_style_review_checks(brief: Brief) -> str:
|
||||||
|
if not korean_experience_contract_applies(brief):
|
||||||
|
return ""
|
||||||
|
return """\
|
||||||
|
- For the `korean_first_person_experience_v1` contract, verify that `저는` or `제가` expresses 실제 관찰(actual observation) or action rather than decorating an objective explanation.
|
||||||
|
- Verify that the opening and major transitions let the reader follow a concrete starting point, expectation, observed difference, understanding, action, and result or remaining cost.
|
||||||
|
- Verify that an unfamiliar term is explained where the reader first needs it and that each contrast names the actual component and behavior that differ.
|
||||||
|
- Verify consistent `합니다/했습니다` reader prose outside headings, tables, quotations, code blocks, and command output.
|
||||||
|
- Flag any invented personal history, advice, emotion, failure, duration, outcome, or project rationale as an evidence defect.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def revision_style_protocol(brief: Brief) -> str:
|
||||||
|
if not korean_experience_contract_applies(brief):
|
||||||
|
return ""
|
||||||
|
return """\
|
||||||
|
After resolving individual findings, recheck 문서 전체(the complete document) against `korean_first_person_experience_v1`.
|
||||||
|
Do not stop after adding one `저는` sentence. Confirm the opening and major transitions still form supported experience threads, all reader prose still uses `합니다/했습니다`, unfamiliar terms remain explained at first need, and no compliant section regressed during the whole-document rewrite.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def reader_prose_segments(markdown: str) -> list[ReaderProseSegment]:
|
||||||
|
segments: list[ReaderProseSegment] = []
|
||||||
|
in_fence = False
|
||||||
|
current_h2: str | None = None
|
||||||
|
lines = markdown.splitlines()
|
||||||
|
table_lines: set[int] = set()
|
||||||
|
for index, line in enumerate(lines):
|
||||||
|
if not _TABLE_DIVIDER.match(line):
|
||||||
|
continue
|
||||||
|
if index > 0 and "|" in lines[index - 1]:
|
||||||
|
table_lines.add(index - 1)
|
||||||
|
table_lines.add(index)
|
||||||
|
cursor = index + 1
|
||||||
|
while cursor < len(lines) and lines[cursor].strip() and "|" in lines[cursor]:
|
||||||
|
table_lines.add(cursor)
|
||||||
|
cursor += 1
|
||||||
|
|
||||||
|
for line_number, raw_line in enumerate(lines, start=1):
|
||||||
|
if _FENCE.match(raw_line):
|
||||||
|
in_fence = not in_fence
|
||||||
|
continue
|
||||||
|
if in_fence:
|
||||||
|
continue
|
||||||
|
|
||||||
|
heading = _HEADING.match(raw_line)
|
||||||
|
if heading:
|
||||||
|
if len(heading.group(1)) == 2:
|
||||||
|
current_h2 = heading.group(2).strip()
|
||||||
|
continue
|
||||||
|
|
||||||
|
stripped = raw_line.strip()
|
||||||
|
if (
|
||||||
|
not stripped
|
||||||
|
or line_number - 1 in table_lines
|
||||||
|
or stripped.startswith(">")
|
||||||
|
or raw_line.startswith((" ", "\t"))
|
||||||
|
or _IMAGE_ONLY.match(raw_line)
|
||||||
|
or _TABLE_DIVIDER.match(raw_line)
|
||||||
|
or (stripped.startswith("|") and stripped.endswith("|"))
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
prose = re.sub(r"!\[[^\]]*\]\([^)]*\)", "", stripped)
|
||||||
|
prose = re.sub(r"\[([^\]]+)\]\([^)]*\)", r"\1", prose)
|
||||||
|
prose = re.sub(r"`[^`\n]*`", "", prose)
|
||||||
|
for quoted_span in _QUOTED_SPANS:
|
||||||
|
prose = quoted_span.sub("", prose)
|
||||||
|
prose = re.sub(r"^\s*(?:[-*+]|\d+[.)])\s+", "", prose).strip()
|
||||||
|
if prose:
|
||||||
|
segments.append(ReaderProseSegment(prose, line_number, current_h2))
|
||||||
|
|
||||||
|
return segments
|
||||||
|
|
||||||
|
|
||||||
|
def plain_form_ending_locations(markdown: str) -> list[int]:
|
||||||
|
locations: list[int] = []
|
||||||
|
for segment in reader_prose_segments(markdown):
|
||||||
|
locations.extend(segment.line for _ in _PLAIN_FORM_ENDING.finditer(segment.text))
|
||||||
|
return locations
|
||||||
@@ -1,9 +1,270 @@
|
|||||||
AGENTS.md
|
AGENTS.md
|
||||||
|
CHANGELOG.md
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
LICENSE
|
LICENSE
|
||||||
Makefile
|
Makefile
|
||||||
|
PACKAGE_MANIFEST.json
|
||||||
README.md
|
README.md
|
||||||
pyproject.toml
|
pyproject.toml
|
||||||
|
.agents/skills/revising-korean-technical-prose/SKILL.md
|
||||||
|
.agents/skills/revising-korean-technical-prose/agents/openai.yaml
|
||||||
|
.agents/skills/revising-korean-technical-prose/references/sentence-patterns.md
|
||||||
|
.run/executable-clean-architecture/assets/architecture-layered-2026-07-04.svg
|
||||||
|
.run/executable-clean-architecture/assets/architecture-three-lenses.svg
|
||||||
|
.run/executable-clean-architecture/assets/big-picture.svg
|
||||||
|
.run/executable-clean-architecture/assets/boundary-enforcement-ladder.svg
|
||||||
|
.run/executable-clean-architecture/assets/context-system-boundary.svg
|
||||||
|
.run/executable-clean-architecture/assets/decision-spectrum-1.svg
|
||||||
|
.run/executable-clean-architecture/assets/decision-spectrum-3.svg
|
||||||
|
.run/executable-clean-architecture/assets/enforcement-ladder.svg
|
||||||
|
.run/executable-clean-architecture/assets/hexagonal-ports.svg
|
||||||
|
.run/executable-clean-architecture/assets/idempotency-four-branches.svg
|
||||||
|
.run/executable-clean-architecture/assets/lock-timeout-routing-gap.svg
|
||||||
|
.run/executable-clean-architecture/assets/logical-four-rings.svg
|
||||||
|
.run/executable-clean-architecture/assets/mdc-request-lifecycle.svg
|
||||||
|
.run/executable-clean-architecture/assets/module-graph-measured.svg
|
||||||
|
.run/executable-clean-architecture/assets/module-vs-single.svg
|
||||||
|
.run/executable-clean-architecture/assets/outbox-state-machine.svg
|
||||||
|
.run/executable-clean-architecture/assets/outbox-two-paths.svg
|
||||||
|
.run/executable-clean-architecture/assets/production-vs-optin.drawio
|
||||||
|
.run/executable-clean-architecture/assets/production-vs-optin.svg
|
||||||
|
.run/executable-clean-architecture/assets/runtime-call-source-dependency.svg
|
||||||
|
.run/executable-clean-architecture/assets/runtime-seq-feed.svg
|
||||||
|
.run/executable-clean-architecture/assets/static-analysis-venn.svg
|
||||||
|
.run/executable-clean-architecture/assets/test-contrast.svg
|
||||||
|
.run/executable-clean-architecture/assets/test-taxonomy-layers.svg
|
||||||
|
.run/executable-clean-architecture/assets/three-gate-flow.svg
|
||||||
|
.run/executable-clean-architecture/assets/transaction-lock-independent-contracts.svg
|
||||||
|
.run/executable-clean-architecture/assets/bootstrap-dependency-guards/bootstrap-dependency-guards.svg
|
||||||
|
.run/executable-clean-architecture/assets/inbound-transport-boundary/inbound-transport-boundary.svg
|
||||||
|
.run/executable-clean-architecture/final/document.md
|
||||||
|
.run/executable-clean-architecture/final/.techviz/production-vs-optin/spec.json
|
||||||
|
.run/keycloak-four-patterns/brief.json
|
||||||
|
.run/keycloak-four-patterns/collected.develop.json
|
||||||
|
.run/keycloak-four-patterns/manifest.json
|
||||||
|
.run/keycloak-four-patterns/outline.json
|
||||||
|
.run/keycloak-four-patterns/outline.preliminary.json
|
||||||
|
.run/keycloak-four-patterns/sources.json
|
||||||
|
.run/keycloak-four-patterns/sources.manual.json
|
||||||
|
.run/keycloak-four-patterns/final/deterministic-lint.md
|
||||||
|
.run/keycloak-four-patterns/final/document.md
|
||||||
|
.run/keycloak-four-patterns/final/evidence-map.json
|
||||||
|
.run/keycloak-four-patterns/final/provenance.md
|
||||||
|
.run/keycloak-four-patterns/final/quality-report.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap1-browser-bearer-flow/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap1-browser-bearer-flow/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap1-browser-bearer-flow/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap1-direct-architecture/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap1-direct-architecture/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap1-direct-architecture/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap2-mediator-architecture/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap2-mediator-architecture/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap2-mediator-architecture/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap2-mediator-handoff-flow/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap2-mediator-handoff-flow/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap2-mediator-handoff-flow/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap3-bff-architecture/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap3-bff-architecture/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap3-bff-architecture/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap3-bff-session-flow/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap3-bff-session-flow/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap3-bff-session-flow/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap3-csrf-boundary/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap3-csrf-boundary/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap3-csrf-boundary/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap4-edge-forward-auth-flow/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap4-edge-forward-auth-flow/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap4-edge-forward-auth-flow/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap4-edge-trust-architecture/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap4-edge-trust-architecture/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/ap4-edge-trust-architecture/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/credential-contract-migration/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/credential-contract-migration/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/credential-contract-migration/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/credential-custody-map/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/credential-custody-map/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/credential-custody-map/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/four-pattern-request-boundaries/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/four-pattern-request-boundaries/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/four-pattern-request-boundaries/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/login-api-phase-split/context.json
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/login-api-phase-split/prompt.md
|
||||||
|
.run/keycloak-four-patterns/final/.techviz/login-api-phase-split/spec.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-browser-bearer-flow/ap1-browser-bearer-flow.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-browser-bearer-flow/ap1-browser-bearer-flow.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-browser-bearer-flow/ap1-browser-bearer-flow.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-browser-bearer-flow/ap1-browser-bearer-flow.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-browser-bearer-flow/ap1-browser-bearer-flow.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-browser-bearer-flow/ap1-browser-bearer-flow.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-browser-bearer-flow/ap1-browser-bearer-flow.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-browser-bearer-flow/ap1-browser-bearer-flow.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-direct-architecture/ap1-direct-architecture.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-direct-architecture/ap1-direct-architecture.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-direct-architecture/ap1-direct-architecture.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-direct-architecture/ap1-direct-architecture.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-direct-architecture/ap1-direct-architecture.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-direct-architecture/ap1-direct-architecture.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-direct-architecture/ap1-direct-architecture.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap1-direct-architecture/ap1-direct-architecture.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-architecture/ap2-mediator-architecture.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-architecture/ap2-mediator-architecture.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-architecture/ap2-mediator-architecture.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-architecture/ap2-mediator-architecture.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-architecture/ap2-mediator-architecture.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-architecture/ap2-mediator-architecture.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-architecture/ap2-mediator-architecture.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-architecture/ap2-mediator-architecture.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-handoff-flow/ap2-mediator-handoff-flow.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-handoff-flow/ap2-mediator-handoff-flow.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-handoff-flow/ap2-mediator-handoff-flow.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-handoff-flow/ap2-mediator-handoff-flow.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-handoff-flow/ap2-mediator-handoff-flow.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-handoff-flow/ap2-mediator-handoff-flow.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-handoff-flow/ap2-mediator-handoff-flow.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap2-mediator-handoff-flow/ap2-mediator-handoff-flow.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-architecture/ap3-bff-architecture.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-architecture/ap3-bff-architecture.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-architecture/ap3-bff-architecture.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-architecture/ap3-bff-architecture.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-architecture/ap3-bff-architecture.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-architecture/ap3-bff-architecture.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-architecture/ap3-bff-architecture.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-architecture/ap3-bff-architecture.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-session-flow/ap3-bff-session-flow.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-session-flow/ap3-bff-session-flow.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-session-flow/ap3-bff-session-flow.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-session-flow/ap3-bff-session-flow.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-session-flow/ap3-bff-session-flow.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-session-flow/ap3-bff-session-flow.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-session-flow/ap3-bff-session-flow.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-bff-session-flow/ap3-bff-session-flow.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-csrf-boundary/ap3-csrf-boundary.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-csrf-boundary/ap3-csrf-boundary.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-csrf-boundary/ap3-csrf-boundary.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-csrf-boundary/ap3-csrf-boundary.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-csrf-boundary/ap3-csrf-boundary.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-csrf-boundary/ap3-csrf-boundary.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-csrf-boundary/ap3-csrf-boundary.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap3-csrf-boundary/ap3-csrf-boundary.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-forward-auth-flow/ap4-edge-forward-auth-flow.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-forward-auth-flow/ap4-edge-forward-auth-flow.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-forward-auth-flow/ap4-edge-forward-auth-flow.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-forward-auth-flow/ap4-edge-forward-auth-flow.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-forward-auth-flow/ap4-edge-forward-auth-flow.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-forward-auth-flow/ap4-edge-forward-auth-flow.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-forward-auth-flow/ap4-edge-forward-auth-flow.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-forward-auth-flow/ap4-edge-forward-auth-flow.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-trust-architecture/ap4-edge-trust-architecture.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-trust-architecture/ap4-edge-trust-architecture.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-trust-architecture/ap4-edge-trust-architecture.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-trust-architecture/ap4-edge-trust-architecture.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-trust-architecture/ap4-edge-trust-architecture.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-trust-architecture/ap4-edge-trust-architecture.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-trust-architecture/ap4-edge-trust-architecture.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/ap4-edge-trust-architecture/ap4-edge-trust-architecture.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-contract-migration/credential-contract-migration.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-contract-migration/credential-contract-migration.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-contract-migration/credential-contract-migration.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-contract-migration/credential-contract-migration.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-contract-migration/credential-contract-migration.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-contract-migration/credential-contract-migration.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-contract-migration/credential-contract-migration.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-contract-migration/credential-contract-migration.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-custody-map/credential-custody-map.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-custody-map/credential-custody-map.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-custody-map/credential-custody-map.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-custody-map/credential-custody-map.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-custody-map/credential-custody-map.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-custody-map/credential-custody-map.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-custody-map/credential-custody-map.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/credential-custody-map/credential-custody-map.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/four-pattern-request-boundaries/four-pattern-request-boundaries.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/four-pattern-request-boundaries/four-pattern-request-boundaries.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/four-pattern-request-boundaries/four-pattern-request-boundaries.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/four-pattern-request-boundaries/four-pattern-request-boundaries.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/four-pattern-request-boundaries/four-pattern-request-boundaries.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/four-pattern-request-boundaries/four-pattern-request-boundaries.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/four-pattern-request-boundaries/four-pattern-request-boundaries.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/four-pattern-request-boundaries/four-pattern-request-boundaries.svg
|
||||||
|
.run/keycloak-four-patterns/final/assets/login-api-phase-split/login-api-phase-split.alt.md
|
||||||
|
.run/keycloak-four-patterns/final/assets/login-api-phase-split/login-api-phase-split.d2
|
||||||
|
.run/keycloak-four-patterns/final/assets/login-api-phase-split/login-api-phase-split.dot
|
||||||
|
.run/keycloak-four-patterns/final/assets/login-api-phase-split/login-api-phase-split.drawio
|
||||||
|
.run/keycloak-four-patterns/final/assets/login-api-phase-split/login-api-phase-split.excalidraw
|
||||||
|
.run/keycloak-four-patterns/final/assets/login-api-phase-split/login-api-phase-split.manifest.json
|
||||||
|
.run/keycloak-four-patterns/final/assets/login-api-phase-split/login-api-phase-split.mmd
|
||||||
|
.run/keycloak-four-patterns/final/assets/login-api-phase-split/login-api-phase-split.svg
|
||||||
|
.run/n+1liner/final/document.md
|
||||||
|
.run/n+1liner/final/.techviz/baseline-schema/spec.json
|
||||||
|
.run/n+1liner/final/.techviz/eager-lazy-query-sequence/spec.json
|
||||||
|
.run/n+1liner/final/.techviz/nplus1-query-fanout/spec.json
|
||||||
|
.run/n+1liner/final/.techviz/query-port-boundary/spec.json
|
||||||
|
.run/n+1liner/final/.techviz/skew-profile/spec.json
|
||||||
|
.run/n+1liner/final/.techviz/strategy-journey/spec.json
|
||||||
|
.run/n+1liner/final/.techviz/target-schema/spec.json
|
||||||
|
.run/n+1liner/final/assets/README.md
|
||||||
|
.run/n+1liner/final/assets/diagrams/baseline-schema/baseline-schema.drawio
|
||||||
|
.run/n+1liner/final/assets/diagrams/baseline-schema/baseline-schema.svg
|
||||||
|
.run/n+1liner/final/assets/diagrams/eager-lazy-query-sequence/eager-lazy-query-sequence.drawio
|
||||||
|
.run/n+1liner/final/assets/diagrams/eager-lazy-query-sequence/eager-lazy-query-sequence.svg
|
||||||
|
.run/n+1liner/final/assets/diagrams/nplus1-query-fanout/nplus1-query-fanout.drawio
|
||||||
|
.run/n+1liner/final/assets/diagrams/nplus1-query-fanout/nplus1-query-fanout.svg
|
||||||
|
.run/n+1liner/final/assets/diagrams/query-port-boundary/query-port-boundary.drawio
|
||||||
|
.run/n+1liner/final/assets/diagrams/query-port-boundary/query-port-boundary.svg
|
||||||
|
.run/n+1liner/final/assets/diagrams/skew-profile/skew-profile.drawio
|
||||||
|
.run/n+1liner/final/assets/diagrams/skew-profile/skew-profile.svg
|
||||||
|
.run/n+1liner/final/assets/diagrams/strategy-journey/strategy-journey.drawio
|
||||||
|
.run/n+1liner/final/assets/diagrams/strategy-journey/strategy-journey.svg
|
||||||
|
.run/n+1liner/final/assets/diagrams/target-schema/target-schema.drawio
|
||||||
|
.run/n+1liner/final/assets/diagrams/target-schema/target-schema.svg
|
||||||
|
.run/n+1liner/final/evidence/explain/crown-deep-keyset-precompute.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/crown-deep-keyset-single-or.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/crown-unified-precompute-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/highlights-child-plan-A.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l14-lateral-no-index.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l14-lateral-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l14-twostep-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l14-window-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l15-keyset-index-seek.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l15-keyset-no-index.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l15-offset-deep-page.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l15-visibility-or-probe.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l16-precompute-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l16-single-or-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l16-union-branches.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l16-union-decompose-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l3-cartesian-join-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l4-collection-join-no-limit.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l4-entity-paging-limit.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l5-batch-in-semijoin.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l5-entity-paging-limit.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l6-child-projection.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/l6-parent-projection.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/toone-pages-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/explain/toone-users-plan.txt
|
||||||
|
.run/n+1liner/final/evidence/metrics/crown-unified-plan.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l1-query-growth.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l1-skew-distribution.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l14-group-size.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l14-index-toggle.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l14-plan-compare.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l14-topn-resolution.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l15-deep-page-compare.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l15-depth-curve.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l16-plan-compare.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l2-toone-split.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l3-cartesian.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l4-cost-curve.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l4-inmemory-paging.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l5-batch-resolution.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l5-hydration-probe.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l6-explain-width.csv
|
||||||
|
.run/n+1liner/final/evidence/metrics/l6-projection-resolution.csv
|
||||||
|
.verify/application-core-golden-lint.json
|
||||||
|
.verify/application-core-outline.json
|
||||||
|
.verify/application-core-sources.json
|
||||||
|
.verify/coverage.txt
|
||||||
|
.verify/pip-wheel.log
|
||||||
config/pipeline.mock.json
|
config/pipeline.mock.json
|
||||||
config/pipeline.multi-agent.example.json
|
config/pipeline.multi-agent.example.json
|
||||||
docs/ARCHITECTURE.md
|
docs/ARCHITECTURE.md
|
||||||
@@ -11,11 +272,119 @@ docs/EXTENDING.md
|
|||||||
docs/LOGIC_MODEL.md
|
docs/LOGIC_MODEL.md
|
||||||
docs/PROVIDERS.md
|
docs/PROVIDERS.md
|
||||||
docs/SECURITY.md
|
docs/SECURITY.md
|
||||||
|
docs/superpowers/plans/2026-07-29-korean-experience-prose-contract.md
|
||||||
|
docs/superpowers/specs/2026-07-29-korean-experience-prose-contract-design.md
|
||||||
|
examples/briefs/application-core-spring-di-blog.json
|
||||||
examples/briefs/retry-policy-blog.json
|
examples/briefs/retry-policy-blog.json
|
||||||
|
examples/corpus/llm-wiki-mini/raw/branch-notes/feature-application-port-usecase-contract.md
|
||||||
|
examples/corpus/llm-wiki-mini/raw/branch-notes/feature-log-management-contract.md
|
||||||
|
examples/corpus/llm-wiki-mini/raw/official-docs/spring-component-scanning.md
|
||||||
|
examples/corpus/llm-wiki-mini/wiki/projects/ca-tmpl/clean-architecture-package-layout.md
|
||||||
|
examples/golden/application-core-spring-di-boundary.evidence-map.json
|
||||||
|
examples/golden/application-core-spring-di-boundary.md
|
||||||
|
examples/golden/application-core-spring-di-boundary.provenance.md
|
||||||
|
examples/golden/executable-clean-architecture/assets/architecture-layered-2026-07-04.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/architecture-three-lenses.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/big-picture.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/boundary-enforcement-ladder.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/context-system-boundary.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/decision-spectrum-1.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/decision-spectrum-3.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/enforcement-ladder.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/hexagonal-ports.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/idempotency-four-branches.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/lock-timeout-routing-gap.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/logical-four-rings.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/mdc-request-lifecycle.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/module-graph-measured.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/module-vs-single.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/outbox-state-machine.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/outbox-two-paths.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/production-vs-optin.drawio
|
||||||
|
examples/golden/executable-clean-architecture/assets/production-vs-optin.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/runtime-call-source-dependency.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/runtime-seq-feed.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/static-analysis-venn.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/test-contrast.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/test-taxonomy-layers.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/three-gate-flow.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/transaction-lock-independent-contracts.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/bootstrap-dependency-guards/bootstrap-dependency-guards.svg
|
||||||
|
examples/golden/executable-clean-architecture/assets/inbound-transport-boundary/inbound-transport-boundary.svg
|
||||||
|
examples/golden/executable-clean-architecture/claridoc-rewrite/document.md
|
||||||
|
examples/golden/executable-clean-architecture/claridoc-rewrite/.techviz/production-vs-optin/spec.json
|
||||||
|
examples/golden/n+1liner/n+1liner.md
|
||||||
|
examples/golden/n+1liner/.techviz/baseline-schema/spec.json
|
||||||
|
examples/golden/n+1liner/.techviz/eager-lazy-query-sequence/spec.json
|
||||||
|
examples/golden/n+1liner/.techviz/nplus1-query-fanout/spec.json
|
||||||
|
examples/golden/n+1liner/.techviz/query-port-boundary/spec.json
|
||||||
|
examples/golden/n+1liner/.techviz/skew-profile/spec.json
|
||||||
|
examples/golden/n+1liner/.techviz/strategy-journey/spec.json
|
||||||
|
examples/golden/n+1liner/.techviz/target-schema/spec.json
|
||||||
|
examples/golden/n+1liner/assets/README.md
|
||||||
|
examples/golden/n+1liner/assets/diagrams/baseline-schema/baseline-schema.drawio
|
||||||
|
examples/golden/n+1liner/assets/diagrams/baseline-schema/baseline-schema.svg
|
||||||
|
examples/golden/n+1liner/assets/diagrams/eager-lazy-query-sequence/eager-lazy-query-sequence.drawio
|
||||||
|
examples/golden/n+1liner/assets/diagrams/eager-lazy-query-sequence/eager-lazy-query-sequence.svg
|
||||||
|
examples/golden/n+1liner/assets/diagrams/nplus1-query-fanout/nplus1-query-fanout.drawio
|
||||||
|
examples/golden/n+1liner/assets/diagrams/nplus1-query-fanout/nplus1-query-fanout.svg
|
||||||
|
examples/golden/n+1liner/assets/diagrams/query-port-boundary/query-port-boundary.drawio
|
||||||
|
examples/golden/n+1liner/assets/diagrams/query-port-boundary/query-port-boundary.svg
|
||||||
|
examples/golden/n+1liner/assets/diagrams/skew-profile/skew-profile.drawio
|
||||||
|
examples/golden/n+1liner/assets/diagrams/skew-profile/skew-profile.svg
|
||||||
|
examples/golden/n+1liner/assets/diagrams/strategy-journey/strategy-journey.drawio
|
||||||
|
examples/golden/n+1liner/assets/diagrams/strategy-journey/strategy-journey.svg
|
||||||
|
examples/golden/n+1liner/assets/diagrams/target-schema/target-schema.drawio
|
||||||
|
examples/golden/n+1liner/assets/diagrams/target-schema/target-schema.svg
|
||||||
|
examples/golden/n+1liner/evidence/explain/crown-deep-keyset-precompute.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/crown-deep-keyset-single-or.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/crown-unified-precompute-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/highlights-child-plan-A.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l14-lateral-no-index.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l14-lateral-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l14-twostep-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l14-window-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l15-keyset-index-seek.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l15-keyset-no-index.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l15-offset-deep-page.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l15-visibility-or-probe.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l16-precompute-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l16-single-or-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l16-union-branches.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l16-union-decompose-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l3-cartesian-join-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l4-collection-join-no-limit.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l4-entity-paging-limit.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l5-batch-in-semijoin.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l5-entity-paging-limit.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l6-child-projection.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/l6-parent-projection.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/toone-pages-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/explain/toone-users-plan.txt
|
||||||
|
examples/golden/n+1liner/evidence/metrics/crown-unified-plan.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l1-query-growth.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l1-skew-distribution.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l14-group-size.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l14-index-toggle.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l14-plan-compare.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l14-topn-resolution.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l15-deep-page-compare.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l15-depth-curve.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l16-plan-compare.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l2-toone-split.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l3-cartesian.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l4-cost-curve.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l4-inmemory-paging.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l5-batch-resolution.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l5-hydration-probe.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l6-explain-width.csv
|
||||||
|
examples/golden/n+1liner/evidence/metrics/l6-projection-resolution.csv
|
||||||
examples/output/retry-policy-demo/manifest.json
|
examples/output/retry-policy-demo/manifest.json
|
||||||
examples/output/retry-policy-demo/provider-events.jsonl
|
examples/output/retry-policy-demo/provider-events.jsonl
|
||||||
examples/output/retry-policy-demo/run.json
|
examples/output/retry-policy-demo/run.json
|
||||||
examples/output/retry-policy-demo/final/document.md
|
examples/output/retry-policy-demo/final/document.md
|
||||||
|
examples/output/retry-policy-demo/final/evidence-map.json
|
||||||
|
examples/output/retry-policy-demo/final/provenance.md
|
||||||
examples/output/retry-policy-demo/final/quality-report.md
|
examples/output/retry-policy-demo/final/quality-report.md
|
||||||
examples/output/retry-policy-demo/inputs/brief.normalized.json
|
examples/output/retry-policy-demo/inputs/brief.normalized.json
|
||||||
examples/output/retry-policy-demo/inputs/pipeline.normalized.json
|
examples/output/retry-policy-demo/inputs/pipeline.normalized.json
|
||||||
@@ -26,6 +395,16 @@ examples/output/retry-policy-demo/rounds/round-01/lint.md
|
|||||||
examples/output/retry-policy-demo/rounds/round-01/quality-gate.json
|
examples/output/retry-policy-demo/rounds/round-01/quality-gate.json
|
||||||
examples/output/retry-policy-demo/rounds/round-01/review-01-logic.json
|
examples/output/retry-policy-demo/rounds/round-01/review-01-logic.json
|
||||||
examples/output/retry-policy-demo/rounds/round-01/review-01-logic.raw.txt
|
examples/output/retry-policy-demo/rounds/round-01/review-01-logic.raw.txt
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-02-decision.json
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-02-decision.raw.txt
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-03-reader.json
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-03-reader.raw.txt
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-04-editor.json
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-04-editor.raw.txt
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-05-evidence.json
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-05-evidence.raw.txt
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-06-operations.json
|
||||||
|
examples/output/retry-policy-demo/rounds/round-01/review-06-operations.raw.txt
|
||||||
examples/output/retry-policy-demo/stages/01-planner.raw.txt
|
examples/output/retry-policy-demo/stages/01-planner.raw.txt
|
||||||
examples/output/retry-policy-demo/stages/02-outline.json
|
examples/output/retry-policy-demo/stages/02-outline.json
|
||||||
examples/output/retry-policy-demo/stages/02-outline.md
|
examples/output/retry-policy-demo/stages/02-outline.md
|
||||||
@@ -40,6 +419,7 @@ schemas/review.schema.json
|
|||||||
schemas/source-pack.schema.json
|
schemas/source-pack.schema.json
|
||||||
scripts/run-demo.ps1
|
scripts/run-demo.ps1
|
||||||
scripts/run-demo.sh
|
scripts/run-demo.sh
|
||||||
|
scripts/run-local-corpus-example.sh
|
||||||
scripts/test.sh
|
scripts/test.sh
|
||||||
scripts/verify.sh
|
scripts/verify.sh
|
||||||
src/claridoc/__init__.py
|
src/claridoc/__init__.py
|
||||||
@@ -58,10 +438,12 @@ src/claridoc/utils.py
|
|||||||
src/claridoc/__pycache__/__init__.cpython-312.pyc
|
src/claridoc/__pycache__/__init__.cpython-312.pyc
|
||||||
src/claridoc/__pycache__/__main__.cpython-312.pyc
|
src/claridoc/__pycache__/__main__.cpython-312.pyc
|
||||||
src/claridoc/__pycache__/cli.cpython-312.pyc
|
src/claridoc/__pycache__/cli.cpython-312.pyc
|
||||||
|
src/claridoc/__pycache__/corpus.cpython-312.pyc
|
||||||
src/claridoc/__pycache__/lint.cpython-312.pyc
|
src/claridoc/__pycache__/lint.cpython-312.pyc
|
||||||
src/claridoc/__pycache__/models.cpython-312.pyc
|
src/claridoc/__pycache__/models.cpython-312.pyc
|
||||||
src/claridoc/__pycache__/pipeline.cpython-312.pyc
|
src/claridoc/__pycache__/pipeline.cpython-312.pyc
|
||||||
src/claridoc/__pycache__/prompts.cpython-312.pyc
|
src/claridoc/__pycache__/prompts.cpython-312.pyc
|
||||||
|
src/claridoc/__pycache__/provenance.cpython-312.pyc
|
||||||
src/claridoc/__pycache__/report.cpython-312.pyc
|
src/claridoc/__pycache__/report.cpython-312.pyc
|
||||||
src/claridoc/__pycache__/structures.cpython-312.pyc
|
src/claridoc/__pycache__/structures.cpython-312.pyc
|
||||||
src/claridoc/__pycache__/templates.cpython-312.pyc
|
src/claridoc/__pycache__/templates.cpython-312.pyc
|
||||||
@@ -97,4 +479,15 @@ tests/test_prompts.py
|
|||||||
tests/test_providers.py
|
tests/test_providers.py
|
||||||
tests/test_schemas.py
|
tests/test_schemas.py
|
||||||
tests/test_structures.py
|
tests/test_structures.py
|
||||||
|
tests/__pycache__/__init__.cpython-312.pyc
|
||||||
|
tests/__pycache__/helpers.cpython-312.pyc
|
||||||
|
tests/__pycache__/test_cli.cpython-312.pyc
|
||||||
|
tests/__pycache__/test_corpus.cpython-312.pyc
|
||||||
|
tests/__pycache__/test_lint.cpython-312.pyc
|
||||||
|
tests/__pycache__/test_models.cpython-312.pyc
|
||||||
|
tests/__pycache__/test_pipeline.cpython-312.pyc
|
||||||
|
tests/__pycache__/test_prompts.cpython-312.pyc
|
||||||
|
tests/__pycache__/test_providers.cpython-312.pyc
|
||||||
|
tests/__pycache__/test_schemas.cpython-312.pyc
|
||||||
|
tests/__pycache__/test_structures.cpython-312.pyc
|
||||||
verification/TEST_REPORT.md
|
verification/TEST_REPORT.md
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,6 +13,50 @@ from tests.helpers import brief_dict, make_brief, make_sources
|
|||||||
|
|
||||||
|
|
||||||
class LintTests(unittest.TestCase):
|
class LintTests(unittest.TestCase):
|
||||||
|
@staticmethod
|
||||||
|
def _korean_experience_brief(document_type: str = "technical_blog") -> Brief:
|
||||||
|
data = brief_dict(document_type)
|
||||||
|
data["title"] = "기술적 선택을 설명하는 글"
|
||||||
|
data["language"] = "ko-KR"
|
||||||
|
data["reader_goal"] = "안전한 구현 방식을 선택합니다"
|
||||||
|
data["core_message"] = "기술 선택은 문제와 비용을 함께 설명해야 합니다."
|
||||||
|
data["constraints"]["style_profile"] = "auto"
|
||||||
|
return Brief.from_dict(data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _experience_document(
|
||||||
|
brief: Brief,
|
||||||
|
*,
|
||||||
|
marked_sections: set[int] | None = None,
|
||||||
|
body_by_section: dict[int, str] | None = None,
|
||||||
|
extra_by_section: dict[int, str] | None = None,
|
||||||
|
) -> tuple[str, object]:
|
||||||
|
sources = make_sources()
|
||||||
|
outline = create_outline(brief, sources)
|
||||||
|
marked_sections = (
|
||||||
|
set(range(len(outline.sections)))
|
||||||
|
if marked_sections is None
|
||||||
|
else marked_sections
|
||||||
|
)
|
||||||
|
body_by_section = body_by_section or {}
|
||||||
|
extra_by_section = extra_by_section or {}
|
||||||
|
lines = [f"# {brief.title}", ""]
|
||||||
|
for index, section in enumerate(outline.sections):
|
||||||
|
lines.extend([f"## {section.title}", ""])
|
||||||
|
if index in body_by_section:
|
||||||
|
lines.append(body_by_section[index])
|
||||||
|
else:
|
||||||
|
subject = "저는 " if index in marked_sections else ""
|
||||||
|
lines.append(
|
||||||
|
f"{subject}이 절의 입력과 실제 동작을 확인했습니다. "
|
||||||
|
"현재 구현은 명시된 경계를 사용합니다. "
|
||||||
|
"대안과 비용, 검증 범위도 함께 설명합니다."
|
||||||
|
)
|
||||||
|
if index in extra_by_section:
|
||||||
|
lines.extend(["", extra_by_section[index]])
|
||||||
|
lines.append("")
|
||||||
|
return "\n".join(lines), outline
|
||||||
|
|
||||||
def test_mock_document_meets_structural_gate(self) -> None:
|
def test_mock_document_meets_structural_gate(self) -> None:
|
||||||
brief = make_brief()
|
brief = make_brief()
|
||||||
sources = make_sources()
|
sources = make_sources()
|
||||||
@@ -142,6 +186,121 @@ class LintTests(unittest.TestCase):
|
|||||||
self.assertIn("STYLE001", {issue.code for issue in report.issues})
|
self.assertIn("STYLE001", {issue.code for issue in report.issues})
|
||||||
self.assertEqual(report.metrics["formulaic_ordinal_opening_count"], 3)
|
self.assertEqual(report.metrics["formulaic_ordinal_opening_count"], 3)
|
||||||
|
|
||||||
|
def test_korean_experience_contract_blocks_plain_form_endings(self) -> None:
|
||||||
|
brief = self._korean_experience_brief()
|
||||||
|
sources = make_sources()
|
||||||
|
text, outline = self._experience_document(
|
||||||
|
brief,
|
||||||
|
extra_by_section={0: "현재 구현은 이 값을 사용한다."},
|
||||||
|
)
|
||||||
|
|
||||||
|
report = lint_document(text, brief, outline, sources)
|
||||||
|
|
||||||
|
issues = [issue for issue in report.issues if issue.code == "STYLE002"]
|
||||||
|
self.assertEqual(len(issues), 1)
|
||||||
|
self.assertEqual(issues[0].severity, Severity.BLOCKER)
|
||||||
|
self.assertEqual(report.metrics["plain_form_ending_count"], 1)
|
||||||
|
|
||||||
|
def test_korean_style_lint_exempts_non_reader_prose(self) -> None:
|
||||||
|
brief = self._korean_experience_brief()
|
||||||
|
sources = make_sources()
|
||||||
|
text, outline = self._experience_document(
|
||||||
|
brief,
|
||||||
|
extra_by_section={
|
||||||
|
0: (
|
||||||
|
"### 현재 구현은 사용한다.\n\n"
|
||||||
|
"> 원문 인용은 현재 구현을 사용한다.\n\n"
|
||||||
|
"항목 | 설명\n"
|
||||||
|
"--- | ---\n"
|
||||||
|
"현재 값 | 현재 구현은 사용한다.\n\n"
|
||||||
|
"\n\n"
|
||||||
|
" 명령 출력은 현재 구현을 사용한다.\n\n"
|
||||||
|
"`현재 구현은 사용한다.`\n\n"
|
||||||
|
"직접 기록에는 “현재 구현은 사용한다.”라고 적혀 있습니다.\n\n"
|
||||||
|
"```text\n"
|
||||||
|
"현재 구현은 사용한다.\n"
|
||||||
|
"```"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
report = lint_document(text, brief, outline, sources)
|
||||||
|
|
||||||
|
self.assertNotIn("STYLE002", {issue.code for issue in report.issues})
|
||||||
|
self.assertEqual(report.metrics["plain_form_ending_count"], 0)
|
||||||
|
|
||||||
|
def test_korean_style_lint_requires_first_person_opening(self) -> None:
|
||||||
|
brief = self._korean_experience_brief()
|
||||||
|
sources = make_sources()
|
||||||
|
text, outline = self._experience_document(
|
||||||
|
brief,
|
||||||
|
marked_sections=set(range(1, 8)),
|
||||||
|
)
|
||||||
|
|
||||||
|
report = lint_document(text, brief, outline, sources)
|
||||||
|
|
||||||
|
issues = [issue for issue in report.issues if issue.code == "STYLE003"]
|
||||||
|
self.assertEqual(len(issues), 1)
|
||||||
|
self.assertEqual(issues[0].severity, Severity.BLOCKER)
|
||||||
|
self.assertFalse(report.metrics["opening_has_first_person"])
|
||||||
|
|
||||||
|
def test_korean_style_lint_requires_major_section_coverage(self) -> None:
|
||||||
|
brief = self._korean_experience_brief()
|
||||||
|
sources = make_sources()
|
||||||
|
text, outline = self._experience_document(
|
||||||
|
brief,
|
||||||
|
marked_sections={0},
|
||||||
|
)
|
||||||
|
|
||||||
|
report = lint_document(text, brief, outline, sources)
|
||||||
|
|
||||||
|
self.assertIn("STYLE003", {issue.code for issue in report.issues})
|
||||||
|
self.assertEqual(report.metrics["experience_section_count"], 8)
|
||||||
|
self.assertEqual(report.metrics["marked_experience_section_count"], 1)
|
||||||
|
self.assertEqual(report.metrics["experience_section_coverage"], 0.125)
|
||||||
|
|
||||||
|
def test_korean_style_lint_ignores_non_prose_sections(self) -> None:
|
||||||
|
brief = self._korean_experience_brief()
|
||||||
|
sources = make_sources()
|
||||||
|
text, outline = self._experience_document(
|
||||||
|
brief,
|
||||||
|
marked_sections={0, 1},
|
||||||
|
body_by_section={
|
||||||
|
4: "```text\n현재 구현은 사용한다.\n```",
|
||||||
|
5: "항목 | 값\n--- | ---\n구현 | 현재 값",
|
||||||
|
6: "",
|
||||||
|
7: "> 원문 인용만 남아 있습니다.",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
report = lint_document(text, brief, outline, sources)
|
||||||
|
|
||||||
|
self.assertNotIn("STYLE003", {issue.code for issue in report.issues})
|
||||||
|
self.assertEqual(report.metrics["experience_section_count"], 4)
|
||||||
|
self.assertEqual(report.metrics["marked_experience_section_count"], 2)
|
||||||
|
self.assertEqual(report.metrics["experience_section_coverage"], 0.5)
|
||||||
|
|
||||||
|
def test_korean_style_lint_accepts_compliant_experience_prose(self) -> None:
|
||||||
|
brief = self._korean_experience_brief("readme")
|
||||||
|
sources = make_sources()
|
||||||
|
text, outline = self._experience_document(
|
||||||
|
brief,
|
||||||
|
marked_sections={0, 2, 4, 6},
|
||||||
|
)
|
||||||
|
|
||||||
|
report = lint_document(text, brief, outline, sources)
|
||||||
|
|
||||||
|
codes = {issue.code for issue in report.issues}
|
||||||
|
self.assertNotIn("STYLE002", codes)
|
||||||
|
self.assertNotIn("STYLE003", codes)
|
||||||
|
self.assertEqual(
|
||||||
|
report.metrics["style_contract"],
|
||||||
|
"korean_first_person_experience_v1",
|
||||||
|
)
|
||||||
|
self.assertTrue(report.metrics["opening_has_first_person"])
|
||||||
|
self.assertEqual(report.metrics["first_person_marker_count"], 4)
|
||||||
|
self.assertEqual(report.metrics["experience_section_coverage"], 0.5)
|
||||||
|
|
||||||
def test_numbered_procedure_is_not_formulaic_ordinal_prose(self) -> None:
|
def test_numbered_procedure_is_not_formulaic_ordinal_prose(self) -> None:
|
||||||
data = brief_dict()
|
data = brief_dict()
|
||||||
data["title"] = "기술적 선택을 설명하는 글"
|
data["title"] = "기술적 선택을 설명하는 글"
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ class ModelTests(unittest.TestCase):
|
|||||||
self.assertEqual(brief.document_type, DocumentType.TECHNICAL_BLOG)
|
self.assertEqual(brief.document_type, DocumentType.TECHNICAL_BLOG)
|
||||||
self.assertEqual(Brief.from_dict(brief.to_dict()).title, brief.title)
|
self.assertEqual(Brief.from_dict(brief.to_dict()).title, brief.title)
|
||||||
|
|
||||||
|
def test_readme_brief_round_trip(self) -> None:
|
||||||
|
brief = Brief.from_dict(brief_dict("readme"))
|
||||||
|
self.assertEqual(brief.document_type, DocumentType.README)
|
||||||
|
self.assertEqual(
|
||||||
|
Brief.from_dict(brief.to_dict()).document_type,
|
||||||
|
DocumentType.README,
|
||||||
|
)
|
||||||
|
|
||||||
def test_invalid_document_type_is_rejected(self) -> None:
|
def test_invalid_document_type_is_rejected(self) -> None:
|
||||||
data = brief_dict()
|
data = brief_dict()
|
||||||
data["document_type"] = "essay"
|
data["document_type"] = "essay"
|
||||||
|
|||||||
+44
-12
@@ -9,17 +9,22 @@ from tests.helpers import brief_dict, make_sources
|
|||||||
|
|
||||||
|
|
||||||
class PromptTests(unittest.TestCase):
|
class PromptTests(unittest.TestCase):
|
||||||
def _korean_blog(self) -> Brief:
|
def _brief(
|
||||||
data = brief_dict()
|
self,
|
||||||
|
document_type: str = "technical_blog",
|
||||||
|
*,
|
||||||
|
language: str = "ko-KR",
|
||||||
|
style_profile: str = "woowahan_tech_blog_ko",
|
||||||
|
) -> Brief:
|
||||||
|
data = brief_dict(document_type)
|
||||||
data["title"] = "기술적 선택을 설명하는 글"
|
data["title"] = "기술적 선택을 설명하는 글"
|
||||||
data["language"] = "ko-KR"
|
data["language"] = language
|
||||||
data["reader_goal"] = "안전한 구현 방식을 선택한다"
|
data["reader_goal"] = "안전한 구현 방식을 선택한다"
|
||||||
data["core_message"] = "기술 선택은 문제와 비용을 함께 설명해야 한다."
|
data["core_message"] = "기술 선택은 문제와 비용을 함께 설명해야 한다."
|
||||||
data["constraints"]["style_profile"] = "woowahan_tech_blog_ko"
|
data["constraints"]["style_profile"] = style_profile
|
||||||
return Brief.from_dict(data)
|
return Brief.from_dict(data)
|
||||||
|
|
||||||
def test_korean_blog_prompts_separate_information_structure_from_sentence_form(self) -> None:
|
def _prompts(self, brief: Brief) -> tuple[str, str, str]:
|
||||||
brief = self._korean_blog()
|
|
||||||
sources = make_sources()
|
sources = make_sources()
|
||||||
outline = create_outline(brief, sources)
|
outline = create_outline(brief, sources)
|
||||||
lint_report = LintReport(score=100.0, word_count=0, issues=[], metrics={})
|
lint_report = LintReport(score=100.0, word_count=0, issues=[], metrics={})
|
||||||
@@ -27,13 +32,40 @@ class PromptTests(unittest.TestCase):
|
|||||||
draft = drafting_prompt(brief, outline, sources)
|
draft = drafting_prompt(brief, outline, sources)
|
||||||
review = review_prompt(brief, outline, sources, "# draft", lint_report, "editor")
|
review = review_prompt(brief, outline, sources, "# draft", lint_report, "editor")
|
||||||
revision = revision_prompt(brief, outline, sources, "# draft", lint_report, [])
|
revision = revision_prompt(brief, outline, sources, "# draft", lint_report, [])
|
||||||
|
return draft, review, revision
|
||||||
|
|
||||||
self.assertIn("semantic order, never as a sentence template", draft)
|
def test_korean_blog_prompts_share_experience_prose_contract(self) -> None:
|
||||||
self.assertIn("첫 번째 제약은", draft)
|
prompts = self._prompts(self._brief())
|
||||||
self.assertIn("Information-architecture labels must not leak", review)
|
|
||||||
self.assertIn("real ordered sequences", review)
|
for prompt in prompts:
|
||||||
self.assertIn("Remove repeated ordinal sentence scaffolding", revision)
|
with self.subTest(stage=prompt[:40]):
|
||||||
self.assertIn("real procedure, method, layer, or figure", revision)
|
self.assertIn("korean_first_person_experience_v1", prompt)
|
||||||
|
self.assertIn("저는", prompt)
|
||||||
|
self.assertIn("제가", prompt)
|
||||||
|
self.assertIn("했습니다", prompt)
|
||||||
|
self.assertIn("현재 동작과 기술 설명", prompt)
|
||||||
|
|
||||||
|
self.assertIn("semantic order, never as a sentence template", prompts[0])
|
||||||
|
self.assertIn("실제 관찰", prompts[1])
|
||||||
|
self.assertIn("문서 전체", prompts[2])
|
||||||
|
|
||||||
|
def test_korean_readme_prompts_share_experience_prose_contract(self) -> None:
|
||||||
|
prompts = self._prompts(self._brief("readme", style_profile="auto"))
|
||||||
|
|
||||||
|
for prompt in prompts:
|
||||||
|
self.assertIn("korean_first_person_experience_v1", prompt)
|
||||||
|
self.assertIn("저는", prompt)
|
||||||
|
self.assertIn("했습니다", prompt)
|
||||||
|
|
||||||
|
def test_unrelated_document_types_do_not_receive_experience_contract(self) -> None:
|
||||||
|
briefs = [
|
||||||
|
self._brief(language="en-US"),
|
||||||
|
self._brief("tutorial", style_profile="auto"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for brief in briefs:
|
||||||
|
for prompt in self._prompts(brief):
|
||||||
|
self.assertNotIn("korean_first_person_experience_v1", prompt)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import json
|
|||||||
import unittest
|
import unittest
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
import jsonschema
|
||||||
|
|
||||||
from claridoc.models import Brief, Outline, PipelineConfig, SourcePack
|
from claridoc.models import Brief, Outline, PipelineConfig, SourcePack
|
||||||
|
from claridoc.structures import create_outline
|
||||||
|
from tests.helpers import brief_dict, make_sources
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
@@ -57,6 +61,20 @@ class SchemaTests(unittest.TestCase):
|
|||||||
outline = Outline.from_dict(sample)
|
outline = Outline.from_dict(sample)
|
||||||
self.assertEqual(outline.sections[0].intent, "promise")
|
self.assertEqual(outline.sections[0].intent, "promise")
|
||||||
|
|
||||||
|
def test_readme_is_accepted_by_brief_and_outline_schemas(self) -> None:
|
||||||
|
brief_data = brief_dict("readme")
|
||||||
|
brief_schema = json.loads(
|
||||||
|
(ROOT / "schemas" / "brief.schema.json").read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
outline_schema = json.loads(
|
||||||
|
(ROOT / "schemas" / "outline.schema.json").read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
|
||||||
|
jsonschema.Draft202012Validator(brief_schema).validate(brief_data)
|
||||||
|
brief = Brief.from_dict(brief_data)
|
||||||
|
outline = create_outline(brief, make_sources())
|
||||||
|
jsonschema.Draft202012Validator(outline_schema).validate(outline.to_dict())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -17,6 +17,23 @@ class StructureTests(unittest.TestCase):
|
|||||||
self.assertEqual(len(intents), len(set(intents)), document_type.value)
|
self.assertEqual(len(intents), len(set(intents)), document_type.value)
|
||||||
self.assertGreaterEqual(len(intents), 7, document_type.value)
|
self.assertGreaterEqual(len(intents), 7, document_type.value)
|
||||||
|
|
||||||
|
def test_readme_outline_preserves_reader_onboarding_order(self) -> None:
|
||||||
|
brief = Brief.from_dict(brief_dict("readme"))
|
||||||
|
outline = create_outline(brief, make_sources())
|
||||||
|
self.assertEqual(
|
||||||
|
[section.intent for section in outline.sections],
|
||||||
|
[
|
||||||
|
"problem_value",
|
||||||
|
"principles",
|
||||||
|
"workflow",
|
||||||
|
"installation",
|
||||||
|
"quickstart",
|
||||||
|
"configuration",
|
||||||
|
"verification",
|
||||||
|
"limits_next",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_reconcile_preserves_contract_order(self) -> None:
|
def test_reconcile_preserves_contract_order(self) -> None:
|
||||||
brief = Brief.from_dict(brief_dict())
|
brief = Brief.from_dict(brief_dict())
|
||||||
sources = make_sources()
|
sources = make_sources()
|
||||||
|
|||||||
Reference in New Issue
Block a user