hanlint 0.0.18 → 0.0.19

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 (125) hide show
  1. package/README.md +17 -16
  2. package/data/calqueFrame.json +20 -20
  3. package/data/cliches.json +33 -33
  4. package/data/confusable.json +20 -20
  5. package/data/easyWords.json +12 -12
  6. package/data/exemplars.json +30 -30
  7. package/data/hiddenDeps.txt +11 -11
  8. package/data/japaneseLoan.json +27 -27
  9. package/data/koreanProse.txt +31 -31
  10. package/data/patterns.json +26 -26
  11. package/data/phraseRepeat.json +2 -2
  12. package/data/platformApis.txt +11 -11
  13. package/data/redundantPair.json +16 -16
  14. package/data/ruleDocs.json +56 -56
  15. package/data/screenNarration.json +10 -10
  16. package/data/screenSentence.json +12 -12
  17. package/data/screenTone.json +6 -6
  18. package/data/screenWord.json +70 -70
  19. package/data/spacing.json +11 -11
  20. package/data/spelling.json +29 -29
  21. package/data/translationese.json +23 -24
  22. package/data/usageCounts.report.json +2 -2
  23. package/data/version.json +1 -1
  24. package/package.json +1 -1
  25. package/src/analysis/grammar/ending.js +1 -1
  26. package/src/analysis/grammar/hangul.js +4 -4
  27. package/src/analysis/grammar/josa.js +1 -1
  28. package/src/analysis/grammar/register.js +1 -1
  29. package/src/analysis/grammar/voice.js +1 -1
  30. package/src/analysis/index.js +2 -2
  31. package/src/analysis/josa.js +1 -1
  32. package/src/analysis/splitSentences.js +1 -1
  33. package/src/analysis/tokenize.js +13 -13
  34. package/src/baseline/store.js +8 -8
  35. package/src/cli/help.js +11 -11
  36. package/src/cli/main.js +135 -124
  37. package/src/cli/walk.js +1 -1
  38. package/src/cli/welcome.js +8 -8
  39. package/src/config/loadConfig.js +3 -3
  40. package/src/config/patch.js +6 -6
  41. package/src/config/readerContract.js +26 -26
  42. package/src/config/settings.js +30 -28
  43. package/src/config/toml.js +9 -9
  44. package/src/data/exemplars.js +21 -21
  45. package/src/data/load.js +6 -6
  46. package/src/data/operations.js +14 -14
  47. package/src/data/patches.js +10 -10
  48. package/src/data/patterns.js +5 -5
  49. package/src/data/profiles.js +5 -5
  50. package/src/data/prose.js +5 -5
  51. package/src/document/model.js +4 -4
  52. package/src/document/parseMarkdown.js +7 -7
  53. package/src/document/plainText.js +3 -3
  54. package/src/document/sourceText.js +24 -24
  55. package/src/edit/applyFixes.js +0 -0
  56. package/src/fingerprint/build.js +6 -6
  57. package/src/fingerprint/codeBlocks.js +3 -3
  58. package/src/fingerprint/codeMarkers.js +4 -4
  59. package/src/fingerprint/dictionaries.js +7 -7
  60. package/src/fingerprint/markers.js +7 -7
  61. package/src/fingerprint/readerState.js +9 -9
  62. package/src/fingerprint/sourceSentence.js +1 -1
  63. package/src/fingerprint/topics.js +1 -1
  64. package/src/guard/contract.js +6 -6
  65. package/src/guard/editLocality.js +1 -1
  66. package/src/guard/outline.js +1 -1
  67. package/src/guard/receipt.js +13 -13
  68. package/src/guard/surface.js +4 -4
  69. package/src/index.js +6 -6
  70. package/src/learn/edits.js +2 -2
  71. package/src/learn/operations.js +1 -1
  72. package/src/regex.js +2 -2
  73. package/src/report/compactReport.js +3 -3
  74. package/src/report/fingerprintJson.js +2 -2
  75. package/src/report/githubReport.js +1 -1
  76. package/src/report/jsonReport.js +2 -2
  77. package/src/report/registerMatch.js +2 -2
  78. package/src/report/sheet.js +16 -16
  79. package/src/report/textReport.js +12 -12
  80. package/src/report/writingSpec.js +15 -15
  81. package/src/rules/code/inputFileSource.js +1 -1
  82. package/src/rules/code/installImport.js +2 -2
  83. package/src/rules/code/platformApi.js +1 -1
  84. package/src/rules/document/countMismatch.js +2 -1
  85. package/src/rules/document/enoughOnce.js +2 -1
  86. package/src/rules/document/fieldEcho.js +10 -6
  87. package/src/rules/document/keywordHeading.js +1 -1
  88. package/src/rules/document/keywordMissing.js +1 -1
  89. package/src/rules/document/noQuestion.js +2 -2
  90. package/src/rules/document/promiseRecall.js +1 -1
  91. package/src/rules/document/tableOddCell.js +1 -1
  92. package/src/rules/finding.js +4 -4
  93. package/src/rules/paragraph/factListParagraph.js +1 -1
  94. package/src/rules/paragraph/paraFragment.js +1 -1
  95. package/src/rules/readerDebt.js +1 -1
  96. package/src/rules/registry.js +26 -26
  97. package/src/rules/sentence/connectorRepeat.js +2 -1
  98. package/src/rules/sentence/danglingDeixis.js +1 -1
  99. package/src/rules/sentence/dash.js +1 -1
  100. package/src/rules/sentence/deixis.js +1 -1
  101. package/src/rules/sentence/doubleNegative.js +2 -1
  102. package/src/rules/sentence/doublePassive.js +2 -1
  103. package/src/rules/sentence/draftHistory.js +2 -1
  104. package/src/rules/sentence/endingRepeat.js +3 -2
  105. package/src/rules/sentence/euiChain.js +1 -1
  106. package/src/rules/sentence/fillerOpener.js +1 -1
  107. package/src/rules/sentence/imperativePeriod.js +1 -1
  108. package/src/rules/sentence/longSentence.js +1 -1
  109. package/src/rules/sentence/negationRedefine.js +1 -1
  110. package/src/rules/sentence/numberOrphan.js +1 -1
  111. package/src/rules/sentence/outsideProfile.js +8 -3
  112. package/src/rules/sentence/phraseRepeat.js +2 -1
  113. package/src/rules/shared/candidates.js +2 -2
  114. package/src/rules/shared/dictionaryRule.js +9 -2
  115. package/src/rules/shared/repeat.js +2 -2
  116. package/src/rules/structure/bridgeRepeat.js +3 -3
  117. package/src/rules/structure/emojiBullet.js +1 -1
  118. package/src/rules/structure/headingSkip.js +1 -1
  119. package/src/rules/structure/headingUniform.js +2 -1
  120. package/src/rules/structure/loneSubheading.js +1 -1
  121. package/src/rules/structure/moreLater.js +1 -1
  122. package/src/text.js +2 -2
  123. package/src/usage/counts.js +3 -3
  124. package/src/usage/index.js +3 -3
  125. package/src/usage/sentences.js +17 -17
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 지적 하나의 모양. 파이썬 rules/finding.py 와 같은 필드와 같은 JSON 순서다. */
2
+ /** 지적 하나의 형식. 파이썬 rules/finding.py와 필드도 JSON 순서도 같다. */
3
3
 
4
4
  export const ERROR = "error";
5
5
  export const NOTICE = "notice";
@@ -18,10 +18,10 @@ const MIN_LOCAL_CUE = 2;
18
18
  * @property {string} quote
19
19
  * @property {string} why
20
20
  * @property {string | null} fix 기계가 낼 수 있을 때만
21
- * @property {string} severity error 는 규칙 위반, notice 는 확인이 필요한 것
21
+ * @property {string} severity error는 규칙 위반, notice는 확인이 필요한 것
22
22
  * @property {string} scope sentence, paragraph, section, document
23
- * @property {number} at scope 가 가리키는 지문의 index
24
- * @property {string | null} fragment 원문에서 바꿀 조각. hanlint fix 가 replacement 로 바꾼다
23
+ * @property {number} at scope가 가리키는 분석 결과의 index
24
+ * @property {string | null} fragment 원문에서 바꿀 조각. hanlint fix가 replacement로 바꾼다
25
25
  * @property {string | null} replacement
26
26
  * @property {Candidate[]} candidates 뜻을 정하지 않고 형태로 좁힐 수 있을 때만. 순위와 점수는 없다
27
27
  */
@@ -20,7 +20,7 @@ export function run(doc, config) {
20
20
  if (paragraph.sentenceCount < config.factListMinSentences || isLinked(paragraph)) continue;
21
21
  if (paragraph.meanLength <= config.factListMaxMeanLength) {
22
22
  findings.push(
23
- finding(name, paragraph.startLine, paragraph.sentences[0].text, `문장 ${paragraph.sentenceCount}개에 인과 표지가 하나도 없다. 사실만 나열한 문단일 수 있다. 그래서, 때문에 로 잇는다`, null, NOTICE, PARAGRAPH, paragraph.index),
23
+ finding(name, paragraph.startLine, paragraph.sentences[0].text, `문장 ${paragraph.sentenceCount}개에 인과 접속사가 하나도 없다. 사실만 나열한 문단일 수 있다. \`그래서\`, \`때문에\`로 잇는다`, null, NOTICE, PARAGRAPH, paragraph.index),
24
24
  );
25
25
  }
26
26
  }
@@ -31,7 +31,7 @@ export function run(doc, config) {
31
31
  for (const [start] of runsOf(keys, config.fragmentRun)) {
32
32
  const first = paragraphs[start];
33
33
  findings.push(
34
- finding(name, first.startLine, first.sentences.length ? first.sentences[0].text : "", `${config.fragmentRun}개 문단이 연달아 한두 문장씩이다. 화제가 같으면 한 문단으로 묶는다. 줄바꿈은 화제가 바뀌는 자리에만`, null, "error", PARAGRAPH, first.index),
34
+ finding(name, first.startLine, first.sentences.length ? first.sentences[0].text : "", `${config.fragmentRun}개 문단이 연달아 한두 문장씩이다. 화제가 같으면 한 문단으로 묶는다. 줄바꿈은 화제가 바뀌는 곳에만 둔다`, null, "error", PARAGRAPH, first.index),
35
35
  );
36
36
  }
37
37
  }
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 독자 부채는 새 판정이 아니라 reader 기제 Finding을 사람이 읽는 이름으로 묶은 것이다. */
2
+ /** 독자가 막힐 곳. 새로 판정하지 않고, reader 방법을 쓰는 규칙의 지적을 사람이 읽을 이름으로 묶는다. */
3
3
 
4
4
  import { ruleMechanism } from "./registry.js";
5
5
 
@@ -1,8 +1,8 @@
1
1
  // @ts-check
2
2
  /**
3
- * 규칙 등록부. 규칙 하나는 파일 하나이고 `name` 과 `run(doc, config)` 을 내보낸다. 파이썬은 폴더를 걸어 찾고
4
- * 여기는 목록으로 든다. test/rules.test.js 가 목록과 폴더와 ruleDocs.json 이 같은지 본다.
5
- * 기술서 (왜, 어디서, 고치기, 안 잡는 것) 는 파이썬 docstring 의 투영 data/ruleDocs.json 에서 읽는다.
3
+ * 규칙 목록. 규칙 하나는 파일 하나이고 `name`과 `run(doc, config)`을 내보낸다. 파이썬은 폴더를 걸어 찾고
4
+ * 여기는 목록으로 든다. test/rules.test.js가 목록과 폴더와 ruleDocs.json이 같은지 본다.
5
+ * 규칙 설명(왜, 어디서, 고치기, 안 잡는 것)은 파이썬 docstring을 옮긴 data/ruleDocs.json에서 읽는다.
6
6
  */
7
7
  import { enabled } from "../config/settings.js";
8
8
  import { loadRuleCategories, loadRuleDocs } from "../data/load.js";
@@ -66,7 +66,7 @@ import * as screenWord from "./screen/screenWord.js";
66
66
  /**
67
67
  * @typedef {object} Rule
68
68
  * @property {string} name
69
- * @property {string} mechanism 세는 방법. MECHANISMS 의 키 하나
69
+ * @property {string} mechanism 세는 방법. MECHANISMS의 키 하나
70
70
  * @property {(doc: import("../fingerprint/build.js").DocumentPrint, config: import("../config/settings.js").Config) => import("./finding.js").Finding[]} run
71
71
  */
72
72
 
@@ -131,19 +131,19 @@ export const RULES = [
131
131
  ];
132
132
 
133
133
  /**
134
- * 규칙이 세는 방법의 닫힌 집합. 파이썬 rules/registry.py 의 MECHANISMS 와 같은 값과 같은 순서다.
134
+ * 규칙이 방식의 닫힌 목록. 파이썬 rules/registry.py의 MECHANISMS와 같은 값과 같은 순서다.
135
135
  * @type {Record<string, string>}
136
136
  */
137
137
  export const MECHANISMS = {
138
- dictionary: "사전. 낱말 목록과 정규식이 맞는 자리",
139
- repeat: "반복. 같은 모양이 창 안에서 N 번",
140
- threshold: "셈. 분석표의 값이 임계를 넘거나 모양이 계약과 다른 자리",
141
- contrast: "대조. 두 자리를 맞대 어긋난 곳",
142
- reader: "독자 상태. 문장 순서대로 독자가 손에 든 것과 본 것에 그 자리가 요구하는 것을 맞댄다",
138
+ dictionary: "사전. 단어 목록과 정규식이 맞는 곳",
139
+ repeat: "반복. 같은 모양이 가까이에서 여러 번 나오는 곳",
140
+ threshold: "수치. 글에서 잰 값이 기준값을 넘거나 정해 둔 형식과 다른 곳",
141
+ contrast: "비교. 두 곳을 비교해 어긋난 곳",
142
+ reader: "독자. 앞에서 읽은 내용만으로 그 문장을 이해할 수 있는지 문장 순서대로 본다",
143
143
  };
144
144
  for (const rule of RULES) {
145
145
  if (!(rule.mechanism in MECHANISMS)) {
146
- throw new Error(`규칙 ${rule.name} 의 기제 ${rule.mechanism} 은 닫힌 집합 밖이다 (${Object.keys(MECHANISMS).join(", ")}). 새 기제는 규칙 하나 때문에 들어오지 않는다. 멈춰서 묻는다`);
146
+ throw new Error(`규칙 ${rule.name}의 방식이 닫힌 집합 밖이다: ${rule.mechanism} (있는 것: ${Object.keys(MECHANISMS).join(", ")}). 새 방법은 규칙 하나 때문에 더하지 않는다. 멈추고 묻는다`);
147
147
  }
148
148
  }
149
149
 
@@ -157,7 +157,7 @@ export function ruleNames() {
157
157
  /** @param {string} name */
158
158
  export function ruleDoc(name) {
159
159
  const docs = loadRuleDocs();
160
- if (!(name in docs) || !BY_NAME.has(name)) throw new Error(`모르는 규칙: ${name}. hanlint rules 로 목록을 본다`);
160
+ if (!(name in docs) || !BY_NAME.has(name)) throw new Error(`모르는 규칙: ${name}. hanlint rules로 목록을 본다`);
161
161
  return docs[name];
162
162
  }
163
163
 
@@ -166,10 +166,10 @@ export function ruleSummary(name) {
166
166
  return ruleDoc(name).split("\n")[0];
167
167
  }
168
168
 
169
- /** 기술서의 네 절 표. 파이썬 rules/registry.py 의 REQUIRED_SECTIONS 와 같다. */
169
+ /** 기술서의 네 절 표. 파이썬 rules/registry.py의 REQUIRED_SECTIONS와 같다. */
170
170
  export const REQUIRED_SECTIONS = ["왜:", "어디서:", "고치기:", "안 잡는 것:"];
171
171
 
172
- /** 기술서에서 절 하나의 본문을 한 줄로. 파이썬 docSection 과 같다. @param {string} doc @param {string} label */
172
+ /** 기술서에서 절 하나의 본문을 한 줄로. 파이썬 docSection과 같다. @param {string} doc @param {string} label */
173
173
  export function docSection(doc, label) {
174
174
  const collected = [];
175
175
  let inside = false;
@@ -188,41 +188,41 @@ export function docSection(doc, label) {
188
188
  return collected.filter(Boolean).join(" ");
189
189
  }
190
190
 
191
- /** 규칙의 고치기 절 한 줄. primer 가 쓰기 전에 읽히는 그 문장이다. @param {string} name */
191
+ /** 규칙의 고치기 절 한 줄. primer가 쓰기 전에 읽히는 그 문장이다. @param {string} name */
192
192
  export function ruleFix(name) {
193
193
  return docSection(ruleDoc(name), "고치기:");
194
194
  }
195
195
 
196
196
  /**
197
- * 부류의 사람 이름. 파이썬 rules/registry.py 의 CATEGORY_TITLES 와 같은 값과 같은 순서다.
197
+ * 묶음의 사람 이름. 파이썬 rules/registry.py의 CATEGORY_TITLES와 같은 값과 같은 순서다.
198
198
  * @type {Record<string, string>}
199
199
  */
200
200
  export const CATEGORY_TITLES = {
201
- sentence: "문장 안에서 세는 것",
202
- paragraph: "문단 사이에서 세는 것",
203
- structure: "글의 짜임에서 세는 것",
204
- document: "두 자리를 대조해 세는 것",
201
+ sentence: "문장",
202
+ paragraph: "문단",
203
+ structure: "글의 짜임",
204
+ document: "문서",
205
205
  orthography: "표기와 띄어쓰기",
206
- code: "코드 블록 사이를 대조하는 것",
207
- screen: "화면의 글에서 세는 것",
206
+ code: "코드 블록",
207
+ screen: "화면의 글",
208
208
  };
209
209
 
210
210
  /** 규칙이 세는 방법. @param {string} name */
211
211
  export function ruleMechanism(name) {
212
212
  const rule = BY_NAME.get(name);
213
- if (!rule) throw new Error(`모르는 규칙: ${name}. hanlint rules 로 목록을 본다`);
213
+ if (!rule) throw new Error(`모르는 규칙: ${name}. hanlint rules로 목록을 본다`);
214
214
  return rule.mechanism;
215
215
  }
216
216
 
217
- /** 규칙이 사는 부류. 정본은 파이썬 쪽 폴더이고 여기는 그 투영을 읽는다. @param {string} name */
217
+ /** 규칙이 사는 묶음. 정본은 파이썬 쪽 폴더이고 여기는 그 투영을 읽는다. @param {string} name */
218
218
  export function ruleCategory(name) {
219
219
  const categories = loadRuleCategories();
220
- if (!(name in categories)) throw new Error(`모르는 규칙: ${name}. hanlint rules 로 목록을 본다`);
220
+ if (!(name in categories)) throw new Error(`모르는 규칙: ${name}. hanlint rules로 목록을 본다`);
221
221
  return categories[name];
222
222
  }
223
223
 
224
224
  /**
225
- * 인라인 제어가 그 줄에서 그 규칙을 껐는가. `*` 는 전부다.
225
+ * 인라인 제어가 그 줄에서 그 규칙을 껐는가. `*`는 전부다.
226
226
  * @param {import("./finding.js").Finding} f
227
227
  * @param {[string, number, number][]} disabled
228
228
  */
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import { fitJosa } from "../../analysis/grammar/josa.js";
2
3
  import { SENTENCE, finding } from "../finding.js";
3
4
  import { runsOf } from "../shared/repeat.js";
4
5
 
@@ -15,7 +16,7 @@ export function run(doc) {
15
16
  for (const [start, length, connector] of runsOf(keys, 2)) {
16
17
  for (const sentence of sentences.slice(start + 1, start + length)) {
17
18
  findings.push(
18
- finding(name, sentence.line, sentence.text, `\`${connector}\` 로 시작하는 문장이 연달아 온다. 앞에서 만든 것의 이름을 불러 잇는다`, null, "error", SENTENCE, sentence.index),
19
+ finding(name, sentence.line, sentence.text, `\`${connector}\`${fitJosa(connector, "로")} 시작하는 문장이 연달아 온다. 앞에서 만든 것의 이름을 불러 잇는다`, null, "error", SENTENCE, sentence.index),
19
20
  );
20
21
  }
21
22
  }
@@ -32,7 +32,7 @@ export function run(doc) {
32
32
  if (overlap(reader.recent, sentence.topics) > 0) continue;
33
33
  if (isPrompt(reader.previous)) continue;
34
34
  findings.push(
35
- finding(name, sentence.line, sentence.text, `\`${sentence.deixis[0]}\` ${fitJosa(sentence.deixis[0], "이")} 가리킬 것이 앞 문장에 없다. 가리키는 파일, 값, 코드의 이름을 쓴다`, null, "error", SENTENCE, sentence.index, null, null, danglingDeixisCandidates(sentence, reader.previous)),
35
+ finding(name, sentence.line, sentence.text, `\`${sentence.deixis[0]}\`${fitJosa(sentence.deixis[0], "이")} 가리킬 것이 앞 문장에 없다. 가리키는 파일, 값, 코드의 이름을 쓴다`, null, "error", SENTENCE, sentence.index, null, null, danglingDeixisCandidates(sentence, reader.previous)),
36
36
  );
37
37
  }
38
38
  return findings;
@@ -13,7 +13,7 @@ export function run(doc) {
13
13
  block.text.split("\n").forEach((lineText, offset) => {
14
14
  if (DASHES.test(lineText)) {
15
15
  findings.push(
16
- finding(name, block.startLine + offset, lineText.trim(), "긴 줄표다. 부연은 마침표로 끊거나 괄호로, 범위는 물결표 ~ 로 쓴다", null, "error", DOCUMENT, block.index),
16
+ finding(name, block.startLine + offset, lineText.trim(), "긴 줄표다. 덧붙이는 말은 마침표로 끊거나 괄호에 넣고, 범위는 물결표(~)로 쓴다", null, "error", DOCUMENT, block.index),
17
17
  );
18
18
  }
19
19
  });
@@ -16,7 +16,7 @@ export function run(doc) {
16
16
  const reader = doc.reader.beforeSentence[sentence.index];
17
17
  if (overlap(reader.recent, sentence.topics) === 0) continue;
18
18
  findings.push(
19
- finding(name, sentence.line, sentence.text, `\`${sentence.deixis[0]}\` ${fitJosa(sentence.deixis[0], "은")} 독자가 스크롤을 되돌려야 하는 지시어다. 가리키는 파일, 값, 코드의 이름을 쓴다`, null, "error", SENTENCE, sentence.index),
19
+ finding(name, sentence.line, sentence.text, `\`${sentence.deixis[0]}\`${fitJosa(sentence.deixis[0], "은")} 독자가 스크롤을 되돌려야 하는 지시어다. 가리키는 파일, 값, 코드의 이름을 쓴다`, null, "error", SENTENCE, sentence.index),
20
20
  );
21
21
  }
22
22
  return findings;
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import { fitJosa } from "../../analysis/grammar/josa.js";
2
3
  import { SENTENCE, finding } from "../finding.js";
3
4
 
4
5
  export const name = "doubleNegative";
@@ -24,7 +25,7 @@ export function run(doc) {
24
25
  const start = /** @type {number} */ (match.index);
25
26
  const fix = replacement ? sentence.text.slice(0, start) + replacement + sentence.text.slice(start + match[0].length) : null;
26
27
  findings.push(
27
- finding(name, sentence.line, sentence.text, `\`${match[1]}\` 는 이중 부정이다. 긍정으로 쓴다`, fix, "error", SENTENCE, sentence.index, replacement ? match[1] : null, replacement ?? null),
28
+ finding(name, sentence.line, sentence.text, `\`${match[1]}\`${fitJosa(match[1], "는")} 이중 부정이다. 긍정으로 쓴다`, fix, "error", SENTENCE, sentence.index, replacement ? match[1] : null, replacement ?? null),
28
29
  );
29
30
  }
30
31
  return findings;
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import { fitJosa } from "../../analysis/grammar/josa.js";
2
3
  import { SENTENCE, finding } from "../finding.js";
3
4
  import { doublePassiveCandidates } from "../shared/candidates.js";
4
5
 
@@ -38,7 +39,7 @@ export function run(doc) {
38
39
  name,
39
40
  sentence.line,
40
41
  sentence.text,
41
- `\`${sentence.passives[0]}\` 는 피동에 어지다 를 또 붙인 이중 피동이다. 하나만 남긴다`,
42
+ `\`${sentence.passives[0]}\`${fitJosa(sentence.passives[0], "는")} 피동에 \`-어지다\`를 또 붙인 이중 피동이다. 하나만 남긴다`,
42
43
  approved ? candidates[0].text : null,
43
44
  "error",
44
45
  SENTENCE,
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import { fitJosa } from "../../analysis/grammar/josa.js";
2
3
  import { insideAny, matchedSpans } from "../../fingerprint/markers.js";
3
4
  import { SENTENCE, finding } from "../finding.js";
4
5
 
@@ -16,7 +17,7 @@ export function run(doc, config) {
16
17
  for (const [start, end, text] of matchedSpans(sentence.text, "draftHistory.txt")) {
17
18
  if (insideAny(start, end, sentence.quoted)) continue;
18
19
  findings.push(
19
- finding(name, sentence.line, sentence.text, `\`${text}\` 는 글쓴이의 과정이다. 독자는 초고를 본 적이 없다. 알아낸 사실만 결과 문장으로 남긴다`, null, "error", SENTENCE, sentence.index),
20
+ finding(name, sentence.line, sentence.text, `\`${text}\`${fitJosa(text, "는")} 글쓴이의 과정이다. 독자는 초고를 본 적이 없다. 알아낸 사실만 결과 문장으로 남긴다`, null, "error", SENTENCE, sentence.index),
20
21
  );
21
22
  }
22
23
  }
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import { fitJosa } from "../../analysis/grammar/josa.js";
2
3
  import { NOTICE, SENTENCE, finding } from "../finding.js";
3
4
  import { runsOf } from "../shared/repeat.js";
4
5
 
@@ -16,12 +17,12 @@ export function run(doc, config) {
16
17
  const sentences = section.paragraphs.flatMap((p) => p.sentences);
17
18
  const endings = sentences.map((s) => (COUNTED_ENDINGS.has(s.ending) ? s.ending : `__${s.index}`));
18
19
  for (const [start, length, ending] of runsOf(endings, config.endingRun)) {
19
- // 구간 어딘가에 이유를 잇는 말이나 독자를 부르는 말이 있으면 리듬이 산 것이다. 구간을 쪼개면
20
+ // 구간 어딘가에 인과 접속사나 독자에게 말을 거는 문장이 있으면 리듬이 산 것이다. 구간을 쪼개면
20
21
  // 조각마다 지적이 되어 오히려 늘어난다. 구간 전체를 하나로 보고 살린다.
21
22
  if (sentences.slice(start, start + length).some((s) => s.causal || s.readerCall)) continue;
22
23
  const first = sentences[start];
23
24
  findings.push(
24
- finding(name, first.line, first.text, `\`${ending}\` 로 끝나는 문장 ${length}개에 인과도 질문도 독자를 부르는 말도 없다. 질문, 인과, 행동 동사로 리듬을 바꾼다`, null, NOTICE, SENTENCE, first.index),
25
+ finding(name, first.line, first.text, `\`${ending}\`${fitJosa(ending, "로")} 끝나는 문장 ${length}개에 인과 접속사도 질문도 독자를 부르는 말도 없다. 질문, 인과 접속사, 행동 동사로 리듬을 바꾼다`, null, NOTICE, SENTENCE, first.index),
25
26
  );
26
27
  }
27
28
  }
@@ -11,7 +11,7 @@ export function run(doc) {
11
11
  for (const sentence of doc.sentences) {
12
12
  if (sentence.euiCount >= 3 || (sentence.euiCount >= 2 && euiAdjacent(sentence.text))) {
13
13
  findings.push(
14
- finding(name, sentence.line, sentence.text, `한 문장에 \`의\` 가 ${sentence.euiCount}번이다. 명사를 쌓은 자리라 관계가 표시되지 않는다. 동사로 편다`, null, "error", SENTENCE, sentence.index),
14
+ finding(name, sentence.line, sentence.text, `한 문장에 \`의\`가 ${sentence.euiCount}번이다. 명사를 쌓은 문장이라 관계가 표시되지 않는다. 동사로 편다`, null, "error", SENTENCE, sentence.index),
15
15
  );
16
16
  }
17
17
  }
@@ -12,7 +12,7 @@ export function run(doc) {
12
12
  const match = FILLER.exec(sentence.text);
13
13
  if (!match) continue;
14
14
  findings.push(
15
- finding(name, sentence.line, sentence.text, `\`${match[1]}\` 만 붙여 잇는 문장이다. 앞에서 만든 파일이나 값을 이름으로 다시 부르고 다음 행동을 붙인다`, null, "error", SENTENCE, sentence.index),
15
+ finding(name, sentence.line, sentence.text, `\`${match[1]}\`만 붙여 잇는 문장이다. 앞에서 만든 파일이나 값을 이름으로 다시 언급하고 다음 행동을 붙인다`, null, "error", SENTENCE, sentence.index),
16
16
  );
17
17
  }
18
18
  return findings;
@@ -14,7 +14,7 @@ export function run(doc) {
14
14
  const start = /** @type {number} */ (match.index);
15
15
  const fix = sentence.text.slice(0, start) + match[1] + sentence.text.slice(start + match[0].length);
16
16
  findings.push(
17
- finding(name, sentence.line, sentence.text, `\`${match[1]}\` 처럼 명령형과 청유형 뒤에는 마침표를 붙이지 않는다`, fix, "error", SENTENCE, sentence.index, match[0], match[1]),
17
+ finding(name, sentence.line, sentence.text, `명령·청유 문장 끝(\`${match[1]}\`)에 마침표를 붙이지 않는 표기 방침이다`, fix, "error", SENTENCE, sentence.index, match[0], match[1]),
18
18
  );
19
19
  }
20
20
  return findings;
@@ -14,7 +14,7 @@ export function run(doc, config) {
14
14
  for (const sentence of doc.sentences) {
15
15
  if (sentence.length > config.longSentenceMax) {
16
16
  findings.push(
17
- finding(name, sentence.line, sentence.text, `어절 ${sentence.length}개다. 상한은 ${config.longSentenceMax}. 마침표로 끊거나 나열이면 목록으로 꺼낸다`, null, NOTICE, SENTENCE, sentence.index, null, null, longSentenceCandidates(sentence.text)),
17
+ finding(name, sentence.line, sentence.text, `어절이 ${sentence.length}개로 상한 ${config.longSentenceMax}개를 넘는다. 마침표로 끊거나 나열이면 목록으로 꺼낸다`, null, NOTICE, SENTENCE, sentence.index, null, null, longSentenceCandidates(sentence.text)),
18
18
  );
19
19
  }
20
20
  }
@@ -24,7 +24,7 @@ export function run(doc) {
24
24
  const current = sentences[i];
25
25
  const following = sentences[i + 1];
26
26
  if (NEGATION.test(current.text) && isDefinition(following.text)) {
27
- findings.push(finding(name, current.line, current.text, "단순한 X 가 아닙니다 뒤에 Y 입니다 로 재정의하는 공식이다. 앞 문장을 지우고 Y 를 바로 쓴다", null, "error", SENTENCE, current.index));
27
+ findings.push(finding(name, current.line, current.text, "`단순한 X가 아닙니다` 뒤에 `Y입니다`로 다시 정의하는 공식이다. 앞 문장을 지우고 Y를 바로 쓴다", null, "error", SENTENCE, current.index));
28
28
  }
29
29
  }
30
30
  }
@@ -37,7 +37,7 @@ export function run(doc, config) {
37
37
  if (reader.numerals.has(base) || numeralsIn(sentence.text.slice(0, start)).has(base)) continue;
38
38
  if (isAnchored(sentence.text, start)) continue;
39
39
  findings.push(
40
- finding(name, sentence.line, sentence.text, `\`${base}\` ${fitJosa(base, "가")} 여기서 처음 나오는 값이다. 무엇에서 올라간 것인지 알 수 없다. 그 값이 어디서 나왔는지 밝힌다`, null, NOTICE, SENTENCE, sentence.index),
40
+ finding(name, sentence.line, sentence.text, `\`${base}\`${fitJosa(base, "가")} 여기서 처음 나오는 값이다. 무엇에서 올라간 것인지 알 수 없다. 그 값이 어디서 나왔는지 밝힌다`, null, NOTICE, SENTENCE, sentence.index),
41
41
  );
42
42
  }
43
43
  }
@@ -10,9 +10,14 @@ const FEATURES = [
10
10
  ["length", "문장 길이", "어절"],
11
11
  ["commas", "쉼표", "개"],
12
12
  ["newTopics", "처음 나온 화제어", "개"],
13
- ["hedges", "헤지 표현", "개"],
13
+ ["hedges", "얼버무리는 말", "개"],
14
14
  ];
15
15
 
16
+ /** 세 자리마다 쉼표를 찍는다. 파이썬 `f"{n:,}"`와 같은 글을 낸다. @param {number} count */
17
+ function grouped(count) {
18
+ return String(count).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
19
+ }
20
+
16
21
  /** @param {import("../../config/settings.js").Config} config */
17
22
  function profileFor(config) {
18
23
  if (config.profile) return userProfile(config.profile);
@@ -22,7 +27,7 @@ function profileFor(config) {
22
27
 
23
28
  /** @param {number} permille */
24
29
  function shareText(permille) {
25
- return permille === 0 ? "0.1% 아래" : `${Math.floor(permille / 10)}.${permille % 10}%`;
30
+ return permille === 0 ? "0.1% 안" : `${Math.floor(permille / 10)}.${permille % 10}%`;
26
31
  }
27
32
 
28
33
  /** @param {Set<string>} topics @param {Set<string>} known */
@@ -55,7 +60,7 @@ export function run(doc, config) {
55
60
  name,
56
61
  sentence.line,
57
62
  sentence.text,
58
- `${label} ${value}${unit}. ${profile.label} ${profile.sentences}문장 가운데 상위 ${shareText(shareAtOrAbove(hist, value))}다. ${p}%는 ${limit}${unit} 이하다`,
63
+ `${label} ${value}${unit}인 문장이다. ${profile.label} ${grouped(profile.sentences)}문장 가운데 상위 ${shareText(shareAtOrAbove(hist, value))}에 든다. ${p}%는 ${limit}${unit} 이하다`,
59
64
  null,
60
65
  NOTICE,
61
66
  SENTENCE,
@@ -36,12 +36,13 @@ export function run(doc, config) {
36
36
  if (hit.length < MIN_HITS || hit.length / prose.length <= share) continue;
37
37
  const lines = [...new Set(hit.map((one) => one.line))].sort((a, b) => a - b).join(", ");
38
38
  const share100 = `${Math.round(share * 100)}%`;
39
+ const cited = source ? ` (${source})` : "";
39
40
  findings.push(
40
41
  finding(
41
42
  name,
42
43
  hit[0].line,
43
44
  hit[0].text,
44
- `\`${label}\` 꼴이 산문 ${prose.length}문장 가운데 ${hit.length}번이다 (${lines}줄. 상한 ${share100}). ${why} (${source})`,
45
+ `\`${label}\` 문형이 산문 ${prose.length}문장 가운데 ${hit.length}번(${lines}줄) 나와 상한 ${share100}를 넘는다. ${why}${cited}`,
45
46
  null,
46
47
  NOTICE,
47
48
  DOCUMENT,
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 파이썬 rules/shared/candidates.py의 투영. 후보에는 점수와 순위가 없다. */
2
+ /** 파이썬 rules/shared/candidates.py와 같은 일을 한다. 후보에는 점수와 순위가 없다. */
3
3
  import { HAEYO, HANDA, HAPNIDA, REGISTERS, conjugate, fitJosa } from "../../analysis/grammar/index.js";
4
4
  import * as hangul from "../../analysis/grammar/hangul.js";
5
5
  import { decomposePassive } from "../../analysis/grammar/voice.js";
@@ -123,7 +123,7 @@ export function doublePassiveCandidates(text, passives) {
123
123
  const candidateText = reducedPassiveText(text, surface, voice.reduced);
124
124
  if (candidateText === null || seen.has(candidateText)) continue;
125
125
  seen.add(candidateText);
126
- candidates.push({ text: candidateText, why: `\`${surface}\`의 피동 겹을 하나로 줄인다` });
126
+ candidates.push({ text: candidateText, why: `\`${surface}\`에서 겹친 피동을 하나로 줄인다` });
127
127
  }
128
128
  return candidates;
129
129
  }
@@ -18,6 +18,13 @@ function countNewlines(text) {
18
18
  return count;
19
19
  }
20
20
 
21
+ /** 사전 지적의 글. 찾은 말 뒤에 받침에 맞는 은/는을 붙인다. 뜻은 파이썬 rules/shared/dictionaryRule.py 의 message 가 소유한다.
22
+ * @param {import("../../fingerprint/dictionaries.js").DictionaryMatch} match */
23
+ function message(match) {
24
+ const source = match.source ? ` (${match.source})` : "";
25
+ return `\`${match.text}\`${fitJosa(match.text, "는")} ${match.why}${source}`;
26
+ }
27
+
21
28
  /**
22
29
  * @param {import("../../fingerprint/build.js").SentencePrint} sentence
23
30
  * @param {import("../../fingerprint/dictionaries.js").DictionaryMatch} match
@@ -32,7 +39,7 @@ function matchFinding(sentence, match, ruleName, severity) {
32
39
  ruleName,
33
40
  sentence.line + countNewlines(sentence.text.slice(0, match.start)),
34
41
  sentence.text,
35
- `\`${match.text}\` ${match.why} (${match.source})`,
42
+ message(match),
36
43
  fix,
37
44
  severity,
38
45
  SENTENCE,
@@ -108,7 +115,7 @@ export function firstMatchFindings(doc, dictionary, ruleName, severity = ERROR)
108
115
  ruleName,
109
116
  sentence.line + countNewlines(sentence.text.slice(0, match.start)),
110
117
  sentence.text,
111
- `\`${match.text}\` ${match.why} (${match.source})`,
118
+ message(match),
112
119
  match.rewrite,
113
120
  severity,
114
121
  SENTENCE,
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  /**
3
- * 반복 기제. 같은 모양이 창 안에서 이어지거나 (runsOf) 한 모양이 창을 채우는 (shareOf) 자리를 센다.
4
- * 파이썬 rules/shared/repeat.py 와 같다. 규칙은 열쇠와 창과 임계만 선언하고 셈은 여기서 한다.
3
+ * 반복을 세는 방법. 같은 값이 이어지는 구간(runsOf)과 한 값이 차지하는 비율(shareOf)을 센다.
4
+ * 파이썬 rules/shared/repeat.py와 같다. 규칙은 열쇠와 기준값만 정하고 세는 일은 여기서 한다.
5
5
  */
6
6
 
7
7
  /**
@@ -6,8 +6,8 @@ export const name = "bridgeRepeat";
6
6
  export const mechanism = "repeat";
7
7
 
8
8
  /**
9
- * 절을 닫는 문장이 이음 표지 (이번에는, 이제, 다음으로) 로 시작하는 절이 bridgeRepeatMin 개 이상인 글.
10
- * 파이썬 rules/structure/bridgeRepeat.py 와 같다. 표지 목록은 data/bridgeOpeners.txt.
9
+ * 절을 닫는 문장이 접속사(이번에는, 이제, 다음으로)로 시작하는 절이 bridgeRepeatMin개 이상인 글.
10
+ * 파이썬 rules/structure/bridgeRepeat.py와 같다. 접속사 목록은 data/bridgeOpeners.txt에 있다.
11
11
  * @param {import("../../fingerprint/build.js").DocumentPrint} doc
12
12
  * @param {import("../../config/settings.js").Config} config
13
13
  */
@@ -30,7 +30,7 @@ export function run(doc, config) {
30
30
  name,
31
31
  first.line,
32
32
  first.text,
33
- `절 ${closers.length}개가 \`${markers.join(", ")}\` 같은 이음 표지로 시작하는 문장으로 닫힌다. 예고가 절마다 되풀이되면 틀이다. 방금 만든 결과를 이름으로 부르고 아직 못 하는 일을 적는다`,
33
+ `절 ${closers.length}개가 \`${markers.join(", ")}\` 같은 접속사로 시작하는 문장으로 끝난다. 예고가 절마다 되풀이되면 틀에 박힌 글이 된다. 방금 만든 결과를 이름으로 부르고 아직 못 하는 일을 적는다`,
34
34
  null,
35
35
  "error",
36
36
  DOCUMENT,
@@ -26,7 +26,7 @@ export function run(doc) {
26
26
  const lines = block.text.split("\n");
27
27
  for (let offset = 0; offset < lines.length; offset++) {
28
28
  if (BULLET_WITH_EMOJI.test(lines[offset])) {
29
- findings.push(finding(name, block.startLine + offset, lines[offset].trim(), "목록 항목이 이모지로 시작한다. 이모지를 지우고 첫 낱말이 내용을 말하게 한다", null, "error", DOCUMENT, block.index));
29
+ findings.push(finding(name, block.startLine + offset, lines[offset].trim(), "목록 항목이 이모지로 시작한다. 이모지를 지우고 첫 단어가 내용을 말하게 한다", null, "error", DOCUMENT, block.index));
30
30
  break;
31
31
  }
32
32
  }
@@ -11,7 +11,7 @@ export function run(doc) {
11
11
  let previous = doc.frontmatter.title ? 1 : null;
12
12
  for (const [level, text, line] of doc.headings) {
13
13
  if (previous !== null && level > previous + 1) {
14
- findings.push(finding(name, line, text, `H${previous} 다음에 H${level} 이 온다. 한 수준씩 내려간다`, null, "error", DOCUMENT, -1));
14
+ findings.push(finding(name, line, text, `H${previous} 다음에 바로 H${level} 제목이 온다. 한 수준씩 내려간다`, null, "error", DOCUMENT, -1));
15
15
  }
16
16
  previous = level;
17
17
  }
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import { fitJosa } from "../../analysis/grammar/josa.js";
2
3
  import { DOCUMENT, finding } from "../finding.js";
3
4
  import { shareOf } from "../shared/repeat.js";
4
5
 
@@ -27,7 +28,7 @@ export function run(doc, config) {
27
28
  );
28
29
  if (count / total >= config.headingUniformRatio) {
29
30
  return [
30
- finding(name, eligible[0][2], eligible.map(([, text]) => text).join(" / "), `H2 ${eligible.length}개 중 ${count}개가 \`${best}\` 로 끝난다. 목차가 한 어미로 끝나면 과정이 아니라 나열로 읽힌다. 그 문서의 관례라면 둔다`, null, "notice", DOCUMENT, -1),
31
+ finding(name, eligible[0][2], eligible.map(([, text]) => text).join(" / "), `H2 ${eligible.length}개 가운데 ${count}개가 \`${best}\`${fitJosa(best, "로")} 끝난다. 목차가 한 어미로 끝나면 과정이 아니라 나열로 읽힌다. 그 문서의 관례라면 그대로 둔다`, null, "notice", DOCUMENT, -1),
31
32
  ];
32
33
  }
33
34
  return [];
@@ -5,7 +5,7 @@ import { NOTICE, SECTION, finding } from "../finding.js";
5
5
  export const name = "loneSubheading";
6
6
  export const mechanism = "contrast";
7
7
 
8
- /** (부모 H2 제목, 외동 H3 제목, 그 줄). H2 하나 아래 H3 이 정확히 하나일 때만. @param {[number, string, number][]} headings */
8
+ /** (부모 H2 제목, 하나뿐인 H3 제목, 그 줄). H2 하나 아래 H3이 정확히 하나일 때만. @param {[number, string, number][]} headings */
9
9
  function onlyChild(headings) {
10
10
  /** @type {[string, string, number][]} */
11
11
  const found = [];
@@ -43,6 +43,6 @@ export function run(doc, config) {
43
43
  let best = over[0];
44
44
  for (const pair of over) if (pair[1].length > best[1].length) best = pair;
45
45
  return [
46
- finding(name, best[0], best[1], `마지막 절의 목록 항목 ${over.length}개가 ${config.moreLaterMaxChars}자를 넘는다 (가장 긴 것 ${best[1].length}자). 미룬 것이 아니라 옮겨 적은 것이다. 한 줄과 링크로 줄인다`, null, NOTICE, SECTION, last.index),
46
+ finding(name, best[0], best[1], `마지막 절의 목록 항목 ${over.length}개가 ${config.moreLaterMaxChars}자를 넘는다. 가장 긴 항목은 ${best[1].length}자다. 미룬 것이 아니라 옮겨 적은 것이다. 한 줄과 링크로 줄인다`, null, NOTICE, SECTION, last.index),
47
47
  ];
48
48
  }
package/src/text.js CHANGED
@@ -64,8 +64,8 @@ export function countIn(text, needle, end = text.length) {
64
64
 
65
65
  /**
66
66
  * 파이썬 round(value, digits). 정확한 이진 값을 십진으로 풀어 반올림하고 정확히 절반이면 짝수로 간다.
67
- * Math.round 는 절반을 위로 올려 13/16 을 0.813 으로 만들고 파이썬은 0.812 다. 두 구현의 글자가 같으려면 파이썬 쪽이다.
68
- * toFixed(100) 은 1e21 아래의 수를 정확한 십진 전개로 준다. 지문의 비율과 평균이 사는 범위다.
67
+ * Math.round는 절반을 위로 올려 13/16을 0.813으로 만들고 파이썬은 0.812다. 두 구현의 글자가 같으려면 파이썬 쪽이다.
68
+ * toFixed(100)은 1e21 아래의 수를 정확한 십진 전개로 준다. 분석 결과의 비율과 평균이 있는 범위다.
69
69
  * @param {number} value
70
70
  * @param {number} digits
71
71
  */
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  /**
3
3
  * 명사 연쇄 빈도표. 글 종류마다 data/usageCounts.<종류>.json 하나이고 값은 그 연쇄가 나온 문서 수다.
4
- * 뜻은 파이썬 usage/counts.py 가 소유한다. 표의 키는 nounRuns 의 어절들을 빈칸 하나로 이은 것이다.
4
+ * 뜻은 파이썬 usage/counts.py가 정한다. 표의 키는 nounRuns의 어절들을 빈칸 하나로 이은 것이다.
5
5
  */
6
6
  import { USAGE_OF } from "../config/settings.js";
7
7
  import { readText } from "../data/load.js";
@@ -12,7 +12,7 @@ import { readText } from "../data/load.js";
12
12
  const tables = new Map();
13
13
 
14
14
  /**
15
- * 이 설정이 볼 빈도표의 종류. usageKind 가 null 이면 프리셋이 정하고 "" 면 보지 않는다.
15
+ * 이 설정이 볼 빈도표의 종류. usageKind가 null이면 프리셋이 정하고 ""면 보지 않는다.
16
16
  * @param {import("../config/settings.js").Config} config @returns {string | null}
17
17
  */
18
18
  export function usageKindOf(config) {
@@ -46,7 +46,7 @@ export function patternDocuments(dictionary, pattern, kind) {
46
46
  }
47
47
 
48
48
  /**
49
- * 사전 항목이 그 종류의 문서 share 이상에 나오면 관용이다. 뜻은 파이썬 usage/counts.py 의 conventional 이 소유한다.
49
+ * 사전 항목이 그 종류의 문서 share 이상에 나오면 관용이다. 뜻은 파이썬 usage/counts.py의 conventional이 정한다.
50
50
  * @param {string} dictionary @param {string} pattern @param {string} kind @param {number} share
51
51
  */
52
52
  export function conventional(dictionary, pattern, kind, share) {
@@ -1,8 +1,8 @@
1
1
  // @ts-check
2
2
  /**
3
- * 용례 (usage). 실제 글에서 낱말이 어떻게 쓰였는지를 든다. 뜻은 파이썬 usage/ 가 소유한다.
4
- * counts 는 실리는 빈도표, sentences 는 사용자 기계의 문장 역인덱스다.
5
- * fingerprint 와 같은 층이다. rules 와 cli 가 쓰고, analysis 와 config 와 data 만 쓴다.
3
+ * 용례(usage). 실제 글에서 단어가 어떻게 쓰였는지를 든다. 뜻은 파이썬 usage/가 정한다.
4
+ * counts는 실리는 빈도표, sentences는 사용자 기계의 문장 역인덱스다.
5
+ * fingerprint와 같은 단계다. rules와 cli가 이 부분을 쓰고, 이 부분은 analysis와 config와 data만 쓴다.
6
6
  */
7
7
  export { attested, chainDocuments, conventional, patternDocuments, usageKindOf, usageTable } from "./counts.js";
8
8
  export { UsageIndex, buildIndex, defaultRoot as defaultUsageRoot, indexTokens, listIndexes, loadIndex, queryCores, readDocuments } from "./sentences.js";