easyread-mcp 0.1.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.
Files changed (117) hide show
  1. package/LICENSE +29 -0
  2. package/README.md +41 -0
  3. package/assets/dictionary.json +118 -0
  4. package/assets/resources.json +1149 -0
  5. package/dist/data/dictionary.js +61 -0
  6. package/dist/data/dictionary.js.map +1 -0
  7. package/dist/data/guidelines.js +63 -0
  8. package/dist/data/guidelines.js.map +1 -0
  9. package/dist/data/resources.js +76 -0
  10. package/dist/data/resources.js.map +1 -0
  11. package/dist/index.js +26 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/messages.js +9 -0
  14. package/dist/messages.js.map +1 -0
  15. package/dist/prompts/review.js +30 -0
  16. package/dist/prompts/review.js.map +1 -0
  17. package/dist/prompts/simplify.js +40 -0
  18. package/dist/prompts/simplify.js.map +1 -0
  19. package/dist/rules/acc/acc-01.js +28 -0
  20. package/dist/rules/acc/acc-01.js.map +1 -0
  21. package/dist/rules/acc/acc-02.js +28 -0
  22. package/dist/rules/acc/acc-02.js.map +1 -0
  23. package/dist/rules/acc/acc-03.js +28 -0
  24. package/dist/rules/acc/acc-03.js.map +1 -0
  25. package/dist/rules/acc/acc-04.js +28 -0
  26. package/dist/rules/acc/acc-04.js.map +1 -0
  27. package/dist/rules/acc/index.js +8 -0
  28. package/dist/rules/acc/index.js.map +1 -0
  29. package/dist/rules/config.js +20 -0
  30. package/dist/rules/config.js.map +1 -0
  31. package/dist/rules/engine.js +49 -0
  32. package/dist/rules/engine.js.map +1 -0
  33. package/dist/rules/index.js +11 -0
  34. package/dist/rules/index.js.map +1 -0
  35. package/dist/rules/num/index.js +8 -0
  36. package/dist/rules/num/index.js.map +1 -0
  37. package/dist/rules/num/num-01.js +27 -0
  38. package/dist/rules/num/num-01.js.map +1 -0
  39. package/dist/rules/num/num-02.js +21 -0
  40. package/dist/rules/num/num-02.js.map +1 -0
  41. package/dist/rules/num/num-03.js +24 -0
  42. package/dist/rules/num/num-03.js.map +1 -0
  43. package/dist/rules/num/num-04.js +21 -0
  44. package/dist/rules/num/num-04.js.map +1 -0
  45. package/dist/rules/registry.js +30 -0
  46. package/dist/rules/registry.js.map +1 -0
  47. package/dist/rules/report.js +69 -0
  48. package/dist/rules/report.js.map +1 -0
  49. package/dist/rules/sen/index.js +10 -0
  50. package/dist/rules/sen/index.js.map +1 -0
  51. package/dist/rules/sen/sen-01.js +33 -0
  52. package/dist/rules/sen/sen-01.js.map +1 -0
  53. package/dist/rules/sen/sen-02.js +40 -0
  54. package/dist/rules/sen/sen-02.js.map +1 -0
  55. package/dist/rules/sen/sen-03.js +33 -0
  56. package/dist/rules/sen/sen-03.js.map +1 -0
  57. package/dist/rules/sen/sen-04.js +31 -0
  58. package/dist/rules/sen/sen-04.js.map +1 -0
  59. package/dist/rules/sen/sen-05.js +27 -0
  60. package/dist/rules/sen/sen-05.js.map +1 -0
  61. package/dist/rules/sen/sen-07.js +58 -0
  62. package/dist/rules/sen/sen-07.js.map +1 -0
  63. package/dist/rules/str/index.js +9 -0
  64. package/dist/rules/str/index.js.map +1 -0
  65. package/dist/rules/str/str-02.js +22 -0
  66. package/dist/rules/str/str-02.js.map +1 -0
  67. package/dist/rules/str/str-03.js +24 -0
  68. package/dist/rules/str/str-03.js.map +1 -0
  69. package/dist/rules/typ/index.js +7 -0
  70. package/dist/rules/typ/index.js.map +1 -0
  71. package/dist/rules/typ/typ-01.js +21 -0
  72. package/dist/rules/typ/typ-01.js.map +1 -0
  73. package/dist/rules/typ/typ-02.js +23 -0
  74. package/dist/rules/typ/typ-02.js.map +1 -0
  75. package/dist/rules/typ/typ-03.js +28 -0
  76. package/dist/rules/typ/typ-03.js.map +1 -0
  77. package/dist/rules/types.js +2 -0
  78. package/dist/rules/types.js.map +1 -0
  79. package/dist/rules/voc/index.js +10 -0
  80. package/dist/rules/voc/index.js.map +1 -0
  81. package/dist/rules/voc/voc-01.js +27 -0
  82. package/dist/rules/voc/voc-01.js.map +1 -0
  83. package/dist/rules/voc/voc-02.js +38 -0
  84. package/dist/rules/voc/voc-02.js.map +1 -0
  85. package/dist/rules/voc/voc-03.js +30 -0
  86. package/dist/rules/voc/voc-03.js.map +1 -0
  87. package/dist/rules/voc/voc-04.js +26 -0
  88. package/dist/rules/voc/voc-04.js.map +1 -0
  89. package/dist/rules/voc/voc-05.js +34 -0
  90. package/dist/rules/voc/voc-05.js.map +1 -0
  91. package/dist/rules/voc/voc-06.js +36 -0
  92. package/dist/rules/voc/voc-06.js.map +1 -0
  93. package/dist/server.js +49 -0
  94. package/dist/server.js.map +1 -0
  95. package/dist/text/extractors.js +40 -0
  96. package/dist/text/extractors.js.map +1 -0
  97. package/dist/text/index.js +38 -0
  98. package/dist/text/index.js.map +1 -0
  99. package/dist/text/paragraphs.js +28 -0
  100. package/dist/text/paragraphs.js.map +1 -0
  101. package/dist/text/sentences.js +117 -0
  102. package/dist/text/sentences.js.map +1 -0
  103. package/dist/text/spans.js +9 -0
  104. package/dist/text/spans.js.map +1 -0
  105. package/dist/text/types.js +6 -0
  106. package/dist/text/types.js.map +1 -0
  107. package/dist/text/words.js +20 -0
  108. package/dist/text/words.js.map +1 -0
  109. package/dist/tools/analyze.js +73 -0
  110. package/dist/tools/analyze.js.map +1 -0
  111. package/dist/tools/guidelines.js +55 -0
  112. package/dist/tools/guidelines.js.map +1 -0
  113. package/dist/tools/lookup.js +79 -0
  114. package/dist/tools/lookup.js.map +1 -0
  115. package/dist/tools/validate.js +79 -0
  116. package/dist/tools/validate.js.map +1 -0
  117. package/package.json +65 -0
@@ -0,0 +1,61 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { z } from "zod";
3
+ const entrySchema = z.object({
4
+ word: z.string().min(1),
5
+ category: z.enum(["difficult", "loanword", "terminology", "idiom", "abbreviation"]),
6
+ alternatives: z.array(z.string().min(1)).min(1),
7
+ explanation: z.string().min(1).optional(),
8
+ example: z.string().min(1).optional(),
9
+ source: z.string().min(1), // 출처 필드 의무(NFR-04, R-01)
10
+ });
11
+ const dictionaryFileSchema = z.object({
12
+ version: z.string().min(1),
13
+ updatedAt: z.string().min(1),
14
+ entries: z.array(entrySchema).min(1),
15
+ });
16
+ function buildDictionary(entries) {
17
+ const byWord = new Map();
18
+ for (const entry of entries)
19
+ byWord.set(entry.word, entry);
20
+ return {
21
+ entries,
22
+ lookup: (word) => byWord.get(word),
23
+ };
24
+ }
25
+ /**
26
+ * 데이터를 zod로 검증해 Dictionary로 만든다. 실패 시 **어떤 항목이 왜** 실패했는지 담아 throw
27
+ * (조용한 데이터 오류 방지, 03 §5). 파일 I/O와 분리해 테스트 가능하게 둔다.
28
+ */
29
+ export function parseDictionary(data) {
30
+ const result = dictionaryFileSchema.safeParse(data);
31
+ if (!result.success) {
32
+ const detail = result.error.issues
33
+ .slice(0, 8)
34
+ .map((issue) => `- ${issue.path.join(".")}: ${issue.message}`)
35
+ .join("\n");
36
+ throw new Error(`사전 데이터 검증에 실패했습니다:\n${detail}`);
37
+ }
38
+ const seen = new Set();
39
+ for (const entry of result.data.entries) {
40
+ if (seen.has(entry.word))
41
+ throw new Error(`사전에 중복된 단어가 있습니다: "${entry.word}"`);
42
+ seen.add(entry.word);
43
+ }
44
+ return buildDictionary(result.data.entries);
45
+ }
46
+ /**
47
+ * assets/dictionary.json을 기동 시 1회 읽어 검증한다(03 §5). 실패 시 throw →
48
+ * index.ts가 stderr로 알리고 exit 1. 경로는 import.meta.url 기준(전역 설치·npx 모두 동작).
49
+ */
50
+ export function loadDictionary() {
51
+ const url = new URL("../../assets/dictionary.json", import.meta.url);
52
+ let json;
53
+ try {
54
+ json = JSON.parse(readFileSync(url, "utf8"));
55
+ }
56
+ catch {
57
+ throw new Error(`사전 파일을 읽을 수 없습니다: ${decodeURIComponent(url.pathname)}`);
58
+ }
59
+ return parseDictionary(json);
60
+ }
61
+ //# sourceMappingURL=dictionary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dictionary.js","sourceRoot":"","sources":["../../src/data/dictionary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * easyread://guidelines 리소스 본문(정적 Markdown). 자동 규칙 6개 군과 절차·정확성 원칙을 요약한다.
3
+ * 규칙별 상세·근거는 easyread-domain 지침을, 규칙 ID 목록은 checklist(아래)를 단일 소스로 한다.
4
+ */
5
+ export const GUIDELINES_MARKDOWN = `# 쉬운 정보(Easy-Read) 작성 지침
6
+
7
+ 발달장애인·저문해력 독자를 위한 한국어 쉬운 정보 작성 원칙입니다.
8
+
9
+ ## 문장 (SEN)
10
+ - 한 문장에 한 가지 정보만 담고 짧게 씁니다.
11
+ - 복문·피동·이중부정·명사화('~하는 것', '~에 대한')를 피합니다.
12
+
13
+ ## 어휘 (VOC)
14
+ - 어려운 한자어·외래어 대신 쉬운 말을 씁니다.
15
+ - 전문용어·약어는 처음 나올 때 뜻을 풀어 줍니다. 지시어는 줄입니다.
16
+
17
+ ## 숫자 (NUM)
18
+ - 숫자는 아라비아 숫자로, 큰 수는 만·억 단위로 끊어 씁니다.
19
+ - 상대적 날짜('익일') 대신 정확한 날짜를 씁니다.
20
+
21
+ ## 구성 (STR)
22
+ - 한 단락에 한 주제만 담고, 문장이 많으면 나눕니다.
23
+ - 여러 항목 나열은 글머리표 목록으로 만듭니다.
24
+
25
+ ## 표기 (TYP)
26
+ - 기호(※, ~, &, /)는 말로 풀어 씁니다.
27
+ - 괄호·쌍점(:)·쌍반점(;)을 절제합니다.
28
+
29
+ ## 정확성 (ACC)
30
+ - 원문의 날짜·금액·기관명·연락처를 바꾸거나 빠뜨리지 않습니다.
31
+ - 쉽게 바꾸더라도 사실은 그대로 유지합니다.
32
+
33
+ ## 절차·감수
34
+ - 변환 절차를 따르고, 결과물은 당사자 감수 전 초안임을 밝힙니다.
35
+ - 발달장애인 당사자의 확인·감수를 거쳐 최종본을 확정합니다.
36
+ `;
37
+ const GROUP_TITLE = {
38
+ SEN: "문장",
39
+ VOC: "어휘",
40
+ NUM: "숫자",
41
+ STR: "구성",
42
+ TYP: "표기",
43
+ ACC: "정확성",
44
+ };
45
+ const GROUP_ORDER = ["SEN", "VOC", "NUM", "STR", "TYP", "ACC"];
46
+ /**
47
+ * easyread://guidelines/checklist 리소스 본문. 등록된 규칙(registry)에서 규칙 ID 표를 만든다.
48
+ * validation-checklist.md가 규칙 ID의 단일 소스이며, 여기서는 라이브 등록 규칙을 반영한다.
49
+ */
50
+ export function buildChecklistMarkdown(rules) {
51
+ const lines = ["# 검증 규칙 체크리스트", "", "현재 서버에 등록된 자동/보조 검증 규칙입니다.", ""];
52
+ for (const group of GROUP_ORDER) {
53
+ const inGroup = rules.filter((r) => r.group === group);
54
+ if (inGroup.length === 0)
55
+ continue;
56
+ lines.push(`## ${GROUP_TITLE[group] ?? group} (${group})`);
57
+ for (const r of inGroup)
58
+ lines.push(`- ${r.id} · 기본 심각도: ${r.defaultSeverity}`);
59
+ lines.push("");
60
+ }
61
+ return lines.join("\n");
62
+ }
63
+ //# sourceMappingURL=guidelines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guidelines.js","sourceRoot":"","sources":["../../src/data/guidelines.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BlC,CAAC;AAEF,MAAM,WAAW,GAA2B;IAC1C,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,KAAK;CACX,CAAC;AACF,MAAM,WAAW,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAE/D;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAsB;IAC3D,MAAM,KAAK,GAAG,CAAC,eAAe,EAAE,EAAE,EAAE,4BAA4B,EAAE,EAAE,CAAC,CAAC;IACtE,KAAK,MAAM,KAAK,IAAI,WAAW,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;QACvD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACnC,KAAK,CAAC,IAAI,CAAC,MAAM,WAAW,CAAC,KAAK,CAAC,IAAI,KAAK,KAAK,KAAK,GAAG,CAAC,CAAC;QAC3D,KAAK,MAAM,CAAC,IAAI,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;QAChF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,76 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { z } from "zod";
3
+ /**
4
+ * T-14 — Easy-Read 자료 카탈로그 로더 (02 ADR-07 데이터모델).
5
+ * dictionary.ts와 동일 패턴: 기동 시 1회 zod 검증, url_status·조사시점 보존, 런타임 URL fetch 금지.
6
+ * 타입은 zod 스키마에서 추론(z.infer)해 검증 결과와 export 타입이 항상 일치하도록 한다.
7
+ */
8
+ /** 자원 항목 스키마. 필수 필드 + 옵션(pdf_url·note). */
9
+ const resourceEntrySchema = z.object({
10
+ id: z.string().min(1),
11
+ region: z.string().min(1),
12
+ org_type: z.string().min(1),
13
+ organization: z.string().min(1),
14
+ title: z.string().min(1),
15
+ category: z.array(z.enum(["guideline_standard", "example", "law_policy", "portal_center"])).min(1),
16
+ language: z.enum(["ko", "en", "multi"]),
17
+ year: z.string(), // 빈 문자열 허용(연도 미상)
18
+ url: z.string().url(),
19
+ url_status: z.enum(["verified", "unverified"]),
20
+ description: z.string().min(1),
21
+ pdf_url: z.string().url().optional(),
22
+ note: z.string().min(1).optional(),
23
+ /**
24
+ * 원출처 저작물의 재사용 조건 분류(참고용). 본 카탈로그는 서지정보·자체 요약만 수록하고
25
+ * 원문을 전재하지 않는다(NFR-04). "확인 필요(미검증)" 항목의 최종 이용조건은 개별 확인 대상이며
26
+ * 법적 확인은 별도로 진행한다. 대한민국 법령은 저작권법 제7조에 따라 보호 대상이 아니다.
27
+ */
28
+ license: z.string().min(1).optional(),
29
+ });
30
+ /** meta: counts.total만 타입 고정, 나머지 라벨·집계는 passthrough로 원형 보존. */
31
+ const catalogMetaSchema = z
32
+ .object({
33
+ counts: z.object({ total: z.number() }).passthrough(),
34
+ })
35
+ .passthrough();
36
+ const catalogSchema = z.object({
37
+ meta: catalogMetaSchema,
38
+ resources: z.array(resourceEntrySchema).min(1),
39
+ });
40
+ /**
41
+ * 데이터를 zod로 검증해 Catalog로 만든다.
42
+ * 실패 시 어떤 항목이 왜 실패했는지 담아 throw(dictionary.ts 패턴). id 중복은 별도로 거부.
43
+ */
44
+ export function parseResources(data) {
45
+ const result = catalogSchema.safeParse(data);
46
+ if (!result.success) {
47
+ const detail = result.error.issues
48
+ .slice(0, 8)
49
+ .map((issue) => `- ${issue.path.join(".")}: ${issue.message}`)
50
+ .join("\n");
51
+ throw new Error(`자료 카탈로그 검증에 실패했습니다:\n${detail}`);
52
+ }
53
+ const seen = new Set();
54
+ for (const entry of result.data.resources) {
55
+ if (seen.has(entry.id))
56
+ throw new Error(`카탈로그에 중복된 ID가 있습니다: "${entry.id}"`);
57
+ seen.add(entry.id);
58
+ }
59
+ return result.data;
60
+ }
61
+ /**
62
+ * assets/resources.json을 기동 시 1회 읽어 검증한다(ADR-07).
63
+ * 실패 시 throw → index.ts가 stderr로 알리고 exit 1. 런타임 URL fetch 금지.
64
+ */
65
+ export function loadResources() {
66
+ const url = new URL("../../assets/resources.json", import.meta.url);
67
+ let json;
68
+ try {
69
+ json = JSON.parse(readFileSync(url, "utf8"));
70
+ }
71
+ catch {
72
+ throw new Error(`자료 카탈로그 파일을 읽을 수 없습니다: ${decodeURIComponent(url.pathname)}`);
73
+ }
74
+ return parseResources(json);
75
+ }
76
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/data/resources.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * EASYREAD MCP 서버 엔트리. stdio 트랜스포트로 기동한다.
4
+ * 계약: docs/plan/02-architecture.md §3 · 구현 방침: docs/plan/03-backend-plan.md
5
+ *
6
+ * 로깅 규약: stdout은 JSON-RPC 채널이므로 절대 쓰지 않는다. 로그는 stderr(console.error)만,
7
+ * 그리고 사용자 텍스트 본문·스택은 싣지 않는다(NFR-03).
8
+ */
9
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
+ import { loadDictionary } from "./data/dictionary.js";
11
+ import { loadResources } from "./data/resources.js";
12
+ import { createServer } from "./server.js";
13
+ async function main() {
14
+ // 데이터 로드(실패 시 throw → 아래 catch에서 stderr 후 exit 1).
15
+ const dictionary = loadDictionary();
16
+ const resources = loadResources();
17
+ const server = createServer(dictionary, resources);
18
+ const transport = new StdioServerTransport();
19
+ await server.connect(transport);
20
+ }
21
+ main().catch((error) => {
22
+ const detail = error instanceof Error ? error.message : "";
23
+ console.error(`[easyread] 서버를 시작하지 못했습니다. ${detail}`);
24
+ process.exit(1);
25
+ });
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;GAMG;AACH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,mDAAmD;IACnD,MAAM,UAAU,GAAG,cAAc,EAAE,CAAC;IACpC,MAAM,SAAS,GAAG,aAAa,EAAE,CAAC;IAClC,MAAM,MAAM,GAAG,YAAY,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,OAAO,CAAC,KAAK,CAAC,8BAA8B,MAAM,EAAE,CAAC,CAAC;IACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 사용자 대면 문자열 모음(03 §7). 쉬운 정보 프로젝트답게 오류·안내도 쉬운 문장으로 쓴다:
3
+ * 짧게, 한 문장에 한 가지, 무엇을 하면 되는지 함께.
4
+ */
5
+ /** 검증 리포트에 항상 붙는 안내(FR-06, PROC-01~03). 도구는 당사자 감수를 대체하지 않는다. */
6
+ export const PROC_NOTICE = "이 결과는 당사자 감수를 대신하지 않습니다. 발달장애 당사자에게 직접 읽혀서 이해가 되는지 확인하세요. 그림이나 사진을 함께 넣으면 더 좋습니다.";
7
+ /** assembleReport의 기본 notices. */
8
+ export const DEFAULT_NOTICES = [PROC_NOTICE];
9
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,iEAAiE;AACjE,MAAM,CAAC,MAAM,WAAW,GACtB,mFAAmF,CAAC;AAEtF,kCAAkC;AAClC,MAAM,CAAC,MAAM,eAAe,GAAsB,CAAC,WAAW,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * easy-read-review 프롬프트를 등록한다(FR-08, 계약: 02 §3.2).
4
+ * validate_easy_read 호출 후 위반을 규칙 ID로 인용하며 수정안을 제시하도록 지시한다.
5
+ * original을 주면 사실 대조 지시와 원문을 함께 담는다.
6
+ */
7
+ export function registerReviewPrompt(server) {
8
+ server.registerPrompt("easy-read-review", {
9
+ title: "쉬운 정보 검토",
10
+ description: "쉬운 정보 초안을 검토하는 지시를 만든다. 규칙 ID 인용과 validate_easy_read 호출을 포함하고, 원문을 주면 사실 대조를 지시한다.",
11
+ argsSchema: {
12
+ text: z.string().min(1).describe("검토할 변환 초안."),
13
+ original: z.string().optional().describe("원문. 주면 사실 보존까지 대조한다."),
14
+ },
15
+ }, ({ text, original }) => {
16
+ const parts = [
17
+ "당신은 쉬운 정보(Easy-Read) 검토자입니다. 아래 변환 초안을 점검하고 개선안을 제시하세요.",
18
+ "",
19
+ "## 검토 방법",
20
+ "1. validate_easy_read 도구를 호출해 규칙 위반을 확인합니다.",
21
+ "2. 발견한 문제는 규칙 ID(예: SEN-01)를 인용해 지적하고, 문제마다 쉬운 수정안을 함께 제시합니다.",
22
+ ];
23
+ if (original !== undefined && original.trim().length > 0) {
24
+ parts.push("3. 원문과 대조해 날짜·금액·기관명 같은 사실이 바뀌거나 빠지지 않았는지 확인합니다.", "", "## 원문", original);
25
+ }
26
+ parts.push("", "## 검토할 초안", text);
27
+ return { messages: [{ role: "user", content: { type: "text", text: parts.join("\n") } }] };
28
+ });
29
+ }
30
+ //# sourceMappingURL=review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review.js","sourceRoot":"","sources":["../../src/prompts/review.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAiB;IACpD,MAAM,CAAC,cAAc,CACnB,kBAAkB,EAClB;QACE,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,oFAAoF;QACjG,UAAU,EAAE;YACV,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;YAC9C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;SACjE;KACF,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrB,MAAM,KAAK,GAAG;YACZ,yDAAyD;YACzD,EAAE;YACF,UAAU;YACV,6CAA6C;YAC7C,+DAA+D;SAChE,CAAC;QACF,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzD,KAAK,CAAC,IAAI,CACR,kDAAkD,EAClD,EAAE,EACF,OAAO,EACP,QAAQ,CACT,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC;QAClC,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAC7F,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,40 @@
1
+ import { z } from "zod";
2
+ const DEFAULT_AUDIENCE = "발달장애인 등 낮은 문해력 독자";
3
+ /**
4
+ * simplify-text 프롬프트를 등록한다(FR-05·06, 계약: 02 §3.2).
5
+ * 생성 메시지에 ① 변환 절차 ② 정확성 원칙 ③ 당사자 감수 전 초안 고지 3요소를 담는다.
6
+ */
7
+ export function registerSimplifyPrompt(server) {
8
+ server.registerPrompt("simplify-text", {
9
+ title: "쉬운 정보로 바꾸기",
10
+ description: "원문을 쉬운 정보(Easy-Read)로 바꾸는 지시를 만든다. 변환 절차·정확성 원칙·감수 전 초안 고지를 포함한다.",
11
+ argsSchema: {
12
+ text: z.string().min(1).describe("쉽게 바꿀 원문."),
13
+ audience: z.string().optional().describe(`대상 독자(기본: ${DEFAULT_AUDIENCE}).`),
14
+ },
15
+ }, ({ text, audience }) => {
16
+ const reader = audience !== undefined && audience.trim().length > 0 ? audience : DEFAULT_AUDIENCE;
17
+ const message = [
18
+ `당신은 한국어 쉬운 정보(Easy-Read) 변환 전문가입니다. 아래 원문을 "${reader}"가 이해하기 쉽게 바꾸세요.`,
19
+ "",
20
+ "## 변환 절차",
21
+ "1. 핵심 메시지와 꼭 필요한 정보를 먼저 정리합니다.",
22
+ "2. 한 문장에 한 가지 정보만 담아 짧게 씁니다.",
23
+ "3. 어려운 낱말은 lookup_easy_word로 쉬운 말을 찾아 바꿉니다.",
24
+ "4. 숫자·날짜·금액은 쉽게 쓰되 값은 원문과 똑같이 둡니다.",
25
+ "5. 구성을 정리하고 필요하면 그림·목록을 제안합니다.",
26
+ "6. validate_easy_read로 검사하고 경고를 고칩니다.",
27
+ "",
28
+ "## 정확성 원칙",
29
+ "쉽게 바꾸더라도 날짜·금액·기관명·연락처 같은 사실은 절대 바꾸거나 빼지 마세요.",
30
+ "",
31
+ "## 감수 고지",
32
+ "이 결과물은 당사자 감수 전 초안입니다. 발달장애인 당사자의 확인·감수를 거쳐 최종본을 확정하세요.",
33
+ "",
34
+ "## 원문",
35
+ text,
36
+ ].join("\n");
37
+ return { messages: [{ role: "user", content: { type: "text", text: message } }] };
38
+ });
39
+ }
40
+ //# sourceMappingURL=simplify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simplify.js","sourceRoot":"","sources":["../../src/prompts/simplify.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,gBAAgB,GAAG,mBAAmB,CAAC;AAE7C;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,CAAC,cAAc,CACnB,eAAe,EACf;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,mEAAmE;QAChF,UAAU,EAAE;YACV,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;YAC7C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,aAAa,gBAAgB,IAAI,CAAC;SAC5E;KACF,EACD,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrB,MAAM,MAAM,GAAG,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC;QAClG,MAAM,OAAO,GAAG;YACd,+CAA+C,MAAM,kBAAkB;YACvE,EAAE;YACF,UAAU;YACV,gCAAgC;YAChC,8BAA8B;YAC9B,6CAA6C;YAC7C,oCAAoC;YACpC,gCAAgC;YAChC,uCAAuC;YACvC,EAAE;YACF,WAAW;YACX,+CAA+C;YAC/C,EAAE;YACF,UAAU;YACV,yDAAyD;YACzD,EAAE;YACF,OAAO;YACP,IAAI;SACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,CAAC;IACpF,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { extractDates } from "../../text/extractors.js";
2
+ // ACC-01 날짜/시간 불일치: 원문의 날짜·시간이 변환문에 없으면 error. requiresOriginal(원문 있을 때만).
3
+ // 근거: 정확성 원칙(guidelines §7) · ISO 24495. 사실 왜곡은 error.
4
+ export const acc01 = {
5
+ id: "ACC-01",
6
+ group: "ACC",
7
+ defaultSeverity: "error",
8
+ requiresOriginal: true,
9
+ check(ctx) {
10
+ if (ctx.original === undefined)
11
+ return [];
12
+ const inConverted = new Set(extractDates(ctx.raw));
13
+ const findings = [];
14
+ const seen = new Set();
15
+ for (const value of extractDates(ctx.original.raw)) {
16
+ if (inConverted.has(value) || seen.has(value))
17
+ continue;
18
+ seen.add(value);
19
+ findings.push({
20
+ ruleId: "ACC-01",
21
+ message: `원문의 날짜 '${value}'이(가) 변환문에 없습니다. 날짜·시간은 원문과 똑같이 유지하세요.`,
22
+ span: { start: 0, end: 0 },
23
+ });
24
+ }
25
+ return findings;
26
+ },
27
+ };
28
+ //# sourceMappingURL=acc-01.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acc-01.js","sourceRoot":"","sources":["../../../src/rules/acc/acc-01.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGxD,2EAA2E;AAC3E,uDAAuD;AACvD,MAAM,CAAC,MAAM,KAAK,GAAS;IACzB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,KAAK;IACZ,eAAe,EAAE,OAAO;IACxB,gBAAgB,EAAE,IAAI;IACtB,KAAK,CAAC,GAAG;QACP,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAkB,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACnD,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YACxD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,QAAQ;gBAChB,OAAO,EAAE,WAAW,KAAK,wCAAwC;gBACjE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;aAC3B,CAAC,CAAC;QACL,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { extractAmounts } from "../../text/extractors.js";
2
+ // ACC-02 금액/수량 불일치: 원문의 금액·수량이 변환문에 없으면 error. requiresOriginal.
3
+ // 근거: 정확성 원칙(guidelines §7). NUM-02식 의도된 단순화 병기 판정은 backlog.
4
+ export const acc02 = {
5
+ id: "ACC-02",
6
+ group: "ACC",
7
+ defaultSeverity: "error",
8
+ requiresOriginal: true,
9
+ check(ctx) {
10
+ if (ctx.original === undefined)
11
+ return [];
12
+ const inConverted = new Set(extractAmounts(ctx.raw));
13
+ const findings = [];
14
+ const seen = new Set();
15
+ for (const value of extractAmounts(ctx.original.raw)) {
16
+ if (inConverted.has(value) || seen.has(value))
17
+ continue;
18
+ seen.add(value);
19
+ findings.push({
20
+ ruleId: "ACC-02",
21
+ message: `원문의 금액·수량 '${value}'이(가) 변환문에 없습니다. 숫자는 원문과 똑같이 유지하세요.`,
22
+ span: { start: 0, end: 0 },
23
+ });
24
+ }
25
+ return findings;
26
+ },
27
+ };
28
+ //# sourceMappingURL=acc-02.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acc-02.js","sourceRoot":"","sources":["../../../src/rules/acc/acc-02.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAG1D,iEAAiE;AACjE,6DAA6D;AAC7D,MAAM,CAAC,MAAM,KAAK,GAAS;IACzB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,KAAK;IACZ,eAAe,EAAE,OAAO;IACxB,gBAAgB,EAAE,IAAI;IACtB,KAAK,CAAC,GAAG;QACP,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACrD,MAAM,QAAQ,GAAkB,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACrD,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YACxD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,QAAQ;gBAChB,OAAO,EAAE,cAAc,KAAK,qCAAqC;gBACjE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;aAC3B,CAAC,CAAC;QACL,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { extractProperNouns } from "../../text/extractors.js";
2
+ // ACC-03 기관명/고유명사 누락: 원문의 기관명 후보가 변환문에 없으면 warning. requiresOriginal.
3
+ // 근거: 정확성 원칙(guidelines §7). 접미 휴리스틱이라 오탐 있음 → error가 아닌 warning(보조).
4
+ export const acc03 = {
5
+ id: "ACC-03",
6
+ group: "ACC",
7
+ defaultSeverity: "warning",
8
+ requiresOriginal: true,
9
+ check(ctx) {
10
+ if (ctx.original === undefined)
11
+ return [];
12
+ const inConverted = new Set(extractProperNouns(ctx.raw));
13
+ const findings = [];
14
+ const seen = new Set();
15
+ for (const value of extractProperNouns(ctx.original.raw)) {
16
+ if (inConverted.has(value) || seen.has(value))
17
+ continue;
18
+ seen.add(value);
19
+ findings.push({
20
+ ruleId: "ACC-03",
21
+ message: `원문의 기관명 '${value}'이(가) 변환문에 없습니다. 기관명은 줄이지 말고 그대로 쓰세요.`,
22
+ span: { start: 0, end: 0 },
23
+ });
24
+ }
25
+ return findings;
26
+ },
27
+ };
28
+ //# sourceMappingURL=acc-03.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acc-03.js","sourceRoot":"","sources":["../../../src/rules/acc/acc-03.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAG9D,sEAAsE;AACtE,sEAAsE;AACtE,MAAM,CAAC,MAAM,KAAK,GAAS;IACzB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,KAAK;IACZ,eAAe,EAAE,SAAS;IAC1B,gBAAgB,EAAE,IAAI;IACtB,KAAK,CAAC,GAAG;QACP,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,kBAAkB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAkB,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACzD,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YACxD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,QAAQ;gBAChB,OAAO,EAAE,YAAY,KAAK,uCAAuC;gBACjE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;aAC3B,CAAC,CAAC;QACL,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { extractContacts } from "../../text/extractors.js";
2
+ // ACC-04 연락처/URL 불일치: 원문의 전화번호·URL이 변환문에 없으면 error. requiresOriginal.
3
+ // 근거: 정확성 원칙(guidelines §7). 연락처 오류는 실제 피해로 이어지므로 error.
4
+ export const acc04 = {
5
+ id: "ACC-04",
6
+ group: "ACC",
7
+ defaultSeverity: "error",
8
+ requiresOriginal: true,
9
+ check(ctx) {
10
+ if (ctx.original === undefined)
11
+ return [];
12
+ const inConverted = new Set(extractContacts(ctx.raw));
13
+ const findings = [];
14
+ const seen = new Set();
15
+ for (const value of extractContacts(ctx.original.raw)) {
16
+ if (inConverted.has(value) || seen.has(value))
17
+ continue;
18
+ seen.add(value);
19
+ findings.push({
20
+ ruleId: "ACC-04",
21
+ message: `원문의 연락처 '${value}'이(가) 변환문에 없습니다. 전화번호·주소는 원문과 똑같이 유지하세요.`,
22
+ span: { start: 0, end: 0 },
23
+ });
24
+ }
25
+ return findings;
26
+ },
27
+ };
28
+ //# sourceMappingURL=acc-04.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acc-04.js","sourceRoot":"","sources":["../../../src/rules/acc/acc-04.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAG3D,sEAAsE;AACtE,yDAAyD;AACzD,MAAM,CAAC,MAAM,KAAK,GAAS;IACzB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,KAAK;IACZ,eAAe,EAAE,OAAO;IACxB,gBAAgB,EAAE,IAAI;IACtB,KAAK,CAAC,GAAG;QACP,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAkB,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACtD,IAAI,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YACxD,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,QAAQ;gBAChB,OAAO,EAAE,YAAY,KAAK,0CAA0C;gBACpE,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE;aAC3B,CAAC,CAAC;QACL,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { acc01 } from "./acc-01.js";
2
+ import { acc02 } from "./acc-02.js";
3
+ import { acc03 } from "./acc-03.js";
4
+ import { acc04 } from "./acc-04.js";
5
+ export { acc01, acc02, acc03, acc04 };
6
+ /** ACC 규칙군(사실 보존). requiresOriginal:true — 원문이 있을 때만 활성. */
7
+ export const accRules = [acc01, acc02, acc03, acc04];
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/acc/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAEtC,4DAA4D;AAC5D,MAAM,CAAC,MAAM,QAAQ,GAAoB,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 기본 설정. rules-config.json(assets) 도입(T-06/T-07) 전까지의 기본값이며,
3
+ * SEN-01 임계값 등 validation-checklist의 기본값과 일치한다.
4
+ */
5
+ export const defaultRuleConfig = {
6
+ maxWordsWarning: 10,
7
+ maxWordsError: 15,
8
+ severity: {},
9
+ excludeRules: [],
10
+ };
11
+ /** 사용자 값 우선, 미지정은 기본값(03 §5 병합 규칙). */
12
+ export function resolveConfig(input = {}) {
13
+ return {
14
+ maxWordsWarning: input.maxWordsWarning ?? defaultRuleConfig.maxWordsWarning,
15
+ maxWordsError: input.maxWordsError ?? defaultRuleConfig.maxWordsError,
16
+ severity: { ...defaultRuleConfig.severity, ...(input.severity ?? {}) },
17
+ excludeRules: input.excludeRules ?? defaultRuleConfig.excludeRules,
18
+ };
19
+ }
20
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/rules/config.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAuB;IACnD,eAAe,EAAE,EAAE;IACnB,aAAa,EAAE,EAAE;IACjB,QAAQ,EAAE,EAAE;IACZ,YAAY,EAAE,EAAE;CACjB,CAAC;AAEF,uCAAuC;AACvC,MAAM,UAAU,aAAa,CAAC,QAAyB,EAAE;IACvD,OAAO;QACL,eAAe,EAAE,KAAK,CAAC,eAAe,IAAI,iBAAiB,CAAC,eAAe;QAC3E,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,iBAAiB,CAAC,aAAa;QACrE,QAAQ,EAAE,EAAE,GAAG,iBAAiB,CAAC,QAAQ,EAAE,GAAG,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,EAAE;QACtE,YAAY,EAAE,KAAK,CAAC,YAAY,IAAI,iBAAiB,CAAC,YAAY;KACnE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { parseText } from "../text/index.js";
2
+ import { resolveConfig } from "./config.js";
3
+ import { getActiveRules } from "./registry.js";
4
+ import { assembleReport, makeExcerpt } from "./report.js";
5
+ const EMPTY_DICTIONARY = { entries: [], lookup: () => undefined };
6
+ /** RuleContext 조립: 텍스트 파싱 + 설정 병합 + 원문(선택) 파싱. */
7
+ export function createRuleContext(input) {
8
+ const parsed = parseText(input.raw);
9
+ return {
10
+ raw: input.raw,
11
+ sentences: parsed.sentences,
12
+ paragraphs: parsed.paragraphs,
13
+ original: input.original !== undefined ? parseText(input.original) : undefined,
14
+ dictionary: input.dictionary ?? EMPTY_DICTIONARY,
15
+ config: resolveConfig(input.config),
16
+ };
17
+ }
18
+ /**
19
+ * 규칙들을 실행해 위반 목록을 만든다. severity 확정(발견값 → config → 규칙 기본값)과
20
+ * excerpt 생성은 여기서 일괄 처리한다(규칙 파일의 반복을 줄인다).
21
+ * 규칙 예외는 잡지 않는다 — rules/는 순수 계층이며 로깅·복구는 도구 핸들러(T-05)가 맡는다.
22
+ */
23
+ export function runRules(context, activeRules) {
24
+ const violations = [];
25
+ for (const rule of activeRules) {
26
+ for (const finding of rule.check(context)) {
27
+ const severity = finding.severity ?? context.config.severity[finding.ruleId] ?? rule.defaultSeverity;
28
+ violations.push({
29
+ ruleId: finding.ruleId,
30
+ severity,
31
+ message: finding.message,
32
+ span: finding.span,
33
+ excerpt: makeExcerpt(context.raw, finding.span),
34
+ suggestion: finding.suggestion,
35
+ });
36
+ }
37
+ }
38
+ return violations;
39
+ }
40
+ /** 전체 파이프라인: 파싱 → 활성 규칙 선별 → 실행 → 리포트 조립. */
41
+ export function evaluate(input, allRules) {
42
+ const context = createRuleContext(input);
43
+ const activeRules = getActiveRules(allRules, {
44
+ excludeRules: context.config.excludeRules,
45
+ hasOriginal: context.original !== undefined,
46
+ });
47
+ return assembleReport(runRules(context, activeRules));
48
+ }
49
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/rules/engine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1D,MAAM,gBAAgB,GAAe,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC;AAU9E,kDAAkD;AAClD,MAAM,UAAU,iBAAiB,CAAC,KAAoB;IACpD,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,OAAO;QACL,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,QAAQ,EAAE,KAAK,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;QAC9E,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,gBAAgB;QAChD,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC;KACpC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAoB,EAAE,WAA4B;IACzE,MAAM,UAAU,GAAgB,EAAE,CAAC;IACnC,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1C,MAAM,QAAQ,GACZ,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,eAAe,CAAC;YACtF,UAAU,CAAC,IAAI,CAAC;gBACd,MAAM,EAAE,OAAO,CAAC,MAAM;gBACtB,QAAQ;gBACR,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,IAAI,EAAE,OAAO,CAAC,IAAI;gBAClB,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC;gBAC/C,UAAU,EAAE,OAAO,CAAC,UAAU;aAC/B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,QAAQ,CAAC,KAAoB,EAAE,QAAyB;IACtE,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,cAAc,CAAC,QAAQ,EAAE;QAC3C,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY;QACzC,WAAW,EAAE,OAAO,CAAC,QAAQ,KAAK,SAAS;KAC5C,CAAC,CAAC;IACH,OAAO,cAAc,CAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { evaluate } from "./engine.js";
2
+ import { rules } from "./registry.js";
3
+ /** 등록된 전체 규칙으로 검증한다. 도구 계층(validate_easy_read, T-05)의 진입점. */
4
+ export function validate(input) {
5
+ return evaluate(input, rules);
6
+ }
7
+ export { createRuleContext, runRules, evaluate } from "./engine.js";
8
+ export { getActiveRules, rules } from "./registry.js";
9
+ export { resolveConfig, defaultRuleConfig } from "./config.js";
10
+ export { assembleReport, makeExcerpt, validationReportSchema, MAX_VIOLATIONS } from "./report.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC,8DAA8D;AAC9D,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,OAAO,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAChC,CAAC;AAiBD,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,sBAAsB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { num01 } from "./num-01.js";
2
+ import { num02 } from "./num-02.js";
3
+ import { num03 } from "./num-03.js";
4
+ import { num04 } from "./num-04.js";
5
+ export { num01, num02, num03, num04 };
6
+ /** NUM 규칙군. registry가 정적으로 조립한다. */
7
+ export const numRules = [num01, num02, num03, num04];
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/num/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAEtC,oCAAoC;AACpC,MAAM,CAAC,MAAM,QAAQ,GAAoB,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC"}