hanlint 0.0.17 → 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 (136) hide show
  1. package/README.md +23 -20
  2. package/data/calqueFrame.json +60 -0
  3. package/data/cliches.json +39 -29
  4. package/data/collocationStops.txt +28 -0
  5. package/data/confusable.json +20 -20
  6. package/data/easyWords.json +12 -246
  7. package/data/exemplars.json +50 -29
  8. package/data/hiddenDeps.txt +11 -11
  9. package/data/japaneseLoan.json +27 -27
  10. package/data/koreanProse.txt +64 -0
  11. package/data/patterns.json +26 -26
  12. package/data/phraseRepeat.json +11 -0
  13. package/data/platformApis.txt +11 -11
  14. package/data/redundantPair.json +16 -16
  15. package/data/ruleCategories.json +2 -0
  16. package/data/ruleDocs.json +56 -54
  17. package/data/ruleMechanisms.json +2 -0
  18. package/data/screenNarration.json +10 -10
  19. package/data/screenSentence.json +60 -12
  20. package/data/screenTone.json +6 -6
  21. package/data/screenWord.json +70 -110
  22. package/data/spacing.json +12 -12
  23. package/data/spelling.json +29 -29
  24. package/data/translationese.json +23 -24
  25. package/data/usageCounts.report.json +1676 -0
  26. package/data/version.json +1 -1
  27. package/package.json +1 -1
  28. package/src/analysis/grammar/ending.js +1 -1
  29. package/src/analysis/grammar/hangul.js +4 -4
  30. package/src/analysis/grammar/josa.js +1 -1
  31. package/src/analysis/grammar/register.js +1 -1
  32. package/src/analysis/grammar/voice.js +1 -1
  33. package/src/analysis/index.js +3 -3
  34. package/src/analysis/josa.js +1 -1
  35. package/src/analysis/splitSentences.js +1 -1
  36. package/src/analysis/tokenize.js +41 -26
  37. package/src/baseline/store.js +8 -8
  38. package/src/cli/help.js +11 -10
  39. package/src/cli/main.js +332 -182
  40. package/src/cli/walk.js +2 -2
  41. package/src/cli/welcome.js +8 -8
  42. package/src/config/loadConfig.js +3 -3
  43. package/src/config/patch.js +6 -6
  44. package/src/config/readerContract.js +26 -26
  45. package/src/config/settings.js +44 -25
  46. package/src/config/toml.js +9 -9
  47. package/src/data/exemplars.js +21 -21
  48. package/src/data/load.js +6 -6
  49. package/src/data/operations.js +14 -14
  50. package/src/data/patches.js +10 -10
  51. package/src/data/patterns.js +5 -5
  52. package/src/data/profiles.js +6 -6
  53. package/src/data/prose.js +27 -0
  54. package/src/document/model.js +4 -4
  55. package/src/document/parseMarkdown.js +7 -7
  56. package/src/document/plainText.js +3 -3
  57. package/src/document/sourceText.js +117 -19
  58. package/src/edit/applyFixes.js +0 -0
  59. package/src/fingerprint/build.js +18 -9
  60. package/src/fingerprint/codeBlocks.js +3 -3
  61. package/src/fingerprint/codeMarkers.js +4 -4
  62. package/src/fingerprint/dictionaries.js +34 -7
  63. package/src/fingerprint/markers.js +27 -5
  64. package/src/fingerprint/readerState.js +9 -9
  65. package/src/fingerprint/sourceSentence.js +1 -1
  66. package/src/fingerprint/topics.js +1 -1
  67. package/src/guard/contract.js +6 -6
  68. package/src/guard/editLocality.js +1 -1
  69. package/src/guard/outline.js +1 -1
  70. package/src/guard/receipt.js +13 -13
  71. package/src/guard/surface.js +4 -4
  72. package/src/index.js +9 -7
  73. package/src/learn/edits.js +2 -2
  74. package/src/learn/operations.js +1 -1
  75. package/src/regex.js +2 -2
  76. package/src/report/compactReport.js +7 -3
  77. package/src/report/fingerprintJson.js +2 -2
  78. package/src/report/githubReport.js +1 -1
  79. package/src/report/jsonReport.js +3 -3
  80. package/src/report/registerMatch.js +2 -2
  81. package/src/report/sheet.js +0 -0
  82. package/src/report/textReport.js +35 -16
  83. package/src/report/writingSpec.js +17 -17
  84. package/src/rules/code/inputFileSource.js +1 -1
  85. package/src/rules/code/installImport.js +2 -2
  86. package/src/rules/code/platformApi.js +1 -1
  87. package/src/rules/document/countMismatch.js +2 -1
  88. package/src/rules/document/enoughOnce.js +2 -1
  89. package/src/rules/document/fieldEcho.js +10 -6
  90. package/src/rules/document/keywordHeading.js +1 -1
  91. package/src/rules/document/keywordMissing.js +1 -1
  92. package/src/rules/document/noQuestion.js +2 -2
  93. package/src/rules/document/promiseRecall.js +1 -1
  94. package/src/rules/document/tableOddCell.js +1 -1
  95. package/src/rules/finding.js +4 -4
  96. package/src/rules/paragraph/factListParagraph.js +1 -1
  97. package/src/rules/paragraph/paraFragment.js +1 -1
  98. package/src/rules/readerDebt.js +1 -1
  99. package/src/rules/registry.js +30 -26
  100. package/src/rules/screen/screenWord.js +2 -1
  101. package/src/rules/sentence/calqueFrame.js +11 -0
  102. package/src/rules/sentence/cliche.js +7 -3
  103. package/src/rules/sentence/connectorRepeat.js +2 -1
  104. package/src/rules/sentence/danglingDeixis.js +1 -1
  105. package/src/rules/sentence/dash.js +1 -1
  106. package/src/rules/sentence/deixis.js +1 -1
  107. package/src/rules/sentence/doubleNegative.js +2 -1
  108. package/src/rules/sentence/doublePassive.js +2 -1
  109. package/src/rules/sentence/draftHistory.js +2 -1
  110. package/src/rules/sentence/endingRepeat.js +3 -2
  111. package/src/rules/sentence/euiChain.js +1 -1
  112. package/src/rules/sentence/fillerOpener.js +1 -1
  113. package/src/rules/sentence/hardWord.js +6 -3
  114. package/src/rules/sentence/imperativePeriod.js +1 -1
  115. package/src/rules/sentence/japaneseLoan.js +7 -3
  116. package/src/rules/sentence/longSentence.js +1 -1
  117. package/src/rules/sentence/negationRedefine.js +1 -1
  118. package/src/rules/sentence/nounPile.js +10 -4
  119. package/src/rules/sentence/numberOrphan.js +1 -1
  120. package/src/rules/sentence/outsideProfile.js +8 -3
  121. package/src/rules/sentence/phraseRepeat.js +54 -0
  122. package/src/rules/sentence/redundantPair.js +7 -3
  123. package/src/rules/sentence/translationese.js +7 -3
  124. package/src/rules/shared/candidates.js +2 -2
  125. package/src/rules/shared/dictionaryRule.js +46 -8
  126. package/src/rules/shared/repeat.js +2 -2
  127. package/src/rules/structure/bridgeRepeat.js +3 -3
  128. package/src/rules/structure/emojiBullet.js +1 -1
  129. package/src/rules/structure/headingSkip.js +1 -1
  130. package/src/rules/structure/headingUniform.js +2 -1
  131. package/src/rules/structure/loneSubheading.js +1 -1
  132. package/src/rules/structure/moreLater.js +1 -1
  133. package/src/text.js +2 -2
  134. package/src/usage/counts.js +54 -0
  135. package/src/usage/index.js +8 -0
  136. package/src/usage/sentences.js +681 -0
package/data/version.json CHANGED
@@ -1 +1 @@
1
- {"version": "0.0.17"}
1
+ {"version": "0.0.19"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanlint",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "한국어 글 검사기. Korean prose linter for Markdown: translationese, noun piles, double passives, weak structure. Zero dependencies",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 파이썬 `analysis/grammar/ending.py`와 `predicate.py`의 투영. 종결 서술어를 풀고 세 문체로 짠다. */
2
+ /** 파이썬 `analysis/grammar/ending.py`와 `predicate.py`를 옮긴 것. 종결 서술어를 풀고 세 문체로 짠다. */
3
3
  import { loadLines } from "../../data/load.js";
4
4
  import * as hangul from "./hangul.js";
5
5
 
@@ -1,6 +1,6 @@
1
1
  // @ts-check
2
2
  /**
3
- * 한글 음절의 자모 산술. 파이썬 `analysis/grammar/hangul.py` 의 투영이다. 표 대신 산술로 하는 까닭은 그 파일이 소유한다.
3
+ * 한글 음절의 자모 산술. 파이썬 `analysis/grammar/hangul.py`를 옮긴 것이다. 표 대신 산술로 하는 까닭은 그 파일에 적었다.
4
4
  */
5
5
 
6
6
  export const BASE = 0xac00;
@@ -31,7 +31,7 @@ export const EU = 18;
31
31
  export const UI = 19;
32
32
  export const IH = 20;
33
33
 
34
- // 종성 자리 번호 가운데 형태 층이 이름으로 부르는 것.
34
+ // 종성 자리 번호 가운데 형태 부분이 이름으로 부르는 것.
35
35
  export const NONE = 0;
36
36
  export const GIYEOK = 1;
37
37
  export const NIEUN = 4;
@@ -44,7 +44,7 @@ export const SSANGSIOT = 20;
44
44
  export const IEUNG = 21;
45
45
  export const HIEUT = 27;
46
46
 
47
- /** 양성 모음. 이 모음 뒤에는 `아` 가 붙고 나머지에는 `어` 가 붙는다. */
47
+ /** 양성 모음. 이 모음 뒤에는 `아`가 붙고 나머지에는 `어`가 붙는다. */
48
48
  export const BRIGHT = new Set([A, OH, YA, YO, WA]);
49
49
 
50
50
  /** @param {string} ch */
@@ -88,7 +88,7 @@ export function withVowel(ch, vowel) {
88
88
  return join(initial, vowel, final);
89
89
  }
90
90
 
91
- /** 낱말의 마지막 한글 음절. 없으면 null. @param {string} word */
91
+ /** 단어의 마지막 한글 음절. 없으면 null. @param {string} word */
92
92
  export function lastSyllable(word) {
93
93
  return word && isSyllable(word[word.length - 1]) ? word[word.length - 1] : null;
94
94
  }
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 파이썬 `analysis/grammar/josa.py`의 투영. 받침에 따라 조사 꼴을 맞춘다. */
2
+ /** 파이썬 `analysis/grammar/josa.py`를 옮긴 것. 받침에 따라 조사 꼴을 맞춘다. */
3
3
  import * as hangul from "./hangul.js";
4
4
 
5
5
  const PAIRS = [
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 파이썬 `analysis/grammar/register.py`의 투영. 글의 문체를 판별하고 문장 끝 서술어를 맞춘다. */
2
+ /** 파이썬 `analysis/grammar/register.py`를 옮긴 것. 글의 문체를 판별하고 문장 끝 서술어를 맞춘다. */
3
3
  import * as hangul from "./hangul.js";
4
4
  import { HAEYO, HANDA, HAPNIDA, REGISTERS, parsePredicate, render } from "./ending.js";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 파이썬 `analysis/grammar/voice.py`의 투영. 확정되는 사동과 피동의 겹만 분해한다. */
2
+ /** 파이썬 `analysis/grammar/voice.py`를 옮긴 것. 확정되는 사동과 피동의 겹만 분해한다. */
3
3
  import { loadLines } from "../../data/load.js";
4
4
 
5
5
  export const PASSIVE = "피동";
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  /**
3
- * 분석 층. 문장 분리와 어절 판정을 표층 (어절과 꼬리 사전) 으로 한다. 형태소 분석기는 없다. 파이썬 analysis/__init__.py 와 같다.
4
- * 지문이 필요로 하는 것은 넷이다. splitSentences, euiCount 와 euiAdjacent, longestNounRun, doublePassives.
3
+ * 분석 부분. 문장 분리와 어절 판정을 글자 모양(어절과 꼬리 사전)만으로 한다. 형태소 분석기는 없다. 파이썬 analysis/__init__.py와 같다.
4
+ * 글 분석에 필요한 것은 넷이다. splitSentences, euiCount와 euiAdjacent, longestNounRun, doublePassives.
5
5
  */
6
6
  export { splitSentences } from "./splitSentences.js";
7
- export { doublePassives, doublePassiveSpans, euiAdjacent, euiCount, longestNounRun } from "./tokenize.js";
7
+ export { doublePassives, doublePassiveSpans, euiAdjacent, euiCount, longestNounRun, nounRuns } from "./tokenize.js";
@@ -1,2 +1,2 @@
1
- // 이전 deep path 호환. 정본 투영은 grammar/josa.js다.
1
+ // 이전 deep path 호환. 실제 구현은 grammar/josa.js에 있다.
2
2
  export * from "./grammar/josa.js";
@@ -1,5 +1,5 @@
1
1
  // @ts-check
2
- /** 종결 부호 뒤 공백에서 문장을 나눈다. 파이썬 analysis/splitSentences.py 와 같다. */
2
+ /** 종결 부호 뒤 공백에서 문장을 나눈다. 파이썬 analysis/splitSentences.py와 같다. */
3
3
 
4
4
  const TERMINAL = /[.?!]+\s+(?=\S)/g;
5
5
  const ABBREVIATION = /(?:^|[^A-Za-z])[A-Za-z]$/;
@@ -1,6 +1,6 @@
1
1
  // @ts-check
2
2
  /**
3
- * 어절 단위 근사. 조사와 어미 꼬리 사전으로 명사 어절을 가려낸다. 파이썬 analysis/tokenize.py 와 같은 판정이다.
3
+ * 어절 단위 어림. 조사와 어미 꼬리 사전으로 명사 어절을 가려낸다. 파이썬 analysis/tokenize.py와 같은 판정이다.
4
4
  * 띄어 쓴 조사와 계사는 앞 어절에 붙은 것으로, 영문 어절 연속은 한 덩어리로, 수사와 바로 뒤 단위는 수량으로 본다.
5
5
  */
6
6
  import { loadLines } from "../data/load.js";
@@ -9,16 +9,16 @@ import { splitWords, stripChars } from "../text.js";
9
9
  const HANGUL = /[가-힣]/;
10
10
  const HANGUL_WORD = /^[가-힣]+$/;
11
11
  const WORD_CHARS = /^[가-힣A-Za-z0-9]+$/;
12
- /** 관형격 조사 의. 영문과 숫자 뒤 (API의, 600MiB의) 와 닫는 괄호 뒤 (L7(HTTP)의) 도 센다. 파이썬 tokenize.py 와 같다. */
12
+ /** 관형격 조사 `의`. 영문과 숫자 뒤(`API의`, `600MiB의`)와 닫는 괄호 뒤(`L7(HTTP)의`)도 센다. 파이썬 tokenize.py와 같다. */
13
13
  const GENITIVE = /([가-힣A-Za-z0-9]+|\))의(?=[\s,.)\]]|$)/g;
14
14
  const SPACED_GENITIVE = /(?<=\s)의(?=\s)/g;
15
- const COPULA = /^(?:이(?:고|며|다|라|란|면|라서|므로|지만|어서|었다|었고)|인|인데|입니다|였다)$/;
15
+ export const COPULA = /^(?:이(?:고|며|다|라|란|면|라서|므로|지만|어서|었다|었고)|인|인데|입니다|였다)$/;
16
16
  const DIGITS = /^\d+$/;
17
17
  /** 수에 단위가 붙은 어절 (2012년, 30분쯤, 제16호, 100명). 수량이라 명사 나열에서 세지도 끊지도 않는다. */
18
18
  const QUANTITY = /^제?\d[\d,.]*[가-힣]{1,3}$/;
19
19
  const OPENERS = "([{\"“‘'";
20
20
  const CLOSERS = ",.?!;:)]}\"”’'";
21
- const EDGE_PUNCTUATION = ".,?!:;\"'“”‘’()[]{}<>";
21
+ export const EDGE_PUNCTUATION = ".,?!:;\"'“”‘’()[]{}<>";
22
22
 
23
23
  /**
24
24
  * @typedef {object} Word
@@ -77,8 +77,8 @@ export function inNouns() {
77
77
  }
78
78
 
79
79
  /**
80
- * `도구인`, `규모인` 처럼 명사에 계사 관형형 인 이 붙은 어절. 서술어라 명사 나열을 끊는다. 어간이 두 음절 이상일 때만이고
81
- * 외국인 같은 세 음절 명사는 data/inNouns.txt 가 뺀다. 파이썬 tokenize.py 와 같다.
80
+ * `도구인`, `규모인`처럼 명사에 계사 관형형 `인`이 붙은 어절. 서술어라 명사 나열을 끊는다. 어간이 두 음절 이상일 때만이고
81
+ * 외국인 같은 세 음절 명사는 data/inNouns.txt가 뺀다. 파이썬 tokenize.py와 같다.
82
82
  * @param {string} core
83
83
  */
84
84
  export function isCopulaAdnominal(core) {
@@ -126,40 +126,55 @@ export function isBareNoun(core) {
126
126
  }
127
127
 
128
128
  /**
129
- * 명사 어절 연속의 최대 길이. 의존명사와 관형사와 부사 (`수`, `몇`, `직접`) 는 조사도 어미도 안 붙어
130
- * 표층으로는 명사로 보이지만 명사 쌓기의 재료가 아니다. data/nonNouns.txt 가 그 목록이고 연속을 끊는다.
129
+ * 명사 어절 연속의 최대 길이. 의존명사와 관형사와 부사(`수`, `몇`, `직접`)는 조사도 어미도 안 붙어
130
+ * 글자 모양으로는 명사로 보이지만 명사 쌓기의 재료가 아니다. data/nonNouns.txt가 그 목록이고 연속을 끊는다.
131
131
  * @param {string} text
132
132
  */
133
- export function longestNounRun(text) {
134
- let longest = 0;
133
+ /**
134
+ * 명사 어절 연속마다 [어절들, 센 길이]. 뜻은 파이썬 analysis/tokenize.py의 nounRuns가 정한다.
135
+ * 수사와 단위는 세지도 끊지도 않고 어절에도 안 넣는다. 잇달은 영문 어절은 하나로 센다.
136
+ * @param {string} text @returns {[string[], number][]}
137
+ */
138
+ export function nounRuns(text) {
139
+ /** @type {[string[], number][]} */
140
+ const runs = [];
141
+ /** @type {string[]} */
142
+ let chain = [];
135
143
  let run = 0;
136
144
  let previousAscii = false;
137
145
  let afterNumeral = false;
146
+ const close = () => {
147
+ if (run) runs.push([chain, run]);
148
+ chain = [];
149
+ run = 0;
150
+ previousAscii = false;
151
+ };
138
152
  for (const word of words(text)) {
139
- if (word.opens) {
140
- run = 0;
141
- previousAscii = false;
142
- }
153
+ if (word.opens) close();
143
154
  const transparent = isNumeral(word.core) || afterNumeral || isQuantity(word.core);
144
155
  afterNumeral = isNumeral(word.core);
145
156
  if (word.particle || nonNouns().has(word.core) || !isBareNoun(word.core) || isCopulaAdnominal(word.core)) {
146
- run = 0;
147
- previousAscii = false;
157
+ close();
148
158
  } else if (!transparent) {
149
159
  const isAscii = !HANGUL.test(word.core);
150
160
  if (!(isAscii && previousAscii)) run += 1;
161
+ chain.push(word.core);
151
162
  previousAscii = isAscii;
152
- longest = Math.max(longest, run);
153
- }
154
- if (word.endsClause) {
155
- run = 0;
156
- previousAscii = false;
157
163
  }
164
+ if (word.endsClause) close();
158
165
  }
166
+ close();
167
+ return runs;
168
+ }
169
+
170
+ /** 명사 어절 연속의 최대 길이. nounRuns의 센 길이 가운데 가장 큰 것. @param {string} text */
171
+ export function longestNounRun(text) {
172
+ let longest = 0;
173
+ for (const [, length] of nounRuns(text)) longest = Math.max(longest, length);
159
174
  return longest;
160
175
  }
161
176
 
162
- /** 관형격 조사 의 가 붙은 자리 [시작, 끝]. 정의, 회의 처럼 의 로 끝나는 낱말은 빼고 띄어 쓴 의 는 넣는다. @param {string} text @returns {[number, number][]} */
177
+ /** 관형격 조사 `의`가 붙은 곳 [시작, 끝]. `정의`, `회의`처럼 `의`로 끝나는 단어는 빼고 띄어 쓴 `의`는 넣는다. @param {string} text @returns {[number, number][]} */
163
178
  export function genitiveSpans(text) {
164
179
  /** @type {[number, number][]} */
165
180
  const spans = [];
@@ -170,13 +185,13 @@ export function genitiveSpans(text) {
170
185
  return spans.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
171
186
  }
172
187
 
173
- /** 의 로 끝나는 어절 둘이 붙어 있는가 (회사의 팀의). 사이가 공백뿐이어야 한다. @param {string} text */
188
+ /** `의`로 끝나는 어절 둘이 붙어 있는가(`회사의 팀의`). 사이가 공백뿐이어야 한다. @param {string} text */
174
189
  export function euiAdjacent(text) {
175
190
  const spans = genitiveSpans(text);
176
191
  return spans.some((span, index) => index > 0 && /^\s+$/.test(text.slice(spans[index - 1][1], span[0])));
177
192
  }
178
193
 
179
- /** 관형격 조사 의 의 수. @param {string} text */
194
+ /** 관형격 조사 `의`의 수. @param {string} text */
180
195
  export function euiCount(text) {
181
196
  return genitiveSpans(text).length;
182
197
  }
@@ -205,7 +220,7 @@ export function doublePassivePattern() {
205
220
  return passivePattern;
206
221
  }
207
222
 
208
- /** 이중 피동의 [시작, 끝, `되어지` 꼴 표층형]. @param {string} text @returns {[number, number, string][]} */
223
+ /** 이중 피동의 [시작, 끝, `되어지` 꼴로 쓴 글자]. @param {string} text @returns {[number, number, string][]} */
209
224
  export function doublePassiveSpans(text) {
210
225
  return [...text.matchAll(doublePassivePattern())].map((match) => {
211
226
  const start = /** @type {number} */ (match.index);
@@ -213,7 +228,7 @@ export function doublePassiveSpans(text) {
213
228
  });
214
229
  }
215
230
 
216
- /** 이중 피동의 표층형을 `되어지` 꼴로. @param {string} text */
231
+ /** 이중 피동의 글자를 `되어지` 꼴로. @param {string} text */
217
232
  export function doublePassives(text) {
218
233
  return doublePassiveSpans(text).map(([, , surface]) => surface);
219
234
  }
@@ -1,7 +1,7 @@
1
1
  // @ts-check
2
2
  /**
3
- * 이미 있는 지적을 잠가 두고 새로 생긴 것만 막는다. 파이썬 `hanlint/baseline/store.py` 의 투영이다.
4
- * 왜 줄 번호가 아니라 글자인지, 왜 사람이 읽는 꼴인지는 그 파일의 docstring 이 정본이다.
3
+ * 이미 있는 지적을 baseline 파일에 적어 두고 새로 생긴 지적만 막는다. 파이썬 `hanlint/baseline/store.py`를 옮긴 것이다.
4
+ * 왜 줄 번호가 아니라 글자인지, 왜 사람이 읽는 형식인지는 그 파일의 docstring에 있다.
5
5
  */
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import { dirname, posix, relative, resolve, sep } from "node:path";
@@ -20,7 +20,7 @@ export function keyOf(finding) {
20
20
  return `${finding.rule}\u0000${normalizeQuote(finding.quote)}`;
21
21
  }
22
22
 
23
- /** 잠금 파일이 있는 폴더 기준의 상대 경로. @param {string} path @param {string | null} target */
23
+ /** baseline 파일이 있는 폴더에서 본 상대 경로. @param {string} path @param {string | null} target */
24
24
  export function pathKey(path, target) {
25
25
  if (!target || !path || path.startsWith("<")) return path;
26
26
  return relative(dirname(resolve(target)), resolve(path)).split(sep).join(posix.sep);
@@ -44,7 +44,7 @@ export class Baseline {
44
44
  return (this.locked.get(pathKey(path, this.source)) ?? new Set()).has(keyOf(finding));
45
45
  }
46
46
 
47
- /** 잠기지 않은 지적만 남긴다. @param {string} path @param {import("../rules/finding.js").Finding[]} findings */
47
+ /** baseline 파일에 없는 지적만 남긴다. @param {string} path @param {import("../rules/finding.js").Finding[]} findings */
48
48
  keep(path, findings) {
49
49
  return findings.filter((finding) => !this.isLocked(path, finding));
50
50
  }
@@ -60,7 +60,7 @@ export function build(results, target = null) {
60
60
  return new Baseline(locked, target ? resolve(target) : null);
61
61
  }
62
62
 
63
- /** 사람이 읽고 리뷰하는 꼴. 정렬해 담아 같은 입력에 같은 파일이 나온다. @param {Baseline} baseline */
63
+ /** 사람이 읽고 리뷰하는 형식. 정렬해 담아 같은 입력에 같은 파일이 나온다. @param {Baseline} baseline */
64
64
  export function render(baseline) {
65
65
  /** @type {Record<string, {rule: string, quote: string}[]>} */
66
66
  const files = {};
@@ -80,7 +80,7 @@ export function render(baseline) {
80
80
  /** @param {string} text @param {string | null} [source] */
81
81
  export function parse(text, source = null) {
82
82
  const data = JSON.parse(text);
83
- if (data.version !== VERSION) throw new Error(`모르는 baseline 판 ${data.version}. hanlint baseline 으로 다시 만든다`);
83
+ if (data.version !== VERSION) throw new Error(`지원하지 않는 baseline 버전: ${data.version}. hanlint baseline으로 다시 만든다`);
84
84
  /** @type {Map<string, Set<string>>} */
85
85
  const locked = new Map();
86
86
  for (const [path, entries] of Object.entries(data.files ?? {})) {
@@ -92,7 +92,7 @@ export function parse(text, source = null) {
92
92
  /** @param {string} path */
93
93
  export function load(path) {
94
94
  if (!existsSync(path)) {
95
- const error = /** @type {NodeJS.ErrnoException} */ (new Error(`${path} 를 찾지 못했다`));
95
+ const error = /** @type {NodeJS.ErrnoException} */ (new Error(`파일을 찾지 못했다: ${path}`));
96
96
  error.code = "ENOENT";
97
97
  error.path = path;
98
98
  throw error;
@@ -100,7 +100,7 @@ export function load(path) {
100
100
  return parse(readFileSync(path, "utf-8"), resolve(path));
101
101
  }
102
102
 
103
- /** 지금 글에 더 없는 잠금을 지운다. @param {Baseline} baseline @param {Map<string, import("../rules/finding.js").Finding[]>} results */
103
+ /** 지금 글에 더는 없는 지적을 baseline 파일에서 지운다. @param {Baseline} baseline @param {Map<string, import("../rules/finding.js").Finding[]>} results */
104
104
  export function prune(baseline, results) {
105
105
  const kept = new Map(baseline.locked);
106
106
  for (const [path, findings] of results) {
package/src/cli/help.js CHANGED
@@ -2,14 +2,14 @@
2
2
  /** 첫 화면 다음에 보이는 작업 흐름 중심 도움말. */
3
3
 
4
4
  export function rootHelp() {
5
- return `hanlint: 한국어 마크다운에서 반복되는 결함과 계약 위반을 결정적으로 찾는다
5
+ return `hanlint: 한국어 글에서 규칙으로 잡을 수 있는 잘못을 찾는다
6
6
 
7
7
  일상 검사
8
8
  hanlint 글.md 글을 검사한다
9
9
  hanlint fix 글.md 안전한 고침만 적용한다
10
- hanlint hook AI가 저장한 마크다운을 같은 턴에 검사한다
10
+ hanlint hook AI가 쓰기 전에 글쓰기 지침을 넣어 주고, 저장한 마크다운을 같은 턴에 검사한다
11
11
 
12
- 요구사항 잠금
12
+ 요구사항 고정
13
13
  hanlint contract init 글.md --reader "독자" --goal "목표" --outline h2
14
14
  hanlint check contract.json 글.md --format text
15
15
  hanlint verify-patch contract.json 글.md patch.json
@@ -17,19 +17,20 @@ export function rootHelp() {
17
17
  설정과 이해
18
18
  hanlint init 설정 파일을 만든다
19
19
  hanlint doctor 적용 설정과 꺼진 규칙을 본다
20
- hanlint rules 규칙을 부류별로 본다
21
- hanlint explain <규칙> 규칙의 이유와 본보기를 본다
20
+ hanlint rules 규칙을 카테고리별로 본다
21
+ hanlint explain <규칙> 규칙의 이유와 예시를 본다
22
22
  hanlint patterns --rule <규칙> 다시 쓸 문장 틀을 본다
23
23
  hanlint spec --preset <종류> --chars 800
24
- 쓰기 전 숫자 사양을 만든다
25
- hanlint primer --preset <종류> 쓰기 전에 규칙의 고치는 법과 본보기를 한 장으로 본다
26
- hanlint sheet src/ --preset screen 소스의 화면 글을 표 하나로 떨군다. 고친 표는 sheet apply 로 되돌려 쓴다
24
+ 쓰기 전에 볼 스펙을 만든다
25
+ hanlint primer --preset <종류> 쓰기 전에 규칙의 고치는 법과 예시를 한 장으로 본다
26
+ hanlint sheet src/ --preset screen 소스의 화면 글을 표 하나로 내보낸다. 고친 표는 sheet apply로 원본에 반영한다
27
+ hanlint usage "단어 단어" 그 종류의 실제 글에서 단어가 쓰인 문장을 본다. 색인은 usage build가 만든다
27
28
 
28
29
  추가 명령
29
30
  baseline, print
30
31
  Python 전용: audit, map, watch, profile, coverage, diff, learn, packet,
31
32
  guard, arena, blueprint, evidence, entailment, terms
32
33
 
33
- 자세한 옵션은 hanlint <명령> --help 로 본다. 폴더를 주면 그 아래 마크다운을 검사한다.
34
- 종료 코드: 0 error 없음, 1 error 있음, 2 입력이나 설정 문제. 좋은 글인지는 판정하지 않는다.`;
34
+ 명령마다의 옵션은 README의 명령 표에 있다. 폴더를 주면 그 아래 마크다운을 검사한다.
35
+ 종료 코드: 0은 오류 없음, 1은 오류 있음, 2는 입력이나 설정 문제. 좋은 글인지는 판정하지 않는다.`;
35
36
  }