hanlint 0.0.8 → 0.0.10

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 (58) hide show
  1. package/README.md +45 -5
  2. package/data/checkResult.schema.json +75 -0
  3. package/data/checkResultV2.schema.json +98 -0
  4. package/data/exemplars.json +2 -26
  5. package/data/finding.schema.json +38 -0
  6. package/data/holeKinds.json +1 -12
  7. package/data/patch.schema.json +14 -0
  8. package/data/patchResult.schema.json +86 -0
  9. package/data/patchResultV2.schema.json +71 -0
  10. package/data/patterns.json +0 -11
  11. package/data/platformApis.txt +3 -1
  12. package/data/profiles.json +1 -1
  13. package/data/pythonPackages.txt +3 -2
  14. package/data/readerContract.schema.json +20 -0
  15. package/data/readerContractConformanceV1.json +168 -0
  16. package/data/readerContractV2.schema.json +64 -0
  17. package/data/ruleCategories.json +0 -4
  18. package/data/ruleDocs.json +7 -11
  19. package/data/ruleMechanisms.json +0 -4
  20. package/data/spelling.json +1 -1
  21. package/data/version.json +1 -1
  22. package/data/writingBrief.schema.json +1 -1
  23. package/data/writingBriefV2.schema.json +1 -1
  24. package/koglType1.LICENSE.md +25 -0
  25. package/package.json +6 -2
  26. package/src/cli/help.js +34 -0
  27. package/src/cli/main.js +247 -35
  28. package/src/config/patch.js +48 -0
  29. package/src/config/readerContract.js +245 -0
  30. package/src/config/settings.js +47 -14
  31. package/src/data/profiles.js +12 -0
  32. package/src/fingerprint/build.js +10 -7
  33. package/src/fingerprint/codeMarkers.js +1 -1
  34. package/src/guard/contract.js +174 -0
  35. package/src/guard/editLocality.js +27 -0
  36. package/src/guard/outline.js +46 -0
  37. package/src/guard/receipt.js +180 -0
  38. package/src/guard/surface.js +184 -0
  39. package/src/index.js +39 -1
  40. package/src/report/compactReport.js +1 -1
  41. package/src/report/writingSpec.js +174 -0
  42. package/src/rules/code/inputFileSource.js +5 -3
  43. package/src/rules/code/installImport.js +8 -6
  44. package/src/rules/document/keywordHeading.js +7 -2
  45. package/src/rules/document/noQuestion.js +4 -4
  46. package/src/rules/document/tableOddCell.js +2 -1
  47. package/src/rules/readerDebt.js +9 -0
  48. package/src/rules/registry.js +27 -8
  49. package/src/rules/sentence/danglingDeixis.js +15 -0
  50. package/src/rules/sentence/negationRedefine.js +2 -1
  51. package/src/rules/sentence/nounPile.js +1 -1
  52. package/src/rules/structure/headingUniform.js +3 -3
  53. package/src/rules/structure/introLong.js +3 -1
  54. package/data/coverageTypes.txt +0 -7
  55. package/src/rules/paragraph/topicBreak.js +0 -26
  56. package/src/rules/sentence/ellipsis.js +0 -32
  57. package/src/rules/structure/headingQuestion.js +0 -19
  58. package/src/rules/structure/sectionResult.js +0 -38
package/README.md CHANGED
@@ -1,13 +1,21 @@
1
1
  # hanlint
2
2
 
3
- 한국어 글에서 반복되는 결함을 결정적으로 잡는 린터다. 번역투, 상투어, 자주 틀리는 맞춤법과 띄어쓰기,
4
- 헷갈리는 말, 조각난 문단, 코드 튜토리얼의 계약 위반까지 집는다. 의존성이 없고 Node 18 이상이면 된다.
3
+ 한국어 글에서 반복되는 결함을 결정적으로 잡는 린터다. 번역투, 상투어, 이중 피동, 명사 나열, 조각난
4
+ 문단과 코드 튜토리얼의 계약 위반을 집는다. 맞춤법 전체를 추측하지 않고 앞뒤 낱말로 확정되는 일부 표기만
5
+ 본다. 런타임 의존성이 없고 Node 18 이상이면 된다.
5
6
 
6
7
  ```powershell
7
8
  npx hanlint
8
9
  npx hanlint 글.md
9
10
  npx hanlint fix 글.md
10
11
  npx hanlint 글.md --format compact --errors-only
12
+ npx hanlint spec --preset blog --register 합니다 --chars 800
13
+ npx hanlint hook
14
+ npx hanlint hook --reply
15
+ npx hanlint contract init 초안.md --reader "배포를 결정할 운영자" --goal "예산을 확인한다"
16
+ npx hanlint contract init 초안.md --reader "개발자" --goal "섹션별로 비교한다" --outline h2
17
+ npx hanlint check contract.json 초안.md --format text
18
+ npx hanlint verify-patch contract.json 초안.md patch.json
11
19
  ```
12
20
 
13
21
  인자 없이 치면 첫 화면이 나온다. 이 폴더의 마크다운 이름으로 만든 예시와 지금 칠 수 있는 명령이 거기 있다.
@@ -32,14 +40,46 @@ npx hanlint 글.md --format compact --errors-only
32
40
  규칙을 끈 설정 파일을 만든다. `blog`, `report`, `docs`, `guide`, `essay`, `fiction`, `encyclopedia` 일곱이고
33
41
  `npx hanlint rules` 가 지금 도는 목록을 낸다.
34
42
 
43
+ 글을 쓰기 전에는 `npx hanlint spec --preset <종류> --chars <글자 수>`로 같은 규칙판에서 숫자 사양을 받는다.
44
+ 배포 프로파일에서 관찰한 값과 현재 켜진 규칙 임계를 함께 낸다. JSON의 `rows[].basis`가 각 수의 정본을 밝힌다.
45
+ 새 점수나 품질 판정은 만들지 않는다. `chat`은 견줄 프로파일이 없어 사양을 내지 않는다.
46
+
47
+ Claude Code의 PostToolUse JSON을 stdin으로 `npx hanlint hook`에 연결하면 `Edit|Write`가 저장한 마크다운
48
+ 한 파일을 검사한다. Finding이 있을 때만 다음 모델 요청용 `additionalContext` JSON을 내며 언제나 종료 코드
49
+ 0이라 쓰기를 막지 않는다. Stop에는 `npx hanlint hook --reply`를 연결한다. 마지막 답변을 `chat` 프리셋으로
50
+ 한 번 검사하고 `stop_hook_active` 재진입에서는 침묵한다. 전체 `.claude/settings.json` 예시는 루트 README에 있다.
51
+
35
52
  ```js
36
- import { lintFile } from "hanlint";
53
+ import { Contract, Patch, check, contractFromText, contractFromTextV2, lintFile, renderCheck, verifyPatch } from "hanlint";
37
54
 
38
55
  for (const finding of lintFile("글.md")) console.log(finding.line, finding.rule, finding.why);
56
+
57
+ const draftContract = contractFromText(text, "배포를 결정할 운영자", "예산을 확인한다");
58
+ const structuredContract = contractFromTextV2(text, "개발자", "섹션별로 비교한다", 2);
59
+ const contract = new Contract("배포를 결정할 운영자", "예산을 확인한다", ["예산은 380,000원이다."]);
60
+ const receipt = check(text, contract);
61
+ console.log(renderCheck(check(text, structuredContract)));
62
+ const patch = new Patch("unexpectedNumbers", "400,000", "380,000");
63
+ const verified = verifyPatch(text, patch, contract);
39
64
  ```
40
65
 
66
+ Reader Contract는 `reader`, `goal`, `facts`에서 숫자, URL, 인라인 코드와 링크 목적지를 자동으로 보호한다.
67
+ `contractFromText`와 `contract init`은 보호 원자를 많이 덮는 원문 줄부터 골라 facts 후보를 줄인다.
68
+ 사실의 진실과 보호 원자가 없는 의미는 추측하지 않으므로 사람이 초안을 확인한다.
69
+ 제목 수와 순서가 요구사항이면 `contractFromTextV2` 또는 `contract init --outline h2`를 쓴다. version 2는
70
+ 사람이 승인한 facts, 자동으로 모은 surface, 한 수준의 정확한 outline을 분리한다. `check --format text`는
71
+ 보호 원자, 제목 구조, 전체 절 제목, lint와 다음 행동을 한 화면에 보여 준다.
72
+ check 결과는 Contract와 초안 해시, 보호 원자 차이와 기존 Finding을 담는다. Patch는 원문 한 자리에 정확히
73
+ 맞고 명시한 기존 위반을 줄이며 새 보호 원자 위반과 새 error를 만들지 않을 때만 검증된다. 이 조건은 의미나
74
+ 진실, 자연스러움의 승인이 아니다. version 2 Patch는 새 outline 위반도 거부한다. Python과 npm은 배포물의
75
+ 같은 version 1 적합성 JSON을 독립 실행하고 version 2 결과도 동등성 게이트로 견준다.
76
+
41
77
  파이썬 패키지 (`pip install hanlint`) 와 같은 규칙, 같은 fixture, 같은 출력이다. 지문 지도 (`audit`, `map`),
42
78
  문체 프로파일, 초안 비교 (`diff`), 평가자 겹침 (`coverage`) 은 파이썬 쪽에만 있다. 무엇을 잡고 무엇을 잡지 않는지는
43
- [저장소의 product 문서](https://github.com/eddmpython/hanlint/blob/main/skills/specs/start/product.md) 에 있다.
79
+ [전체 사용 안내](https://github.com/eddmpython/hanlint#readme)와
80
+ [제품 경계](https://github.com/eddmpython/hanlint/blob/main/skills/specs/start/product.md)에서 확인할 수 있다.
81
+
82
+ ## 라이선스
44
83
 
45
- MIT.
84
+ 코드와 나머지 데이터는 [MIT](LICENSE)다. `data/easyWords.json`은 국립국어원 자료의 파생물이며
85
+ [공공누리 제1유형 고지](koglType1.LICENSE.md)가 적용된다.
@@ -0,0 +1,75 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://hanlint.dev/schemas/checkResult.schema.json",
4
+ "title": "hanlint Check Result",
5
+ "description": "A deterministic receipt for a Reader Contract and a prose draft.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "version",
10
+ "kind",
11
+ "violationCount",
12
+ "contractSha256",
13
+ "draftSha256",
14
+ "surface",
15
+ "lint",
16
+ "meaning"
17
+ ],
18
+ "properties": {
19
+ "version": { "const": 1 },
20
+ "kind": { "const": "hanlint.checkResult" },
21
+ "violationCount": { "type": "integer", "minimum": 0 },
22
+ "contractSha256": { "$ref": "#/$defs/sha256" },
23
+ "draftSha256": { "$ref": "#/$defs/sha256" },
24
+ "surface": {
25
+ "type": "object",
26
+ "additionalProperties": false,
27
+ "required": [
28
+ "missingNumbers",
29
+ "unexpectedNumbers",
30
+ "missingUrls",
31
+ "unexpectedUrls",
32
+ "missingCode",
33
+ "unexpectedCode",
34
+ "missingLinks",
35
+ "unexpectedLinks"
36
+ ],
37
+ "properties": {
38
+ "missingNumbers": { "$ref": "#/$defs/strings" },
39
+ "unexpectedNumbers": { "$ref": "#/$defs/strings" },
40
+ "missingUrls": { "$ref": "#/$defs/strings" },
41
+ "unexpectedUrls": { "$ref": "#/$defs/strings" },
42
+ "missingCode": { "$ref": "#/$defs/strings" },
43
+ "unexpectedCode": { "$ref": "#/$defs/strings" },
44
+ "missingLinks": { "$ref": "#/$defs/strings" },
45
+ "unexpectedLinks": { "$ref": "#/$defs/strings" }
46
+ }
47
+ },
48
+ "lint": {
49
+ "type": "object",
50
+ "additionalProperties": false,
51
+ "required": ["errorCount", "noticeCount", "errorRules", "items"],
52
+ "properties": {
53
+ "errorCount": { "type": "integer", "minimum": 0 },
54
+ "noticeCount": { "type": "integer", "minimum": 0 },
55
+ "errorRules": {
56
+ "type": "object",
57
+ "additionalProperties": { "type": "integer", "minimum": 1 }
58
+ },
59
+ "items": {
60
+ "type": "array",
61
+ "items": { "$ref": "finding.schema.json" }
62
+ }
63
+ }
64
+ },
65
+ "meaning": { "type": "string", "minLength": 1 }
66
+ },
67
+ "$defs": {
68
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
69
+ "strings": {
70
+ "type": "array",
71
+ "uniqueItems": true,
72
+ "items": { "type": "string", "minLength": 1 }
73
+ }
74
+ }
75
+ }
@@ -0,0 +1,98 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://hanlint.dev/schemas/checkResultV2.schema.json",
4
+ "title": "hanlint Check Result v2",
5
+ "description": "A deterministic receipt for protected surface atoms, an exact heading outline, document shape, and lint findings.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "version",
10
+ "kind",
11
+ "violationCount",
12
+ "contractSha256",
13
+ "draftSha256",
14
+ "surface",
15
+ "missingFacts",
16
+ "lint",
17
+ "meaning",
18
+ "outline",
19
+ "document"
20
+ ],
21
+ "properties": {
22
+ "version": { "const": 2 },
23
+ "kind": { "const": "hanlint.checkResult" },
24
+ "violationCount": { "type": "integer", "minimum": 0 },
25
+ "contractSha256": { "$ref": "#/$defs/sha256" },
26
+ "draftSha256": { "$ref": "#/$defs/sha256" },
27
+ "missingFacts": { "type": "array", "items": { "type": "string" } },
28
+ "surface": { "$ref": "checkResult.schema.json#/properties/surface" },
29
+ "lint": { "$ref": "checkResult.schema.json#/properties/lint" },
30
+ "meaning": { "type": "string", "minLength": 1 },
31
+ "outline": {
32
+ "type": "object",
33
+ "additionalProperties": false,
34
+ "required": ["matches", "level", "expected", "actual", "mismatches"],
35
+ "properties": {
36
+ "matches": { "type": "boolean" },
37
+ "level": { "type": "integer", "minimum": 1, "maximum": 6 },
38
+ "expected": { "$ref": "#/$defs/strings" },
39
+ "actual": { "$ref": "#/$defs/strings" },
40
+ "mismatches": {
41
+ "type": "array",
42
+ "items": {
43
+ "type": "object",
44
+ "additionalProperties": false,
45
+ "required": ["position", "expected", "actual"],
46
+ "properties": {
47
+ "position": { "type": "integer", "minimum": 1 },
48
+ "expected": { "type": ["string", "null"] },
49
+ "actual": { "type": ["string", "null"] }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ },
55
+ "document": {
56
+ "type": "object",
57
+ "additionalProperties": false,
58
+ "required": [
59
+ "sentenceCount",
60
+ "paragraphCount",
61
+ "sectionCount",
62
+ "wordCount",
63
+ "questionCount",
64
+ "readerCallCount",
65
+ "sections"
66
+ ],
67
+ "properties": {
68
+ "sentenceCount": { "type": "integer", "minimum": 0 },
69
+ "paragraphCount": { "type": "integer", "minimum": 0 },
70
+ "sectionCount": { "type": "integer", "minimum": 0 },
71
+ "wordCount": { "type": "integer", "minimum": 0 },
72
+ "questionCount": { "type": "integer", "minimum": 0 },
73
+ "readerCallCount": { "type": "integer", "minimum": 0 },
74
+ "sections": {
75
+ "type": "array",
76
+ "items": {
77
+ "type": "object",
78
+ "additionalProperties": false,
79
+ "required": ["heading", "line", "paragraphCount", "codeBlockCount"],
80
+ "properties": {
81
+ "heading": { "type": "string" },
82
+ "line": { "type": "integer", "minimum": 1 },
83
+ "paragraphCount": { "type": "integer", "minimum": 0 },
84
+ "codeBlockCount": { "type": "integer", "minimum": 0 }
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ },
91
+ "$defs": {
92
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
93
+ "strings": {
94
+ "type": "array",
95
+ "items": { "type": "string" }
96
+ }
97
+ }
98
+ }
@@ -78,12 +78,6 @@
78
78
  "after": "```text\n한 줄\n두 줄\n세 줄\n네 줄\n다섯 줄\n```\n\n두 번째 실행은 셋째 줄만 다르고 나머지는 위와 같습니다.\n",
79
79
  "moved": "거의 같은 블록을 다시 싣지 않고 달라진 줄만 말했다"
80
80
  },
81
- {
82
- "rule": "ellipsis",
83
- "before": "속도는 조금 느려집니다...",
84
- "after": "속도는 조금 느려집니다.",
85
- "moved": "말끝을 흐리지 않고 같은 사실로 문장을 끝냈다"
86
- },
87
81
  {
88
82
  "rule": "emojiBullet",
89
83
  "before": "- ✅ 파일을 만듭니다\n",
@@ -138,12 +132,6 @@
138
132
  "after": "오늘 안에 정해진 양식을 냅니다.",
139
133
  "moved": "관청 말을 평소에 쓰는 말로 바꿨다"
140
134
  },
141
- {
142
- "rule": "headingQuestion",
143
- "before": "## 파일은 어디에 생길까요?\n\n첫 문단.\n\n## 왜 안 열릴까요?\n\n둘째 문단.\n\n## 크기는 어떻게 바꿀까요?\n\n셋째 문단.\n",
144
- "after": "## 파일이 생기는 자리\n\n첫 문단.\n\n## 왜 안 열릴까요?\n\n둘째 문단.\n\n## 인쇄용 크기로 다시 저장\n\n셋째 문단.\n",
145
- "moved": "질문 제목을 하나만 남기고 나머지는 행동과 결과로 바꿨다"
146
- },
147
135
  {
148
136
  "rule": "headingSentence",
149
137
  "before": "## 표가 제대로 들어왔는지 확인합니다\n\n열 다섯 개가 모두 보이면 된 것입니다.\n",
@@ -188,8 +176,8 @@
188
176
  },
189
177
  {
190
178
  "rule": "introLong",
191
- "before": "하나.\n\n둘.\n\n셋.\n\n넷.\n\n다섯.\n\n## 절\n\n본문입니다.\n",
192
- "after": "하나.\n\n둘.\n\n## 절\n\n본문입니다.\n",
179
+ "before": "무엇을 먼저 볼까요? 여기서 시작합니다. 그래서 준비합니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n## 첫 절\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n## 둘째 절\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n",
180
+ "after": "무엇을 먼저 볼까요? 여기서 시작합니다. 그래서 준비합니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n## 첫 절\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n## 둘째 절\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n표를 엽니다. 값이 보입니다. 그래서 다음으로 넘어갑니다.\n\n",
193
181
  "moved": "미룬 배경 설명을 걷고 도입을 네 문단 안으로 줄였다"
194
182
  },
195
183
  {
@@ -307,12 +295,6 @@
307
295
  "after": "도입입니다.\n\n## 절\n\n이 코드는 줄 수를 출력합니다.\n\n```python\nprint(1)\n```\n\n## 몇 줄이 나올까?\n\n끝.\n",
308
296
  "moved": "코드가 하는 일을 한 문장으로 먼저 말했다"
309
297
  },
310
- {
311
- "rule": "sectionResult",
312
- "before": "도입.\n\n## 말투 찾기\n\n첫 문단입니다. 이유가 붙습니다. 그래서 이어집니다.\n\n둘째 문단입니다. 이유가 붙습니다. 그래서 이어집니다.\n\n셋째 문단입니다. 이유가 붙습니다. 그래서 이어집니다.\n\n넷째 문단입니다. 이유가 붙습니다. 그래서 이어집니다.\n\n## 결과는 어디에 남나?\n\n끝.\n",
313
- "after": "도입.\n\n## 말투 찾기\n\n첫 문단입니다. 이유가 붙습니다. 그래서 이어집니다.\n\n둘째 문단입니다. 이유가 붙습니다. 그래서 이어집니다.\n\n셋째 문단입니다. 이유가 붙습니다. 그래서 이어집니다.\n\npython make_qr.py 를 돌리면 폴더에 link.png 가 생깁니다.\n\n## 결과는 어디에 남나?\n\n끝.\n",
314
- "moved": "절 끝에 독자가 자기 화면에서 확인할 결과를 하나 남겼다"
315
- },
316
298
  {
317
299
  "rule": "spacing",
318
300
  "before": "할수 있다고 생각한지 두번째입니다.",
@@ -331,12 +313,6 @@
331
313
  "after": "| 이름 | 시간 |\n|---|---|\n| pandas | 3.72초 |\n| Polars | 0.18초 |\n| DuckDB | 0.13초 |\n| PyArrow | 0.02초 |\n| Dask | 2.12초 |\n",
332
314
  "moved": "열의 잣대를 하나로 맞췄다"
333
315
  },
334
- {
335
- "rule": "topicBreak",
336
- "before": "## 절\n\n메모리는 절반으로 줄어듭니다. 자리를 덜 쓰면 그만큼 빨라집니다.\n\n이제 정렬을 봅니다. 순서를 바꾸면 결과가 달라집니다.\n",
337
- "after": "## 절\n\n메모리는 절반으로 줄어듭니다. 자리를 덜 쓰면 그만큼 빨라집니다.\n\n메모리를 아낀 그 표를 이번에는 정렬해 봅니다. 순서를 바꾸면 결과가 달라집니다.\n",
338
- "moved": "새 문단 첫 문장에서 앞 문단이 만든 것을 이름으로 다시 불렀다"
339
- },
340
316
  {
341
317
  "rule": "translationese",
342
318
  "before": "모든 분야에 있어서 기준이 필요합니다.",
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://hanlint.dev/schemas/finding.schema.json",
4
+ "title": "hanlint Finding",
5
+ "description": "A deterministic prose issue with its location, reason, and optional local repair data.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["rule", "line", "severity", "scope", "at", "quote", "why"],
9
+ "properties": {
10
+ "rule": { "type": "string", "minLength": 1 },
11
+ "line": { "type": "integer", "minimum": 1 },
12
+ "severity": { "enum": ["error", "notice"] },
13
+ "scope": { "enum": ["sentence", "paragraph", "section", "document"] },
14
+ "at": { "type": "integer", "minimum": -1 },
15
+ "quote": { "type": "string", "minLength": 1 },
16
+ "why": { "type": "string", "minLength": 1 },
17
+ "fix": { "type": "string", "minLength": 1 },
18
+ "fragment": { "type": ["string", "null"] },
19
+ "replacement": { "type": "string" },
20
+ "candidates": {
21
+ "type": "array",
22
+ "minItems": 1,
23
+ "items": {
24
+ "type": "object",
25
+ "additionalProperties": false,
26
+ "required": ["text", "why"],
27
+ "properties": {
28
+ "text": { "type": "string", "minLength": 1 },
29
+ "why": { "type": "string", "minLength": 1 }
30
+ }
31
+ }
32
+ }
33
+ },
34
+ "dependentRequired": {
35
+ "fragment": ["replacement"],
36
+ "replacement": ["fragment"]
37
+ }
38
+ }
@@ -12,16 +12,6 @@
12
12
  "profile"
13
13
  ]
14
14
  },
15
- {
16
- "id": "flow",
17
- "name": "흐름",
18
- "symbol": "F",
19
- "ansi": 214,
20
- "hex": "#E69F00",
21
- "rules": [
22
- "topicBreak"
23
- ]
24
- },
25
15
  {
26
16
  "id": "deixis",
27
17
  "name": "지시",
@@ -136,8 +126,7 @@
136
126
  "doubleNegative",
137
127
  "negationRedefine",
138
128
  "dash",
139
- "imperativePeriod",
140
- "ellipsis"
129
+ "imperativePeriod"
141
130
  ]
142
131
  }
143
132
  ]
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://hanlint.dev/schemas/patch.schema.json",
4
+ "title": "hanlint Patch",
5
+ "description": "An exact local replacement connected to a named existing violation.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["reason", "before", "after"],
9
+ "properties": {
10
+ "reason": { "type": "string", "minLength": 1 },
11
+ "before": { "type": "string", "minLength": 1 },
12
+ "after": { "type": "string", "minLength": 1 }
13
+ }
14
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://hanlint.dev/schemas/patchResult.schema.json",
4
+ "title": "hanlint Patch Result",
5
+ "description": "A deterministic verification receipt for an exact prose Patch.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "version",
10
+ "kind",
11
+ "verified",
12
+ "violationCount",
13
+ "contractSha256",
14
+ "sourceSha256",
15
+ "patchSha256",
16
+ "resultSha256",
17
+ "matchCount",
18
+ "reason",
19
+ "newSurfaceIssues",
20
+ "newErrors",
21
+ "meaning"
22
+ ],
23
+ "properties": {
24
+ "version": { "const": 1 },
25
+ "kind": { "const": "hanlint.patchResult" },
26
+ "verified": { "type": "boolean" },
27
+ "violationCount": { "type": "integer", "minimum": 0 },
28
+ "contractSha256": { "$ref": "#/$defs/sha256" },
29
+ "sourceSha256": { "$ref": "#/$defs/sha256" },
30
+ "patchSha256": { "$ref": "#/$defs/sha256" },
31
+ "resultSha256": {
32
+ "oneOf": [
33
+ { "$ref": "#/$defs/sha256" },
34
+ { "type": "null" }
35
+ ]
36
+ },
37
+ "matchCount": { "type": "integer", "minimum": 0 },
38
+ "reason": {
39
+ "type": "object",
40
+ "additionalProperties": false,
41
+ "required": ["name", "before", "after", "reduced"],
42
+ "properties": {
43
+ "name": { "type": "string", "minLength": 1 },
44
+ "before": { "type": "integer", "minimum": 0 },
45
+ "after": {
46
+ "oneOf": [
47
+ { "type": "integer", "minimum": 0 },
48
+ { "type": "null" }
49
+ ]
50
+ },
51
+ "reduced": { "type": "boolean" }
52
+ }
53
+ },
54
+ "newSurfaceIssues": {
55
+ "type": "array",
56
+ "items": {
57
+ "type": "object",
58
+ "additionalProperties": false,
59
+ "required": ["kind", "value"],
60
+ "properties": {
61
+ "kind": {
62
+ "enum": [
63
+ "missingNumbers",
64
+ "unexpectedNumbers",
65
+ "missingUrls",
66
+ "unexpectedUrls",
67
+ "missingCode",
68
+ "unexpectedCode",
69
+ "missingLinks",
70
+ "unexpectedLinks"
71
+ ]
72
+ },
73
+ "value": { "type": "string", "minLength": 1 }
74
+ }
75
+ }
76
+ },
77
+ "newErrors": {
78
+ "type": "array",
79
+ "items": { "$ref": "finding.schema.json" }
80
+ },
81
+ "meaning": { "type": "string", "minLength": 1 }
82
+ },
83
+ "$defs": {
84
+ "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }
85
+ }
86
+ }
@@ -0,0 +1,71 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://hanlint.dev/schemas/patchResultV2.schema.json",
4
+ "title": "hanlint Patch Result v2",
5
+ "description": "A deterministic verification receipt for a version 2 Reader Contract patch.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "version",
10
+ "kind",
11
+ "verified",
12
+ "violationCount",
13
+ "contractSha256",
14
+ "sourceSha256",
15
+ "patchSha256",
16
+ "resultSha256",
17
+ "matchCount",
18
+ "reason",
19
+ "newContractIssues",
20
+ "newErrors",
21
+ "meaning"
22
+ ],
23
+ "properties": {
24
+ "version": { "const": 2 },
25
+ "kind": { "const": "hanlint.patchResult" },
26
+ "verified": { "type": "boolean" },
27
+ "violationCount": { "type": "integer", "minimum": 0 },
28
+ "contractSha256": { "$ref": "patchResult.schema.json#/$defs/sha256" },
29
+ "sourceSha256": { "$ref": "patchResult.schema.json#/$defs/sha256" },
30
+ "patchSha256": { "$ref": "patchResult.schema.json#/$defs/sha256" },
31
+ "resultSha256": {
32
+ "oneOf": [
33
+ { "$ref": "patchResult.schema.json#/$defs/sha256" },
34
+ { "type": "null" }
35
+ ]
36
+ },
37
+ "matchCount": { "type": "integer", "minimum": 0 },
38
+ "reason": { "$ref": "patchResult.schema.json#/properties/reason" },
39
+ "newContractIssues": {
40
+ "type": "array",
41
+ "items": {
42
+ "type": "object",
43
+ "additionalProperties": false,
44
+ "required": ["kind", "value"],
45
+ "properties": {
46
+ "kind": {
47
+ "enum": [
48
+ "missingNumbers",
49
+ "unexpectedNumbers",
50
+ "missingUrls",
51
+ "unexpectedUrls",
52
+ "missingCode",
53
+ "unexpectedCode",
54
+ "missingLinks",
55
+ "unexpectedLinks",
56
+ "outline",
57
+ "lockedFacts",
58
+ "editPolicy"
59
+ ]
60
+ },
61
+ "value": { "type": "string", "minLength": 1 }
62
+ }
63
+ }
64
+ },
65
+ "newErrors": {
66
+ "type": "array",
67
+ "items": { "$ref": "finding.schema.json" }
68
+ },
69
+ "meaning": { "type": "string", "minLength": 1 }
70
+ }
71
+ }
@@ -88,17 +88,6 @@
88
88
  ],
89
89
  "source": "글쓰기 스킬의 도입은 결핍부터. 이태준 문장강화의 첫머리"
90
90
  },
91
- {
92
- "name": "수치 비교",
93
- "form": "{A}는 {수치}, {B}는 {수치}입니다",
94
- "when": "둘을 견줄 때. 형용사로 말하지 않고 잰 값을 나란히 놓는다",
95
- "example": "같은 질문에 pandas 는 3.7초, Polars 는 0.18초가 걸립니다.",
96
- "instead": "속도는 조금 느려집니다...",
97
- "avoids": [
98
- "ellipsis"
99
- ],
100
- "source": "글쓰기 스킬의 수치와 인용은 직접 확인한 값만 쓴다"
101
- },
102
91
  {
103
92
  "name": "미룬 것 회수",
104
93
  "form": "앞에서 미룬 {이름}을 이제 봅니다",
@@ -6,7 +6,9 @@ os\.startfile\( 윈도우 subprocess.run(["open", 경로]) 나 xdg-open
6
6
  \bmsvcrt\b 윈도우 termios 나 다른 입력 방법
7
7
  \bwin32(?:api|con|gui|com)\b 윈도우 pywin32 는 윈도우 전용이다
8
8
  \bosascript\b 맥 다른 운영체제에는 AppleScript 가 없다
9
- /proc/ 리눅스 psutil 같은 이식 가능한 API
9
+ # /proc/ 는 뺐다. 코드에 박히는 API 호출이 아니라 셸 명령의 경로이고, 컨테이너 안에서는
10
+ # 리눅스가 기본 실행 환경이다. 실측: 기준 말뭉치의 지적 여덟 건이 전부 이 한 줄이었고 전부
11
+ # 쿠버네티스 안내서의 kubectl exec 와 컨테이너 안 셸 기록이라 독자가 멈추지 않는다 (2026-08-31).
10
12
  \bfcntl\b 리눅스와 맥 윈도우에는 fcntl 이 없다
11
13
  resource\.getrusage\( 리눅스와 맥 윈도우에는 resource 모듈이 없다
12
14
  [A-Za-z]:\\\\ 윈도우 절대 경로 대신 상대 경로나 pathlib