스킬

AI에게 시킬 수 있는 작업을 절차로 정리했습니다. 웹 화면만으로 되는 것부터 나옵니다.

스킬 하나는 어떻게 생겼나

스킬 하나는 SKILL.md 파일 한 장이에요. 맨 위 몇 줄에 이름표가 붙고 그 아래로 절차가 이어집니다. 아래 여덟 벌이 모두 같은 뼈대를 씁니다.맨 위: 이름표본문: 다섯 마디아래 카드마다 「이 절차서 전문 보기」를 열면 그 스킬의 SKILL.md 원문이 그대로 나옵니다.

지금 바로 되는 것

웹 ChatGPT·Claude만 쓰시는 분 · 본문 텍스트만으로 완결되어 어느 화면에서든 「이 페이지 복사」로 가져다 붙이시면 그대로 돌아갑니다.

책 서술을 사료와 대조하기

fact-check
책 본문 서술이 사료와 맞는지 문장 단위로 대조합니다.
1 넣는 것검증할 포인트의 본문 문단 전체
2 시키는 것
이렇게 말하면 됩니다
이 포인트 사실 확인해줘
3 나오는 것
항목마다 책 서술·사료·판정·심각도·근거 다섯 줄이 나오고 끝에 건수 요약이 붙습니다.

언제 쓰는가

『30포인트로 읽어내는 로마 제국 쇠망사』(가나모리 시게나리 편역, 북프렌즈) 본문 서술이 실제 사료와 맞는지 검증할 때 쓴다.
발표 대본이나 편데 원고에 이 책의 내용을 인용하기 전, 오류가 그대로 남아있으면 안 되는 단계에서 호출한다.
트리거 예: "이 포인트 사실 확인해줘", "사료 대조해줘", "포인트 03도 검증해줘", "이 서술 근거 있어?"
이 스킬은 이미 실전에서 검증됐다.
2026-08-12에 포인트 01·02 본문 45건을 이 절차로 검증해 심각 6건·중간 18건을 찾아냈다.
아래 절차는 그 작업을 재현 가능한 형태로 정리한 것이다.
이 스킬은 사실관계를 찾아내는 것까지만 한다. 발표 대본 작성이나 원고 수정은 이 스킬의 범위 밖이다.

입력

  • 검증 대상 포인트의 본문 텍스트
포인트 번호와 원문 문단 전체를 그대로 받는다. 발췌하지 않는다 — 발췌하면 문맥이 잘려 인과관계 오류를 놓친다.
  • 관련 온톨로지 노드 (선택)
필요하면 온톨로지 상 관련 인물·사건 노드를 참고용으로 받는다. 다만 이것 자체를 검증 근거로 삼지는 않는다 — 온톨로지는 이전 검증의 산물이지 사료가 아니다.
  • 검증 범위
포인트 단위인지 챕터 단위인지 미리 정한다. 범위를 정하지 않으면 문장 단위 분해가 끝없이 늘어난다.
이 세 가지가 갖춰지지 않으면 절차 1번(문장 단위 분해) 전에 요청자에게 먼저 확인한다.

절차

  1. 검증할 서술을 문장 단위로 하나씩 뽑는다.
한 문단을 통째로 판정하지 않는다 — 한 문장 안에서도 맞는 요소와 틀린 요소가 섞여 있을 수 있다.
  1. 각 문장에서 검증 가능한 요소를 분리한다: 연도 · 인명 · 관직 · 지명 · 승패 · 수치 · 인과관계.
요소별로 따로 확인해야 복합 오류를 놓치지 않는다. 실제 사례로 요소 유형을 구분하면:
  • 연도만 틀린 경우: "기원전 326년 삼니움 전투" — 사건 자체보다 연도를 먼저 떼어 확인한다
  • 인명과 관직이 함께 틀린 경우: "호민관 클라우디우스 풀케르"는 인명(→ 압피우스 클라우디우스 카우덱스)과 관직(→ 집정관)을 각각 확인해야 둘 다 틀렸다는 것이 드러난다
  • 인명만 틀린 경우: 밀라이 해전의 카르타고 지휘관을 하밀카르 바르카스라 했으나 실제는 한니발 기스코다 — 하밀카르의 지휘는 기원전 247년부터라 시기상 불가능하다
  • 승패만 틀린 경우: 클라우디우스의 시라쿠사 공격을 "참패"라 했으나 실제로는 무승부에서 로마 우세에 가깝다
  • 수치만 틀린 경우: 강화 배상금이 초안(2200탈란트/20년)과 최종(3200탈란트/10년)으로 다른데 두 단계가 뒤섞여 서술된다
  • 인과관계가 틀린 경우: 하밀카르가 '젊은 사자들' 네 아들에게 복수를 맹세시켰다는 서술 — 실제 사료는 아홉 살 한니발 혼자다
  1. 사료를 확인한다.
1차 사료를 우선한다 — 폴리비오스, 리비우스, 디오도로스, 플루타르코스. 1차 사료로 확인이 안 되면 그다음으로 현대 학계 정설을 참고한다.
  1. 판정한다: 일치 / 부분 불일치 / 불일치 / 사료 없음.
부분 불일치는 요소 중 일부만 틀린 경우다. 메시나 도하 사례처럼 인명과 관직이 동시에 틀렸다면 부분 불일치가 아니라 불일치로 묶는다 — 두 요소 중 하나라도 발표에 그대로 쓰면 틀린 정보가 나가기 때문이다.
  1. 심각도를 매긴다.
  • 심각 — 발표 서사 자체가 뒤집힌다. 예: 삼니움 전투 연도·승패 전도, 레굴루스 고문사(사료에 없는 후대 전설), 메시나 도하 지휘관 인명·관직 이중 오류, 밀라이 해전 카르타고 지휘관 오인
  • 중간 — 세부 수치나 조건이 어긋난다. 예: 강화 배상금 조건 혼선, 마메르티니 기원 시점 오류, 콜론네 곶 협정 당사자 오인
  • 경미 — 표기 차이 수준이다
  1. 책의 서술과 사료의 값을 둘 다 적는다.
책 쪽 값을 지우고 사료 값만 남기지 않는다 — 책이 뭐라고 썼는지가 발표에서 대조할 재료다.

하지 말 것

  • 기번 원전(source/)을 사료로 쓰지 마라.
기번은 18세기 저작이고 그 자체가 검증 대상이다. 게다가 30포인트 편역본과 다른 계통이라 섞으면 혼선만 커진다.
  • "책이 틀렸다"로 끝내지 마라.
무엇이 맞는지, 근거가 무엇인지까지 적어야 발표에 쓸 수 있다. 판정만 남기고 근거를 생략하면 다음 검증자가 처음부터 다시 확인해야 한다.
  • 사료가 갈리면 한쪽을 고르지 마라.
갈린다는 사실 자체가 발표 재료다. 레굴루스 고문사가 그런 경우다 — 폴리비오스에는 없고, 로마 애국 전설로 후대에 붙었으며, 디오도로스는 자연사를 전한다. 셋 중 하나로 단정하지 말고 셋 다 적는다.
  • AI의 기억으로 판정하지 마라.
연도와 인명은 특히 자주 틀린다. 실제로 하위 에이전트가 제안한 오류 68건 중 19건을 반려했는데, 연도 혼동이 그 가운데 큰 몫을 차지했다. 확인할 수 없으면 "확인 불가"로 남기고 넘어간다 — 근거 없이 틀렸다고 단정하는 편이 검증하지 않은 것보다 나쁘다.
  • 온톨로지를 고치지 마라.
이 스킬은 오류를 찾기만 한다. 찾은 오류를 반영해 온톨로지나 원고를 고치는 것은 propose-change의 몫이다. 두 작업을 한 세션에서 섞으면 "찾기"의 엄격함이 "고치기"의 편의에 밀린다.

출력 형태

검증 항목 하나마다 아래 5행 형식으로 기록한다.
예시 (포인트 01, 실제 검출 사례)
  • 책 서술: 기원전 326년, 로마군이 삼니움과의 전투에서 배후 함정에 걸려 패배했다
  • 사료: 리비우스 『로마사』에 따르면 기원전 326년 나폴리 전투는 로마의 승리다. "배후 함정에 걸린 패배"는 기원전 321년 카우디움 협곡 사건을 가리킨다
  • 판정: 불일치
  • 심각도: 심각 — 연도와 승패가 함께 뒤집혀 있어, 로마 초기 좌절이라는 발표 서사의 시점 자체가 틀어진다
  • 근거: 리비우스 『로마사』 8권(나폴리, 기원전 326) / 9권(카우디움, 기원전 321)
모든 검증 항목을 이 형식으로 나열한다.
문서 끝에 심각도별 건수 요약(심각 N건 / 중간 N건 / 경미 N건)을 덧붙인다.
판정이 "사료 없음"이거나 사료가 갈리는 항목은 심각도를 매기지 말고 별도 목록으로 분리해, 발표 재료로 그대로 넘길 수 있게 한다.
건수 요약과 별도 목록은 검증 완료 시점의 스냅샷이다. 같은 포인트를 다시 검증할 일이 생기면 새 스냅샷으로 남기고, 기존 기록을 덮어쓰지 않는다.

자료를 받아두어야 되는 것

「AI에 자료 연결하기」를 마치신 분 · 인물·관계 파일을 AI가 직접 열어 읽어야 하는 절차라 파일 없이 문장만 붙여넣으면 AI가 지어냅니다.AI에 자료 연결하기를 먼저 하시면 아래가 열립니다.

포인트 하나를 통째로 정리하기

point-context
포인트 하나에 필요한 재료를 한 덩어리로 모읍니다.
1 넣는 것포인트 번호 하나
2 시키는 것
이렇게 말하면 됩니다
3번 포인트 자료 모아줘
3 나오는 것
본문 전문·등장 객체·관계 목록·그 시기 연표가 한 덩어리로 나옵니다.

언제 쓰는가

팀원이 30포인트 중 한 포인트를 발표·집필·검증용으로 준비할 때. 레포 링크만 던지면 AI가 source/나 chapters/(기번 영문 원전 계통, 다른 책)를 헤매다 엉뚱한 것을 읽어온다. 이 스킬은 그 포인트 하나에 필요한 재료 — 본문, 등장 객체, 관계, 시대 배경 — 를 온톨로지 소스(points/·ontology/)에서만 뽑아 붙여넣기 가능한 텍스트 한 덩어리로 조립한다.
한 번 호출에 포인트 1개만 다룬다. 여러 포인트를 비교하거나 포인트를 가로지르는 인물(예: 여러 장에 걸쳐 등장하는 하스드루발)의 전체 서사를 훑는 작업은 이 스킬의 범위가 아니다 — 필요하면 포인트별로 각각 호출해 조립된 덩어리를 나란히 둔다.

입력

  • 포인트 번호 1개 (1~30). "2번", "제1차 포에니 전쟁 포인트" 같은 자연어도 points/ 파일명으로 매핑해 번호를 확정한다.
  • 레포 경로는 고정: ~/project/active/decline-and-fall-of-the-roman-empire/ (읽기 전용으로 다룬다).

절차

  1. 번호를 2자리로 정규화(2 → 02)해 points/NN_*.md 파일을 연다. 00_목차·00_일러두기·00_책머리에·99_옮기고나서는 30포인트가 아니므로 대상에서 제외한다.
  2. 해당 파일 본문 전체를 읽는다. 제목, 소제목(###), 서술, 그리고 파일 끝의 ### 등장 객체 절을 확보한다.
  3. 등장 객체를 두 소스에서 모은다.
    • A: ### 등장 객체 절의 카테고리별 목록(인물/지명/사건/집단/제도/저작 등, 책마다 카테고리 구성이 다를 수 있다).
    • B: ontology/entities.jsonl을 파싱해 각 줄의 points 배열에 해당 번호가 들어있는 엔티티 전체({id, name, type, points, desc, descs}).
    • 두 목록은 어긋난다. 예를 들어 포인트 2는 A(본문)에 인물 8·지명 24·사건 3·집단 6·제도 1·저작 1 = 43개가 있고 B(entities.jsonl)에는 40개가 있는데, 겹치지 않는 항목이 양쪽에 다 있다.
    • A에는 있지만 B의 해당 포인트 목록엔 없는 경우가 두 유형으로 나온다.
      • 언급만 되고 별도 관계로는 안 잡힌 경우: 한니발은 본문에 "하밀카르 바르카스(한니발의 아버지)"로 나오지만, entities.jsonl의 person:한니발 레코드는 points: [3, 4]뿐이고 2는 없다(3·4포인트에서 본격 등장하기 때문).
      • 중의어 링크가 엔티티 자체에 반영 안 된 경우: 본문엔 [[그리스 (지명)|그리스]]와 [[그리스 (집단)|그리스]]가 둘 다 걸려 있지만 place:그리스는 points: [12, 21, 24, 29], group:그리스는 points: [1]이라 어느 쪽도 2를 포함하지 않는다.
    • B에는 있지만 A의 ### 등장 객체에는 카테고리로 안 걸린 경우도 있다: 아그리젠토 포위·마르살라 만 해전·본 곶 전투·탈라모네 전투·팔레르모 탈환 시도 같은 개별 event 엔티티가 그렇다. 본문 "등장 객체 > 사건"엔 메시나 전투·제1차 포에니 전쟁·제2차 포에니 전쟁 3개만 적혀 있다.
    • 합집합을 만들고 각 항목 옆에 [본문] / [entities.jsonl] / [양쪽]을 표시한다. 표시를 생략하면 다음 단계에서 왜 이 항목이 목록에 있는지 아무도 검증할 수 없다.
  4. 합집합의 각 엔티티에 설명을 붙인다. entities.jsonl의 최상위 desc는 그 엔티티가 가장 비중 있게 다뤄진 포인트 기준 요약이라 다른 포인트에서 보면 어긋난다 — 반드시 descs 배열에서 point가 해당 번호와 일치하는 원소의 desc를 쓴다. descs에 해당 포인트가 없으면 entities/<type>/<이름>.md 노트 본문에서 가져온다.
  5. ontology/links.jsonl을 파싱해 point 필드가 해당 번호와 정확히 일치하는 줄만 뽑는다(from, to, rel, 있으면 from_year/to_year/year_basis). 다른 포인트의 관계를 끼워 넣지 않는다.
    • 뽑은 관계의 from/to 엔티티 ID를 3번 합집합과 다시 대조한다. entities.jsonl의 points 배열과 links.jsonl의 point 필드는 서로 다른 소스라 이 둘도 어긋난다 — 예를 들어 person:한니발의 points는 [3, 4]뿐이라 3번 B에서는 빠졌지만, links.jsonl엔 person:한니발 → group:젊은사자들 (member_of)가 point: 2로 버젓이 있다. 이런 항목은 합집합에 [links.jsonl]로 추가한다.
  6. ontology/chronology.csv에서 해당 시기 구간(본문 서술의 연도, 또는 5번에서 뽑은 from_year/to_year)에 걸리는 행을 찾는다. chronology.csv는 82행뿐이라 성긴 연표다 — 구간 안에 행이 하나도 없는 포인트가 흔하다(예: 포인트 2의 기원전 264~241년 구간엔 행이 0개, 앞뒤로 가장 가까운 행은 기원전 509년과 기원전 60년). 없으면 "해당 구간 연표 없음"이라고 명시하고 멀리 떨어진 행을 억지로 끌어오지 않는다.
  7. 필요하면 3~4에서 뽑은 핵심 엔티티(주요 인물·사건 위주, 전체는 아님)의 entities/<type>/<이름>.md 노트를 열어 노트 자체의 ### 관계 절을 5번에서 만든 관계 목록과 대조한다. 둘이 다르면(노트가 더 최근에 갱신됐거나 반대로 낡았을 수 있다) 둘 다 남기고 불일치를 그대로 노출한다 — 임의로 한쪽에 맞춰 지우지 않는다.
  8. 1~7을 아래 출력 형태로 조립한다.

하지 말 것

  • source/·chapters/를 열지 마라. 기번 영문 원전 계통이고 30포인트와 다른 책이다. 온톨로지(entities.jsonl/links.jsonl/chronology.csv)는 30포인트 계통에서만 뽑았다.
  • 본문 위키링크 개수로 등장 객체를 세지 마라. 부분적으로만 박혀 있다(포인트 2 본문엔 위키링크가 91개 있지만 히에론·시라쿠사 같은 이름도 처음 등장 이후엔 링크가 빠진 곳이 있다). 반드시 ### 등장 객체 절 + entities.jsonl의 points 필드 둘 다 확인한다.
  • links.jsonl에 없는 관계를 지어내지 마라. 본문 서술상 그럴듯해 보여도 point가 일치하는 줄이 없으면 "관계 없음"이다.
  • 본문 서술을 그대로 사실로 단정하지 마라. 편역본에 오류가 있다(포인트 1·2 45건 검증에서 심각 6건, 전투 승패가 뒤집힌 곳이 두 장에 하나꼴). 사실 검증이 필요하면 이 스킬이 아니라 fact-check 스킬로 넘긴다.
  • 기원전 연도의 음수 부호를 떨어뜨리지 마라. from_year/to_year/chronology.csv의 year는 기원전이 음수다. 양수로 옮기면 500년이 어긋난다.
  • from_year가 to_year보다 항상 이전이라고 가정하지 마라. 뒤집힌 줄이 실제로 있다(event:제1차포에니전쟁 → place:로마, occurred_at, from_year: -241, to_year: -264 — 전쟁은 기원전 264년에 시작해 241년에 끝났는데 필드 순서는 반대다). 출력할 땐 필드 순서가 아니라 두 값 중 더 이전 연도를 시작으로 표시한다.
  • 합집합 없이 한쪽 소스만 쓰지 마라. 본문만 쓰면 아그리젠토 포위 같은 개별 사건 엔티티가 빠지고, entities.jsonl만 쓰면 한니발처럼 이름만 언급된 인물이 빠진다.
  • 다른 포인트에서 본 카테고리 구성을 그대로 기대하지 마라. ### 등장 객체의 카테고리(인물/지명/사건/집단/제도/저작 등)는 포인트마다 다르게 구성될 수 있다 — 포인트 2에 있는 카테고리가 다른 포인트엔 없거나 새 카테고리가 추가될 수 있으니 그 포인트 파일에 실제로 적힌 대로 따른다.

출력 형태

아래는 포인트 2를 예로 든 뼈대다. 실제 산출물은 이 구조를 그대로 채운다.
# 포인트 2 — 제1차 포에니 전쟁
​
## 본문
(points/02_제1차_포에니_전쟁.md 전문, 소제목 포함)
​
## 등장 객체 (합집합, 출처 표시)
- 인물
- 히에론 [양쪽] — 시라쿠사의 왕. 마메르티니 용병대를 토벌하려 메시나를 포위했으며 후에 로마와 부전동맹을 맺음.
- 하밀카르 바르카스 [양쪽] — 한니발의 아버지. 카르타고 군 총사령관으로 로마의 보급기지를 공격함.
- 한니발 [본문 + links.jsonl] — `entities.jsonl`의 `points`는 `[3, 4]`뿐이라 B엔 없지만, `links.jsonl`엔 `person:한니발 → group:젊은사자들 (member_of)`가 point 2로 있음
- 마고 [entities.jsonl] — 본문 "등장 객체 > 인물" 목록엔 미기재
- 레굴루스 [양쪽] — 로마의 지휘관. 카르타고의 강화 제의를 일축했으나 본 곶 전투에서 패해 포로가 되었고 고문 끝에 살해됨.
- 크산티포스 [양쪽] — 스파르타 출신의 그리스인 장군. 카르타고를 구하기 위해 기병대와 코끼리 부대를 이용한 새로운 전술을 고안함.
- 지명
- 시라쿠사 [양쪽], 카르타고 [양쪽], 메시나 [양쪽] ...
- 그리스 [본문] — `place:그리스`(points 12,21,24,29)·`group:그리스`(points 1) 모두 2를 포함하지 않음. 본문엔 지명·집단 두 중의어 링크로만 존재
- 스파르타 [entities.jsonl] — 본문 "등장 객체 > 지명" 목록엔 미기재(크산티포스의 출신지로만 서술)
- 사건
- 메시나 전투 [양쪽], 제1차 포에니 전쟁 [양쪽]
- 아그리젠토 포위 / 마르살라 만 해전 / 본 곶 전투 / 탈라모네 전투 / 팔레르모 탈환 시도 [entities.jsonl] — 본문 "등장 객체 > 사건"엔 미기재, 서술로만 존재
- 집단 / 제도 / 저작
- 마메르티니 [양쪽], 백인대회 [양쪽], 까마귀 전함 [양쪽]
​
## 관계 (links.jsonl point=2, 총 43건)
- 클라우디우스 풀케르 → 메시나 전투 (participated_in, 기원전 264년, year_basis: text)
- 한노 → 카르타고 (ruled)
- 히에론 → 시라쿠사 (ruled)
- 히에론 → 로마 (allied_with)
- 하밀카르 바르카스 → 카르타고 (ruled)
- 하밀카르 바르카스 → 로마 (opposed)
- 크산티포스 → 카르타고 (ruled)
- 하스드루발 → 카르타고 (ruled)
- 하스드루발 → 하밀카르 바르카스 (succeeded)
- 제1차 포에니 전쟁 → 로마 (occurred_at, 기원전 264~241년)
- 제1차 포에니 전쟁 → 카르타고 (occurred_at, 기원전 264~241년)
- 로마 → 시칠리아 (ruled, 기원전 241년~)
- 로마 → 사르데냐 섬 (ruled, 기원전 241년~)
- 마메르티니 → 메시나 (ruled)
- 마메르티니 → 로마 (allied_with, 기원전 264년)
- 하밀카르 바르카스 → 마르살라 만 해전 (participated_in, 기원전 256년)
- 레굴루스 → 본 곶 전투 (participated_in, 기원전 255년)
- 하스드루발 → 팔레르모 탈환 시도 (participated_in, 기원전 250년, year_basis: inferred)
- 한니발 → 젊은 사자들 (member_of)
- 하밀카르 바르카스 → 젊은 사자들 (created)
(총 43건 중 발췌. year_basis가 `chronology`/`inferred`인 줄은 연도가 본문 직접 서술이 아니라 보강 추정임을 표시한다)
​
## 시대 배경 (chronology.csv)
해당 구간(기원전 264~241년) 연표 없음. 가장 가까운 행: 기원전 509년(왕정 폐지·공화제 시작), 기원전 60년(제1차 삼두정치) — 참고용으로만 표시, 포인트 본문과 직접 연결되지 않음.
이 블록이 산출물 전체다. 앞뒤에 "여기 정리했습니다" 류의 안내문을 덧붙이지 않는다 — 그대로 복사해 다른 AI의 입력창에 붙여넣을 수 있어야 한다. entities/<type>/<이름>.md 노트와 대조해 불일치가 나온 항목이 있으면 "등장 객체" 또는 "관계" 절 해당 줄 옆에 (노트와 불일치: ...)로 짧게 덧붙인다.

인물·장소·사건 한 건 모아 보기

entity-lookup
이름 하나를 받아 그 대상에 관한 모든 것을 모읍니다.
1 넣는 것찾을 이름 하나
2 시키는 것
이렇게 말하면 됩니다
카이사르 정보 줘
3 나오는 것
종류·별칭·등장 포인트·속성·포인트별 서술·관계 전체·노트 위치가 나옵니다.

언제 쓰는가

이름 하나를 받아서 "이게 누구/어디/무슨 사건인가"를 온톨로지 전체(entities.jsonl + links.jsonl + entities/<type>/<이름>.md 노트)에서 긁어모아 한 덩어리로 조립할 때. point-context는 포인트 하나 안의 모든 등장 객체를 넓게 훑고, relation-query는 관계 조건으로 여러 링크를 가로질러 찾는다. 이 스킬은 그 중간이 아니라 객체 하나를 깊게 판다 — 이름을 입력하면 그 객체에 관한 모든 것이 나온다.
한 번 호출에 객체 1개만 다룬다. 여러 객체를 비교하려면 각각 호출해 나란히 둔다.

입력

  • 객체 이름 1개. 자연어("카이사르", "이 사람 누구야")도 이름으로 정규화해 찾는다.
  • 레포 경로는 고정: ~/project/active/decline-and-fall-of-the-roman-empire/ (읽기 전용으로 다룬다).

절차

  1. ontology/entities.jsonl을 name 필드로 grep한다. 정확히 일치하는 게 없으면 aliases 배열도 훑는다(예: "Julius Caesar"로 찾으면 aliases에 그 값이 들어있는 person:카이사르가 잡힌다).
  2. 결과가 여러 건이면 절대 하나로 합치지 않는다. 이름이 같아도 다른 사람인 경우가 실제 있다 — person:카이사르(율리우스 카이사르, points: [5,6,7,8,9,10,13,14])와 person:가이우스카이사르(율리아와 아그리파의 아들, points: [10]뿐, desc: "요절했다")는 이름이 겹치지만 완전히 다른 인물이다. 여러 건이 잡히면 각각 별도 블록으로 출력하고 어느 쪽을 원하는지 확인받는다.
  3. 찾은 레코드의 desc(대표 한 줄)와 descs 배열(포인트별 {point, desc})을 전부 가져온다. desc 하나만 쓰지 마라 — 여러 포인트에 걸친 객체는 국면별 서술이 다르고, 같은 포인트 번호에 desc가 두 개 붙는 경우도 실제 있다(person:카이사르의 descs에 point: 14가 두 번 나온다. 하나는 "무료 배부 대상을 15만 명으로 정한" 얘기, 하나는 "전차 경주 관보에 시합 예상을 실은" 얘기 — 같은 포인트 안에서도 서로 다른 사실이다).
  4. attrs를 그대로 옮긴다. 타입마다 필드 구성이 다르다(person은 role/period/birth/death 등, event는 period/participants 등, place는 좌표가 없다 — 6번 참고).
  5. ontology/links.jsonl을 from과 to 양쪽으로 grep해 관계를 전부 모은다. 단방향 기록이라 한쪽만 보면 절반을 놓친다.
  6. entities/<type>/<이름>.md 노트를 연다.
    • 이름이 다른 타입과 겹치면 파일명에 접미사가 붙는다(entities/place/그리스 (지명).md vs entities/group/그리스 (집단).md). 접미사 없이 찾다 실패해도 "노트 없음"으로 단정하지 말고 접미사 붙은 파일명을 시도한다.
    • 노트에 ### 관계 절이 있으면(전체 642개 중 394개가 이 절을 갖는다) backlink 스크립트가 links.jsonl 양방향을 미리 합쳐놓은 결과다. 5번에서 만든 목록과 대조한다 — 둘은 다른 소스라 어긋날 수 있고, 어긋나면 임의로 한쪽을 지우지 말고 둘 다 보여준다.
    • place 타입이면 좌표는 노트 frontmatter의 location: [lat, lon]에 있다. entities.jsonl의 attrs에는 좌표가 없다 — geo 스크립트가 노트에만 붙이기 때문이다. 좌표가 필요하면 반드시 노트를 열어야 한다. person·event 등 place가 아닌 타입에는 좌표 자체가 없다.
  7. 1~6을 아래 출력 형태로 조립한다.

하지 말 것

  • 여러 결과가 나왔는데 임의로 하나를 골라 합치지 마라. 동명이인을 설명 유사도나 alias 겹침으로 판정하지 마라 — 실제 검증에서 12쌍 중 4쌍이 서로 다른 인물이었다. 확신이 안 서면 갈라서 보여주고 사람에게 넘긴다.
  • desc 하나만 보고 끝내지 마라. descs 배열 전체를 봐야 한다. 같은 포인트 번호에 서로 다른 desc가 두 개 붙는 경우도 있다(카이사르 point 14).
  • links.jsonl을 from만 또는 to만으로 grep하지 마라. 단방향 기록이다 — 예를 들어 person:카이사르를 from으로만 걸면 관계 일부(폼페이우스와의 opposed처럼 상대가 from인 줄)를 놓친다.
  • place가 아닌 타입에 좌표를 지어 붙이지 마라. 좌표는 place 노트의 location frontmatter에만 있고 entities.jsonl에는 없다.
  • 노트의 ### 관계 절을 무조건 정답으로 취급하지 마라. entities/*.md는 ontology/_scripts/rome30_*.py의 재생성 산출물이라 links.jsonl과 시점이 어긋날 수 있다. 어긋나면 둘 다 노출한다.
  • entities/<type>/<이름>.md가 그 이름 그대로 안 열린다고 노트가 없다고 단정하지 마라. 타입이 겹치는 동명 객체는 파일명에 (지명)·(집단) 같은 접미사가 붙는다.
  • links.jsonl·entities.jsonl에 없는 관계·연도·서술을 "아마 이랬을 것"으로 채우지 마라. 없으면 없는 것이다.

출력 형태

아래는 카이사르를 예로 든 뼈대다. 실제 산출물은 이 구조를 그대로 채운다(관계는 발췌 — 전체는 entities.jsonl/links.jsonl 참고).
# person:카이사르 (카이사르)
​
## 기본
- 타입: person
- 별칭: Caesar, Gaius Julius Caesar, Julius Caesar, 가이우스 율리우스 카이사르, 율리우스 카이사르
- 등장 포인트: 5, 6, 7, 8, 9, 10, 13, 14
- attrs: birth -100 · death -44 · role general · period 공화정 말기 · title 갈리아 총독
​
## 대표 설명 (desc)
스물일곱 살에 원로원으로부터 추방당했으나 군중의 지지로 복권되었다. 천재지만 두통과
간질을 앓았고, 여성들에게 인기가 많았으며 동성애자이기도 했던 것으로 추측된다.
​
## 포인트별 서술 (descs)
- 5. 기원전 100년 태어났으며 당대 로마의 정치인과 군인. 스파르타쿠스 반란 시대를 경험했고
카틸리나 사건에서 민중파를 옹호한 청년 귀족.
- 6. 스물일곱 살에 원로원으로부터 추방당했으나 군중의 지지로 복권되었다. (이하 대표 설명과 동일)
- 7. 갈리아를 정복하고 루비콘 강을 건넌 로마의 위대한 장군. 클레오파트라와 연애하여 이집트
정복을 이루었다.
- 8. 로마의 최고 지위에 오른 독재관. 왕권을 손에 넣으려 했으나 원로원 의원들에 의해 암살되었다.
- 9. 로마의 독재관. 옥타비아누스의 양부이자 안토니우스의 상관이었다.
- 10. 아우구스투스가 완성한 대규모 사회 개혁의 시작자.
- 13. 이집트의 알렉산드리아에서 포위당했을 때 안티파테르의 지원군으로 도움을 받은 로마 인물.
후에 암살당했다.
- 14. 로마 정치가. 무료 배부 대상을 15만 명으로 정한 인물.
- 14. (같은 포인트, 다른 desc) 전차 경주의 인기를 부추기기 위해 관보에 시합 예상과 전평을
실은 인물.
​
## 관계 (links.jsonl, from/to 양방향, 총 42건 중 발췌)
- 카이사르 --member_of--> 민중파 (point 5)
- 술라 --opposed--> 카이사르 (point 5)
- 카이사르 --allied_with--> 폼페이우스 (point 6, 기원전 60~49년)
- 카이사르 --allied_with--> 크라수스 (point 6, 기원전 60~53년)
- 카이사르 --ruled--> 갈리아 (point 6, 기원전 58~50년)
- 카이사르 --opposed--> 폼페이우스 (point 7, 기원전 49~48년)
- 브루투스 --opposed--> 카이사르 (point 7)
- 카이사르 --allied_with--> 클레오파트라7세 (point 7, 기원전 48~47년)
- 카이사르 --occurred_at--> 루비콘강 (point 7, 기원전 49년)
- 카이사르 --opposed--> 폼페이우스 (point 8, 기원전 49~45년)
- 카시우스 --opposed--> 카이사르 (point 8, 기원전 44년)
- 카이사르 --child_of--> 율리아카이사르가문 (point 6)
​
(entities.jsonl 대조: 12번 줄 "안쿠스마르키우스 --child_of--> 카이사르"처럼 데이터 자체가
의심스러운 줄도 있다 — 안쿠스 마르키우스는 로마 왕정기 4대 왕으로 카이사르보다 수백 년
앞선 인물이다. links.jsonl에 있는 그대로 보고하되, 이런 항목은 있는 그대로일 뿐 검증된
사실이라고 단정하지 않는다.)
​
## 노트
- 경로: entities/person/카이사르.md
- `### 관계` 절 있음 (노트-links.jsonl 대조 결과: 일치. 노트는 방향별로 묶어서 보여준다 —
"폼페이우스 ← 적대 *기원전 49~기원전 45*"(폼페이우스가 카이사르를 적대, point 8)
+ "적대 → 폼페이우스 *기원전 49~기원전 45*"(point 8)와 "적대 → 폼페이우스
*기원전 49~기원전 48*"(point 7) 셋으로, links.jsonl의 opposed 3줄과 정확히 대응한다)
- 좌표: 해당 없음 (person 타입 — 좌표는 place 노트의 `location` frontmatter에만 있다)
이 블록이 산출물 전체다. 앞뒤에 "여기 정리했습니다" 류의 안내문을 덧붙이지 않는다. 여러 건이 잡혀 동명이인 확인이 필요하면 위 구조 대신 "N건 발견, 어느 쪽?" + 각 건의 id·desc·points 3줄 요약만 먼저 낸다.

관계 조건으로 찾기

relation-query
조건을 걸어 해당하는 관계를 전부 뽑습니다.
1 넣는 것관계 종류·상대·연도 구간·포인트 번호 중 조건 하나 이상
2 시키는 것
이렇게 말하면 됩니다
카이사르가 누구랑 동맹이었어
3 나오는 것
누가 누구에게 어떤 관계로 몇 년에 걸쳤는지가 목록으로 나옵니다.

언제 쓰는가

관계 조건 — rel 종류, 상대 객체, 연도 구간, 포인트 번호 중 하나 이상의 조합 — 으로 ontology/links.jsonl을 걸러 여러 관계를 뽑을 때. "카이사르가 누구와 동맹이었나", "기원전 250~200년 사이 opposed 관계 전부", "이 인물이 참전한 사건 목록" 같은 질의가 대상이다.
entity-lookup과 다르다: entity-lookup은 객체 하나의 설명·좌표·노트까지 포함한 전모를 파는 것이고, 관계는 그 일부다. 이 스킬은 관계 자체가 질문의 중심이고 결과가 여러 줄(여러 객체 쌍)로 나온다. 대상 객체 하나의 전모가 궁금하면 entity-lookup을 먼저 쓴다.

입력

  • 조건 최소 하나: rel 종류 / 대상 객체(from 또는 to로 등장) / 연도 구간 / 포인트 번호.
  • 레포 경로는 고정: ~/project/active/decline-and-fall-of-the-roman-empire/ (읽기 전용으로 다룬다).

절차

  1. rel 필터가 있으면 실제 쓰이는 rel인지 먼저 확인한다. 기존 11종(2026-08-13 기준 links.jsonl 건수): opposed 124 · ruled 106 · participated_in 94 · member_of 81 · allied_with 81 · occurred_at 67 · child_of 57 · succeeded 25 · created 21 · conquered 9 · married 5. 신설 5종(located_in · protected · held_office · decided · applied_to)은 2026-08-13 확정됐지만 아직 links.jsonl에 0건이다 — 마이그레이션 전이라 이 rel로 걸면 무조건 0건이 나오고, 그건 정상이다. 5번을 참고해 오용분과 함께 보고한다.
  2. 상대 객체가 이름으로 주어지면 entities.jsonl에서 id를 먼저 확정한다. 동명이인이 있을 수 있다(entity-lookup 스킬의 2번 절차와 동일 — 이름이 같아도 다른 타입·다른 사람일 수 있으니 결과가 여러 건이면 어느 쪽인지 확인받는다).
  3. links.jsonl을 파싱해 조건에 맞는 줄을 뽑는다. 대상 객체 조건이 있으면 from과 to 양쪽 다 검사한다 — 단방향 기록이라 한쪽만 걸면 절반을 놓친다. 예: person:카이사르가 관련된 opposed 관계는 from으로만 걸면 카이사르가 상대를 적대한 줄만 나오고, 술라 → 카이사르·브루투스 → 카이사르처럼 카이사르가 to인 줄(상대가 카이사르를 적대한 줄)이 빠진다.
  4. 연도 필터가 있으면 from_year/to_year 부호를 그대로 쓴다 — 기원전 음수, 서기 양수. from_year가 to_year보다 항상 이르다고 가정하지 마라. 뒤집힌 줄이 실제 있다(event:제1차포에니전쟁 → place:로마, occurred_at, from_year: -241, to_year: -264 — 전쟁은 기원전 264년에 시작해 241년에 끝났는데 필드 순서는 반대). 연도로 거를 땐 두 값 중 이른 연도를 시작으로 삼는다.
  5. rel 오용을 함께 살핀다. 신설 rel이 아직 없는 자리는 기존 rel이 대신 떠맡고 있다.
    • located_in이 필요해 보이는 질의(인물·집단의 소재/체류)인데 결과가 0건이면, occurred_at인데 from이 event가 아니라 인물·장소인 줄(37건)과 인물→지명 participated_in인데 to가 event가 아니라 place인 줄(27건)을 대신 뽑아 보여준다. 실제 있는 줄: person:카이사르 → place:루비콘강 (occurred_at, point 7), person:한니발 → place:알프스 (participated_in, point 3).
    • 결과를 낼 땐 "정식 매칭 N건"과 "마이그레이션 전 오용분 M건(어느 rel에 있는지 명시)"을 나눠 표시한다. 섞어서 하나의 목록으로 내지 않는다.
  6. rel이 married 또는 succeeded를 묻는 질의라면 주의한다. 원본 추출 과정에서 혼인 관계가 succeeded로 잘못 담긴 이력이 실제 있었다(카이사르 가문 등 — 지금은 족보 스크립트가 수작업 목록으로 바로잡았다). 결과가 의심스러우면(예: 배우자 관계인데 rel이 succeeded로 나옴) entities/<type>/<이름>.md 노트의 ### 관계 절과 대조해 이상 여부를 알린다.
  7. 결과의 from/to는 id(person:카이사르 등)뿐이니 entities.jsonl에서 name을 붙여 사람이 읽을 수 있게 만든다. 이때도 from/to가 entities.jsonl에 실재하는지 대조한다.
  8. 1~7을 아래 출력 형태로 조립한다.

하지 말 것

  • rel 조건을 from만 또는 to만으로 걸지 마라. 단방향 기록이다 — 양쪽 다 훑어야 상대와의 관계 전체가 나온다.
  • 신설 5종 rel로 조회해 0건이 나왔다고 "그런 관계 없음"으로 끝내지 마라. 아직 마이그레이션 전이라 데이터가 없는 것뿐이다. located_in은 occurred_at(from이 인물·집단인 37건)과 인물→지명 participated_in(27건)에 오용 형태로 이미 있다 — 나머지 4종(protected·held_office·decided·applied_to)은 대응하는 오용 패턴이 아직 파악되지 않았다는 점도 그대로 보고하고, 지어내지 마라.
  • from_year가 항상 to_year보다 이르다고 가정하고 연도 필터링을 하지 마라. 뒤집힌 줄이 있다.
  • rel 필드를 항상 정답으로 믿지 마라. married가 succeeded로 잘못 들어간 이력처럼 추출 단계 오류가 있었다. 결과가 의심스러우면 노트의 ### 관계 절과 대조하되, 대조 없이 rel을 임의로 고쳐 보고하지도 마라 — 원본 값과 의심 사유를 같이 낸다.
  • entities.jsonl에 없는 id를 결과에 넣지 마라. from/to를 대조해 실재를 확인한다.
  • 조건에 맞는 줄이 없는데 "아마 있었을 것"으로 채우지 마라. 없으면 없는 것이다.

출력 형태

아래는 "person:카이사르가 관련된 opposed 관계 전부"를 예로 든 뼈대다. 실제 값은 grep 결과를 그대로 옮긴 것이다.
# 관계 조회 — person:카이사르, rel=opposed (양방향)
​
## 조건
- rel: opposed
- 대상 객체: person:카이사르 (from 또는 to)
​
## 결과 (links.jsonl, 12건)
- 술라 --opposed--> 카이사르 (point 5)
- 소카토 --opposed--> 카이사르 (point 6)
- 카이사르 --opposed--> 폼페이우스 (point 7, 기원전 49~48년, year_basis: text)
- 브루투스 --opposed--> 카이사르 (point 7)
- 렌툴루스 --opposed--> 카이사르 (point 7, 연도 없음)
- 카이사르 --opposed--> 폼페이우스 (point 8, 기원전 49~45년, year_basis: chronology)
- 브루투스 --opposed--> 카이사르 (point 8, 기원전 44년)
- 카시우스 --opposed--> 카이사르 (point 8, 기원전 44년)
- 폼페이우스 --opposed--> 카이사르 (point 8, 기원전 49~45년, year_basis: chronology)
- 스푸린나 --opposed--> 카이사르 (point 8, 연도 없음)
- 카스카 --opposed--> 카이사르 (point 8, 기원전 44년)
- 키케로 --opposed--> 카이사르 (point 8, 연도 없음)
​
## 비고
- 폼페이우스와의 관계가 세 줄이다: point 7은 "카이사르→폼페이우스"만 한 방향(기원전
49~48년), point 8은 같은 기원전 49~45년 구간을 "카이사르→폼페이우스"·"폼페이우스→카이사르"
양방향으로 중복 기록했다. 중복으로 보고 임의로 하나를 지우지 말고 셋 다 낸다 — 포인트별
서술 시점 차이와 방향 중복이 섞여 있다는 걸 그대로 보여준다.
- `located_in`류 오용분 없음(이 조회는 `opposed` 고정이라 5번 절차 대상 아님).
이 블록이 산출물 전체다. 여러 조건을 동시에 걸었으면 "조건" 절에 전부 나열하고, rel 필터가 신설 5종이라 0건 + 오용분을 같이 낸 경우엔 "결과" 절을 "정식 매칭"과 "마이그레이션 전 오용분"으로 나눠 두 개의 하위 목록으로 낸다.

연도 구간으로 잘라 보기

timeline-slice
연도 구간에 걸리는 사건·인물·관계를 뽑습니다.
1 넣는 것연도 구간 하나 — 기원전인지 서기인지 밝혀 주세요
2 시키는 것
이렇게 말하면 됩니다
기원전 264~241년 사건 정리해줘
3 나오는 것
구간에 걸리는 관계 목록과 연표가 나오고 연도 없어 빠진 건수도 밝힙니다.

언제 쓰는가

팀원이 연대 구간 하나(예: 기원전 264~241년, 또는 서기 306~324년)를 놓고 그 시기에 무슨 사건이 있었고 누가 누구와 어떤 관계였는지 알고 싶을 때. links.jsonl의 연도 필드는 부호·역전·결측이 전부 함정이라, 링크만 던지면 AI가 기원전 부호를 놓치거나 연도 없는 행을 조용히 빼먹은 채로 결과를 내놓는다. 이 스킬은 구간을 안전하게 정규화하고, 겹치는 링크를 빠짐없이 걸러내고, 무엇을 왜 뺐는지까지 명시해 결과를 조립한다.
한 번 호출에 구간 하나만 다룬다. 여러 구간을 비교하거나 인물 한 명의 전체 생애를 훑는 작업은 포인트별로 나눠 호출하거나 point-context를 대신 쓴다.

입력

  • 연도 구간 1개. 시작·끝을 자연어로 받되("기원전 264년부터 241년까지", "서기 306~324년"), 기원전/서기 여부를 반드시 확정한다. 구간이 기원전·서기에 걸치면(예: 기원전 44년~서기 14년) 양끝을 각각 부호 있는 정수로 바꾼 뒤 다룬다.
  • 선택: 포인트 번호로 결과를 좁히기(point-context와 달리 이 스킬은 기본적으로 포인트를 가로지른다).
  • 레포 경로는 고정: ~/project/active/decline-and-fall-of-the-roman-empire/ (읽기 전용으로 다룬다).

절차

  1. 구간을 부호 있는 정수로 정규화한다. 기원전은 음수, 서기는 양수. 변환하고 나면 오름차순 정렬이 곧 시간순이다(-753이 -218보다 253년 앞선다는 뜻과 일치한다). 함정은 변환 그 자체다 — 사람은 "기원전 264년부터 241년까지"를 264→241 순서로 말하지만 데이터는 -264~-241이고, 부호를 빼먹으면 서기 264~241년(존재하지 않는 순서)이 되어 500년 넘게 어긋난다. 정규화한 뒤 start = min(두 값), end = max(두 값)으로 확정한다.
  2. 역전 행 여부를 전체 스캔으로 확인한다. from_year > to_year인 줄이 있는지 links.jsonl 670줄 전체를 훑는다. 2026-08-12 이전에는 제1차 포에니 전쟁 관련 2건(event:제1차포에니전쟁 → place:로마/카르타고, occurred_at)이 from_year: -241, to_year: -264로 뒤집혀 있었다 — 전쟁은 기원전 264년에 시작해 241년에 끝났는데 필드 순서가 반대였다. ontology/_scripts/rome30_time.py가 _time/years_*.jsonl을 병합할 때 to_year < from_year면 자동으로 스왑하는 로직이 있어(if ty is not None and ty < fy: fy, ty = ty, fy) 2026-08-12에 이걸로 교정됐다. 지금(2026-08-13) 시점 스캔 결과는 역전 0건이지만, propose-change로 사람이 손으로 병합하는 새 링크는 이 자동 보정을 거치지 않으므로 매번 다시 확인한다. 값을 읽을 때도 필드 순서를 믿지 말고 min/max로 시작·끝을 계산한다.
  3. links.jsonl을 파싱해 구간과 겹치는 행을 고른다. 각 줄에서 from_year·to_year를 .get()으로 읽는다(딕셔너리 키 자체가 없는 줄이 있다 — 3번 항목 참고). 둘 다 있으면 구간, to_year만 없으면 그 해 하루짜리 사건(단일 시점)으로 본다. 겹침 판정은 row_start <= end and row_end >= start(양끝 다 min/max로 정리한 뒤).
  4. 연도가 아예 없는 행을 별도로 집계하고, 뺐다는 사실을 결과에 명시한다. year_basis가 null인 행이 670건 중 367건이고, 이 367건은 from_year·to_year 키 자체가 없다(값이 null이 아니라 키가 없다 — 확인 완료). 이 행들은 구간 질의에서 정의상 걸러지지만, "구간 안 관계 N건" 옆에 "연도 미상이라 제외된 행 367건(전체의 55%)"을 항상 붙인다. 조용히 빠지면 구간에 사건이 없는 것처럼 보이는데 실은 연도 표기가 안 된 것뿐일 수 있다.
  5. 매치된 행마다 year_basis로 신뢰도를 표시한다. chronology(206건, 연표 기반) / text(84건, 본문 직접 서술) / inferred(13건, 추정) 셋 중 하나다. inferred로 매치된 행은 결과에 [추정 — 확인 필요]를 붙인다 — 발표·집필에 그대로 쓰기 전에 사람 확인이 필요하다는 뜻이다.
  6. ontology/chronology.csv에서 같은 구간에 걸리는 행을 대조한다. 82행, 기원전 753년(로마 건국)부터 서기 1453년(콘스탄티노플 함락)까지 연도 오름차순으로 정렬돼 있다. 구간 안에 행이 없으면 "해당 구간 연표 없음"이라 명시하고 가장 가까운 앞뒤 행을 참고용으로만 붙인다. chronology.csv와 links.jsonl이 같은 사건에 다른 연도를 붙였으면 임의로 하나를 고르지 말고 둘 다 보고한다.
  7. 매치된 행의 from/to id를 entities.jsonl에서 이름·설명으로 풀어낸다. id가 entities.jsonl에 없으면(끊어진 링크) 결과에 [끊어진 링크]로 표시하고 넘어간다 — 있는 것처럼 지어내지 않는다.
  8. 1~7을 아래 출력 형태로 조립한다.

하지 말 것

  • 기원전 연도의 음수 부호를 떨어뜨리지 마라. 500년 넘게 어긋난 구간이 나온다.
  • from_year가 항상 to_year보다 이전이라고 가정하지 마라. 역전 행이 실제로 있었다(2026-08-12 교정 전). 필드 순서가 아니라 min/max로 시작·끝을 계산한다.
  • from_year·to_year를 딕셔너리 인덱싱(row["from_year"])으로 바로 읽지 마라. 키 자체가 없는 줄이 367건 있어 KeyError가 난다. .get()을 쓴다.
  • year_basis가 null인 367건을 결과에서 조용히 빼지 마라. 뺐다는 사실과 건수를 항상 명시한다.
  • inferred(13건) 연도를 chronology·text 연도와 같은 신뢰도로 취급하지 마라. 발표 전 확인이 필요하다고 표시한다.
  • chronology.csv와 links.jsonl의 연도가 다르면 둘 중 하나를 임의로 정답으로 고르지 마라. 둘 다 보고한다.
  • 구간을 포인트 하나로 좁혀서 생각하지 마라. 이 스킬은 기본적으로 포인트를 가로지른다 — 예를 들어 기원전 264~241년 구간에는 포인트 2(제1차 포에니 전쟁)뿐 아니라 포인트 3에 속한 아르키메데스 → 시라쿠사 (ruled, 기원전 287~212년)처럼 재위 기간이 겹치는 다른 포인트의 행도 걸린다. 사용자가 포인트로 좁혀 달라고 하지 않는 한 걸러내지 않는다.
  • entities.jsonl·links.jsonl을 고치지 마라. 이 스킬은 읽기 전용이다. 오류를 발견하면(예: 연도 역전) 고치자고 제안하는 데 그치고, 실제 수정은 propose-change로 넘긴다.
  • 링크가 없는데 구간 서술이 그럴듯하다고 관계를 지어내지 마라. links.jsonl에 없으면 "관계 없음"이다.

출력 형태

아래는 기원전 264~241년(제1차 포에니 전쟁) 구간을 예로 든 실제 결과다.
# 구간 기원전 264년~241년
​
## 관계 (links.jsonl 겹침 판정, 19건 매치 / 연도 미상 제외 367건)
​
- 클라우디우스 풀케르 --participated_in--> 메시나 전투 [p2, 기원전 264년, text]
- 제1차 포에니 전쟁 --occurred_at--> 로마 [p2, 기원전 264~241년, text]
- 제1차 포에니 전쟁 --occurred_at--> 카르타고 [p2, 기원전 264~241년, text]
- 메시나 전투 --occurred_at--> 메시나 [p2, 기원전 264년, text]
- 마메르티니 --allied_with--> 로마 [p2, 기원전 264년, text]
- 아그리젠토 포위 전투 --occurred_at--> 아그리젠토 시 [p2, 기원전 262년, inferred] [추정 — 확인 필요]
- 마르살라 만 해전 --occurred_at--> 마르살라 만 [p2, 기원전 256년, text]
- 하밀카르 바르카스 --participated_in--> 마르살라 만 해전 [p2, 기원전 256년, text]
- 로마 군 --participated_in--> 마르살라 만 해전 [p2, 기원전 256년, text]
- 본 곶 전투 --occurred_at--> 본 곶 [p2, 기원전 255년, text]
- 레굴루스 --participated_in--> 본 곶 전투 [p2, 기원전 255년, text]
- 크산티포스 --participated_in--> 본 곶 전투 [p2, 기원전 255년, text]
- 팔레르모 탈환 시도 전투 --occurred_at--> 팔레르모 [p2, 기원전 250년, inferred] [추정 — 확인 필요]
- 하스드루발 --participated_in--> 팔레르모 탈환 시도 전투 [p2, 기원전 250년, inferred] [추정 — 확인 필요]
- 로마 군 --participated_in--> 팔레르모 탈환 시도 전투 [p2, 기원전 250년, inferred] [추정 — 확인 필요]
- 로마 --ruled--> 시칠리아 [p2, 기원전 241년~, text]
- 로마 --ruled--> 사르데냐 섬 [p2, 기원전 241년~, text]
- 로마 --ruled--> 코르시카 [p2, 기원전 241년~, text]
- 아르키메데스 --ruled--> 시라쿠사 [p3, 기원전 287~212년, chronology] (포인트 3 소속 — 재위 기간이 이 구간과 겹쳐 포함됨)
​
(구간 안 연도 미상 제외 367건은 전체 링크의 55%. 이 구간에 국한된 수치가 아니라 데이터셋 전체 결측 비율이다.)
​
## 연표 (chronology.csv)
해당 구간(기원전 264~241년) 연표 없음. 가장 가까운 행: 기원전 509년(왕정 폐지·공화제 시작), 기원전 60년(제1차 삼두정치) — 참고용으로만 표시.
이 블록이 산출물 전체다. 앞뒤에 "여기 정리했습니다" 류의 안내문을 덧붙이지 않는다.

같은 이름의 두 인물 구별하기

homonym-check
이름이 같은 두 인물이 같은 사람인지 판정합니다.
1 넣는 것의심되는 인물 이름 둘
2 시키는 것
이렇게 말하면 됩니다
이거 동명이인이야?
3 나오는 것
후보 쌍·대조 근거·판정·확신도가 나오고 갈리지 않으면 보류로 남깁니다.

언제 쓰는가

entities.jsonl에 새 인물을 등록하려 하거나, 이미 있는 두 인물이 같은 사람인지 의심될 때. 로마 인명은 씨족명(gens)·가문명(cognomen)이 세대를 넘어 반복되므로 동명이 구조적으로 많다 — person 타입 262개 중 이름 완전일치·부분문자열·별칭 겹침 세 신호로만 걸러도 후보 쌍이 37개 나온다(아래 「실측」). propose-change 절차 3단계("대상이 이미 있는지 확인한다")에서 동명 후보가 걸리면 이 스킬로 넘어와 판정하고, 판정이 끝나면 다시 propose-change로 돌아간다.

실측 (2026-08-13, entities.jsonl 642줄 기준)

  • person 262명 중 이름 완전일치: 0쌍
  • 부분문자열(한쪽 이름이 다른 쪽에 포함, 2자 이상): 34쌍
  • 별칭이 서로 겹치는 쌍(같은 alias를 두 인물이 씀): 4쌍 — 예: Gaius를 가이우스 카이사르와 칼리굴라가 같이 씀
  • 별칭이 다른 인물의 이름과 같은 쌍: 2쌍
  • 위 세 신호를 합쳐 중복 제거하면 37개 후보 쌍
  • 이 중 points 배열이 완전히 겹치지 않는 쌍: 24/37 — 다른 장(포인트)에서만 등장한다는 뜻이지, 다른 시대라는 확정 신호는 아니다(한 인물이 여러 포인트에 걸쳐 나오되 서술이 point마다 나뉘어 있을 수도 있다 — 카이사르가 그 예다)
  • 참고: 이름이 완전히 같은데 타입이 다른 경우가 5건 있다(그리스·기독교·사산조 페르시아·아리우스파·유다) — 이건 불변식 1번(파일명에 (지명)류 접미사)으로 이미 해결된 케이스라 이 스킬의 판정 대상이 아니다

입력

  • 후보 인물 쌍(또는 집합) — 이름이나 id
  • ontology/entities.jsonl 읽기 접근 — 특히 descs(포인트별 서술 배열)
  • points/*.md 원문 읽기 접근 — entities.jsonl의 desc만으로 안 갈리면 원문으로 내려가야 한다(아래 절차 3 참고)

절차

  1. 후보 쌍을 모은다. 이름 완전일치·부분문자열·aliases 겹침 세 가지로 grep한다. aliases 겹침은 신호일 뿐 증거가 아니다 — propose-change 스킬 문서 자체가 "실제 검증한 12쌍 중 4쌍이 서로 다른 인물이었다"고 기록해 두었다.
  2. descs 배열을 point별로 나란히 놓는다. desc(대표 한 줄) 말고 descs(포인트별 {point, desc})를 봐야 한다. 대표 한 줄은 한 국면만 담아 오판을 부른다.
  3. 갈리지 않으면 points/*.md 원문으로 내려간다. entities.jsonl의 desc는 원문을 요약한 것이라 결정적 단서(괄호 안 다른 인물명, "~의 자손", "~와 달리" 같은 명시적 구분어)가 잘려 있을 수 있다. 원문에서 그 인물이 처음 등장하는 문장을 찾아 위키링크 표시 텍스트와 괄호 설명을 확인한다.
  4. 결정적 신호 두 가지를 우선한다.
    • 원문이 명시적으로 구분하는가 (예: "대 카토에 비해 소 카토는...", "~의 손자") — 이게 있으면 사람이 이미 갈라놓은 것이니 그대로 따른다.
    • 두 인물이 겹치는 point에서 서로 다른 역할·사건으로 서술되는가 — 같은 point에 각자 다른 문장으로 나오면 다른 사람일 가능성이 크다.
  5. 그래도 안 갈리면 판정하지 않는다. 「하지 말 것」에 나열한 방법으로 억지로 결론 내지 않는다. 「출력 형태」의 판정에 "보류"를 쓰고 사람에게 넘긴다.
  6. 판정이 "별개"면 이 스킬은 끝이다. 분리 등록이나 관계 수정은 propose-change 절차로 넘긴다 — 이 스킬은 새 항목을 만들지 않는다.

하지 말 것

  • 설명 유사도로 판정하지 마라. 카이사르처럼 여러 포인트에 걸쳐 서술된 인물은 국면마다 문장이 달라 유사도가 낮게 나온다. 반대로 다른 인물이 비슷한 역할(둘 다 "집정관", 둘 다 "장군")이면 유사도가 높게 나온다. 양방향으로 다 틀린다.
  • alias 일치만으로 판정하지 마라. 못 믿는다 — 12쌍 중 4쌍이 별개였다는 게 이 레포의 실측 기록이다. alias 겹침은 "후보를 찾는 신호"이지 "판정 근거"가 아니다.
  • 연도로만 판정하지 마라. 연도가 없는 객체가 많고, year_basis가 inferred인 항목은 애초에 불확실하다.
  • points 배열이 겹치지 않는다고 바로 "다른 시대라 별개"로 단정하지 마라. 위 실측에서 24/37이 points 비겹침인데, 이건 단서일 뿐이다 — 원문 대조(절차 3) 없이 이 숫자만으로 별개 판정을 내리지 마라.
  • AI 기억(로마사 지식)으로 판정하지 마라. 로마 인명은 AI가 특히 자주 헷갈리는 영역이다. 판정 근거는 항상 이 레포의 데이터(descs, points 원문)여야 한다 — 바깥 지식으로 "이 사람은 역사적으로 누구다"라고 채워 넣지 마라.
  • 확신이 안 서는데 무리하게 결론 내지 마라. 잘못 합치면 두 인물의 관계·사건이 한 노드에 뒤섞여 그래프가 오염되고 되돌리기 어렵다. 잘못 가르는 쪽(별개로 냅두는 쪽)이 덜 위험하다 — 애매하면 "보류"를 택한다.

출력 형태

판정마다 아래 다섯 항목을 채운다.
markdown
### 후보 쌍
person:카토 (points [4, 7, 8]) ↔ person:소카토 (points [6])
​
### 대조한 근거
- entities.jsonl descs:
- person:카토, point 4: "로마의 감찰관으로 제3차 포에니 전쟁 시작을 주도한 호전론자. 모든 연설을 '카르타고를 쳐부수어야 한다'는 말로 끝맺곤 했다."
- person:카토, point 7: "로마의 보수파 원로원 의원. 카이사르의 추방을 주장했다."
- person:카토, point 8: "로마 원로원 의원. 카이사르를 반대했으나 추격을 받으며 자살했다."
- person:소카토, point 6: "대 카토의 자손으로 보수파 귀족의 대표. 아버지와 달리 정직하지 않았고 성미가 까다로우며 도덕군자처럼 행동했으나 호민관 이상의 지위에는 오르지 못했다."
- points/06_천적과의_전쟁.md 원문(97행): "당시 보수파 귀족의 대표는 위대한 감찰관이었던 [[소 카토|카토]]([[대 카토]])의 자손 카토(소 카토)였다." — 원문이 "카토"를 소 카토로 명시하고, 대 카토와의 관계("~의 자손")까지 밝혀 둠
- points/04_제3차_포에니_전쟁.md 원문(35행): "로마에서 절대적인 신뢰를 얻고 있었던 것은 [[카토|감찰관 카토]]였다." — "감찰관"이라는 역할어가 person:대카토의 attrs.role("censor")과 일치, person:소카토의 서술(원로원 의원·반카이사르파)과는 역할이 다름
​
### 판정
보류 — person:카토의 point 4 소속분은 대 카토(감찰관, 제3차 포에니 전쟁, 기원전 149~146)와 역할이 일치하고, point 7·8 소속분은 소 카토(카이사르 반대, 원문이 직접 "소 카토"로 지목)와 일치한다. 즉 person:카토 하나가 두 시대의 서로 다른 인물을 걸치고 있을 가능성이 있다 — 이 스킬은 "쌍"의 동일/별개만 판정하므로, 이 발견은 판정 대상 밖(entity 내부 point 재배정 문제)이다. 사람에게 넘긴다.
​
### confidence
low — points 원문까지 봐도 person:카토를 통째로 대 카토 또는 소 카토 어느 한쪽에 병합할 수 없다. 쪼개서 재배정해야 할 가능성이 있는데 그 판단은 이 스킬 범위 밖이다.
판정이 "별개"로 명확히 끝나는 경우(예: 서로 다른 point 장에서 등장하고 원문도 이름을 공유할 뿐 관계어로 구분되는 경우)에는 confidence를 high로 적고, 다음 줄에 "→ 분리 등록/관계 수정 필요 시 propose-change로 넘김"이라고 적어 다음 단계를 명시한다. 판정이 "동일"로 끝나면 마찬가지로 "→ 병합 제안은 propose-change로 넘김"이라고 적는다. 이 스킬 자체는 entities.jsonl도, /tmp/rome-proposals/도 건드리지 않는다 — 판정 기록만 남긴다.

운영자 전용

자료를 관리하는 사람 · 자료를 고치거나 다시 만들어내는 일이라 원본을 잘못 건드리면 되돌릴 수단이 없습니다.

자료 수정 제안서 만들기

propose-change
원본을 건드리지 않고 인물·관계 수정 제안서를 만듭니다.
1 넣는 것무엇을 왜 바꾸고 싶은지와 근거 — 본문 줄 번호나 사료까지
2 시키는 것
이렇게 말하면 됩니다
온톨로지에 추가해줘
3 나오는 것
무엇을·왜·근거·대상 확인·규칙 대조·확신도를 담은 제안 파일 한 장이 나옵니다.

언제 쓰는가

entities.jsonl 또는 links.jsonl에 새 객체·새 관계를 더하거나 기존 항목의 오류(오타, 잘못된 연도, 끊어진 링크)를 고치자는 요청을 받았을 때. 이 스킬은 제안 파일을 만드는 것으로 끝난다. 파일을 직접 쓰거나 고치는 작업이 아니다.
여러 AI가 같은 jsonl을 동시에 쓰면 경합이 난다. 2026-08-11에 한 에이전트가 links.jsonl을 재작성하는 사이 다른 에이전트가 읽어 항목 5개가 조용히 사라진 사고가 실제로 있었다. 그래서 쓰기 권한은 사람에게만 있고, AI는 /tmp에 제안만 남긴다.

입력

  • 무엇을 추가/수정하고 싶은지에 대한 요청 또는 관찰
  • 근거가 될 원문(본문 줄 번호, 사료명)
  • 기존 entities.jsonl·links.jsonl 읽기 접근
타입 7종: person · place · event · group · institution · work · period
rel 16종만 허용
기존 11종: child_of succeeded allied_with opposed participated_in occurred_at ruled member_of married conquered created
2026-08-13 신설 5종. 기존 rel로 표현이 안 돼 데이터가 왜곡되던 자리를 메운다.
rel방향쓰는 자리
located_in인물·집단·장소 → 장소"어디에 있었다". 기존 오용(occurred_at 주어가 인물, 인물→지명 participated_in)이 이리로 온다
protected보호자 → 피보호자비대칭 보호. allied_with(대등한 조약)와 ruled(정복 후) 사이
held_office인물 → 제도관직 재임. 임기는 from_year/to_year에. member_of로 우회하지 마라
decided인물·제도 → 사건표결·결정. 참전이 아니므로 participated_in이 아니다
applied_to제도·정책 → 장소·집단정책이 어디에 적용됐나
신설분은 아직 데이터에 거의 없다. 마이그레이션 진행 중이므로, 조회 결과가 0이면 옛 오용분을 같이 봐야 한다.
실제 스키마 — 지어내지 말고 이 모양을 그대로 따른다. 파일에서 한 줄 읽어 확인하는 것이 가장 확실하다.
entities.jsonl 한 줄:
json
{"id": "person:가비니우스", "name": "가비니우스", "type": "person", "aliases": [], "attrs": {"role": "consul", "period": "공화정 말기"}, "points": [6], "desc": "카이사르의 부하로 카이사르의 지지로 집정관에 당선된 인물.", "descs": [{"point": 6, "desc": "카이사르의 부하로 ..."}], "note": "가비니우스"}
links.jsonl 한 줄:
json
{"from": "person:로물루스", "to": "place:로마", "rel": "ruled", "point": 1, "from_year": -753, "to_year": null, "year_basis": "chronology"}
주의할 자리 넷.
  • id는 <타입>:<이름> 형식이다. per_gaius_duilius 같은 임의 slug가 아니다
  • points는 포인트 번호 정수 배열이다([6]). 설명을 넣는 자리가 아니다
  • 설명은 desc(대표 한 줄)와 descs(포인트별 {point, desc} 배열) 둘 다 채운다
  • point는 정수다. year_basis는 chronology · text · inferred 셋 중 하나이거나 null이다. 다른 값을 쓰지 않는다

절차

  1. 무엇을 왜 바꾸는지 한 줄로 적는다.
  2. 근거를 먼저 확보한다. 본문 몇 번째 줄인지, 어느 사료인지 명시한다. 근거 없이는 제안을 만들지 않는다.
  3. 대상이 이미 있는지 확인한다. entities.jsonl을 이름·별칭으로 검색한다(grep -n "이름" entities.jsonl 또는 aliases 필드까지 훑는 스크립트 사용). 동명이인일 수 있으므로 아래 「하지 말 것」의 동명이인 항목을 먼저 읽는다.
  4. 불변식 여섯을 하나씩 대조한다.
    1. 객체 하나에 노트 하나 — 새 객체를 제안하면 entities/<type>/<이름>.md 노트도 같이 제안한다. 이름·타입이 겹칠 때만 파일명에 (지명)·(집단) 같은 접미사를 붙이고, jsonl의 name은 접미사 없이 그대로 쓴다.
    2. rel은 16종 중 하나인가. 아니면 데이터를 만들지 말고 rel 신설을 먼저 논의한다.
    3. 연도 부호 — 기원전은 음수, 서기는 양수. year_basis도 함께 채운다.
    4. occurred_at의 주어(from)는 반드시 event인가.
    5. participated_in은 인물·집단 → 사건 방향인가.
    6. from·to가 모두 entities.jsonl에 실재하는 id인가.
  5. /tmp에 제안 파일을 쓴다. 레포의 entities.jsonl·links.jsonl·entities/*.md는 손대지 않는다.
  6. 사람이 검수하고 병합한다. 병합 전까지 이 제안은 초안일 뿐이다.

하지 말 것

  • entities.jsonl·links.jsonl을 직접 고치지 마라. 5개 항목이 조용히 사라진 사고가 그렇게 났다.
  • 없는 관계를 억지로 만들지 마라. 배경 언급뿐인 지명에 링크를 다는 것보다 비워 두는 편이 낫다. 예: 곶(지명)이 그리스와 allied_with일 수 없다. 투리이는 로마에 보호를 요청했을 뿐 동맹을 맺지 않았다 — 이런 관계는 제안하지 않는다.
  • 표현할 rel이 없다고 비슷한 걸 갖다 쓰지 마라. 그렇게 occurred_at 오용 37건, 인물→지명 participated_in 오용 20건이 쌓였다. 자리가 없으면 "이 관계는 표현할 rel이 없다"고 보고하고 rel 신설 논의로 넘긴다. 데이터에 억지로 끼워 넣지 않는다.
  • 동명이인을 설명 유사도로 판정하지 마라. 카이사르처럼 한 인물의 포인트별 서술은 국면이 달라 유사도가 낮게 나올 수 있다. alias 신호도 못 믿는다 — 실제 검증한 12쌍 중 4쌍이 서로 다른 인물이었다. 포인트별 서술을 사람 눈으로 대조해 판정한다. 확신이 안 서면 판정하지 말고 사람에게 넘긴다.
  • 연도·인과를 추정으로 채우지 마라. AI가 가장 자주 틀리는 자리다. 과거 제안 68건 중 19건이 반려됐는데 연도 혼동이 큰 몫이었다 — 마르살라 만 해전을 -241로 적었으나 본문 순서상 -256이 맞았고, 탈라모네를 -240으로 적었으나 실제는 -225였다. 모르면 confidence: low로 표시하거나 아예 제안하지 않는다.
  • entities/*.md를 손으로 대량 수정하지 마라. 이 파일들은 ontology/_scripts/rome30_*.py가 entities.jsonl에서 재생성한다. 노트 변경도 제안으로만 남긴다.
  • rome30_merge.py를 돌리자고 제안하지 마라. 최초 1회용이고 이미 끝났다. 이 스크립트는 현재 jsonl을 읽지 않고 _parts/*.jsonl(2026-07-28에 멈춤, 연도 필드도 없음)만 읽어 통째로 덮어쓴다. 지금 돌리면 이 스킬로 병합한 모든 제안이 사라진다. 하류 재생성(geo → maps → routes → territory → backlink)만 안전하다.

출력 형태

제안 파일은 /tmp/rome-proposals/<주제>.md 로 쓴다. 아래는 신규 객체 하나와 신규 관계 하나를 담은 예시다.
markdown
## 제안: 가이우스 두일리우스 신규 등록 + 밀라이 해전 참전
​
### 무엇을
- 신규 person 객체 `person:두일리우스`
- 신규 관계 `person:두일리우스 --participated_in--> event:밀라이해전`
​
### 왜
포인트 02 본문이 로마 최초의 해전 승리 지휘관으로 서술하는데 entities.jsonl에 없다.
​
### 근거
- `points/02_제1차_포에니_전쟁.md` 본문 (해당 줄 번호를 적는다)
- 인용을 그대로 옮긴다
​
### 대상 확인
- `grep '"name": "두일리우스"' ontology/entities.jsonl` → 결과 없음. 별칭도 없음
- 대상 사건 `event:밀라이해전`이 실재하는지 확인 → **없다.** 포인트 02의 event는
메시나전투 · 제1차포에니전쟁 · 아그리젠토포위 · 마르살라만해전 · 본곶전투 ·
탈라모네전투 · 팔레르모탈환시도 일곱뿐이다
- 따라서 이 제안은 **사건 신설을 함께 제안하거나, 관계를 빼고 객체만 제안해야 한다**
​
### 불변식 대조
1. 노트 매핑 — `entities/person/두일리우스.md` 신규 제안 포함 (통과)
2. rel 유효성 — `participated_in`, 16종 내 (통과)
3. 연도 부호 — 음수 (통과)
4. `occurred_at` 주어 — 해당 없음
5. `participated_in` 방향 — person → event (통과)
6. 링크 무결성 — **`to`가 실재하지 않음 (실패)**. 사건을 먼저 만들어야 한다
​
### confidence
low — 대상 사건이 없어 관계 부분은 이대로 병합할 수 없다
​
### 신규 entities.jsonl 항목
{"id": "person:두일리우스", "name": "두일리우스", "type": "person", "aliases": ["가이우스 두일리우스"], "attrs": {"role": "consul"}, "points": [2], "desc": "제1차 포에니 전쟁에서 로마 최초의 해전 승리를 이끈 집정관.", "descs": [{"point": 2, "desc": "제1차 포에니 전쟁에서 로마 최초의 해전 승리를 이끈 집정관."}], "note": "두일리우스"}
이 예시는 일부러 실패하는 항목을 담고 있다. 불변식 6번이 걸려서 관계는 병합할 수 없다. 제안이 통과하지 못하는 것을 발견하는 것도 이 스킬의 성과다. 억지로 통과시키려고 없는 사건을 지어내는 순간 데이터가 오염된다.
기존 항목을 고치는 정정 제안도 형태가 같다. 「무엇을」에 현재 값과 정정 값을 나란히 적는다.
markdown
## 제안: 포인트 02 연도 부호 정정
​
### 무엇을
`links.jsonl`의 `point: 2` 항목 중 `from_year`·`to_year`가 양수인 것들.
현재 `"from_year": 264` → 정정 `"from_year": -264`
​
### 왜
포인트 02는 전 구간이 기원전 사건인데 부호가 빠져 제1차 포에니 전쟁이
서기 3세기에 찍힌다.
​
### 근거
같은 포인트의 다른 항목이 음수이고, `chronology.csv`가 기원전 264~241년을 준다.
​
### 불변식 대조
3번(연도 부호)이 핵심. 나머지는 변경 없음.
​
### confidence
high
한 파일에 여러 항목을 묶어도 되지만, 항목마다 무엇을·왜·근거·대상 확인·불변식 대조·confidence 여섯을 각각 갖춘다.

자료 다시 만들기

regenerate
스크립트를 대신 실행하지 않고 돌릴 순서만 정리합니다.
1 넣는 것무엇이 바뀌었는지 — 새 좌표인지 새 경로인지 사람이 고친 것인지
2 시키는 것
이렇게 말하면 됩니다
온톨로지 재생성해줘
3 나오는 것
백업·실행 순서·끝난 뒤 확인할 것이 담긴 체크리스트가 나옵니다.

언제 쓰는가

새 리서치(좌표·경로·영토·연도)가 _geo·_routes·_time 같은 원료 폴더에 들어왔거나, entities.jsonl·links.jsonl이 바뀌어서 entities/<type>/<이름>.md 642개에 반영해야 할 때. 스크립트 여러 개가 서로의 산출물을 전제로 쌓이는 구조라, 하나만 돌리면 다른 스크립트가 이미 붙여 둔 내용이 지워진다 — 실제로 벌어졌던 사고다. 이 스킬은 무엇을 어떤 순서로, 어떤 전제 확인 후에 돌려야 하는지 정리한다. 스크립트를 대신 실행하지 않는다.

입력

  • 무엇이 바뀌었는지: 새 좌표 리서치(_geo)인지, 새 경로/영토 리서치(_routes)인지, 새 연도 리서치(_time)인지, 아니면 entities.jsonl/links.jsonl 자체를 사람이 직접 고쳤는지 — 이에 따라 어디서부터 다시 돌려야 하는지가 달라진다.
  • 레포 경로(~/project/active/decline-and-fall-of-the-roman-empire/)와 볼트 경로 둘 다 인지하고 있어야 한다. 아래 「하지 말 것」 첫 항목 참고.

절차

  1. BASE 경로부터 확인한다. ontology/_scripts/rome30_*.py 전부가 스크립트 상단에
BASE = Path("~/Library/Mobile Documents/iCloud~md~obsidian/Documents/" "River's Second Brain/Efforts/Notes/산업스터디/Projects/인생책_읽기_편데/" "Books/로마제국쇠망사")
로 볼트 경로를 하드코딩하고 있다. 레포 터미널에서 스크립트를 실행해도 실제로 읽고 쓰는 대상은 볼트다 — 레포 자신의 ontology/entities.jsonl·ontology/links.jsonl·entities/는 스크립트가 건드리지 않는다(AGENTS.md대로 "볼트가 정본, 레포는 수동 사본"). 재생성 후 레포에 반영하려면 별도로 다시 복사해야 한다.
  1. 백업을 먼저 뜬다. 볼트는 git이 아니다. entities.jsonl·links.jsonl·entities/를 통째로 백업 폴더에 복사해 둔다. --write 없이 dry-run만 돌리는 단계에서는 생략해도 되지만, --write를 붙이는 순간부터는 필수다.
  2. 순서를 확인한다. rome30_backlink.py 자신의 docstring에 순서가 명시돼 있다:
실행 순서: merge → geo → maps → routes → territory → backlink
각 스크립트가 하는 일과 왜 순서가 이런지:
  • rome30_merge.py — _parts/entities_*.jsonl·_parts/links_*.jsonl(원시 추출 조각)을 병합해 entities.jsonl·links.jsonl을 새로 쓰고, entities/<type>/<이름>.md 642개를 그 자리에서 덮어쓴다. 자기 docstring에 "1회성"이라고 적혀 있다 — 아래 「하지 말 것」 참고.
  • rome30_geo.py — 지명 노트에 좌표·이동 통계(### 위치)를 심는다.
  • rome30_maps.py — 인물·집단·사건·시대 노트에 관련 지명이 걸린 지도(### 관련 지도)를 넣는다. 지명 노트의 location 프론트매터(geo가 심음)에 의존하므로 geo 다음이어야 한다.
  • rome30_routes.py — 리서치된 이동 경로를 GeoJSON + 경로 노트로 만든다. 연도 시간순·좌표 사전값 일치·로마 세계 범위(위 -12~125도, 위도 5~71도) 셋을 검증하고, 어긋나면 그 경로는 기록하지 않는다.
  • rome30_territory.py — 리서치된 왕국 영토를 GeoJSON Polygon + 영토 노트로 만든다. _routes 폴더를 같이 읽는다(별도 _territory 폴더가 아니다 — 확인 완료, 현재 레포에 _territory 디렉터리는 없다).
  • rome30_backlink.py — routes·territory 리서치 결과를 그 주체(한니발·카이사르·고트족 등, 스크립트 안 SUBJECT 딕셔너리에 하드코딩)가 되는 노트에 ### 이동 경로로 되돌려 넣는다. routes·territory 산출물이 아직 없으면 넣을 게 없다 — 반드시 그 둘 다음이다.
이 여섯과 별개로 두 트랙이 더 있다:
  • rome30_time.py — _time/years_*.jsonl을 links.jsonl에 병합해 from_year/to_year/year_basis를 채운다. entities.jsonl이 아니라 links.jsonl만 읽고 쓰므로 merge 이후 아무 때나 독립적으로 돌려도 된다(geo·maps·routes·territory·backlink와 순서 종속이 없다). to_year < from_year면 자동으로 스왑하는 보정 로직이 들어 있다.
  • rome30_link.py → rome30_nav.py — points/*.md(30포인트 편역본 본문)에 위키링크를 걸고 이전/다음 목차 띠를 붙이는 트랙이다. entities/ 노트 파이프라인과는 별개 대상이라 섞어 생각하지 않는다. rome30_nav.py는 자기 docstring에 "link → 이 스크립트" 순서를 명시한다.
  • rome30_family.py·rome30_canvas.py(족보), rome30_source_md.py·rome30_split.py(기번 원전 변환, 1회성), apply_local_graph_colors.py(옵시디언 로컬 그래프 색 — 옵시디언을 완전히 종료한 뒤에만 실행, 켜진 채 쓰면 다음 저장 때 덮어써진다)는 이 재생성 체인과 독립적이다.
  1. 어디서부터 돌릴지 정한다. entities.jsonl/links.jsonl 자체를 사람이 직접 고쳤다면(propose-change 병합 등) merge는 건너뛰고 geo부터 시작한다(아래 「하지 말 것」 참고). 새 _geo 리서치만 들어왔다면 geo부터, 새 _routes/_territory 리서치라면 routes부터, 그 다음은 항상 하류로 흘려보낸다.
  2. 각 스크립트를 --write 없이 먼저 돌려 dry-run 결과를 읽는다. 전 스크립트가 python3 rome30_X.py(인자 없음)면 dry-run, python3 rome30_X.py --write면 실제 반영이라는 동일한 패턴을 쓴다(main(write="--write" in sys.argv)). dry-run 출력(대상 개수, 탈락 사유)을 사람이 확인한 뒤에만 --write로 넘어간다.
  3. 정해진 순서대로 --write를 붙여 실행한다.
  4. entities/ 개수를 확인한다. 642개가 유지되는지(find entities -name "*.md" | wc -l), merge가 "옛 파일 N개 정리"라고 보고한 수가 예상과 맞는지 본다.
  5. 레포 쪽을 수동으로 동기화한다. 자동 동기화가 없으므로, 볼트의 ontology/·entities/를 레포의 같은 경로로 다시 복사한다.

하지 말 것

  • 레포에서 스크립트를 돌리면 레포가 갱신된다고 착각하지 마라. BASE가 볼트 경로로 고정돼 있어 실제로는 볼트가 갱신된다. 레포 반영은 별도 수동 복사가 필요하다.
  • merge를 "최신화" 목적으로 그냥 다시 돌리지 마라. merge는 현재 entities.jsonl/links.jsonl을 전혀 읽지 않는다 — _parts/entities_*.jsonl·_parts/links_*.jsonl에서만 완전히 새로 짠다(코드로 확인 완료: main()이 PARTS.glob(...)만 읽는다). propose-change로 사람이 entities.jsonl/links.jsonl에 직접 병합한 내용은 _parts에 반영돼 있지 않은 한 merge를 다시 돌리는 순간 통째로 사라진다.
  • merge만 돌리고 끝내지 마라. merge가 노트를 제자리 덮어쓰기 때문에, geo가 붙인 ### 위치, maps가 붙인 ### 관련 지도, backlink가 붙인 ### 이동 경로 섹션이 지워진다. 실제로 벌어졌던 사고이고, 하류 스크립트를 다시 돌려 복구했다.
  • geo --write 뒤에는 entities/place/ diff를 눈으로 확인하라. geo는 지명 사전으로 매번 다시 해석하므로 사람이 손으로 교정한 좌표를 원래의 오답으로 되돌린다. 2026-08-13에 본 곶이 튀니지 케이프본에서 프랑스 불로뉴쉬르메르로 덮였다(사전이 Bononia로 오매칭). 되돌아갔으면 복구한 뒤 다음 단계로 간다.
  • entities/*.md를 손으로 고치지 마라. 이 파일들은 파이프라인 산출물이다. 다음 재생성(특히 merge) 때 덮어써지거나, merge의 stale 정리 로직에 걸려 지워진다.
  • entities/ 폴더를 미리 손으로 지우고 스크립트를 돌리지 마라. merge.py는 일부러 디렉터리를 통째로 지우지 않고 제자리 덮어쓴 뒤 남은 옛 파일만 지운다 — 자기 코드 주석에 이유가 적혀 있다: "디렉터리를 통째로 지우고 다시 쓰면 iCloud가 삭제·생성 경합을 충돌로 보고 '이름 2.md' 사본을 무더기로 만든다." 미리 지우면 이 보호를 우회해 iCloud 충돌 사본이 생길 수 있다.
  • 순서를 건너뛰지 마라. maps는 geo가 심은 location 프론트매터에, backlink는 routes·territory가 만든 GeoJSON·노트에 의존한다. 앞 단계 없이 뒤 단계만 돌리면 빈손으로 끝나거나(대상 0개) 오래된 데이터를 참조한다.
  • --write 없이 실행한 결과를 반영됐다고 보고하지 마라. 기본은 dry-run이다. 실제 파일이 바뀌는 건 --write를 붙였을 때뿐이다.
  • 백업 없이 --write를 붙이지 마라. 볼트는 git 레포가 아니라서 되돌릴 수단이 백업뿐이다.
  • 스크립트 인자·플래그를 지어내지 마라. 확인한 전부가 --write 하나뿐이다. 다른 플래그가 필요해 보이면 실제로 스크립트를 열어 sys.argv 처리 부분을 확인한다.

출력 형태

이 스킬의 산출물은 실행 계획 체크리스트다. 예:
## 재생성 계획
​
### 무엇이 바뀌었나
새 _geo 리서치 3건 도착 (place:비잔티움 외 2건 좌표 추가)
​
### BASE 확인
스크립트 BASE = 볼트 경로. 레포 자체는 이 실행으로 갱신되지 않음 — 완료 후 수동 복사 필요.
​
### 백업
cp -r ".../로마제국쇠망사/entities" ".../backup_20260813/entities"
cp -r ".../로마제국쇠망사/ontology" ".../backup_20260813/ontology"
​
### 실행 순서 (merge 건너뜀 — entities.jsonl/links.jsonl 변경 없음)
1. python3 rome30_geo.py (dry-run, 대상 확인)
2. python3 rome30_geo.py --write
3. python3 rome30_maps.py (dry-run)
4. python3 rome30_maps.py --write
5. python3 rome30_backlink.py (dry-run) — routes/territory 변경 없으므로 대상 재확인만
6. (routes/territory 변경 없으면 6은 생략)
​
### 확인
- entities/ 파일 수 642 유지 확인
- geo dry-run 대상 개수와 --write 완료 로그 대조
​
### 레포 동기화
볼트 → 레포 ontology/, entities/ 수동 복사 (사람 확인 후)

원문을 보시려면

여덟 벌 모두 위에서 「전문 보기」로 펼쳐 볼 수 있어요. 원본 파일은 깃허브 저장소에 있습니다.

자주 묻는 것

다른 책입니다. 30포인트 편역본은 한국어 편역이고 원전은 기번의 영문입니다. 섞어 인용하면 사실관계가 어긋납니다. 인물·관계 자료는 편역본 계통에서만 뽑았습니다.이어서 읽기 · About
아닙니다. 로마 인명은 가문 이름이 세대를 넘어 반복됩니다. 검증한 12쌍 중 4쌍이 서로 다른 인물이었습니다. 설명이 비슷한지로도, 별칭이 겹치는지로도 판정하면 안 됩니다.이어서 그냥 시키면 틀리는 것
관계가 아닙니다. 책이 같은 포인트에서 함께 이야기했다는 사실일 뿐입니다. 둘 사이에 무슨 사이가 있다는 뜻이 아니니 AI에게 줄 때도 그 차이를 지켜주세요.이어서 AI에 줄 자료
안 됩니다. AI가 폴더를 헤매다 엉뚱한 것을 읽거나, 아예 못 읽고 지어냅니다. 화면의 「이 페이지 복사」로 본문을 통째로 주시는 편이 훨씬 정확합니다.이어서 AI에 줄 자료 · 활용 사례
됩니다. 쓰던 것 그대로 쓰시면 됩니다. 재료를 붙여넣는 방식이라 어느 서비스인지는 상관이 없습니다.이어서 AI에 자료 연결하기 · AI에 줄 자료
자리를 가려 믿으셔야 합니다. AI가 올린 제안 68건 중 19건을 반려했습니다. 좌표를 찾거나 문장을 뽑는 기계적인 일은 대체로 정확했고, 분류·연도·인과 판단에서 틀렸습니다.이어서 그냥 시키면 틀리는 것
됩니다. 읽기·찾아보기·가져가기는 AI가 필요 없습니다. 계정도 설치도 프롬프트도 없이 그냥 보고 복사하시면 됩니다.이어서 읽기 · 찾아보기 · 가져가기
포인트 한 장이 약 1만 자, 5천 토큰입니다. 어느 AI 창에도 그냥 들어갑니다. 잘라 붙이실 필요가 없습니다.이어서 AI에 줄 자료
있습니다. 포인트 01·02 본문 45건을 사료와 대조해 심각한 오류 6건을 찾았습니다. 전투 승패가 뒤집힌 서술이 두 장에 하나꼴이었습니다.이어서 그냥 시키면 틀리는 것 · About
아닙니다. 사료 대조가 끝난 것은 포인트 01과 02, 둘뿐입니다. 나머지는 아직 문장 단위로 확인하지 않았습니다.이어서 그냥 시키면 틀리는 것