Files
llm-wiki/docs/superpowers/specs/2026-06-06-spec-a-deterministic-backbone-gate-design.md
T

9.8 KiB

title, source_type, status, confidence, tags, last_reviewed
title source_type status confidence tags last_reviewed
Spec A — 결정론 backbone을 진짜 게이트로 (하이브리드 PreToolUse/PostToolUse) llm-generated draft medium
harness
claude-code
hooks
design
automation
2026-06-06

Spec A — 결정론 backbone을 진짜 게이트로

상위 감사: 2026-06-06-harness-audit-report §3 G1·G5·G6. 본 spec은 그 첫 슬라이스. 두 설계 결정 확정: (1) 하이브리드 게이트, (2) Lean & safe 범위/SSOT.

1. 문제 (감사에서)

  • G1: wiki_structure_lint.py--hook 경로가 findings가 있어도 항상 sys.exit(0) (line 531) — 582행짜리 결정론 린터가 경고 프린터일 뿐 게이트가 아니다. 게다가 PostToolUse라 사후. 유일한 실제 차단(wiki_claim_gate.py)은 5개 path-prefix만 커버 → wiki/projects·invest-*·파생 산출물·raw/errors는 쓰기-시점 강제 0.
  • G5: SSOT 분열 — structure_linttemplates/에서 도출(좋음), 그러나 claim_gate는 섹션명·컬럼을 inline 하드코딩, SKILL.md는 source_type 재나열 → 택소노미 사본 3개, drift 위험.
  • G6: 유일한 exit-2 차단 훅 claim_gate.py에 유닛 테스트 0개(비차단 린터는 잘 테스트됨 — 비대칭).

2. 비목표 (이 spec 범위 밖)

  • judge 에이전트 return-schema / N-vote quorum → Spec B.
  • funnel stats 계약 → Spec C.
  • 누락 카테고리의 새 의미 규칙(invest source+timestamp, 파생물 canonical-source+status, wiki/projects claim-backed) → 후속(Spec B/C 또는 A.2). 단, 섹션/frontmatter 존재 검사는 C1이 template-derived이므로 게이트 배선만으로 전 카테고리 자동 확장됨.
  • Antigravity 훅 포팅 → 기존 follow-up(docs/superpowers/notes/2026-06-04-phase2-antigravity-hook-coverage.md).
  • branch-spec/project-spec의 Workflow 변환 → Spec D(보류).

3. 핵심 설계 결정 (확정)

DD1 — 하이브리드 게이트 지점·의미

PostToolUse exit-2는 쓰기를 되돌리지 못한다(파일은 이미 디스크, 모델에 "고쳐라"만 전달). 따라서:

  • 항상-틀린 검사(C2 깨진 링크)PreToolUse로 옮겨 projected_content에 검사, exit 2로 쓰기 자체 차단(ghost가 디스크에 안 닿음).
  • 완성성 검사(C1 섹션/frontmatter, C3 선택조건) → 기존 is_completeness_checkable 철학 유지: '완성 선언' 문서에만 PostToolUse exit-2 fix-up.

기존 코드 철학(C2-항상 / C1·C3-완성시)과 정확히 일치한다.

DD2 — Lean & safe 범위 + SSOT

  • claim_gate의 기존 5-prefix 의미 규칙은 그대로. 새 의미 규칙 없음.
  • SSOT: claim_gate의 하드코딩 prefix→요구 맵 + 공유 이벤트 파싱을 wiki_rules.py 모듈로 추출(두 훅이 import). 템플릿-테이블 파싱(Deep SSOT)은 채택 안 함 — fragile.

4. 아키텍처

.claude/hooks/
  wiki_rules.py            ← NEW. 공유 SSOT 모듈 (stdlib only)
  wiki_claim_gate.py       ← MOD. wiki_rules import (projected_content/파싱/PREFIX_REQUIREMENTS 이관)
  wiki_structure_lint.py   ← MOD. --pre 모드 신설 + --hook fix-up 티어링
  test_wiki_structure_lint.py  ← 기존
  test_wiki_claim_gate.py  ← NEW

4.1 wiki_rules.py (NEW 공유 모듈)

  • 이관(claim_gate→여기): tool_name(), tool_input(), target_path(), write_content(), projected_content(), command_string(), rel_to_root(), has_table().
  • 추출(하드코딩→dict, SSOT):
    PREFIX_REQUIREMENTS = {
      "raw/official-docs/":       {"tables": [("## Claims Extracted", [...6 cols])], "sections": ["## Usage Boundaries"]},
      "raw/company-tech-blogs/":  {...동일...},
      "raw/branch-notes/":        {"tables": [("## Decision Evidence Map", [...5 cols])],
                                    "section_regex": [r"^## .*\bClaims To Verify\b"],
                                    "semantic": ["officially_supported_needs_strength"]},
      "wiki/concepts/":           {"tables": [("## Claim-backed Knowledge", [...4 cols])]},
      "docs/superpowers/specs/*-report.md": {"semantic": ["complete_needs_traceability"]},
    }
    
    (claim_gate의 check_markdown_write 로직이 이 dict를 순회하도록 리팩터 — 동작 동일, 출처가 dict로.)
  • 상수: CRITICAL_CODES, FIXUP_CODES, WARN_CODES (structure_lint이 import).
    CRITICAL_CODES = {"BROKEN_LINK", "BROKEN_MD_LINK"}          # PreToolUse block
    FIXUP_CODES = {"MISSING_SECTION","MISSING_FRONTMATTER",
                   "EMPTY_SELECTION_CRITERION","DANGLING_ANCHOR",
                   "PROJECT_NO_DIAGRAM","PROJECT_NO_BRANCH_TABLE",
                   "UNMAPPED_SOURCE_TYPE"}                       # PostToolUse exit-2 (완성시)
    # 그 외(NO_FRONTMATTER 등) → WARN exit 0
    

4.2 wiki_structure_lint.py --pre (NEW 모드)

  • stdin JSON 이벤트 → wiki_rules.target_path() + projected_content()쓰기 후 예상 본문 계산.
  • raw/ 또는 wiki/.md가 아니면 exit 0.
  • build_vault_index(root) + check_c2(projected_doc, ...) 실행.
  • code in CRITICAL_CODES인 finding이 있으면 stderr로 사유 출력 + sys.exit(2) (쓰기 차단).
    • 백틱 placeholder(`[[future]]`)는 _code_spans가 이미 면제 → forward-ref 정상.
    • 차단 메시지에 탈출구 명시: "미존재 타깃은 백틱 코드(`[[slug]]`)로 표기하거나 타깃을 먼저 생성."
  • CRITICAL 없으면 exit 0(DANGLING_ANCHOR 등은 PostToolUse가 처리).
  • 성능: PreToolUse는 매 쓰기마다 발동 → build_vault_index(rglob) 비용. 30s timeout 내 여유지만, 캐시 불가(이벤트마다 새 프로세스). projected 본문에서 추출된 위키링크가 0개면 인덱스 빌드 스킵하는 early-exit 추가.

4.3 wiki_structure_lint.py --hook (MOD, fix-up 티어링)

현재: C2 항상 + (완성시) C1/C3 → 전부 stderr 출력 → 항상 exit 0. 변경:

  • C2 broken-link는 이미 PreToolUse에서 차단됨 → --hook에서는 중복 차단 안 함(여전히 출력은 하되 exit 코드엔 미반영). DANGLING_ANCHOR(타깃 존재, 앵커만 부재)는 여기 fix-up 대상.
  • is_completeness_checkable(doc) 이고 findingscode in FIXUP_CODES가 있으면 → stderr 사유 + sys.exit(2) (fix-up 루프; 파일은 디스크에 있으나 모델이 고침).
  • 그 외 → 기존처럼 WARN 출력 + sys.exit(0).
  • 무삭제: findings[:10] 잘림 시 … 외 {len-10}건 (suppressed) 1줄 추가(--hook·--pre 양쪽).

4.4 settings.json 배선

"PreToolUse": [
  { "matcher": "*", "hooks": [ /* wiki_claim_gate.py (기존) */ ] },
  { "matcher": "Write|Edit|MultiEdit", "hooks": [
      { "type": "command",
        "command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/wiki_structure_lint.py --pre",
        "timeout": 30 } ] }
],
"PostToolUse": [
  { "matcher": "Write|Edit|MultiEdit", "hooks": [ /* wiki_structure_lint.py --hook (기존, 동작 변경) */ ] }
]

SubagentStart/SubagentStop(claim_gate)는 불변.

5. 영향 분석 / 위험

위험 완화
PreToolUse C2-block이 클러스터 작성 중 형제 forward-ref를 막음 백틱 placeholder(코드가 이미 면제). 차단 메시지에 탈출구 명시. C1/C3는 PreToolUse 차단 안 함(완성시 PostToolUse fix-up).
매 쓰기 build_vault_index 비용 projected 본문에 위키링크 0개면 인덱스 빌드 스킵. 30s timeout.
PostToolUse exit-2가 무한 fix-up 루프 claim_gate가 쓰는 stop_hook_active 가드 패턴 동일 적용 — 재진입 시 통과.
wiki_rules.py import가 Codex에서 깨짐 같은 디렉터리 stdlib import → Codex 동일 스크립트 실행이라 동작. --check(sync_automation)와 무관(훅은 SSOT 1벌).
claim_gate 리팩터가 기존 동작 변경 dict-주도로 바꾸되 동작 동치. 신규 test_wiki_claim_gate.py가 5-prefix 케이스 회귀 고정.

6. 수용 기준 (검증 가능)

  1. --pre: 미존재 타깃 [[ghost]]를 본문에 포함한 Write 이벤트(stdin JSON) → exit 2 + stderr에 사유. 백틱 `[[ghost]]`은 exit 0.
  2. --pre: 위키링크 0개 본문 → build_vault_index 미호출(스킵) + exit 0.
  3. --hook: status: verified 문서가 필수 섹션 누락 → exit 2. status: draft 동일 문서 → exit 0(WARN만).
  4. --hook/--pre: findings 11건 → 10건 출력 + … 외 1건 (suppressed).
  5. claim_gate: 리팩터 후 기존 5-prefix 차단 케이스 5종 + 통과 케이스가 test_wiki_claim_gate.py에서 green.
  6. python3 .claude/hooks/test_wiki_structure_lint.py + test_wiki_claim_gate.py 전부 통과.
  7. wiki_structure_lint.py --all 회귀: 본 변경 전후 FAIL 집합 동일(게이트 배선은 --all에 영향 없음).
  8. 실제 wiki 문서 1개 정상 작성(완성 frontmatter + 유효 링크) → Pre/Post 훅 모두 통과(exit 0), 정상 저장.

7. 구현 순서 (writing-plans에서 단계화)

  1. wiki_rules.py 생성 — claim_gate에서 공유 헬퍼 이관 + PREFIX_REQUIREMENTS dict + severity 상수.
  2. wiki_claim_gate.pywiki_rules import, check_markdown_write를 dict-주도로 리팩터(동작 동치).
  3. test_wiki_claim_gate.py — 5-prefix 차단/통과 회귀 고정(2 전에 작성 = TDD).
  4. wiki_structure_lint.py--pre 모드 + --hook fix-up 티어링 + 무삭제 1줄.
  5. test_wiki_structure_lint.py--pre/fix-up 케이스 추가.
  6. settings.json — PreToolUse에 --pre 배선.
  7. 수동 스모크: §6 수용 기준 1-8 실행.

8. 메모

  • 본 spec은 기존 메커니즘 강화(저위험)이며 새 아키텍처를 도입하지 않는다. Workflow/quorum 같은 고위험 변경은 Spec B/D로 분리.
  • claim_gatestructure_lint의 책임 분리 유지: claim_gate=claim 의미, structure_lint=구조/링크. wiki_rules.py공유 기계장치만(정책 아님).