LLM-wiki Ingest flow: A.md / B.md ์ถ”์  ์˜ˆ์‹œ

๋ชฉํ‘œ: A.md, B.md ๋‘ ๋ฌธ์„œ๋ฅผ ingest ํ–ˆ์„ ๋•Œ ์›๋ณธ chunk โ†’ LLM claim โ†’ wiki/claims.json โ†’ wiki/concepts/Matcher.md๊นŒ์ง€ ์–ด๋–ค ID๋กœ ์—ฐ๊ฒฐ๋˜๋Š”์ง€ ํ•œ ๋ฒˆ์— ์ถ”์ ํ•œ๋‹ค.

์ „์ œ:

  • ์˜ˆ์‹œ๋Š” ์ฝ”๋“œ ๊ทœ์น™์— ๋งž์ถ˜ ์ถ”์ ์šฉ ์ƒ˜ํ”Œ์ด๋‹ค.
  • LLM ์‘๋‹ต ๋‚ด์šฉ์€ ์‹คํ–‰๋งˆ๋‹ค ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์ง€๋งŒ, ID ์ƒ์„ฑ/๋ณด์กด/๊ทธ๋ฃนํ•‘ ๊ทœ์น™์€ ์ฝ”๋“œ ๋กœ์ง์„ ๋”ฐ๋ฅธ๋‹ค.
  • ์•„๋ž˜ ์˜ˆ์‹œ๋Š” A.md์—์„œ๋Š” summary=c1, Matcher concept=c2๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , B.md์—์„œ๋Š” summary=c1, Tool concept=c2, Matcher concept=c3๊ฐ€ ์ƒ์„ฑ๋œ ์ƒํ™ฉ์„ ๊ธฐ์ค€์œผ๋กœ ํ•œ๋‹ค.

0. ํ•ต์‹ฌ ๊ฒฐ๋ก 

flowchart LR
  A["A.md<br/>sourcePath=A.md<br/>sourceId=src:2d63174e212b2fdb"] --> AC["source chunk<br/>ordinal 0/1<br/>stableChunkId"]
  B["B.md<br/>sourcePath=B.md<br/>sourceId=src:b5038c9fd9be6c6f"] --> BC["source chunk<br/>ordinal 0/1<br/>stableChunkId"]

  AC --> AE["evidence<br/>src:2d63174e212b2fdb:e2<br/>A.md lines 2-2"]
  BC --> BE["evidence<br/>src:b5038c9fd9be6c6f:e3<br/>B.md lines 4-4"]

  AE --> AK["claimKey<br/>src:2d63174e212b2fdb:c2<br/>subject=Matcher"]
  BE --> BK["claimKey<br/>src:b5038c9fd9be6c6f:c3<br/>subject=Matcher"]

  AK --> REG["wiki/claims.json<br/>claim์€ source-aware๋กœ ๋ณ„๋„ ๋ณด์กด"]
  BK --> REG

  REG --> GROUP["step8 concept grouping<br/>key=lowercase(trim(NFC(subject)))<br/>matcher"]
  GROUP --> WIKI["wiki/concepts/Matcher.md<br/>A.md ์„น์…˜ + B.md ์„น์…˜"]

ํ•ต์‹ฌ:

  • claims.json์—์„œ๋Š” A/B claim์„ dedupeํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • claimKey๋Š” sourcePath + claimId ๊ธฐ๋ฐ˜์ด๋ผ A.md์˜ Matcher์™€ B.md์˜ Matcher๋Š” ์„œ๋กœ ๋‹ค๋ฅธ claim์ด๋‹ค.
  • concept wiki ๋‹จ๊ณ„์—์„œ๋งŒ subject ์ •๊ทœํ™”๊ฐ’์ด ๊ฐ™์€ claim๋“ค์„ ํ•œ ํŽ˜์ด์ง€์— ๋ชจ์•„ ๋ณด์—ฌ์ค€๋‹ค.

1. ์˜ˆ์‹œ ์ž…๋ ฅ

A.md

# A
Matcher๋Š” ์š”์ฒญ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•ด ๋Œ€์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
## Detail
Matcher๋Š” ๋ผ์šฐํŒ… ์ „์— ์ž…๋ ฅ ํŒจํ„ด์„ ๊ฒ€์‚ฌํ•œ๋‹ค.

B.md

# B
Tool ํ˜ธ์ถœ์€ ์ •์ฑ… ๊ฒ€์‚ฌ๋ฅผ ๋จผ์ € ํ†ต๊ณผํ•ด์•ผ ํ•œ๋‹ค.
## Matcher
Matcher๋Š” ๋„๊ตฌ ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋Š” ๊ทœ์น™ ์ง‘ํ•ฉ์ด๋‹ค.

2. ๋‹จ๊ณ„๋ณ„ ์ „์ฒด ํ๋ฆ„

sequenceDiagram
  participant SRC as Source files
  participant S1 as step1 Discover/Classify
  participant S2 as step2 Parse/Chunk
  participant S4 as step4 LLM Generate
  participant S5 as step5 Validate
  participant S6 as step6 Backup/Write
  participant S7 as step7 Merge Claims
  participant S8 as step8 Sync Concept Wiki

  SRC->>S1: A.md, B.md scan
  S1->>S2: changed source documents
  S2->>S4: chunks + raw source
  S4->>S5: generated wiki page + SourceClaim[]
  S5->>S6: validated claims and markdown
  S6->>S7: claimUpdates + generated wiki page checkpoint
  S7->>S7: mergeSourceClaims(existing, updates, currentSourcePaths)
  S7->>S8: wiki/claims.json
  S8->>S8: concept claims grouped by normalized subject
  S8->>SRC: wiki/concepts/Matcher.md

์ฃผ์˜: backend์—๋Š” step3 Queue Wiki work๊ฐ€ ์žˆ์ง€๋งŒ visualizer์˜ ๋ฌธ์„œ/์‹คํ–‰ ํ๋ฆ„์—์„œ๋Š” ๋‚ด๋ถ€ ํ ๋‹จ๊ณ„๋ผ ํ•ต์‹ฌ ์„ค๋ช…์—์„œ ์ƒ๋žตํ–ˆ๋‹ค.

3. Step 1: source ์‹๋ณ„

sourceId๋Š” sourcePath๋งŒ์œผ๋กœ ๋งŒ๋“ ๋‹ค.

sourceId = "src:" + sha256(normalizedSourcePath).slice(0, 16)
claimKey = `${sourceId}:${claimId}`
evidenceId = `${sourceId}:e${n}`
filesourcePathsourceId
A.mdA.mdsrc:2d63174e212b2fdb
B.mdB.mdsrc:b5038c9fd9be6c6f

sourceHash๋Š” ํŒŒ์ผ ๋‚ด์šฉ sha256์ด๋‹ค. ๊ฐ™์€ path๋ผ๋„ ๋‚ด์šฉ์ด ๋ฐ”๋€Œ๋ฉด sourceHash๊ฐ€ ๋ฐ”๋€Œ๊ณ , ๋ณ€๊ฒฝ ๋ฌธ์„œ์˜ claim์€ ์ƒˆ claim์œผ๋กœ ๊ต์ฒด๋œ๋‹ค.

filesourceHash
A.mdf0cc8c7b6269d5a4094467448e032b8d05c10bc4bee9a5525b7102ecc40e7695
B.md5fddc130a5be77b2b79abe79f96d37cfd925511c276e34b3a0b80883c2b970c0

4. Step 2: chunk ๋ถ„ํ• 

Markdown chunk๋Š” heading ๊ธฐ์ค€์œผ๋กœ ๋‚˜๋‰œ๋‹ค. visualizer payload์—๋Š” ordinal, heading, lineStart, lineEnd๊ฐ€ ๋…ธ์ถœ๋œ๋‹ค.

DB์—๋Š” stableChunkId(sourcePath, sourceHash, ordinal, heading)๊ฐ€ ์ €์žฅ๋œ๋‹ค.

flowchart TD
  A["A.md"] --> A0["chunk A#0<br/>heading=A<br/>lines 1-2<br/>stableChunkId=9ecb66ee7cd861b4580c3014"]
  A --> A1["chunk A#1<br/>heading=A > Detail<br/>lines 3-4<br/>stableChunkId=211e2203d68696ce4a1c8241"]

  B["B.md"] --> B0["chunk B#0<br/>heading=B<br/>lines 1-2<br/>stableChunkId=cdc1d32699ceb97f6c595e53"]
  B --> B1["chunk B#1<br/>heading=B > Matcher<br/>lines 3-4<br/>stableChunkId=81527e658148f0b10efd8066"]
sourcePathvisual chunk keyDB stableChunkIdheadinglines
A.mdA.md#09ecb66ee7cd861b4580c3014A1-2
A.mdA.md#1211e2203d68696ce4a1c8241A > Detail3-4
B.mdB.md#0cdc1d32699ceb97f6c595e53B1-2
B.mdB.md#181527e658148f0b10efd8066B > Matcher3-4

5. Step 4~5: LLM claim ์ƒ์„ฑ๊ณผ ๊ฒ€์ฆ

LLM์€ ์›๋ฌธ์—์„œ summary/concept/relationship์„ ๋งŒ๋“ค๊ณ , ๊ฒ€์ฆ ๋‹จ๊ณ„์—์„œ ๊ฐ claim์— ์›๋ฌธ evidence range๊ฐ€ ๋ถ™๋Š”๋‹ค.

์˜ˆ์‹œ์—์„œ๋Š” A.md์˜ Matcher concept claim์€ c2, B.md์˜ Matcher concept claim์€ c3๋กœ ๋งŒ๋“ค์–ด์กŒ๋‹ค๊ณ  ๊ฐ€์ •ํ•œ๋‹ค.

claimId๋Š” ๋ฌธ์„œ๋ณ„ local ์ˆœ๋ฒˆ์ด๋‹ค. ๊ฐ™์€ ๊ฐœ๋…์ด๋ผ๋„ ๋ฌธ์„œ ์•ˆ์—์„œ ๋ช‡ ๋ฒˆ์งธ summary/concept/relationship์œผ๋กœ ๋งŒ๋“ค์–ด์กŒ๋Š”์ง€์— ๋”ฐ๋ผ c2, c3์ฒ˜๋Ÿผ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ๋‹ค. evidence[0].lineStart-lineEnd๋„ ์›๋ฌธ ์ค„ ๋ฒˆํ˜ธ๋ผ ์šฐ์—ฐํžˆ ๊ฐ™์„ ์ˆ˜๋„ ์žˆ๊ณ , ์•„๋ž˜์ฒ˜๋Ÿผ ๋‹ค๋ฅผ ์ˆ˜๋„ ์žˆ๋‹ค.

flowchart LR
  A0["A.md chunk #0<br/>lines 1-2"] --> AE2["evidence<br/>id=src:2d63174e212b2fdb:e2<br/>sourcePath=A.md<br/>lines 2-2"]
  AE2 --> AC2["claim<br/>claimId=c2<br/>claimKey=src:2d63174e212b2fdb:c2<br/>kind=concept<br/>subject=Matcher"]

  B1["B.md chunk #1<br/>lines 3-4"] --> BE3["evidence<br/>id=src:b5038c9fd9be6c6f:e3<br/>sourcePath=B.md<br/>lines 4-4"]
  BE3 --> BC3["claim<br/>claimId=c3<br/>claimKey=src:b5038c9fd9be6c6f:c3<br/>kind=concept<br/>subject=Matcher"]

์ค‘์š”ํ•œ ์—ฐ๊ฒฐ ๊ทœ์น™:

fieldA.md Matcher claimB.md Matcher claim
claimIdc2c3
claimKeysrc:2d63174e212b2fdb:c2src:b5038c9fd9be6c6f:c3
kindconceptconcept
subjectMatcherMatcher
sourcePathA.mdB.md
sourceHashA.md content hashB.md content hash
evidenceIds[0]src:2d63174e212b2fdb:e2src:b5038c9fd9be6c6f:e3
evidence[0].sourcePathA.mdB.md
evidence[0].lineStart-lineEnd2-24-4

์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ๊ฒƒ์€ claimId๊ฐ€ ์•„๋‹ˆ๋ผ claimKey๋‹ค. claimKey๋Š” sourcePath ๊ธฐ๋ฐ˜ sourceId๋ฅผ ํฌํ•จํ•˜๋ฏ€๋กœ source-aware claim identity๊ฐ€ ๋œ๋‹ค.

6. Step 6: generated wiki page ์ €์žฅ

๊ฐ source ๋ฌธ์„œ๋Š” ์ž๊ธฐ ์†Œ์œ  generated wiki page๋ฅผ ๊ฐ€์ง„๋‹ค.

flowchart LR
  A["A.md"] --> AW["wiki/generated/A.wiki.md<br/>sourcePath=A.md<br/>sourceHash=A hash"]
  B["B.md"] --> BW["wiki/generated/B.wiki.md<br/>sourcePath=B.md<br/>sourceHash=B hash"]

  AW --> AWC["wiki page chunks<br/>ordinal/heading/lines"]
  BW --> BWC["wiki page chunks<br/>ordinal/heading/lines"]

์ด ๋‹จ๊ณ„๋Š” claim dedupe ๋‹จ๊ณ„๊ฐ€ ์•„๋‹ˆ๋‹ค.

ํ•˜๋Š” ์ผ:

  • ๊ธฐ์กด generated wiki page ๋ฐฑ์—…
  • ์ƒˆ markdown atomic write
  • generated wiki page chunk๋ฅผ DB์— upsert
  • claimUpdates.push(...page.claims)๋กœ ์ดํ›„ registry merge์— ๋„˜๊น€

ํ•ต์‹ฌ ์š”์•ฝ: A.wiki.md๋Š” A.md๋ฅผ LLM/๊ฒ€์ƒ‰/์‚ฌ๋žŒ์ด ์ฝ๊ธฐ ์ข‹์€ wiki markdown์œผ๋กœ ์žฌ์ž‘์„ฑํ•œ ํŒŒ์ผ์ด๋‹ค. claim ์ €์žฅ์†Œ๋Š” ์•„๋‹ˆ๋ฉฐ, ์ •๊ทœ claim ์ถ”์ ์€ wiki/claims.json์ด ๋‹ด๋‹นํ•œ๋‹ค.

๊ฐ™์€ LLM ์‘๋‹ต์—์„œ ๋‘ ์ข…๋ฅ˜์˜ ์‚ฐ์ถœ๋ฌผ์ด ํ•จ๊ป˜ ๋‚˜์˜จ๋‹ค๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

flowchart LR
  LLM["step4 LLM response<br/>A.md ๋ถ„์„ ๊ฒฐ๊ณผ"] --> PAGE["page.markdown<br/>wiki/generated/A.wiki.md์— ์ €์žฅ"]
  LLM --> CLAIMS["page.claims<br/>step7 wiki/claims.json์— ๋ณ‘ํ•ฉ"]

  PAGE --> INDEX["wiki_pages / wiki_chunks DB upsert"]
  CLAIMS --> REG["claimUpdates ๋ฐฐ์—ด"]

์˜ˆ์‹œ wiki/generated/A.wiki.md ํ•ต์‹ฌ ํ˜•ํƒœ:

---
source_path: "A.md"
source_sha256: "f0cc8c7b6269d5a4094467448e032b8d05c10bc4bee9a5525b7102ecc40e7695"
aliases: ["Matcher"]
---
 
# A
 
Matcher๋Š” ์š”์ฒญ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•ด ๋Œ€์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
 
## Matcher
 
Matcher๋Š” ์š”์ฒญ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•ด ๋Œ€์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•˜๊ณ , ๋ผ์šฐํŒ… ์ „์— ์ž…๋ ฅ ํŒจํ„ด์„ ๊ฒ€์‚ฌํ•œ๋‹ค.
 
## ์›๋ฌธ ๊ทผ๊ฑฐ
 
- A.md:2-2 โ€” Matcher๋Š” ์š”์ฒญ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•ด ๋Œ€์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
- A.md:4-4 โ€” Matcher๋Š” ๋ผ์šฐํŒ… ์ „์— ์ž…๋ ฅ ํŒจํ„ด์„ ๊ฒ€์‚ฌํ•œ๋‹ค.

์ด ํŒŒ์ผ์˜ ์—ญํ• :

  • ๊ฒ€์ƒ‰/์ฝ๊ธฐ์šฉ wiki page๋‹ค.
  • sourcePath=A.md, sourceHash=A hash๋กœ A.md์— ์†Œ์œ ๋œ๋‹ค.
  • page markdown์—์„œ ๋‹ค์‹œ chunk๊ฐ€ ๋งŒ๋“ค์–ด์ ธ wiki_chunks์— ์ €์žฅ๋œ๋‹ค.
  • claim registry์˜ ์ •๊ทœ ์›๋ณธ์€ ์•„๋‹ˆ๋‹ค. ์ •๊ทœ claim ๋ชฉ๋ก์€ ๋‹ค์Œ ๋‹จ๊ณ„์˜ wiki/claims.json์ด๋‹ค.

7. Step 7: wiki/claims.json ํ†ตํ•ฉ

mergeSourceClaims(existing, updates, currentSourcePaths)๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

์œ„์—์„œ ์•„๋ž˜๋กœ ์ฝ์œผ๋ฉด ๋œ๋‹ค. ๋จผ์ € DB์— ์ €์žฅ๋œ ๊ธฐ์กด source ๋ชฉ๋ก๊ณผ ํ˜„์žฌ ๋””์Šคํฌ scan ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์ณ โ€œํ˜„์žฌ ์‚ด์•„์žˆ๋Š” source ๋ชฉ๋กโ€์„ ๋งŒ๋“ ๋‹ค. ๊ทธ ๋‹ค์Œ ๊ธฐ์กด wiki/claims.json๊ณผ ์ด๋ฒˆ ์‹คํ–‰์—์„œ ์ƒˆ๋กœ ์ถ”์ถœํ•œ claim์„ ๋น„๊ตํ•œ๋‹ค. ๋งˆ์ง€๋ง‰์œผ๋กœ ์‚ฌ๋ผ์ง„ source์˜ claim์€ ์ œ๊ฑฐํ•˜๊ณ , ์ด๋ฒˆ์— ๋‹ค์‹œ ์ฒ˜๋ฆฌ๋œ source์˜ claim์€ ์ƒˆ claim์œผ๋กœ ๊ต์ฒดํ•œ๋‹ค.

A.new๋Š” A.md์˜ hash๊ฐ€ ๋ฐ”๋€Œ์–ด LLM์ด ๋‹ค์‹œ ์ƒ์„ฑํ•œ ์ด๋ฒˆ ์‹คํ–‰์˜ ์ƒˆ claim์„ ๋œปํ•œ๋‹ค.

sequenceDiagram
  participant DB as DB ๊ธฐ์กด source ๋ชฉ๋ก
  participant Disk as ํ˜„์žฌ ๋””์Šคํฌ scan
  participant Current as ํ˜„์žฌ source ๋ชฉ๋ก
  participant Prior as ๊ธฐ์กด claims.json
  participant Updates as ์ด๋ฒˆ ์‹คํ–‰ claim
  participant Merge as claim ํ†ตํ•ฉ ๋กœ์ง
  participant Out as ์ตœ์ข… claims.json

  DB->>Current: ๊ธฐ์กด์— ์•Œ๋˜ path๋กœ ์‹œ์ž‘<br/>A.md, B.md, C.md
  Disk->>Current: ์ง€๊ธˆ ๋ฐœ๊ฒฌ๋œ path ๋ฐ˜์˜<br/>A.md, B.md
  Current->>Current: ํ˜„์žฌ source์— ์—†๋Š” path ์ œ๊ฑฐ<br/>C.md ์ œ๊ฑฐ
  Prior->>Merge: ๊ธฐ์กด claim ์ „๋‹ฌ<br/>A.old:c2, B.old:c3, C.old:c2
  Updates->>Merge: ์ด๋ฒˆ์— ์ƒˆ๋กœ ๊ฒ€์ฆ๋œ claim ์ „๋‹ฌ<br/>A.new:c2, B.new:c3
  Current->>Merge: ํ˜„์žฌ ์œ ํšจํ•œ source ์ „๋‹ฌ<br/>A.md, B.md
  Merge->>Merge: ์‚ฌ๋ผ์ง„ source์˜ claim ์ œ๊ฑฐ<br/>C.old:c2 ์ œ๊ฑฐ
  Merge->>Merge: ๋‹ค์‹œ ์ฒ˜๋ฆฌ๋œ source์˜ ๊ธฐ์กด claim ์ œ๊ฑฐ<br/>A.old:c2, B.old:c3 ์ œ๊ฑฐ
  Merge->>Merge: ์ด๋ฒˆ ์‹คํ–‰ claim ์ถ”๊ฐ€<br/>A.new:c2, B.new:c3 ์ถ”๊ฐ€
  Merge->>Out: ์ตœ์ข… registry ์ €์žฅ<br/>A.new:c2, B.new:c3

7-1. merge ์‹œ์ž‘ ์ „ wiki/claims.json

์•„๋ž˜๋Š” ์ด์ „ ์‹คํ–‰ ๊ฒฐ๊ณผ๊ฐ€ ๋‚จ์•„ ์žˆ๋Š” ์ƒํ™ฉ์ด๋‹ค.

{
  "version": 1,
  "claims": [
    {
      "claimKey": "src:2d63174e212b2fdb:c2",
      "subject": "Matcher",
      "sourcePath": "A.md",
      "statement": "OLD: A.md์˜ ์ด์ „ Matcher ์„ค๋ช…"
    },
    {
      "claimKey": "src:b5038c9fd9be6c6f:c3",
      "subject": "Matcher",
      "sourcePath": "B.md",
      "statement": "OLD: B.md์˜ ์ด์ „ Matcher ์„ค๋ช…"
    },
    {
      "claimKey": "src:removedsource0000:c2",
      "subject": "Removed Concept",
      "sourcePath": "C.md",
      "statement": "C.md๋Š” ์ด๋ฒˆ source set์— ์—†์Œ"
    }
  ]
}

7-2. ์ด๋ฒˆ ์‹คํ–‰ ์ž…๋ ฅ

{
  "currentSourcePaths": ["A.md", "B.md"],
  "updates": [
    {
      "claimKey": "src:2d63174e212b2fdb:c2",
      "subject": "Matcher",
      "sourcePath": "A.md",
      "statement": "NEW: A.md์—์„œ ์ƒˆ๋กœ ๊ฒ€์ฆ๋œ Matcher ์„ค๋ช…"
    },
    {
      "claimKey": "src:b5038c9fd9be6c6f:c3",
      "subject": "Matcher",
      "sourcePath": "B.md",
      "statement": "NEW: B.md์—์„œ ์ƒˆ๋กœ ๊ฒ€์ฆ๋œ Matcher ์„ค๋ช…"
    }
  ]
}

7-3. mergeSourceClaims ๊ณผ์ •

// 1) currentSourcePaths์— ์—†๋Š” claim ์ œ๊ฑฐ
// - C.md claim ์ œ๊ฑฐ
 
// 2) ์ด๋ฒˆ updates์— ํฌํ•จ๋œ source์˜ ๊ธฐ์กด claim ์ œ๊ฑฐ
// - A.md old claim ์ œ๊ฑฐ
// - B.md old claim ์ œ๊ฑฐ
 
// 3) updates claim ์ถ”๊ฐ€
// - A.md new claim ์ถ”๊ฐ€
// - B.md new claim ์ถ”๊ฐ€
 
// 4) claimKey ๊ธฐ์ค€ Map์œผ๋กœ ์ •๋ฆฌ ํ›„ ์ •๋ ฌ

7-4. merge ํ›„ wiki/claims.json

์˜ˆ์‹œ ์ตœ์ข… wiki/claims.json ํ•ต์‹ฌ ํ˜•ํƒœ:

{
  "version": 1,
  "claims": [
    {
      "claimId": "c2",
      "claimKey": "src:2d63174e212b2fdb:c2",
      "kind": "concept",
      "subject": "Matcher",
      "statement": "Matcher๋Š” ์š”์ฒญ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•ด ๋Œ€์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.",
      "sourcePath": "A.md",
      "sourceHash": "f0cc8c7b6269d5a4094467448e032b8d05c10bc4bee9a5525b7102ecc40e7695",
      "evidenceIds": ["src:2d63174e212b2fdb:e2"],
      "evidence": [
        {
          "id": "src:2d63174e212b2fdb:e2",
          "sourcePath": "A.md",
          "sourceHash": "f0cc8c7b6269d5a4094467448e032b8d05c10bc4bee9a5525b7102ecc40e7695",
          "lineStart": 2,
          "lineEnd": 2,
          "quote": "Matcher๋Š” ์š”์ฒญ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•ด ๋Œ€์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค."
        }
      ]
    },
    {
      "claimId": "c3",
      "claimKey": "src:b5038c9fd9be6c6f:c3",
      "kind": "concept",
      "subject": "Matcher",
      "statement": "Matcher๋Š” ๋„๊ตฌ ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋Š” ๊ทœ์น™ ์ง‘ํ•ฉ์ด๋‹ค.",
      "sourcePath": "B.md",
      "sourceHash": "5fddc130a5be77b2b79abe79f96d37cfd925511c276e34b3a0b80883c2b970c0",
      "evidenceIds": ["src:b5038c9fd9be6c6f:e3"],
      "evidence": [
        {
          "id": "src:b5038c9fd9be6c6f:e3",
          "sourcePath": "B.md",
          "sourceHash": "5fddc130a5be77b2b79abe79f96d37cfd925511c276e34b3a0b80883c2b970c0",
          "lineStart": 4,
          "lineEnd": 4,
          "quote": "Matcher๋Š” ๋„๊ตฌ ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋Š” ๊ทœ์น™ ์ง‘ํ•ฉ์ด๋‹ค."
        }
      ]
    }
  ]
}

์‹œ๊ฐ์ ์œผ๋กœ ๋ณด๋ฉด:

flowchart LR
  AC2["A.md Matcher claim<br/>src:2d63174e212b2fdb:c2"] --> REG["wiki/claims.json"]
  BC3["B.md Matcher claim<br/>src:b5038c9fd9be6c6f:c3"] --> REG

  REG --> KEEP["๋‘ claim ๋ชจ๋‘ ๋ณด์กด<br/>dedupe ์—†์Œ"]

8. Step 8: concept wiki ์ƒ์„ฑ

์ด ๋‹จ๊ณ„์—์„œ kind === "concept" claim๋งŒ ๋ชจ์€๋‹ค.

๊ทธ๋ฃน ํ‚ค:

conceptGroupKey = claim.subject.normalize("NFC").trim().toLocaleLowerCase()

์˜ˆ์‹œ์—์„œ๋Š” ๋‘ claim ๋ชจ๋‘ subject=Matcher์ด๋ฏ€๋กœ ๊ฐ™์€ ๊ทธ๋ฃน์ด ๋œ๋‹ค.

flowchart TD
  REG["wiki/claims.json"] --> FILTER["kind === concept"]
  FILTER --> ACLAIM["A.md<br/>subject=Matcher<br/>claimKey=src:2d63174e212b2fdb:c2"]
  FILTER --> BCLAIM["B.md<br/>subject=Matcher<br/>claimKey=src:b5038c9fd9be6c6f:c3"]

  ACLAIM --> GROUP["group key: matcher"]
  BCLAIM --> GROUP

  GROUP --> PAGE["wiki/concepts/Matcher.md"]
  PAGE --> SEC_A["section A.md<br/>A claim statement<br/>A evidence quote"]
  PAGE --> SEC_B["section B.md<br/>B claim statement<br/>B evidence quote"]

์˜ˆ์‹œ wiki/concepts/Matcher.md ํ•ต์‹ฌ ํ˜•ํƒœ:

---
concept: "Matcher"
claim_keys: ["src:2d63174e212b2fdb:c2", "src:b5038c9fd9be6c6f:c3"]
sources: ["A.md", "B.md"]
generated_at: "..."
---
 
# Matcher
 
## A.md
 
Matcher๋Š” ์š”์ฒญ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•ด ๋Œ€์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
 
> [!quote] A.md:2-2
> 
> Matcher๋Š” ์š”์ฒญ ์กฐ๊ฑด์„ ํ‰๊ฐ€ํ•ด ๋Œ€์ƒ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
 
## B.md
 
Matcher๋Š” ๋„๊ตฌ ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋Š” ๊ทœ์น™ ์ง‘ํ•ฉ์ด๋‹ค.
 
> [!quote] B.md:4-4
> 
> Matcher๋Š” ๋„๊ตฌ ํ˜ธ์ถœ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๋Š” ๊ทœ์น™ ์ง‘ํ•ฉ์ด๋‹ค.

9. Dedupe๊ฐ€ ์•„๋‹ˆ๋ผ concept projection์ธ ์ด์œ 

flowchart LR
  subgraph "dedupe๋ผ๋ฉด"
    DA["A.md Matcher claim"] --> D["ํ•˜๋‚˜์˜ ํ†ตํ•ฉ claim"]
    DB["B.md Matcher claim"] --> D
  end

  subgraph "ํ˜„์žฌ llm-wiki"
    CA["A.md Matcher claim<br/>claimKey A:c2"] --> R["wiki/claims.json<br/>claim 2๊ฐœ ์œ ์ง€"]
    CB["B.md Matcher claim<br/>claimKey B:c3"] --> R
    R --> P["wiki/concepts/Matcher.md<br/>๊ฐ™์€ ํŽ˜์ด์ง€์— source๋ณ„ ์„น์…˜์œผ๋กœ ํ‘œ์‹œ"]
  end
๊ตฌ๋ถ„dedupeํ˜„์žฌ concept wiki
claim ๊ฐœ์ˆ˜์ค„์–ด๋“ ๋‹ค์œ ์ง€๋œ๋‹ค
claimKeyํ•˜๋‚˜๋กœ ํ•ฉ์ณ์งˆ ์ˆ˜ ์žˆ๋‹คsource-aware key ๊ทธ๋Œ€๋กœ ์œ ์ง€
์›๋ณธ ์ถ”์ ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ๋‹คsourcePath/sourceHash/evidence line ์œ ์ง€
๋ชฉ์ ์ค‘๋ณต ์ œ๊ฑฐ๊ฐ™์€ ๊ฐœ๋…์˜ claim๋“ค์„ ํ•œ ํŽ˜์ด์ง€์— ๋ชจ์•„ ๋ณด๊ธฐ

10. ID ์—ญ์ถ”์  ๋ฐฉ๋ฒ•

wiki/concepts/Matcher.md์—์„œ ์›๋ณธ์œผ๋กœ ๋Œ์•„๊ฐ€๋Š” ๊ฒฝ๋กœ:

flowchart RL
  PAGE["wiki/concepts/Matcher.md"] --> FK["frontmatter claim_keys"]
  FK --> CK["claimKey<br/>src:2d63174e212b2fdb:c2"]
  CK --> REG["wiki/claims.json์—์„œ claim lookup"]
  REG --> SP["sourcePath=A.md"]
  REG --> SH["sourceHash=A hash"]
  REG --> EV["evidenceId=src:2d63174e212b2fdb:e2<br/>lineStart=2<br/>lineEnd=2"]
  EV --> SRC["A.md ์›๋ฌธ 2๋ฒˆ์งธ ์ค„"]

์ฒดํฌ๋ฆฌ์ŠคํŠธ:

  1. concept wiki frontmatter์˜ claim_keys๋ฅผ ํ™•์ธํ•œ๋‹ค.
  2. ๊ฐ™์€ claimKey๋ฅผ wiki/claims.json์—์„œ ์ฐพ๋Š”๋‹ค.
  3. ํ•ด๋‹น claim์˜ sourcePath, sourceHash, evidence[].lineStart-lineEnd๋ฅผ ํ™•์ธํ•œ๋‹ค.
  4. sourcePath ์›๋ฌธ ํŒŒ์ผ์˜ ํ•ด๋‹น line range๋ฅผ ๋ณด๋ฉด claim ๊ทผ๊ฑฐ๋กœ ๋Œ์•„๊ฐ„๋‹ค.
  5. chunk ๋‹จ์œ„๊ฐ€ ํ•„์š”ํ•˜๋ฉด ๊ฐ™์€ sourcePath์—์„œ lineStart-lineEnd๋ฅผ ํฌํ•จํ•˜๋Š” source chunk๋ฅผ ์ฐพ๋Š”๋‹ค.

11. ํ•œ ์ค„ ๋ชจ๋ธ

Source chunk๋Š” ์›๋ฌธ ์œ„์น˜๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
Claim์€ source-aware key์™€ evidence๋กœ ์›๋ฌธ์— ๊ณ ์ •๋œ๋‹ค.
claims.json์€ claim์„ dedupeํ•˜์ง€ ์•Š๊ณ  source ๋‹จ์œ„๋กœ ๊ต์ฒด/๋ณด์กดํ•œ๋‹ค.
concept wiki๋Š” ๊ฐ™์€ subject์˜ concept claim๋“ค์„ ํ•œ ํŽ˜์ด์ง€์— ๋ชจ์•„ ๋ณด์—ฌ์ฃผ๋Š” projection์ด๋‹ค.

12. ์ฝ”๋“œ ๊ธฐ์ค€ ์œ„์น˜

๊ด€์‹ฌ์‚ฌ์ฝ”๋“œ ๊ธฐ์ค€
source chunk ๊ตฌ์กฐsrc/markdown.ts MarkdownChunk, chunkMarkdown()
DB source chunk idsrc/index-store.ts stableChunkId(sourcePath, sourceHash, ordinal, heading)
sourceId / claimKeysrc/claims.ts stableSourceId(), sourceClaimKey()
LLM ์‘๋‹ต โ†’ SourceClaim ์ƒ์„ฑsrc/wiki-generator.ts buildSourceClaims()
source-owned generated wiki pathsrc/wiki-generator.ts wikiPagePath()
generated wiki ์ €์žฅ/checkpointsrc/indexer.ts enqueueWrite() / step6Payload()
wiki/claims.json ํ†ตํ•ฉsrc/indexer.ts mergeSourceClaims() ํ˜ธ์ถœ, src/claims.ts mergeSourceClaims()
concept wiki grouping/writesrc/indexer.ts writeMergedConceptPages()

๊ฒฝ๋กœ ํ‘œ๊ธฐ ๊ธฐ์ค€:

  • ์ฝ”๋“œ์˜ wikiPagePath("A.md") ๊ฒฐ๊ณผ๋Š” generated/A.wiki.md์ด๋‹ค.
  • ๋ฌธ์„œ์—์„œ๋Š” runtime wiki root๋ฅผ ๋“œ๋Ÿฌ๋‚ด๊ธฐ ์œ„ํ•ด wiki/generated/A.wiki.md์ฒ˜๋Ÿผ ํ‘œ๊ธฐํ–ˆ๋‹ค.
  • concept page๋Š” runtime wiki root ์•„๋ž˜ wiki/concepts/Matcher.md๋กœ ์ดํ•ดํ•˜๋ฉด ๋œ๋‹ค.

13. ํ˜„์žฌ ๋ฐฉ์‹์˜ ํ•œ๊ณ„์™€ ๊ฐœ์„  ๋ฐฉํ–ฅ

ํ˜„์žฌ ๋ฐฉ์‹์€ source file ๋‹จ์œ„ ๊ต์ฒด๋‹ค. ์ฆ‰ A.md์˜ chunk 10๊ฐœ ์ค‘ 1๊ฐœ๋งŒ ๋ฐ”๋€Œ์–ด๋„ A.md ์ „์ฒด hash๊ฐ€ ๋ฐ”๋€Œ๊ณ , A.md์—์„œ ๋‚˜์˜จ claim set ์ „์ฒด๊ฐ€ ๋‹ค์‹œ ์ƒ์„ฑยท๊ต์ฒด๋œ๋‹ค.

flowchart LR
  A["A.md<br/>chunk 10๊ฐœ"] --> C["chunk #4๋งŒ ์ˆ˜์ •"]
  C --> H["A.md ์ „์ฒด sourceHash ๋ณ€๊ฒฝ"]
  H --> LLM["A.md ์ „์ฒด๋ฅผ LLM ์žฌ์ฒ˜๋ฆฌ"]
  LLM --> OLD["๊ธฐ์กด A.md claim 5๊ฐœ ์ œ๊ฑฐ"]
  LLM --> NEW["์ƒˆ A.md claim set ์ „์ฒด ์ถ”๊ฐ€"]

์˜ˆ์‹œ:

์‹ค์ œ ์˜ํ–ฅ:
- chunk #4๋งŒ ๋ณ€๊ฒฝ
- ๊ธฐ์กด claim 5๊ฐœ ์ค‘ c2๋งŒ ์˜๋ฏธ์ƒ ์˜ํ–ฅ
 
ํ˜„์žฌ ์ €์žฅ ๊ฒฐ๊ณผ:
- A.old:c1~c5 ์ œ๊ฑฐ
- A.new:c1~c5 ์ถ”๊ฐ€

ํ•œ๊ณ„:

  • ์ž‘์€ ์ˆ˜์ •๋„ source ์ „์ฒด claim churn์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
  • LLM ์žฌ์ƒ์„ฑ ๋•Œ๋ฌธ์— ์˜ํ–ฅ ์—†๋Š” claim์˜ ๋ฌธ์žฅ, ์ˆœ์„œ, evidence๊ฐ€ ํ”๋“ค๋ฆด ์ˆ˜ ์žˆ๋‹ค.
  • concept wiki์™€ ๊ฒ€์ƒ‰ index/ranking๋„ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋ณ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ฐœ์„  ๋ฐฉํ–ฅ:

source file ๋‹จ์œ„ ๊ต์ฒด
โ†’ chunk hash ๋น„๊ต
โ†’ ๋ณ€๊ฒฝ chunk ์ฐพ๊ธฐ
โ†’ ํ•ด๋‹น chunk์— ์—ฐ๊ฒฐ๋œ claim๋งŒ stale ์ฒ˜๋ฆฌ
โ†’ stale claim๋งŒ LLM patch
โ†’ ์˜ํ–ฅ๋ฐ›์€ concept wiki๋งŒ ์žฌ์ƒ์„ฑ

ํ•ต์‹ฌ์€ claim์— dependency๋ฅผ ๋ช…์‹œํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

{
  "claimKey": "src:2d63174e212b2fdb:c2",
  "sourcePath": "A.md",
  "dependsOnChunks": ["A.md#4"],
  "evidenceIds": ["src:2d63174e212b2fdb:e2"]
}

์ด๋ ‡๊ฒŒ ๋˜๋ฉด A.md#4๋งŒ ๋ฐ”๋€ ๊ฒฝ์šฐ c2๋งŒ ์žฌ๊ฒ€์ฆ/์ˆ˜์ •ํ•˜๊ณ , ๋‚˜๋จธ์ง€ claim์€ ๊ทธ๋Œ€๋กœ ๋ณด์กดํ•  ์ˆ˜ ์žˆ๋‹ค.