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:
co-authored by
Claude Fable 5.1
parent
73026cada6
commit
9d2a3725c5
+125
-32
@@ -2,6 +2,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import importlib.util
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
REQUIRED_PATHS = (
|
||||
@@ -9,8 +12,10 @@ REQUIRED_PATHS = (
|
||||
".agents/skills/analyzing-codebase-for-tech-log/SKILL.md",
|
||||
".agents/skills/deriving-tech-log-root-tree/SKILL.md",
|
||||
".agents/skills/deriving-tech-log-root-tree/references/decomposition-checklist.md",
|
||||
".agents/skills/deriving-tech-log-root-tree/references/candidate-disposition.md",
|
||||
".agents/skills/deriving-tech-log-root-tree/references/example-tech-log-tree.md",
|
||||
".agents/skills/writing-tech-log-records/SKILL.md",
|
||||
".agents/skills/writing-tech-log-records/references/root-tree-contract.md",
|
||||
".agents/skills/writing-tech-log-records/references/tech-log-tree-contract.md",
|
||||
".agents/skills/writing-tech-log-records/references/record-kinds.md",
|
||||
".agents/skills/writing-tech-log-records/references/body-syntax.md",
|
||||
".agents/skills/writing-tech-log-records/references/code-tables-diagrams.md",
|
||||
@@ -27,34 +32,49 @@ REQUIRED_PATHS = (
|
||||
".agents/skills/rewriting-technical-prose-naturally/scripts/style_profile.mjs",
|
||||
".agents/skills/technical-visualizer/SKILL.md",
|
||||
".agents/skills/refactoring-from-analysis/SKILL.md",
|
||||
# 틀
|
||||
"docs/_templates/state.json",
|
||||
"docs/_templates/source-index.md",
|
||||
"docs/_templates/root-tree.md",
|
||||
"docs/_templates/analysis/00-project-overview.md",
|
||||
"docs/_templates/analysis/module.md",
|
||||
# 프로젝트 폴더 틀 — 끝난 프로젝트의 모양. 작업 재료는 여기 없다
|
||||
"docs/_templates/README.md",
|
||||
"docs/_templates/final/document.md",
|
||||
"docs/_templates/final/evidence/meta/evidence.json",
|
||||
"docs/_templates/tech-log-studio/tech-log-tree.json",
|
||||
# 분석하는 동안에만 있는 작업 재료의 틀
|
||||
".agents/skills/analyzing-codebase-for-tech-log/templates/state.json",
|
||||
".agents/skills/analyzing-codebase-for-tech-log/templates/source-index.md",
|
||||
".agents/skills/analyzing-codebase-for-tech-log/templates/analysis/00-project-overview.md",
|
||||
".agents/skills/analyzing-codebase-for-tech-log/templates/analysis/module.md",
|
||||
".agents/skills/writing-tech-log-records/templates/case.md",
|
||||
".agents/skills/writing-tech-log-records/templates/concept.md",
|
||||
".agents/skills/writing-tech-log-records/templates/reference.md",
|
||||
".agents/skills/writing-tech-log-records/templates/question.md",
|
||||
".agents/skills/writing-tech-log-records/templates/decision.md",
|
||||
# 도구
|
||||
"scripts/techviz",
|
||||
"scripts/build-tech-log-tree.py",
|
||||
"scripts/techlog.py",
|
||||
"scripts/verify-tech-log-tree.py",
|
||||
"scripts/verify-project-layout.py",
|
||||
"scripts/fold-analysis-into-final.py",
|
||||
"scripts/fold-studio-contract-into-index.py",
|
||||
"scripts/terminal-evidence/render_terminal.py",
|
||||
"scripts/terminal-evidence/README.md",
|
||||
".agents/skills/writing-tech-log-records/scripts/check_body.mjs",
|
||||
".agents/skills/writing-tech-log-records/scripts/check_evidence.mjs",
|
||||
)
|
||||
|
||||
ROOT_TREE_TOKENS = (
|
||||
"PROJECT",
|
||||
"TOPIC",
|
||||
"├── CASE",
|
||||
"├── REFERENCE",
|
||||
"├── OPEN QUESTION",
|
||||
"└── DECISION",
|
||||
"# Node Specifications",
|
||||
"readiness:",
|
||||
"source:",
|
||||
"classification:",
|
||||
# 글감 계약이 요구하는 칸. 틀이 이것들을 보여 주지 않으면 아무도 채우지 않는다
|
||||
INDEX_TOKENS = (
|
||||
"readerQuestion",
|
||||
"candidateScope",
|
||||
"sourceRepository",
|
||||
"readinessValues",
|
||||
"dispositionValues",
|
||||
"KEEP_IN_SSOT",
|
||||
"classification",
|
||||
"missing-verification",
|
||||
"basis-version",
|
||||
"relations",
|
||||
"candidates",
|
||||
"dispositionReview",
|
||||
)
|
||||
|
||||
QUEUE_TOKENS = ("version:", "activeProject:", "projects:")
|
||||
@@ -169,19 +189,20 @@ def verify_pipeline(shared_root: Path) -> list[str]:
|
||||
if refactor_queue.exists():
|
||||
errors.extend(_verify_refactor_queue(refactor_queue))
|
||||
|
||||
state_template = shared_root / "docs/_templates/state.json"
|
||||
state_template = shared_root / (
|
||||
".agents/skills/analyzing-codebase-for-tech-log/templates/state.json")
|
||||
if state_template.exists():
|
||||
state_text = state_template.read_text(encoding="utf-8", errors="replace")
|
||||
for token in STATE_REANALYSIS_TOKENS:
|
||||
if token not in state_text:
|
||||
errors.append(f"state template missing reanalysis token: {token}")
|
||||
|
||||
root_tree = shared_root / "docs/_templates/root-tree.md"
|
||||
if root_tree.exists():
|
||||
text = root_tree.read_text(encoding="utf-8", errors="replace")
|
||||
for token in ROOT_TREE_TOKENS:
|
||||
index = shared_root / "docs/_templates/tech-log-studio/tech-log-tree.json"
|
||||
if index.exists():
|
||||
text = index.read_text(encoding="utf-8", errors="replace")
|
||||
for token in INDEX_TOKENS:
|
||||
if token not in text:
|
||||
errors.append(f"root-tree template missing token: {token}")
|
||||
errors.append(f"tech-log-tree template missing token: {token}")
|
||||
|
||||
for path in _iter_pipeline_text_files(shared_root):
|
||||
text = path.read_text(encoding="utf-8", errors="replace")
|
||||
@@ -200,25 +221,97 @@ def verify_pipeline(shared_root: Path) -> list[str]:
|
||||
return errors
|
||||
|
||||
|
||||
def _load(shared_root: Path, filename: str, name: str):
|
||||
path = shared_root / "scripts" / filename
|
||||
if not path.exists():
|
||||
return None
|
||||
sys.path.insert(0, str(shared_root / "scripts"))
|
||||
spec = importlib.util.spec_from_file_location(name, path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
def verify_projects(shared_root: Path) -> list:
|
||||
"""실제 프로젝트의 분해 계약 정합성. 템플릿에 토큰이 있는지와는 다른 것이다."""
|
||||
verifier = _load(shared_root, "verify-tech-log-tree.py", "verify_tech_log_tree")
|
||||
if verifier is None:
|
||||
return []
|
||||
projects = sorted(p.parent.name for p in shared_root.glob("docs/*/tech-log-studio")
|
||||
if not p.parent.name.startswith("_"))
|
||||
return [verifier.verify(name) for name in projects]
|
||||
|
||||
|
||||
def verify_layouts(shared_root: Path) -> list:
|
||||
"""프로젝트 폴더가 같은 모양인지. 틀은 docs/_templates 다."""
|
||||
verifier = _load(shared_root, "verify-project-layout.py", "verify_project_layout")
|
||||
if verifier is None:
|
||||
return []
|
||||
projects = sorted({p.parent.parent.name
|
||||
for p in shared_root.glob("docs/*/final/document.md")
|
||||
if not p.parent.parent.name.startswith("_")})
|
||||
return [verifier.verify(name) for name in projects]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Verify the Tech Log documentation pipeline workspace.")
|
||||
parser.add_argument("shared_root", nargs="?", type=Path,
|
||||
default=Path(__file__).resolve().parent.parent)
|
||||
parser.add_argument("--skip-projects", action="store_true",
|
||||
help="틀과 스킬만 본다. 프로젝트 트리 정합성은 보지 않는다")
|
||||
parser.add_argument("--samples", type=int, default=2)
|
||||
args = parser.parse_args()
|
||||
|
||||
errors = verify_pipeline(args.shared_root)
|
||||
reports = [] if args.skip_projects else verify_projects(args.shared_root)
|
||||
layouts = [] if args.skip_projects else verify_layouts(args.shared_root)
|
||||
project_errors = sum(r.error_count for r in reports) + sum(r.error_count for r in layouts)
|
||||
|
||||
if errors:
|
||||
print("PIPELINE VERIFICATION: FAIL")
|
||||
print("PIPELINE CONTRACT: FAIL")
|
||||
for error in errors:
|
||||
print(f"- {error}")
|
||||
return 1
|
||||
else:
|
||||
print("PIPELINE CONTRACT: PASS")
|
||||
print(f"- required paths: {len(REQUIRED_PATHS)}")
|
||||
print("- analysis queue contract: valid")
|
||||
print("- tech-log-tree contract: present")
|
||||
print("- forbidden legacy dependency: absent")
|
||||
|
||||
print("PIPELINE VERIFICATION: PASS")
|
||||
print(f"- required paths: {len(REQUIRED_PATHS)}")
|
||||
print("- analysis queue contract: valid")
|
||||
print("- root-tree contract: present")
|
||||
print("- forbidden legacy dependency: absent")
|
||||
return 0
|
||||
if layouts:
|
||||
layout_errors = sum(r.error_count for r in layouts)
|
||||
print()
|
||||
print(f"PROJECT LAYOUT: {'FAIL' if layout_errors else 'PASS'}"
|
||||
f" — 프로젝트 {len(layouts)} · error {layout_errors} ·"
|
||||
f" warn {sum(r.warn_count for r in layouts)}")
|
||||
for report in layouts:
|
||||
verifier_render(report, args.samples)
|
||||
|
||||
if reports:
|
||||
tree_errors = sum(r.error_count for r in reports)
|
||||
print()
|
||||
print(f"TECH LOG TREES: {'FAIL' if tree_errors else 'PASS'}"
|
||||
f" — 프로젝트 {len(reports)} · error {tree_errors} ·"
|
||||
f" warn {sum(r.warn_count for r in reports)}")
|
||||
for report in reports:
|
||||
verifier_render(report, args.samples)
|
||||
|
||||
return 1 if errors or project_errors else 0
|
||||
|
||||
|
||||
def verifier_render(report, samples: int) -> None:
|
||||
facts = " · ".join(
|
||||
f"{k}={json.dumps(v, ensure_ascii=False) if isinstance(v, dict) else v}"
|
||||
for k, v in report.facts.items())
|
||||
print(f" [{report.project}] {facts}")
|
||||
for label, bucket, mark in (("error", report.errors, "✗"), ("warn", report.warns, "!")):
|
||||
for rule, details in sorted(bucket.items(), key=lambda kv: -len(kv[1])):
|
||||
print(f" {mark} {label} {len(details):>4} {rule}")
|
||||
for detail in details[:samples]:
|
||||
if detail:
|
||||
print(f" · {detail}")
|
||||
if samples and len(details) > samples:
|
||||
print(f" … 외 {len(details) - samples}건")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user