genetics-mcp 2.26.0 → 2.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aarch64-apple-darwin/gcell +0 -0
- package/bin/aarch64-unknown-linux-musl/gcell +0 -0
- package/bin/manifest.json +5 -5
- package/bin/x86_64-apple-darwin/gcell +0 -0
- package/bin/x86_64-pc-windows-gnu/gcell.exe +0 -0
- package/bin/x86_64-unknown-linux-musl/gcell +0 -0
- package/genetics.mjs +1527 -191
- package/package.json +1 -1
- package/skills/gazette-report/SKILL.md +11 -1
- package/skills/gazette-report/scripts/tone.mjs +30 -3
- package/skills/gazette-report/scripts/tone.test.mjs +31 -0
- package/skills/genetics-cell-id/SKILL.md +73 -54
- package/skills/genetics-define/SKILL.md +34 -1
- package/skills/genetics-report/SKILL.md +67 -69
- package/skills/genetics-report/assets/example/cases-briefing-designs.json +15 -5
- package/skills/genetics-report/assets/example/cases-briefing.json +14 -5
- package/skills/genetics-report/assets/example/cases.json +25 -9
- package/skills/genetics-report/assets/example/decision.json +313 -0
- package/skills/genetics-report/assets/example/report-briefing-designs.json +2 -2
- package/skills/genetics-report/assets/example/report-briefing.json +1 -1
- package/skills/genetics-report/assets/example/report.json +65 -21
- package/skills/genetics-report/assets/render.py +204 -49
- package/skills/genetics-report/references/content-pipeline.md +6 -6
- package/skills/genetics-report/references/decision-format.md +112 -0
- package/skills/genetics-report/references/diagrams.md +52 -94
- package/skills/genetics-report/references/edition-review.workflow.js +81 -31
- package/skills/genetics-report/references/history.md +35 -0
- package/skills/genetics-report/references/lim-portal-format.md +92 -236
- package/skills/genetics-report/references/linear-read-test.workflow.js +33 -10
- package/skills/genetics-report/references/process.md +32 -25
- package/skills/genetics-report/references/readability.md +9 -8
- package/skills/genetics-report/references/reader-review.workflow.js +167 -25
- package/skills/genetics-report/references/visual-first.md +54 -42
- package/skills/genetics-report/assets/diagram.mjs +0 -208
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 독자 검토 워크플로 — 발행 전에 원고를
|
|
1
|
+
// 독자 검토 워크플로 — 발행 전에 원고를 아홉 관점(상태 표가 있으면 열)으로 읽히고, 발견을 반박 검증해 거른 뒤 합친다(visual-first.md §7).
|
|
2
2
|
// 실행: Workflow 도구에 이 파일을 scriptPath 로 주고 args 를 넘긴다. 배열·객체는 JSON 값 그대로 넘긴다(문자열로 싸면 한 덩어리가 된다).
|
|
3
3
|
// args = { file: "<report.json 절대경로>",
|
|
4
4
|
// text: "<렌더본 텍스트 절대경로(필수) — `python3 assets/render.py <report.json> --text <원고 폴더 밖 빈 폴더>/본문.txt`>",
|
|
@@ -9,9 +9,19 @@
|
|
|
9
9
|
// locked: ["<정해진 것 한 줄>", …], latest: "<사용자 최신 지적 원문>",
|
|
10
10
|
// sources: [{ name: "<자료 이름>", path: "<절대경로>", kind: "code|doc|pdf" }, …],
|
|
11
11
|
// decisions: "<0단계 결정 장부 decisions.json 경로(선택)>",
|
|
12
|
-
// state: "<코드 작업 상태 표 경로(선택) — 주면 state 관점이 켜진다>"
|
|
12
|
+
// state: "<코드 작업 상태 표 경로(선택) — 주면 state 관점이 켜진다>",
|
|
13
|
+
// handoff: "<HANDOFF.md 경로(선택)>", evidence_plan: "<0단계 증거 계획 evidence.md 경로(선택)>", // 둘 다 증거 관점만 받는다
|
|
14
|
+
// skill: "<스킬 폴더 절대경로(선택) — 기본 ~/.claude/skills/genetics-report>", // 도판 관점·반박이 읽을 스킬 참고 파일이 이 아래에 있다
|
|
15
|
+
// mode: "decision"(결정 블록이 있는 원고일 때만 — 옛 template: "lim-decision" 도 받는다), intents: [{ decision: "<결정 이름>", intent: "<이 장이 말하려던 것 한 문장>" }, …] }
|
|
16
|
+
// 결정 모드(결정 블록 decision_brief.decisions 가 있는 원고)는 아래 아홉 관점 대신 원고를 모르는 독자 한 번이다(2026-10-06 사용자 승인 · decision-format.md 6절):
|
|
17
|
+
// 독자(렌더본만 · Explore)가 결정 장마다 자기 한 문장을 쓰고 → 작성자 문장(intents — 독자에게는 주지 않는다)과 맞대 갈린 자리를 칸 이름으로 옮긴다.
|
|
18
|
+
// 돌아오는 것: { mode, sheets[](decision·intent·reader·blocked·split·field·fix), unclaimed[], asked[], hidden[], four{depth·judgement·subject·links}, overall }
|
|
19
|
+
// — 점수·개수·심각도는 없다. 실패는 head 「검토 미완 — …」 와 failed: true.
|
|
13
20
|
// 단계: Read(관점 병렬) → Verify(severity≥3 최대 12건 반박 — 읽기·구조 발견에 4칸 이상) → JS 거름(dropped·questions·사실 틀림 가르기) → Merge(합쳐 상위 10).
|
|
14
21
|
// 돌아오는 것: { merged: { findings[](ids·where·problem·severity·class·evidence·rewrite·visual·rx·verdicts), overall },
|
|
22
|
+
// judgement: { task(할 수 있음|할 수 없음|모름) · task_by[] · evidence_missing · evidence_claims · filled_lines } — 두 판정을 따로 센다,
|
|
23
|
+
// evidence: { rows[](증거 관점 표 전부 — 줄·주장·작성자 근거·문서 안 증거·닿나·처방), missing[](문서 안 증거 「없음」 행) },
|
|
24
|
+
// filled[](렌더본만 읽은 독자가 증거 줄을 못 댄 관찰 주장 — 일반 지식으로 메운 것),
|
|
15
25
|
// questions[](작성자가 사람에게 물을 것), passed[](관점별로 재서 통과한 것 — 「안 봄」과 가른다),
|
|
16
26
|
// dropped[](버린 발견 + reasons — 사람이 되살릴 수 있게), jumps[](linear 관점의 뜀 지점 전부 — 0 이 목표),
|
|
17
27
|
// names[](class 이름 발견 전부 — 문서가 붙인 이름·정의 없는 이름, 0 이 목표),
|
|
@@ -20,6 +30,7 @@
|
|
|
20
30
|
// failed: true 가 붙고, merged.overall 첫 줄도 같은 문장이다 — 실패를 「발견 0」으로 읽지 않게.
|
|
21
31
|
// 렌더본 관점(first-time·linear)이 파일을 못 열었다고 답하면(opened=false) 그 관점의 발견은 쓰지 않고 head 에 「렌더본 못 엶」을 더한다.
|
|
22
32
|
// 시작 전 검사(Check): args.file · args.text 가 없거나, 렌더본 파일이 없거나 비었거나 원고보다 오래되면 읽기 전에 멈추고 무엇이 필요한지 말한다.
|
|
33
|
+
// 스킬 폴더(args.skill)에 references/readability.md 가 없어도 멈춘다 — 조사관이 그 문서를 찾으러 디스크를 뒤지기 전에.
|
|
23
34
|
// 워크플로 스크립트는 파일을 못 연다(Workflow 런타임 — 파일 시스템·Node API 없음). 그래서 에이전트 하나가 정해진 명령 한 줄을 돌려
|
|
24
35
|
// 표준 출력을 글자 그대로 옮기고, 판정은 JS 가 한다. 멈춘 뒤 렌더본을 다시 뽑았으면 resumeFromRunId 없이 새로 띄운다(검사 결과도 캐시를 탄다).
|
|
25
36
|
// merged.findings 앞에는 사실 틀림(class 사실틀림) 가운데 **Verify 가 참·부분으로 받은 것**이 합치지 않은 독립 항목으로 서고 개수 제한 밖이다.
|
|
@@ -48,41 +59,62 @@
|
|
|
48
59
|
// ② 발견이 사람에게 닿지 않았다: 검증 12칸이 사실 틀림(+10 가중)으로 차 읽기 발견이 늘 검증 밖이었고(15회 중 14회),
|
|
49
60
|
// 거를 사유가 하나만 있어도 버려 진단이 참인 발견 11건이 기각됐고, 사용량 한도로 검증 12건이 전부 실패한 실행이 「반영할 발견 0」으로 찍혔다.
|
|
50
61
|
// 그래서 읽기·구조에 4칸을 떼어 두고, 진단(참/부분/거짓/미확인)과 처방 적합을 따로 판정해 거짓만 버리고, 실패는 머리에 적는다.
|
|
62
|
+
// 2026-10-01 관보 합의 착각 수리(사용자 결정)에서 더한 것 — 작성자가 코드·로그·대화로 본 증거는 원고 밖에 두고 결론 문장만 냈는데,
|
|
63
|
+
// 어느 관점도 「독자가 문서 안에서 그 증거를 볼 수 있나」를 묻지 않았다(작성자 지식을 가진 관점은 참·확인 수준을, 렌더본 독자는 뜀을 물었다).
|
|
64
|
+
// 판 5 독자 1인은 「할 일 가능」만 물어 「예」를 냈고, 사람 메모 셋(증빙 요구)은 어떤 검토도 재현하지 못했다. 그래서 두 쪽을 다 받는
|
|
65
|
+
// 새 관점 evidence(관찰 주장마다 표 한 행 — 상한 없음)·class 증거·visual 칸 capture·video(도판 예산 밖)를 더하고, 렌더본 독자에게
|
|
66
|
+
// 주장마다 증거 줄을 대게 하고(claims — 못 대면 메운 것), 판정을 「할 일」과 「증거 없는 관찰 주장 수」 둘로 나눴다.
|
|
51
67
|
// 집행자: 이 워크플로의 agent 읽기(스크립트) + 거름 규칙(JS, 결정적). 렌더러 ⚠ 는 없음 — 링크·도판·코드를 못 여는 환경이면 그 판정은 「미확인」,
|
|
52
68
|
// 못 연 도판 수는 overall 머리에 박히고, questions[]·dropped[] 는 사람의 눈으로 넘긴다.
|
|
53
69
|
// PART·절 사이 되풀이도 렌더러·서버 dry_run 이 세지 않는다 — structure 관점이 유일한 기계 쪽 읽기다(readability R12g).
|
|
54
70
|
export const meta = {
|
|
55
71
|
name: 'report-reader-review',
|
|
56
|
-
description: '보고서를 처음 읽는 사람·선형 읽기(두 관점은 렌더본 텍스트만)·결정권자·정보 디자이너·원본 대조·구조·도판·사실
|
|
72
|
+
description: '보고서를 처음 읽는 사람·선형 읽기(두 관점은 렌더본 텍스트만)·결정권자·정보 디자이너·원본 대조·구조·도판·사실 대조·증거 가시성(상태 표가 있으면 상태 대조까지) 관점으로 읽고, 발견을 반박 검증해 진단이 거짓인 것만 거르고(처방이 규칙에 걸리면 「처방 다시」) 합친다 — 판정은 할 일과 증거 없는 관찰 주장 수를 따로',
|
|
57
73
|
phases: [
|
|
58
|
-
{ title: 'Check', detail: '렌더본이 있고 비지 않았고 원고보다 새로운지 — 명령 한 줄' },
|
|
59
|
-
{ title: 'Read', detail: '독자
|
|
74
|
+
{ title: 'Check', detail: '렌더본이 있고 비지 않았고 원고보다 새로운지 · 스킬 참고 파일이 기준 경로에 있는지 — 명령 한 줄' },
|
|
75
|
+
{ title: 'Read', detail: '독자 9관점 병렬(상태 표가 있으면 10)' },
|
|
60
76
|
{ title: 'Verify', detail: 'severity≥3 최대 12건(읽기·구조 4칸 이상) 반박 · 진단·처방 따로 · JS 거름' },
|
|
61
77
|
{ title: 'Merge', detail: '사실 틀림 독립 · 나머지 상위 10 · 구조 빼기 우선' },
|
|
62
78
|
],
|
|
63
79
|
}
|
|
64
80
|
const A = args || {}
|
|
65
|
-
|
|
81
|
+
// 스킬 폴더 — 조사관에게 스킬 참고 파일을 가리킬 때는 늘 이 경로에서 시작한다(판 갱신 검토의 patch.py 기본값과 같은 관례).
|
|
82
|
+
// 이름만 주자 도판 관점이 스킬 자리를 몰라 `find /` 로 디스크 전체를 뒤졌다(2026-10-06, 한 실행에 두 번 각 2분 시간 초과 — 이 맥에는 내용이 다른 옛 사본도 있다).
|
|
83
|
+
// `~` 는 여기서 못 푼다(Workflow 런타임 — 파일 시스템 없음). 시작 전 검사의 파이썬이 풀고, 조사관의 Read 는 그대로 연다.
|
|
84
|
+
const SKILL = A.skill || '~/.claude/skills/genetics-report'
|
|
85
|
+
const MAKE_TEXT = `python3 ${SKILL}/assets/render.py <report.json> --text <원고 폴더 밖 빈 폴더>/본문.txt`
|
|
66
86
|
if (!A.text) throw new Error(`args.text 가 필요하다 — 렌더본 텍스트 파일 절대경로. 「처음 읽는 사람」·「선형 읽기」 관점은 이 파일 하나만 읽는다. 만드는 법: ${MAKE_TEXT}`)
|
|
67
87
|
if (!A.file) throw new Error('args.file 이 필요하다 — 원고 report.json 절대경로. 나머지 관점이 읽고, 렌더본이 이 원고보다 새로운지 잰다')
|
|
68
88
|
const FILE = A.file
|
|
69
89
|
const TEXT = A.text
|
|
90
|
+
// 작성자 문장이 없으면 맞댈 것이 없다 — 에이전트를 부르기 전에 멈춘다.
|
|
91
|
+
// 관보 v3(2026-10-07 ⑨): 서식은 하나이고 모드는 원고 내용이 정한다 — 부르는 쪽이 mode 로 알린다(옛 인자 template 도 받는다).
|
|
92
|
+
const DECISION = A.mode === 'decision' || A.template === 'lim-decision'
|
|
93
|
+
const INTENTS = Array.isArray(A.intents) ? A.intents.filter((x) => x && String(x.decision || '').trim() && String(x.intent || '').trim()) : []
|
|
94
|
+
if (DECISION && !INTENTS.length) throw new Error('args.intents 가 필요하다 — 결정 모드 원고는 결정 장마다 「이 장이 말하려던 것」 한 문장을 먼저 적는다: [{ decision: "<결정 이름>", intent: "<한 문장>" }, …] (decision-format.md 6절)')
|
|
70
95
|
|
|
71
96
|
phase('Check')
|
|
72
|
-
// 렌더본이 있고 비지 않았고 원고보다 새로운지 — 파일 상태는 에이전트가 옮긴 명령 출력으로만 읽고 판정은 여기서 한다(머리 주석 「시작 전 검사」).
|
|
97
|
+
// 렌더본이 있고 비지 않았고 원고보다 새로운지 · 셋째 칸은 스킬 참고 파일(`~` 는 파이썬이 푼다) — 파일 상태는 에이전트가 옮긴 명령 출력으로만 읽고 판정은 여기서 한다(머리 주석 「시작 전 검사」).
|
|
73
98
|
const shq = (p) => `'${String(p).replace(/'/g, `'\\''`)}'`
|
|
74
|
-
const STAT_CMD = `python3 -c 'import json,os,sys; print(json.dumps([dict(f=os.path.isfile(p), b=(len(open(p,"rb").read().strip()) if os.path.isfile(p) else -1), m=(os.path.getmtime(p) if os.path.isfile(p) else -1)) for p in sys.argv[1:]]))' ${shq(TEXT)} ${shq(FILE)}`
|
|
99
|
+
const STAT_CMD = `python3 -c 'import json,os,sys; print(json.dumps([dict(f=os.path.isfile(p), b=(len(open(p,"rb").read().strip()) if os.path.isfile(p) else -1), m=(os.path.getmtime(p) if os.path.isfile(p) else -1)) for p in map(os.path.expanduser, sys.argv[1:])]))' ${shq(TEXT)} ${shq(FILE)} ${shq(`${SKILL}/references/readability.md`)}`
|
|
75
100
|
const PRE = { type: 'object', properties: { stdout: { type: 'string', description: '명령의 표준 출력 한 줄 — 글자 그대로' }, exit: { type: 'integer' } }, required: ['stdout', 'exit'] }
|
|
76
101
|
const pre = await agent(`아래 명령을 Bash 로 **한 번만, 글자 그대로** 실행하고 표준 출력 한 줄을 stdout 에 고치지 말고 옮긴다. 종료 코드는 exit 에. 명령을 바꾸거나 다른 명령·파일을 열지 않는다.\n${STAT_CMD}`,
|
|
77
102
|
{ label: 'check:files', phase: 'Check', schema: PRE, effort: 'low' })
|
|
78
103
|
let st = null
|
|
79
104
|
try { st = pre && pre.exit === 0 ? JSON.parse(pre.stdout) : null } catch (e) { st = null }
|
|
80
|
-
if (!Array.isArray(st) || st.length !==
|
|
105
|
+
if (!Array.isArray(st) || st.length !== 3) throw new Error(`시작 전 검사 실패 — 렌더본·원고·스킬 참고 파일 상태를 못 읽었다(${pre ? `exit ${pre.exit} · ${String(pre.stdout).slice(0, 200)}` : '검사 에이전트 결과 없음'}). resumeFromRunId 없이 새로 띄운다`)
|
|
81
106
|
if (!st[1].f) throw new Error(`args.file 파일이 없다 — ${FILE}. 원고 report.json 절대경로를 준다`)
|
|
82
107
|
if (!st[0].f) throw new Error(`args.text 파일이 없다 — ${TEXT}. 만드는 법: ${MAKE_TEXT}`)
|
|
83
108
|
if (st[0].b <= 0) throw new Error(`args.text 파일이 비었다 — ${TEXT}. 렌더가 실패했을 수 있다 — 다시 뽑고 출력의 ⚠·오류를 본다: ${MAKE_TEXT}`)
|
|
84
109
|
if (st[0].m < st[1].m) throw new Error(`렌더본이 원고보다 오래됐다 — ${TEXT} 가 ${FILE} 보다 ${Math.round(st[1].m - st[0].m)}초 앞선다. 원고를 고친 뒤 다시 뽑는다: ${MAKE_TEXT} (그다음 resumeFromRunId 없이 새로 띄운다)`)
|
|
110
|
+
if (!st[2].f) throw new Error(`스킬 참고 파일이 없다 — ${SKILL}/references/readability.md. args.skill 로 스킬 폴더를 넘긴다(SKILL.md 가 있는 폴더의 절대경로) — 도판 관점·반박이 그 아래 문서를 읽는다`)
|
|
85
111
|
const READER = A.reader || '고객사 기획 담당(비개발자). 판단 요청에 회신해야 한다'
|
|
112
|
+
// 독자 전제에 「…안다고 가정해도 된다」 류가 있으면 경고한다 — 독자가 무엇을 아는지를 작성자가 정하면 렌더본 독자도 그만큼 메워 읽는다
|
|
113
|
+
// (2026-10-01 관보 합의 착각 수리: 판 5 독자 프롬프트의 전제를 작성자가 썼다). 멈추지는 않는다 — 결과 머리에 남긴다.
|
|
114
|
+
const RX_ASSUME = /(?:안다|알고\s*있다|익숙하다)고\s*(?:가정|치|보|전제)|아는\s*것으로\s*(?:가정|치|보)|가정해도\s*(?:된다|좋다|됨)|이미\s*(?:안다|알고\s*있|아는)|설명(?:은|을)?\s*(?:없이도|안\s*해도|하지\s*않아도)/
|
|
115
|
+
const assumed = RX_ASSUME.exec(READER)
|
|
116
|
+
const readerWarn = assumed ? `⚠ 독자 전제(args.reader)에 독자 지식을 가정하는 말 「${assumed[0]}」 — 독자가 무엇을 아는지는 작성자가 정하지 않는다. 0단계에서 사용자가 확인한 독자 한 줄만 준다` : ''
|
|
117
|
+
if (readerWarn) log(readerWarn)
|
|
86
118
|
const EXAMPLE = A.example || '(예시 한 명 미지정 — 원고에서 찾아 쓰라)'
|
|
87
119
|
const BUDGET = Number(A.bitmapBudget ?? 3)
|
|
88
120
|
const SCRATCH = A.scratch || '(미지정 — 세션 스크래치 디렉토리 아래 reader-review/ 에 쓴다. 원고 폴더에는 쓰지 않는다)'
|
|
@@ -90,14 +122,16 @@ const PLATES = A.plates || '(미지정 — assets[].file 경로를 쓴다)'
|
|
|
90
122
|
const LOCKED = Array.isArray(A.locked) ? A.locked : (A.locked ? [String(A.locked)] : [])
|
|
91
123
|
const SOURCES = Array.isArray(A.sources) ? A.sources : []
|
|
92
124
|
const STATE = A.state || ''
|
|
125
|
+
const HANDOFF = A.handoff || ''
|
|
126
|
+
const PLAN = A.evidence_plan || ''
|
|
93
127
|
const VERIFY_MAX = 12
|
|
94
128
|
const CTX = `[원고·도판·저장소 파일은 수정하지 않는다. 측정 스크립트는 scratch 아래에만 쓴다. 한글로 답할 것.]
|
|
95
|
-
원고: ${FILE} (Lim 포털 관보 서식 보고서의 발행 JSON. parts[] PART(title·lede·questions — 그 PART 가 답하는 물음), sections[] 절(sections[].part 로 PART 에 속함), cases[] 건, open_items[] 판단 요청, summary, guide 읽는 법, appendix, assets
|
|
129
|
+
원고: ${FILE} (Lim 포털 관보 서식 보고서의 발행 JSON. parts[] PART(title·lede·questions — 그 PART 가 답하는 물음), sections[] 절(sections[].part 로 PART 에 속함), cases[] 건, open_items[] 판단 요청, summary, guide 읽는 법, appendix, assets 도판. 결정 블록이 있으면(결정 모드) 본체는 decision_brief — decisions[] 결정(물음·기한·지금 상태·options[] 선택지·대칭 표 rows·cost·no_reply·opinion) · confirms[] 확인 · facts[] 사실(basis·source·bears_on) · risks[]·unknowns[] · names[] 낯선 말 · scope · reply_to — 이고 sections[] 는 대장 절(note·cases)뿐)
|
|
96
130
|
도판: assets[].file 은 열어 볼 로컬 PNG 다. 서버 보관 도판(data 가 「(생략 — NB)」 표식)은 get_report 로 바이트를 받을 수 없다 — 도판 작업 폴더에 같은 도판의 원본 PNG 가 있으면 그것을 열고(파일 이름이 id 와 달라도 캡션·내용으로 찾는다), 없으면 unopened 에 그 장의 id 하나씩, why 를 「서버 보관」으로 둔다. 로고(role 이 logo)는 도판이 아니다 — unopened 에 넣지 않는다. 도판 작업 폴더: ${PLATES}
|
|
97
131
|
scratch: ${SCRATCH}
|
|
98
132
|
독자: ${READER}
|
|
99
133
|
예시 한 명: ${EXAMPLE}
|
|
100
|
-
도판 예산: 새 비트맵
|
|
134
|
+
도판 예산: 새 비트맵 그림(우리가 그리는 그림)은 ${BUDGET}장까지. 실제 화면 캡처(capture)·녹화(video)·표·flow(4열 흐름도)·charts(막대)는 예산 밖이라 자유롭게.
|
|
101
135
|
정해진 것(되돌리는 처방을 내지 않는다):
|
|
102
136
|
${LOCKED.length ? LOCKED.map((s) => `- ${s}`).join('\n') : '(지정 없음)'}
|
|
103
137
|
결정 장부: ${A.decisions || '(지정 없음)'}
|
|
@@ -108,10 +142,16 @@ ${STATE ? `상태 표: ${STATE}\n` : ''}작성자 기준: 「글은 직관적이
|
|
|
108
142
|
// 「처음 읽는 사람」·「선형 읽기」 관점의 머리말 — 위 CTX(정해진 것·결정 장부·사용자 최신 지적·내부 자료·상태 표)를 받지 않는다.
|
|
109
143
|
// 독자가 보는 것은 렌더본뿐이라 검토자도 렌더본에 보이는 글만 받는다(render.py --text — origin·alt·스크립트는 빠져 있다).
|
|
110
144
|
const TEXT_LENSES = new Set(['first-time', 'linear'])
|
|
145
|
+
// 그림 자리 표지와 메움 세기 — 「원고에 없는 지식으로 메웠다면 발견」 한 줄은 자기 신고라 증거 없는 화면 주장을 0~2 로 넘겼다
|
|
146
|
+
// (2026-10-01 관보 합의 착각 수리). 그래서 주장마다 이 파일 안 증거 줄을 대게 하고, 못 대면 메운 것으로 센다.
|
|
147
|
+
const MARKS = '그림·영상 자리는 「[그림] <도판 id> · <갈래>」「[영상] <id> · <갈래>」 한 줄이다 — 갈래는 캡처·녹화·도안·합성·문서이고 도안은 우리가 그린 설명 그림이다. 이 표지 줄과 글 끝의 「[그림 파일]」 목록은 검토용이라 독자 화면에 없다(그 id 를 이름 발견으로 세지 않는다).'
|
|
148
|
+
const FILL_STEP = '메움 세기 — 화면·흐름·화면 문구·동작을 말하는 문장(「…뜸·보임·켜짐·막힘·넘어감·저장됨」, 「」로 옮긴 화면 글자)마다 claims 에 한 행을 적는다: 그 줄 번호, 그리고 그것을 보여 주는 증거(캡처·녹화·합성 그림 자리 · 원문 인용 · 센 대상이 있는 표)가 선 이 파일의 본문 줄 번호(shown_at — 끝 목록 줄은 대지 않는다). 댈 줄이 없거나 도안뿐이면 「없음」 — 읽으며 떠올린 화면이 이 파일의 어느 줄에서 왔는지 못 대면 일반 지식으로 메운 것으로 센다.'
|
|
111
149
|
const TEXT_CTX = `[읽기 전용. 파일을 수정하지 말 것. 한글로 답할 것. 철저함: very thorough — 발췌하지 말고 파일 전체를 처음부터 끝까지 읽는다.]
|
|
112
|
-
읽을 것: ${TEXT} — 보고서 렌더본에서 화면에 보이는 글만 화면 순서대로 뽑은 텍스트(
|
|
113
|
-
이 파일 하나만
|
|
114
|
-
|
|
150
|
+
읽을 것: ${TEXT} — 보고서 렌더본에서 화면에 보이는 글만 화면 순서대로 뽑은 텍스트(표 칸은 「 | 」). ${MARKS}
|
|
151
|
+
이 파일 하나만 연다 — 끝 「[그림 파일]」 목록에 경로가 적힌 그림은 독자가 보는 그림이라 Read 로 열어 봐도 된다. 원고 JSON·같은 폴더의 다른 파일·저장소·메모·다른 문서는 열지 않는다.
|
|
152
|
+
${FILL_STEP}
|
|
153
|
+
이 파일을 끝까지 읽었으면 opened=true. 열지 못했거나 비어 있으면 opened=false 로 두고 findings·passed·claims 는 비운 채 overall 에 까닭을 적는다 — 다른 파일로 대신 읽지 않는다.
|
|
154
|
+
다 읽은 뒤 task 에 답한다 — 아래 독자가 이 파일의 글만으로 자기 할 일을 할 수 있나(ok)와 막히는 자리(why). 증거가 보이는지는 여기서 묻지 않는다(claims 가 따로 센다).
|
|
115
155
|
독자: ${READER}
|
|
116
156
|
where 는 이 파일에 보이는 절 번호·절 이름·건 번호·표 이름으로, evidence 는 이 파일의 줄 번호와 그 줄 글자로 적는다.`
|
|
117
157
|
// 문서가 붙인 이름을 묻는다 — 판정 기준이 「정의가 앞에 있나」뿐이면 문서 안에서 정의한 조어가 통과한다(2026-09-26 스킬 검토).
|
|
@@ -120,10 +160,10 @@ const FINDING = { type: 'object', properties: {
|
|
|
120
160
|
where: { type: 'string', description: '절·건·항목·도판 id 와 자리' },
|
|
121
161
|
problem: { type: 'string', description: '독자가 왜 막히는가, 또는 원고가 원본과 어떻게 다른가 — 한 문장' },
|
|
122
162
|
severity: { type: 'integer', minimum: 1, maximum: 5 },
|
|
123
|
-
class: { type: 'string', enum: ['사실틀림', '구조', '읽기', '이름', '시각', '묻기'], description: '사실틀림=원고의 수치·인용·앱 동작·상태·그림↔본문이 원본과 다르다 · 구조=PART·절 합치기·빼기 · 읽기=용어·흐름·문장 · 이름=대상·판·시험·상태·선택지를 이 문서가 붙인 이름(정의가 있어도)이나 정의 없는 이름으로 부른다(처방은 원래 이름) · 시각=그림·표·흐름도로 바꿈 · 묻기=원고와 자료만으로 못 정해 작성자가 사람에게 물어야 한다(rewrite 에 물을 문장)' },
|
|
163
|
+
class: { type: 'string', enum: ['사실틀림', '구조', '읽기', '이름', '시각', '증거', '묻기'], description: '사실틀림=원고의 수치·인용·앱 동작·상태·그림↔본문이 원본과 다르다 · 구조=PART·절 합치기·빼기 · 읽기=용어·흐름·문장 · 이름=대상·판·시험·상태·선택지를 이 문서가 붙인 이름(정의가 있어도)이나 정의 없는 이름으로 부른다(처방은 원래 이름) · 시각=그림·표·흐름도로 바꿈 · 증거=화면·흐름·화면 문구·동작을 말하는 문장(관찰 주장)인데 그 건·절에 독자가 볼 증거(실제 캡처·녹화·합성·원문 인용·외부 문서 — 센 대상이 있는 표는 수·대조 결과만 받친다)가 없다 — 도안은 증거가 아니다(처방은 찍어 싣기·원문·원자료 표, 우리가 안 본 것이면 남은 것으로 옮기기) · 묻기=원고와 자료만으로 못 정해 작성자가 사람에게 물어야 한다(rewrite 에 물을 문장)' },
|
|
124
164
|
evidence: { type: 'string', description: '연 것·잰 것 — 파일:줄 · 쪽(행) · px 값 · 주소 · 다시 한 계산. 원고 글만 읽고 낸 판단이면 「추정 — 」으로 시작' },
|
|
125
165
|
rewrite: { type: 'string', description: '글로 고친다면 그 자리의 문체로 2~4문장 — 요청 자리(open_items·cta·PART I 간지·판권지 회신 안내)는 사실 문장까지 하십시오체, 그 밖은 주어가 우리·사물·화면인 개조식(독자를 주어로 쓰지 않음). 필요 없으면 빈 문자열' },
|
|
126
|
-
visual: { type: 'string', description: '그림/표/flow/chart 로 바꾼다면 — 종류(bitmap|flow|table|chart|none) + 축·칸·숫자·라벨. 예시 한 명의
|
|
166
|
+
visual: { type: 'string', description: '그림/표/flow/chart 로 바꾼다면 — 종류(bitmap|capture|video|flow|table|chart|none) + 축·칸·숫자·라벨. 예시 한 명의 숫자로. capture·video 는 실제 화면 캡처·녹화(증거)라 무엇을 어느 환경·기기에서 찍을지' } },
|
|
127
167
|
required: ['where', 'problem', 'severity', 'class', 'evidence', 'rewrite', 'visual'] }
|
|
128
168
|
const PASSED = { type: 'array', description: '재서 통과한 것 — 안 본 것은 넣지 않는다', items: { type: 'object', properties: {
|
|
129
169
|
where: { type: 'string' }, check: { type: 'string', description: '무엇을 어떻게 쟀고 값이 얼마였나' } }, required: ['where', 'check'] } }
|
|
@@ -131,7 +171,26 @@ const FIND = { type: 'object', properties: { findings: { type: 'array', items: F
|
|
|
131
171
|
const FIND_FIG = { type: 'object', properties: { ...FIND.properties, unopened: { type: 'array', description: '열지 못한 도판', items: { type: 'object', properties: {
|
|
132
172
|
id: { type: 'string' }, why: { type: 'string' } }, required: ['id', 'why'] } } }, required: [...FIND.required, 'unopened'] }
|
|
133
173
|
// 렌더본 관점은 파일을 열었는지부터 답한다 — 못 연 관점의 「발견 0」을 「문제 없음」으로 읽지 않게(머리 주석).
|
|
134
|
-
|
|
174
|
+
// claims 는 메움 세기(FILL_STEP), task 는 할 일 판정 — 증거 셈과 따로 낸다.
|
|
175
|
+
const CLAIM_ROW = { type: 'object', properties: {
|
|
176
|
+
line: { type: 'integer', description: '주장이 선 줄 번호' },
|
|
177
|
+
claim: { type: 'string', description: '그 문장 — 줄 글자 그대로(길면 앞 60자)' },
|
|
178
|
+
shown_at: { type: 'string', description: '그것을 보여 주는 증거가 선 이 파일의 줄 번호와 그 줄 글자 — 없거나 도안뿐이면 「없음」' } },
|
|
179
|
+
required: ['line', 'claim', 'shown_at'] }
|
|
180
|
+
const FIND_TEXT = { type: 'object', properties: { ...FIND.properties, opened: { type: 'boolean', description: '렌더본 파일을 열어 끝까지 읽었으면 true, 못 열었거나 비었으면 false' },
|
|
181
|
+
claims: { type: 'array', items: CLAIM_ROW, description: '화면·흐름·화면 문구·동작을 말하는 문장마다 한 행 — 상한 없음' },
|
|
182
|
+
task: { type: 'object', properties: { ok: { type: 'boolean' }, why: { type: 'string' } }, required: ['ok', 'why'], description: '독자가 이 파일의 글만으로 자기 할 일을 할 수 있나 — 증거가 보이는지는 묻지 않는다' } },
|
|
183
|
+
required: [...FIND.required, 'opened', 'claims', 'task'] }
|
|
184
|
+
// 증거 관점의 표 — 관찰 주장 한 문장에 한 행(상한 없음). 판단보다 표 채우기이고, 「문서 안 증거 없음」 행은 JS 가 센다.
|
|
185
|
+
const EV_ROW = { type: 'object', properties: {
|
|
186
|
+
line: { type: 'integer', description: '렌더본 텍스트에서 그 문장이 선 줄 번호' },
|
|
187
|
+
claim: { type: 'string', description: '그 문장 — 줄 글자 그대로(길면 앞 80자)' },
|
|
188
|
+
basis: { type: 'string', description: '작성자 근거 — 파일:줄 · 증거 파일 · 「코드만」 · 「관찰 하나」(구조 주장인데 코드 분기 근거 없음) · 「없음」(우리가 안 본 결과)' },
|
|
189
|
+
shown: { type: 'string', description: '문서 안 증거 — 그 건·절의 그림 id(갈래) · 표 이름 · 원문 인용 · 외부 문서. 없으면 「없음」, 도안뿐이면 「없음 — 도안만」, 화면 주장에 표뿐이면 「없음 — 표만」' },
|
|
190
|
+
reach: { type: 'string', description: '독자가 그 화면·동작에 닿나 — 자기 화면 · 다른 나라·다른 앱 · 권한·계정 필요 · 운영에 아직 없음 · 모름' },
|
|
191
|
+
rx: { type: 'string', enum: ['캡처', '영상', '원자료 표', '원문 인용', '남은 것으로 옮김', '없음'], description: '처방 종류 — 문서 안 증거가 있으면 없음' } },
|
|
192
|
+
required: ['line', 'claim', 'basis', 'shown', 'reach', 'rx'] }
|
|
193
|
+
const FIND_EV = { type: 'object', properties: { ...FIND.properties, rows: { type: 'array', items: EV_ROW, description: '관찰 주장마다 한 행 — 상한 없음' } }, required: [...FIND.required, 'rows'] }
|
|
135
194
|
const VERDICT = { type: 'object', properties: {
|
|
136
195
|
verdict: { type: 'string', enum: ['참', '부분', '거짓', '미확인'] },
|
|
137
196
|
finding_basis: { type: 'string', enum: ['확인', '추정'], description: '발견의 evidence 가 원본·코드·잰 값에 닿으면 확인, 원고 글만 읽은 짐작이면 추정' },
|
|
@@ -153,16 +212,78 @@ const LENSES = [
|
|
|
153
212
|
['source', '원본 대조 — 고객이 누를 수 있는 원본과 원고 인용의 일치를 본다. 원고가 가리키는 원본을 실제로 연다: 건 cases[].source_url, 도판 출처 줄 assets[].source_text 안의 주소·쪽, 부록 출처 목록과 본문 글자 안의 맨 주소·[글자](주소). assets[].origin 은 렌더되지 않는 제공자 기록이라 대조 대상이 아니다. 인용 문구·쪽(행)·값을 원문과 대조한다. 어긋나는 인용, 원문에 없는 문장, 쪽·절 번호 오기, 고객이 눌러도 안 열리는 링크를 발견으로(원고가 원문과 다르면 class 사실틀림). 「흐리다」「못 연다」는 주장은 원본 해상도로 재측정해(PDF 는 pdfimages -list 전체, 웹은 실제로 열어) 잰 값을 evidence 에 적고, 못 열었으면 「미확인」이라 적는다 — 짐작으로 판정하지 않는다. 앱 동작·내부 수치·결정의 주체는 facts 관점 몫이라 여기서 판정하지 않는다 — 다른 문서에 그 말이 없다고 앱 동작을 거짓이라 하지 않는다. rewrite 에는 원문 문구 그대로 + 원본 이름·쪽(행)·주소로 고친 출처 줄.'],
|
|
154
213
|
['structure', '구조 검토자 — parts[] 의 title·lede·questions 와 그 PART 에 속한 절(sections[].part)을 맞대어 읽는다. 짚을 곳: 앞 PART 의 요지 lead·marg.key·표 행이 뒤 PART 의 물음에 먼저 답하는 곳 / 같은 사실(시각·금액·건 제목·핵심 명제)이 두 PART 이상에 서는 곳 — 선 자리를 전부 세어 적는다 / 같은 건의 세 배율(summary·status·cases)이 asks 없이 서로 다른 PART 로 갈린 곳 / 두 절의 marg.key 가 낱말만 다르고 같은 명제인 곳 / 어느 PART 도 선언하지 않은 물음에 답하는 절 / 결정권자 독자인데 status 대조표가 건 제목을 한 번 더 세우는 곳 / 요지 문단(summary)·읽는 법(guide) 물음이 건의 관찰·판단을 바꿔 말한 곳 — 문장 쌍(요지·읽는 법 쪽 ↔ 건 쪽)으로 전수해 evidence 에 적는다. 요약면 수치·요지 lead 가 문서 전체를 줄여 말하는 것은 설계라 결함이 아니다 — 단 요지 문단이 건을 하나씩 옮겨 적으면 결함. 요지(summary)의 모양도 여기서 본다: 이야기 순서(문제 → 원인 → 해법 → 정할 것)대로 서는지, 결정 문서면 칸이 4~6문단인지, 결정에 직접 닿지 않는 **근거 문단의 body 가 비어 있는지**(lead 한 줄 + refs 만 — 본문은 건이 가진다). rewrite 에는 합칠 절·지울 문장을 적는다 — 더하기가 아니라 빼기로. 단 뺀 자리에 뒤 절 번호 포인터를 남기지 않는다: 그 자리에서 뜻이 서게 하는 한 구절(낱말 정의·조건 이름)은 남긴다.'],
|
|
155
214
|
['linear', '선형 읽기 — 렌더 순서(표지 → 이력 → 차례 → 활용법 → 요약면 → PART 간지 → 절 → 부록 → 판권지)대로 앞에서 뒤로 딱 한 번 읽는다. 「이 낱말/번호/표가 뭐지, 앞에서 봤나, 뒤에 있나」 하고 멈추는 지점을 남김없이 적는다 — 뒤에 있는 것을 앞에서 전제하거나(선참조: 요지 본문 안의 §n.n, 흐름도 「건」 열의 건 번호, 도판 출처 줄의 §. refs[] 는 「근거」 단추라 뜀이 아니되 문단 내용과 맞는 건이어야 함), 앞의 정의를 뒤에서 다시 찾아 올라가야 하거나, 정의가 부록·원고 밖(앞 회차 보고)에만 있으면 전부 뜀이다. 표 한 칸·흐름도 칸·캡션·여백 주석의 낱말도 본다. 결정권자 자리에서는 판단 항목 각각이 그 항목 안의 글만으로 두 길 중 하나를 고를 수 있는지, 항목과 한눈표가 서로를 전제하는지 본다. where 에 읽던 자리, problem 에 필요했던 것과 그것이 있는 자리(없으면 「원고 어디에도 없음」), rewrite 에 그 자리에 넣거나 뺄 문장 — 「다른 절을 보라」는 처방 금지. class 는 읽기(아래 물음의 발견은 이름), severity 5=이해 불가 · 3=되짚어야 함 · 1=사소. 절 순서가 이야기처럼 이어지는지도 overall 에 적는다. ' + NAME_Q],
|
|
156
|
-
['figures', `도판 검토자 — 캡션·alt 만으로 판정하지 않는다. 먼저 readability.md §1 R13 행과 visual-first.md §4 를
|
|
215
|
+
['figures', `도판 검토자 — 캡션·alt 만으로 판정하지 않는다. 먼저 ${SKILL}/references/readability.md §1 R13 행과 ${SKILL}/references/visual-first.md §4 를 읽는다 — 두 문서가 이름만 적은 도판 도구는 ${SKILL}/assets/figure.mjs·pair.mjs·shot.mjs(같은 폴더)이니 디스크를 뒤지지 않는다. assets[] 마다 file(PNG)을 Read 로 열어 눈으로 보고, 같은 폴더에 같은 이름 .html 이 있으면 머리 주석과 CSS 를 읽는다. 두 칸(비교 컷)이면 칸마다 틀 윗선·아랫선 y 를 재어 px 차를 evidence 에 적는다 — .html 이 있으면 헤드리스 크롬에서 틀 요소의 getBoundingClientRect, 없으면 PNG 픽셀 스캔으로. 측정 스크립트는 ${SCRATCH}/figures/ 아래에만 둔다. 짚을 곳: R13 위반(좌우 배치·틀 높이·배율·틀 안 글자·표식 색·칩 이름표) / 틀에 잘림(윗선 여백 0 포함) / 선택지 도안 가운데 하나만 합성·실사·크게·먼저 선 곳 / 조건표·캡션이 말한 동작이 그림에 없거나 그림이 본문을 반박하는 곳 — class 사실틀림 / 캡션 「왼쪽은…」과 실제 칸의 불일치 / 칩 이름과 캡션·표 이름이 다른 곳. 열어서 잰 값이 기준 안인 도판은 passed 에 id 와 잰 값을. 못 연 도판은 unopened 에 id 와 까닭을 적고, 그 도판에 대한 판정은 「미확인」이다.`],
|
|
157
216
|
['facts', '사실 대조 — FCG 내부 1차 자료(내부 1차 자료 목록)와 결정 장부로 원고의 수치·앱 동작·결정의 주체와 시점을 판정한다. 수치는 원고의 입력값에서 다시 계산한다(반올림·날짜 산술 포함). 앱 동작은 kind=code 파일을 열어 파일:줄로만 판정한다 — 다른 문서에 그 말이 없다는 것은 반증이 아니다. 결정은 누가·어디서 정했는지 결정 장부·정해진 것과 대조한다(주체 없는 「정한 사항」, 장부와 다른 차수·자리). 판정 못 하면 「미확인」이라 적는다. source 관점과의 경계: 고객이 누를 수 있는 원본(링크·쪽)과 인용의 일치는 source 몫이고, 여기는 고객에게 보이지 않는 내부 자료 몫이다. 원고가 틀린 발견은 class 사실틀림. 다시 계산해 맞은 수치·코드와 맞은 동작은 passed 에 파일:줄·계산식과 함께 적는다.'],
|
|
217
|
+
['evidence', `증거 가시성 — 두 쪽을 다 받는다. 독자가 보는 것: 렌더본 텍스트 ${TEXT}(${MARKS} 끝 목록의 그림 파일을 Read 로 연다). 작성자가 아는 것: 원고 JSON · 내부 1차 자료 · 결정 장부 · 상태 표${HANDOFF ? ` · 핸드오프 ${HANDOFF}` : ''}${PLAN ? ` · 증거 계획 ${PLAN}` : ''}. 판단보다 표 채우기다 — 렌더본에서 화면·흐름·화면 문구·동작을 말하는 문장(관찰 주장: 「…뜸·보임·표시됨·켜짐·막힘·넘어감·저장됨」, 「」로 옮긴 화면 글자)을 하나도 빼지 않고 rows 에 한 행씩 적는다(상한 없음, 칸 뜻은 스키마). 증거로 치는 것: 실제 캡처·녹화·합성(실제 캡처 일부 + 옮겨 적은 글자)·원문 인용·외부 문서. 센 대상이 있는 표는 수·대조 결과를 말하는 문장의 증거다 — 화면·화면 문구·동작을 말하는 문장을 표만으로 받쳤으면 shown 은 「없음 — 표만」이다(표는 우리가 옮겨 적은 글이라 독자가 그 화면을 못 본다, 렌더러도 같다). 도안(갈래 도안 — 우리가 그린 그림)은 설명이지 증거가 아니다. 헤지 낱말(「화면 미확인」「(확인 전)」「코드상」)은 증거가 아니고 행을 지우지 않는다. 관찰 주장이 아닌 것: 안 본 것을 말하는 문장 — 화면·동작이 「확인하지 않았음·보지 않았음」의 목적어 안에 든 꼴(「…뜨는지는 확인하지 않았음」). 단정 뒤에 붙인 헤지(「…보임 — 화면은 확인하지 않았음」·「…나타나지만 실기기는 보지 않았음」)는 관찰 주장이다. 관찰 동사 없이 우리 행위만 말한 문장(「첫 화면 목록 50건을 건별 대조함」)은 관찰 주장이 아니다. 「…나타나는 것까지 확인함」처럼 화면·동작을 품은 확인 기록은 관찰 주장이다. 코드·서버 조회로 참임을 알아도 문서 안에 증거가 없으면 shown 은 「없음」이다. 우리가 안 본 결과(basis 「없음」)의 처방은 「남은 것으로 옮김」이다. 「X 하면 항상 Y」 같은 구조 주장은 관찰 하나로 단정하지 않는다 — 코드 분기(파일:줄)가 없으면 basis 에 「관찰 하나」. findings 에는 shown 이 「없음」인 행을 건·절마다 하나로 묶어 class 증거로 낸다 — evidence 에 그 행들의 줄 번호, visual 에 capture·video·table 가운데 처방, severity 는 독자가 그 화면에 닿지 못하면(다른 나라·다른 앱 · 권한·계정 필요 · 운영에 아직 없음) 4, 자기 화면이면 3. 증거가 있는 행은 passed 에 따로 적지 않는다 — rows 가 기록이다.`],
|
|
158
218
|
...(STATE ? [['state', '상태 대조 — 상태 표를 연다. 원고에서 「반영·적용·고침·보이지 않음·빠짐」 같은 완료형 동사가 선 문장마다 상태 표의 실제 확인 수준(코드 · 시험판 · 실기기 · 운영)과 대조한다. 확인 수준보다 앞선 시제·단정(코드만 고쳤는데 앱에서 보이지 않는다고 쓴 문장)은 class 사실틀림으로 두고, rewrite 에 확인 수준에 맞춘 문장을 적는다. 수준과 맞는 문장은 passed 에.']] : []),
|
|
159
219
|
]
|
|
220
|
+
if (DECISION) {
|
|
221
|
+
// ── 결정 브리핑: 처음 읽는 독자 한 번 → 작성자 문장과 맞대기(머리 주석). 아홉 관점·검증·합치기는 돌지 않는다.
|
|
222
|
+
phase('Read')
|
|
223
|
+
const SPOTS = { type: 'array', items: { type: 'object', properties: {
|
|
224
|
+
where: { type: 'string', description: '이 파일의 줄 번호와 그 줄 글자' }, why: { type: 'string' } }, required: ['where', 'why'] } }
|
|
225
|
+
const DEC_READ = { type: 'object', properties: {
|
|
226
|
+
opened: { type: 'boolean', description: '렌더본 파일을 열어 끝까지 읽었으면 true, 못 열었거나 비었으면 false' },
|
|
227
|
+
sheets: { type: 'array', items: { type: 'object', properties: {
|
|
228
|
+
decision: { type: 'string', description: '결정 장 머리의 이름 — 글자 그대로' },
|
|
229
|
+
sentence: { type: 'string', description: '독자 자기 말 한 문장 — 무엇에 관한, 언제까지의, 어떤 선택지이고, 고르면·안 고르면 무엇이 달라지나' },
|
|
230
|
+
blocked: SPOTS }, required: ['decision', 'sentence', 'blocked'] } },
|
|
231
|
+
asked: { type: 'array', items: { type: 'string' }, description: '이 문서가 정하라고 하는 것 — 결정 장과 상관없이 독자가 센 대로 하나씩' },
|
|
232
|
+
depth: SPOTS, judgement: SPOTS, subject: SPOTS, links: SPOTS, overall: { type: 'string' } },
|
|
233
|
+
required: ['opened', 'sheets', 'asked', 'depth', 'judgement', 'subject', 'links', 'overall'] }
|
|
234
|
+
const DEC_CTX = `[읽기 전용. 파일을 수정하지 말 것. 한글로 답할 것. 철저함: very thorough — 발췌하지 말고 파일 전체를 처음부터 끝까지 읽는다.]
|
|
235
|
+
읽을 것: ${TEXT} — 결정 문서 렌더본에서 화면에 보이는 글만 화면 순서대로 뽑은 텍스트(표 칸은 「 | 」). ${MARKS}
|
|
236
|
+
이 파일 하나만 연다 — 끝 「[그림 파일]」 목록의 그림은 독자가 보는 그림이라 Read 로 열어 봐도 된다. 원고 JSON·같은 폴더의 다른 파일·저장소·메모·다른 문서는 열지 않는다.
|
|
237
|
+
이 파일을 끝까지 읽었으면 opened=true. 열지 못했거나 비어 있으면 opened=false 로 두고 나머지는 비운 채 overall 에 까닭을 적는다 — 다른 파일로 대신 읽지 않는다.
|
|
238
|
+
독자: ${READER}`
|
|
239
|
+
const rd = await agent(`${DEC_CTX}
|
|
240
|
+
[관점: decision-reader] 너는 이 문서를 처음 받은 결정권자다. 「정하실 것 · <이름>」으로 시작하는 결정 장마다 sheets 에 한 행을 적는다 — decision 은 그 머리의 이름 글자 그대로, sentence 는 네 말로 한 문장(무엇에 관한, 언제까지의, 어떤 선택지이고, 고르면·안 고르면 무엇이 달라지나). 문서 글을 옮겨 붙이지 않는다. 그 장을 읽다 멈춘 자리는 blocked 에 — why 에 무엇이 필요했고 어디에 있었나(없으면 「이 파일 어디에도 없음」).
|
|
241
|
+
asked 에는 이 문서가 너에게 정하라고 하는 것을 결정 장과 상관없이 네가 센 대로 하나씩 적는다(본문·표·위험·확인 속에 묻힌 것까지).
|
|
242
|
+
그리고 넷을 본다 — depth: 한 장에서 선택지들이 같은 무게로 읽히지 않는 자리(한쪽만 자세하거나 먼저 보임) · judgement: 보낸 쪽의 판단이 의견 자리 밖에 사실처럼 선 곳 · subject: 독자를 주어로 한 할 일 문장이 부탁하는 문장(회신·확인 요청) 밖에 선 곳 · links: 네가 눌러 열 수 없는 링크·원본. 없으면 빈 목록.
|
|
243
|
+
점수·개수·심각도를 매기지 않는다. overall 에 결정할 수 있었는지와 막힌 곳을 3문장.`,
|
|
244
|
+
{ label: 'read:decision-reader', phase: 'Read', schema: DEC_READ, agentType: 'Explore' })
|
|
245
|
+
const read = !!rd && rd.opened === true
|
|
246
|
+
const norm = (x) => String(x || '').replace(/\s+/g, '')
|
|
247
|
+
const rs = read && Array.isArray(rd.sheets) ? rd.sheets : []
|
|
248
|
+
const matchOf = (name) => rs.find((x) => norm(x.decision) === norm(name))
|
|
249
|
+
|| rs.find((x) => norm(x.decision) && (norm(x.decision).includes(norm(name)) || norm(name).includes(norm(x.decision))))
|
|
250
|
+
const pairs = INTENTS.map((it) => { const r = matchOf(it.decision); return { decision: it.decision, intent: it.intent, reader: r ? String(r.sentence || '') : '', blocked: r ? (r.blocked || []) : [], found: !!r } })
|
|
251
|
+
// 독자는 봤는데 작성자가 문장을 안 적은 장 — 작성자가 그 장을 셈에서 뺐다는 뜻이다.
|
|
252
|
+
const unclaimed = rs.filter((x) => !INTENTS.some((it) => matchOf(it.decision) === x)).map((x) => x.decision)
|
|
253
|
+
phase('Merge')
|
|
254
|
+
const DEC_CMP = { type: 'object', properties: {
|
|
255
|
+
sheets: { type: 'array', items: { type: 'object', properties: {
|
|
256
|
+
decision: { type: 'string' },
|
|
257
|
+
split: { type: 'string', description: '작성자 문장과 독자 문장이 갈린 자리 — 같은 뜻이면 빈 문자열' },
|
|
258
|
+
field: { type: 'string', description: '그 자리를 지는 원고 칸 경로(decision_brief.decisions[0].options[1].what 꼴) — 칸이 없으면 「로직에 그 칸이 없음」, 갈리지 않았으면 빈 문자열' },
|
|
259
|
+
fix: { type: 'string', description: '원고를 어떻게 고치나 한두 문장 — 「행 하나 더」「낱말 하나 더」로 덧대는 처방이면 그렇다고 밝힌다' } },
|
|
260
|
+
required: ['decision', 'split', 'field', 'fix'] } },
|
|
261
|
+
hidden: { type: 'array', items: { type: 'string' }, description: '독자가 정하라는 것으로 셌는데 결정 장이 아닌 것' },
|
|
262
|
+
overall: { type: 'string' } }, required: ['sheets', 'hidden', 'overall'] }
|
|
263
|
+
const cmp = read ? await agent(`[원고·저장소 파일은 수정하지 않는다. 한글로 답할 것.]
|
|
264
|
+
원고: ${FILE} — 결정 브리핑 원고 JSON(본체 decision_brief). 칸 뜻은 칸 이름과 원고 값으로 읽는다.
|
|
265
|
+
결정 장마다 작성자가 「이 장이 말하려던 것」을 한 문장으로 적었고, 원고를 모르는 독자가 렌더본만 읽고 자기 한 문장을 적었다(found 가 false 면 독자가 그 결정 장을 못 찾았다):
|
|
266
|
+
${JSON.stringify(pairs.map(({ decision, intent, reader, blocked, found }) => ({ decision, intent, reader, blocked, found })), null, 1)}
|
|
267
|
+
독자가 정하라는 것으로 센 것: ${JSON.stringify(rd.asked || [])}
|
|
268
|
+
[관점: decision-compare] 결정 장마다 두 문장이 갈린 자리만 적는다(split — 같은 뜻이면 빈 문자열) — 무엇에 관한·언제까지·어떤 선택지·고르면·안 고르면 가운데 어디가 다른가. 갈렸으면 원고를 열어 그 자리를 지는 칸 경로(field)와 고칠 방법(fix)을 적는다 — 원고에 그 사실이 없다 / 있는데 다른 자리에 있다 / 있는데 낱말이 막았다 / 로직에 그 칸이 없다 가운데 무엇인지를 fix 첫머리에. hidden 에는 독자가 정하라는 것으로 셌는데 결정 장이 아닌 것을 적는다. 점수·개수·심각도를 매기지 않는다.`,
|
|
269
|
+
{ label: 'merge:decision-compare', phase: 'Merge', schema: DEC_CMP }) : null
|
|
270
|
+
const cs = cmp && Array.isArray(cmp.sheets) ? cmp.sheets : []
|
|
271
|
+
const miss = [!rd && '독자 관점 실패', rd && !read && '렌더본 못 엶', read && !cmp && '맞대기 실패'].filter(Boolean)
|
|
272
|
+
const head = miss.length ? `검토 미완 — ${miss.join(' · ')}` : ''
|
|
273
|
+
if (head) log(head)
|
|
274
|
+
const spots = (k) => (read && Array.isArray(rd[k]) ? rd[k] : [])
|
|
275
|
+
return { mode: 'decision', ...(head ? { head, failed: true } : {}),
|
|
276
|
+
sheets: pairs.map((p) => { const c = cs.find((x) => norm(x.decision) === norm(p.decision)) || {}; return { ...p, split: c.split || '', field: c.field || '', fix: c.fix || '' } }),
|
|
277
|
+
unclaimed, asked: read ? (rd.asked || []) : [], hidden: cmp ? (cmp.hidden || []) : [],
|
|
278
|
+
four: { depth: spots('depth'), judgement: spots('judgement'), subject: spots('subject'), links: spots('links') },
|
|
279
|
+
overall: [head, readerWarn, read ? rd.overall : '', cmp ? cmp.overall : ''].filter(Boolean).join('\n'), raw: { reader: rd, compare: cmp } }
|
|
280
|
+
}
|
|
160
281
|
phase('Read')
|
|
161
282
|
// 렌더본 관점(TEXT_LENSES)은 CLAUDE.md 를 싣지 않는 내장 유형 Explore 로 돈다 — 기본 워크플로 에이전트는 CLAUDE.md 가 첨부로 실려
|
|
162
283
|
// 프롬프트만 고쳐서는 0 상태가 안 된다(2026-09-26 스킬 검토: 첨부에 MEMORY.md 가 실린 에이전트 22/23·15/16).
|
|
163
284
|
// Explore 는 읽기 전용이고 모델은 세션 모델을 물려받는다(Claude Code 2.1.278 내장 정의 omitClaudeMd·model inherit).
|
|
164
285
|
// 확인 전: MEMORY.md(자동 메모리)와 하네스가 전하는 사용자 요청까지 빠지는지는 첫 실행 전사의 첨부로 확인해야 한다.
|
|
165
|
-
const res = await parallel(LENSES.map(([k, inst]) => () => agent(`${TEXT_LENSES.has(k) ? TEXT_CTX : CTX}\n[관점: ${k}] ${inst}\n${TEXT_LENSES.has(k) ? '이 파일 전체' : '원고 전체'}를 읽고 findings 를 ${k === 'linear' ? '남김없이(0개면 0개)' : '8~14개'}, severity 로 정직하게 매겨라. 발견마다 class 와 evidence 를 단다. 재서 통과한 것은 passed 에. overall 에 이 원고의 흐름이 어디서 끊기는지 3문장.`, { label: `read:${k}`, phase: 'Read', schema: k === 'figures' ? FIND_FIG : (TEXT_LENSES.has(k) ? FIND_TEXT : FIND), ...(TEXT_LENSES.has(k) ? { agentType: 'Explore' } : {}) })))
|
|
286
|
+
const res = await parallel(LENSES.map(([k, inst]) => () => agent(`${TEXT_LENSES.has(k) ? TEXT_CTX : CTX}\n[관점: ${k}] ${inst}\n${TEXT_LENSES.has(k) ? '이 파일 전체' : '원고 전체'}를 읽고 findings 를 ${k === 'linear' ? '남김없이(0개면 0개)' : (k === 'evidence' ? '위 묶음대로(0개면 0개)' : '8~14개')}, severity 로 정직하게 매겨라. 발견마다 class 와 evidence 를 단다. 재서 통과한 것은 passed 에. overall 에 이 원고의 흐름이 어디서 끊기는지 3문장.`, { label: `read:${k}`, phase: 'Read', schema: k === 'figures' ? FIND_FIG : (k === 'evidence' ? FIND_EV : (TEXT_LENSES.has(k) ? FIND_TEXT : FIND)), ...(TEXT_LENSES.has(k) ? { agentType: 'Explore' } : {}) })))
|
|
166
287
|
const failed = LENSES.filter((_, i) => !res[i]).map(([k]) => k)
|
|
167
288
|
// 렌더본을 못 열었다고 답한 관점 — 그 발견은 쓰지 않고 머리에 적는다(검사를 통과한 뒤에도 권한·경로 문제로 못 열 수 있다).
|
|
168
289
|
const unread = LENSES.filter(([k], i) => TEXT_LENSES.has(k) && res[i] && res[i].opened !== true).map(([k]) => k)
|
|
@@ -179,6 +300,26 @@ const figHead = !fig ? '⚠ 도판 관점 실패 — 도판 전부 미열람' :
|
|
|
179
300
|
held.length ? `도판 ${held.length}장 서버 보관 — 검토자가 열지 못했다, 열람 주소에서 사람이 본다(${held.map((u) => u.id).join(', ')})` : '',
|
|
180
301
|
].filter(Boolean).join('\n')
|
|
181
302
|
if (figHead) log(figHead)
|
|
303
|
+
// 판정 둘 — 「할 일을 할 수 있나」(렌더본 관점의 task)와 「증거 없는 관찰 주장 수」(증거 관점 표)를 따로 낸다. 판정이 할 일 하나뿐이면
|
|
304
|
+
// 증거 없는 단정이 「예」로 지나간다(2026-10-01 관보 합의 착각 수리 — 판 5 독자 1인이 「할 일 가능」만 물었다). 셈은 반박 전 표 그대로다.
|
|
305
|
+
const NONE = /^\s*없음/
|
|
306
|
+
const textReads = reads.filter((r) => TEXT_LENSES.has(r.lens))
|
|
307
|
+
const tasks = textReads.filter((r) => r.task && typeof r.task.ok === 'boolean').map((r) => ({ lens: r.lens, ok: r.task.ok, why: String(r.task.why || '') }))
|
|
308
|
+
const filled = textReads.flatMap((r) => (Array.isArray(r.claims) ? r.claims : []).filter((c) => NONE.test(String(c.shown_at))).map((c) => ({ lens: r.lens, ...c })))
|
|
309
|
+
const evRead = reads.find((r) => r.lens === 'evidence')
|
|
310
|
+
const evRows = evRead && Array.isArray(evRead.rows) ? evRead.rows : null
|
|
311
|
+
const evMissing = evRows ? evRows.filter((x) => NONE.test(String(x.shown))) : []
|
|
312
|
+
const judgement = {
|
|
313
|
+
task: !tasks.length ? '모름' : (tasks.every((t) => t.ok) ? '할 수 있음' : '할 수 없음'),
|
|
314
|
+
task_by: tasks,
|
|
315
|
+
evidence_missing: evRows ? evMissing.length : null,
|
|
316
|
+
evidence_claims: evRows ? evRows.length : null,
|
|
317
|
+
filled_lines: new Set(filled.map((c) => c.line)).size,
|
|
318
|
+
}
|
|
319
|
+
const taskLine = `할 일 판정: ${judgement.task}${!tasks.length ? '(렌더본 관점이 답하지 않았다)' : ` — ${tasks.map((t) => `${t.lens} ${t.ok ? '예' : `아니오(${t.why})`}`).join(' · ')}`}`
|
|
320
|
+
const evLine = `증거 판정: ${evRows ? `증거 없는 관찰 주장 ${evMissing.length}곳(관찰 주장 ${evRows.length}곳 중 · 증거 관점 표, 반박 전)` : '모름(증거 관점이 표를 내지 않았다)'}${judgement.filled_lines ? ` · 렌더본만 읽은 독자가 증거 줄을 못 댄 주장 ${judgement.filled_lines}줄` : ''} — 할 일 판정과 따로 센다`
|
|
321
|
+
log(taskLine)
|
|
322
|
+
log(evLine)
|
|
182
323
|
|
|
183
324
|
phase('Verify')
|
|
184
325
|
// 거를 대상 고르기 — 사실 틀림을 먼저, 그다음 severity. 상한 밖은 반영하지 않고 raw.unverified 로 남긴다(조용히 자르지 않는다).
|
|
@@ -206,11 +347,12 @@ log(`검증 ${toVerify.length}건 · 검증 밖 ${unverified.length}건(raw.unve
|
|
|
206
347
|
const vs = await parallel(toVerify.map((f) => () => agent(`${CTX}\n[반박 검증 — 발견 ${f.id}] 아래 발견은 ${f.lens} 관점 검토자가 냈다. 검토자 주장도 틀린다. 기본 자세는 반박이다 — 발견이 가리킨 원본(코드 파일:줄 · PDF 쪽 · PNG · 주소)과 원고의 그 자리를 직접 열어 판정한다. 열지 못했으면 verdict 는 미확인이다. 원고 글만 읽고 참이라 하지 않는다.
|
|
207
348
|
렌더본 텍스트: ${TEXT} — first-time·linear 관점은 이 파일만 읽은 독자다. 그 관점의 읽기·이름 발견은 이 파일(독자에게 보이는 글)로 판정한다 — 원고 JSON 의 origin·결정 장부·사용자 지적은 독자가 못 보는 것이라 반증이 아니다.
|
|
208
349
|
class 가 이름이면 판정 물음은 「이 낱말을 독자가 문서 밖(자기 화면 글자·원본 문서·공급사 공식 명칭)에서 본 적이 있는가」다 — 문서 안의 정의·캡션·용어표는 반증이 아니다(2026-09-26: 「거울 정의 없음」 같은 발견이 「표 캡션에 정의 있음」으로 기각됐었다). 원래 이름이 정말 없는 새 개념이면 verdict 는 참으로 두고 note 를 「원래 이름 없음」으로 시작한다.
|
|
350
|
+
class 가 증거면 판정 물음은 「독자가 문서 안에서 그 증거를 볼 수 있는가」다 — 렌더본의 그림 자리 표지(「[그림] id · 갈래」)·캡션·원문 인용·표와 끝 「[그림 파일]」 목록의 그림을 직접 연다. 코드·서버 조회로 주장이 참임을 보여도 증거 관점 발견의 반증이 아니다. 도안(갈래 도안)은 증거가 아니다.
|
|
209
351
|
verdict(진단): 발견의 주장(problem)이 참/부분/거짓/미확인. 거짓은 반증을 열어 확인했을 때만 — 확신이 없으면 미확인이다. class 가 묻기면 「원고와 자료만으로 정말 못 정하는가」를 판정한다 — 자료에 답이 있으면 거짓.
|
|
210
352
|
진단과 처방은 따로 판정한다 — 처방이 틀려도 주장이 맞으면 verdict 는 참이다. 처방 적합은 아래 세 칸이 잰다.
|
|
211
|
-
rx_fit: 처방이 「N절을 보라」「부록에 있다」 같은 포인터이거나, 원래 이름 대신 이 문서가 새 이름·설명형 이름을 붙이거나, 만든 이름은 두고 뜻풀이만 더하면 다시. 그 밖에 규칙에 맞으면 맞음. rx_why 에 걸린 규칙과 까닭.
|
|
353
|
+
rx_fit: 처방이 「N절을 보라」「부록에 있다」 같은 포인터이거나, 원래 이름 대신 이 문서가 새 이름·설명형 이름을 붙이거나, 만든 이름은 두고 뜻풀이만 더하면 다시. 증거 발견을 헤지 낱말(「화면 미확인」「(확인 전)」「코드상」)·동사 낮추기·도안으로 덮는 처방도 다시. 증거를 싣는 처방(캡처·영상·원문 인용·센 대상이 있는 표·캡션 끝 증거 범위 한 구절)은 말투(주어)·지은 이름 규칙 위반을 까닭으로 다시로 두지 않는다 — 걸린 낱말은 note 에 적는다. 그 밖에 규칙에 맞으면 맞음. rx_why 에 걸린 규칙과 까닭.
|
|
212
354
|
touches_locked: 처방(rewrite·visual)이 정해진 것이나 결정 장부의 확인된 결정을 되돌리면 true.
|
|
213
|
-
noise_kind: 처방이 본문·캡션·출처 줄에 제작 방식(
|
|
355
|
+
noise_kind: 처방이 본문·캡션·출처 줄에 제작 방식(제작 도구·배율·자르기 좌표·스크립트 이름·그린 방법)을 더하면 제작, 결정 날짜·결정 경로(몇 차·어느 회의에서)를 더하면 날짜경로, 판 이력(몇 판에서 바뀜)을 더하면 판이력, 아니면 없음. 캡션 끝의 증거 범위 한 구절(찍은 환경 — 독자가 부르는 이름·기기, 날짜, 옮겨 적은 부분, 「— 합성」「— 도안」「— 재현」)은 노이즈가 아니다 — 없음(2026-10-01 사용자 결정). 원본 위치 한 구절(원본 이름·쪽·행·주소·건 §, 캡처면 화면 이름 · 날짜)이 빠졌다는 처방은 노이즈가 아니라 ${SKILL}/SKILL.md 0.5단계 5a행(원본·증거 확인) 결함이다 — 없음.
|
|
214
356
|
${JSON.stringify(f)}`, { label: `verify:${f.id}`, phase: 'Verify', schema: VERDICT })))
|
|
215
357
|
// JS 거름(결정적) — 진단 거짓만 dropped(사유 포함), 묻기는 questions, 사실 틀림은 합치지 않는 독립 항목.
|
|
216
358
|
// 정해진 것 되돌리기·노이즈·규칙에 걸린 처방은 발견을 버리지 않고 rx 「처방 다시 — 까닭」을 단다. 전에는 사유 하나로 발견째 버려
|
|
@@ -238,9 +380,9 @@ const failHead = (vfail.length || failed.length || unread.length) ? `검토 미
|
|
|
238
380
|
if (failHead) log(failHead)
|
|
239
381
|
|
|
240
382
|
phase('Merge')
|
|
241
|
-
const m = (candidates.length || errors.length) ? await agent(`${CTX}\n[합치기] 아래 「합칠 것」은 Verify 를 통과한 발견이다(verify 에 판정). 같은 자리끼리 합치고(중복 제거 — ids 에 합친 발견 id 를 모두 적는다), severity 합산으로 정렬해 상위 10개만 남겨라. 각 항목에 가장 구체적인 rewrite 와 visual 을 고르되,
|
|
383
|
+
const m = (candidates.length || errors.length) ? await agent(`${CTX}\n[합치기] 아래 「합칠 것」은 Verify 를 통과한 발견이다(verify 에 판정). 같은 자리끼리 합치고(중복 제거 — ids 에 합친 발견 id 를 모두 적는다), severity 합산으로 정렬해 상위 10개만 남겨라. 각 항목에 가장 구체적인 rewrite 와 visual 을 고르되, 비트맵(우리가 그리는 그림)은 ${BUDGET}장까지만 'bitmap' 으로 두고 나머지는 flow/table/chart 로 바꿔 적어라 — capture·video(실제 화면 캡처·녹화)는 증거라 예산 밖이고 flow/table/chart 로 바꾸지 않는다. class 증거 발견의 처방은 찍어 싣기(capture·video)·원문 인용·원자료 표, 우리가 안 본 것이면 그 문장을 남은 것으로 옮기기다 — 다른 관점의 헤지 낱말·동사 낮추기·도안 처방과 부딪히면 증거 처방이 이긴다. 다른 관점의 더하기 처방(표·문장·행 추가)이 structure 관점의 빼기 처방과 부딪히면 빼기가 이긴다 — 같은 사실을 다른 PART·절에 또 세우는 처방은 버린다. 단 빼기가 뒤 절 번호 포인터를 남기면 안 된다 — linear 관점의 뜀 지점과 부딪히는 「N절·건 §·부록을 보라」 처방은 「그 자리에 한 구절」로 바꾼다. linear 관점의 뜀 지점과 class 이름 발견, 증거 관점의 표는 상위 10 과 별개로 전부(jumps·names·evidence) 남는다. verdict 가 미확인인 발견을 합치면 problem 끝에 「(미확인)」을 남긴다.
|
|
242
384
|
rx 가 빈 문자열이 아닌 발견은 진단은 맞고 처방이 규칙에 걸린 것이다 — 그 rewrite·visual 을 고르지 말고 규칙에 맞는 처방을 새로 적되(이름 발견은 원래 이름으로 — 새 이름·뜻풀이 금지), 못 적으면 rewrite 를 rx 문장 그대로 둔다. 합친 항목의 rx 칸에는 처방을 다시 써야 하면 「처방 다시 — 까닭」, 아니면 빈 문자열.
|
|
243
|
-
「사실 틀림」은 원고가 원본과 다른 곳(그림↔본문 반박 포함)이다 — 사실 대조에서 틀림·부분으로 판정된 발견은 다른 발견에 합쳐 넣지 않고 독립 항목으로 이미 앞에 서며 개수 제한 밖이다. findings 에 다시 적지 말고 overall 순서에만 반영하라. overall 에는 「작성자가 지금 바로 할 일 순서」를 5줄로 — 사실 틀림 고침이 맨 먼저, 그다음 구조(PART·절 합치기·빼기), 용어 잠금과 수치·참조·원본 대조,
|
|
385
|
+
「사실 틀림」은 원고가 원본과 다른 곳(그림↔본문 반박 포함)이다 — 사실 대조에서 틀림·부분으로 판정된 발견은 다른 발견에 합쳐 넣지 않고 독립 항목으로 이미 앞에 서며 개수 제한 밖이다. findings 에 다시 적지 말고 overall 순서에만 반영하라. overall 에는 「작성자가 지금 바로 할 일 순서」를 5줄로 — 사실 틀림 고침이 맨 먼저, 그다음 증거(찍을 것과 사용자에게 한 번에 청할 접근 — 못 찍을 것은 남은 것으로), 구조(PART·절 합치기·빼기), 용어 잠금과 수치·참조·원본 대조, 그림(도안)은 마지막이다.
|
|
244
386
|
[사실 틀림 — 참고만]
|
|
245
387
|
${JSON.stringify(errors)}
|
|
246
388
|
[합칠 것]
|
|
@@ -252,11 +394,11 @@ const merged = {
|
|
|
252
394
|
...errors.map((e) => ({ ids: [e.id], ...pick(e), rx: e.rx, verdicts: [`${e.id}:${e.verdict}`] })),
|
|
253
395
|
...(m ? m.findings : []).map((x) => ({ ...x, verdicts: x.ids.map((id) => `${id}:${verdictOf[id] || '?'}`) })),
|
|
254
396
|
],
|
|
255
|
-
overall: [failHead, figHead, body].filter(Boolean).join('\n'),
|
|
397
|
+
overall: [failHead, readerWarn, figHead, taskLine, evLine, body].filter(Boolean).join('\n'),
|
|
256
398
|
}
|
|
257
399
|
const passed = reads.flatMap((r) => r.passed.map((p) => ({ lens: r.lens, ...p })))
|
|
258
400
|
// linear 관점의 뜀 지점은 상위 10·검증 상한과 별개로 전부 돌려준다 — 0 이 목표라 하나도 숨기지 않는다(linear-read-test.workflow.js 와 같은 셈).
|
|
259
401
|
const jumps = (reads.find((r) => r.lens === 'linear') || {}).findings || []
|
|
260
402
|
// 이름 발견(문서가 붙인 이름·정의 없는 이름)도 관점·검증 상한과 별개로 전부 돌려준다 — 검증 단계까지 간 것이 164건 중 4건이었다(2026-09-26 스킬 검토).
|
|
261
403
|
const names = all.filter((f) => f.class === '이름')
|
|
262
|
-
return { ...(failHead ? { head: failHead, failed: true } : {}), merged, questions, passed, dropped, jumps, names, raw: { reads, verdicts, unverified } }
|
|
404
|
+
return { ...(failHead ? { head: failHead, failed: true } : {}), merged, judgement, evidence: { rows: evRows || [], missing: evMissing }, filled, questions, passed, dropped, jumps, names, raw: { reads, verdicts, unverified } }
|