pipeline: make tech-log-tree.json the one decomposition contract and enforce it

리뷰 두 건을 반영했다.

계약
- tech-log-tree.json 하나가 분해 계약이자 색인이다. 사람이 읽는 트리·Node Specification·
  후보 대장은 없어졌고, 문서에 남아 있던 그 개념을 걷어냈다
- candidateScope — 후보를 찾는 SSOT 범위. 접어 넣은 제2부·제3부는 근거이지 후보가 아니다
- sourceRepository — 분석한 저장소의 경로·리비전·판단 근거. 리비전을 모르면 null 로 두고
  지어내지 않는다. 갈래가 여럿이면 revisions
- 검사기: 계약 미채택·PENDING·PROMOTE↔글감 양방향·candidateScope·sourceRepository 를
  error/warn 으로 센다. 옛 스키마도 검사를 피하지 못한다. 테스트 22 → 31

기록 쓰기
- 템플릿 5종에 source·sourceRevision·topicName, Question 에 닫는 조건, 본문 없는 종류에서
  assets 제거. 고정 절 개수 삭제
- check_evidence.mjs — 인용한 코드가 SSOT 에 있는지, 앵커가 SSOT 를 가리키는지, 제목이
  계약과 같은지, 리비전이 저장소에 있는지. 게시된 기록에서 SSOT 와 다른 URL 을 잡았다

문체
- 문체 규칙의 정본을 ai-tells.md 로. explaining.md 의 질문체 제목·절 끝 대조 반복·그림 예고
  규칙을 삭제해 충돌을 없앴다. 첫 절 「설명 뒤에 평가를 붙이지 않는다」에 지우는 사례 네 유형
- voice 스킬의 「독자 쪽을 본다」를 자료에 오독 기록이 있을 때로 좁히고, 평가만 더한 예시를 교체
- check_prose: 안내 문장을 요구하던 경고 제거, 문장이 끝나지 않은 채 문단이 끝나는 조각 검사 추가

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-07 12:39:20 +09:00
co-authored by Claude Fable 5.1
parent 73026cada6
commit 9d2a3725c5
54 changed files with 3583 additions and 871 deletions
@@ -0,0 +1,172 @@
# Tech Log Tree Contract
`tech-log-tree.json` is the explicit boundary between deep project analysis and Tech Log
record generation. **It is the decomposition contract and the index at once, and it is the
source of truth.** There is one file, so nothing can disagree with it.
A finished `tech-log-studio/` holds `tech-log-tree.json` and the record folders. Nothing
else.
A project whose index predates this contract fails verification with one error until it is
migrated. The per-field checks stay off for such a project — "not written yet" must not read
as "written wrong" — but non-adoption itself is counted, because a warning lets an old index
avoid every check indefinitely.
## Required top level
- `schemaVersion`
- `project`
- `ssot` and `ssotSha256` — the hash prevents treating a tree derived from old material
as current
- `sourceRevision`
- `generatedAt`
- `sourceRepository``path`, `revision`, and `verified`: which checkout the analysis read,
which commit the document describes, and how that was confirmed. Leave `revision` null rather
than inventing one; the verifier warns instead of accepting a made-up label. When the work
is spread over branches rather than one line of commits, use `revisions` — a label to commit
map — and pin every tip the document describes
- `candidateScope` — which part of the SSOT candidates may come from
- `contract` — the decomposition rules, `readinessValues`, `dispositionValues`
- `topics`, `candidates`, `counts`, `unlisted`
## Candidate scope
A folded `final/document.md` carries the integrated analysis, the module analyses, and the
analysis material in one file. Only the first is candidate material.
```json
"candidateScope": {
"document": "final/document.md",
"sections": ["§3", "§4", "§5", "§6", "§7", "§8", "§9", "§10", "§11"],
"excluded": ["제2부 — 모듈 분석 전문", "제3부 — 분석 재료"]
}
```
`document` names the SSOT and must match `ssot`. `sections` names the candidate scope, and
`excluded` names the parts that are evidence rather than candidates. A node may cite an
anchor from an excluded part in `source`; it may not exist because of one.
## Topics
Each Topic has `topic` (its key), `title`, **`readerQuestion`**, and `kinds` with the five
record kinds.
```json
"oauth-oidc-auth-boundary": {
"topic": "oauth-oidc-auth-boundary",
"title": "OAuth 자격증명과 세션의 보관 경계",
"readerQuestion": "자격증명과 세션을 누가 보관하고, 누가 API 요청을 만들며, 보호 자원은 무엇을 신뢰하는가?",
"kinds": { "case": [], "concept": [], "reference": [], "question": [], "decision": [] }
}
```
Every node in the Topic must help answer the reader question. Two Topics do not share a
question; one Topic does not need two. Empty kinds are allowed — do not manufacture nodes
to fill all five.
## Candidates
Everything the analysis found lives in `candidates` with its disposition —
`.agents/skills/deriving-tech-log-root-tree/references/candidate-disposition.md`. Only
`PROMOTE` candidates become nodes under `topics`, and the relation runs both ways: a
`PROMOTE` candidate whose target is not a node, and a node no `PROMOTE` candidate points
at, are both contract errors.
`dispositionReview` records whether a person re-read the candidate under the independence
test. `PENDING` means it reached the tree by recall alone, and a `PENDING` candidate is an
error, not a warning — a record written over an unreviewed tree inherits the
over-classification the disposition step exists to catch. Write records only for nodes
whose candidate is `PROMOTE` and `CONFIRMED`.
## Written by hand, refreshed by script
`readiness` · `source` · `code` · `evidence` · `classification` · `relations` and the rest
of each kind's fields are written by a person. `build-tech-log-tree.py` never touches them.
It refreshes only what it can read from the record files — `file`, `publication`, `status`,
`studioId`, `assets`, `evidenceFiles` — and lists records that have no node in `unlisted`.
### Case
`slug` · `readiness` · `source` · `classification` · `missing-verification` · `relations`,
plus `code`/`evidence` when the conclusion depends on them.
One problem, an observation or reproduction, a diagnosis, a conclusion that closes. Several
observations that answer the same question with the same conclusion are one Case with a
table or sub-sections, not several partial Cases.
### Concept
`slug` · `readiness` · `source` · `basis-version` · `classification` · `relations`.
`basis-version` names what the explanation was written against — `Keycloak 26.7.0 identity
brokering`, `Spring Boot 3.3 auto-configuration`. A Concept without it cannot be known to
be stale.
A Concept exists because a Case, Decision, or Question needs it to be understood. Absence,
call-counts, unwired subsystems, analysis scope, and coverage ledgers are not Concepts.
### Reference
`slug` · `readiness` · `source` · `classification` · `scope` · `exceptions` · `relations`.
A Reference must be useful beyond retelling one Case. If removing the originating
project's names leaves no rule, it is still a Case.
### Open Question
`slug` · `readiness: OPEN` · `source` · `known` · `unknown` · `next-verification` ·
`decision-criterion` · `relations`.
Do not create a Question when the analysis already contains a verified answer. Move the
material to Case/Reference/Decision and update the tree first.
### Decision
`slug` · `readiness` · `decision-status` · `source` · `decision-evidence` · `grounds` ·
`classification` · `relations`.
`decision-status` is `PROPOSED`, `ADOPTED`, `SUPERSEDED`, or `NOT_DECIDED`. A
`NOT_DECIDED` candidate uses `NEEDS_DECISION` and is not written as a Decision.
## Readiness semantics
**`readiness` is about evidence, not about publication.** Whether a record has been
written, and whether it has been saved into Studio, are separate facts that the generated
index carries as `file` and `publication`. A published record with thin evidence is still
`NEEDS_EVIDENCE`.
| readiness | meaning | generation |
|---|---|---|
| `READY` | grounded enough for the kind | allowed |
| `OPEN` | legitimate unresolved Question | allowed as Open Question |
| `NEEDS_EVIDENCE` | material assertion still lacks verification | blocked |
| `NEEDS_DECISION` | direction sounds plausible but the project has not decided | blocked |
| `BLOCKED` | sources are incomplete or contradictory | blocked |
`REJECTED` is not a readiness. Whether a candidate becomes a record at all is a
disposition, and it lives in `candidates`, not on the node.
## Derivation rules
1. Discover candidates from `final/document.md` only. It is the whole analysis, folded in —
there is no `analysis/` folder to search in a finished project.
2. Give every candidate a disposition before writing any node. `KEEP_IN_SSOT` is a normal
outcome, and a decomposition that excludes nothing has not selected anything.
3. Apply the independence test: if folding the record into a related Case or Concept as
one section changes nothing, it is not an independent record.
4. Take Cases, References, Decisions, and Questions first; add Concepts backwards from
what those four require.
5. Prefer several narrowly grounded Cases over one broad Case combining unrelated
incidents — but merge observations that share a question and a conclusion.
6. Extract Decisions only from explicit choice evidence: ADR, commit/history, configuration
plus recorded rationale, issue/PR decision, or a user-supplied decision record.
7. A node may relate to several siblings, but each record has one primary purpose.
8. If new runtime evidence changes the answer, update the analysis and revise the tree
before editing downstream records.
## Generation and verification
```bash
python3 scripts/build-tech-log-tree.py <project> # 파생 칸을 다시 채운다
python3 scripts/verify-tech-log-tree.py <project> # error 0 이어야 한다
```