hanlint 0.0.1__tar.gz

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 (381) hide show
  1. hanlint-0.0.1/.claude/settings.json +16 -0
  2. hanlint-0.0.1/.gitattributes +4 -0
  3. hanlint-0.0.1/.githooks/commit-msg +22 -0
  4. hanlint-0.0.1/.githooks/pre-commit +47 -0
  5. hanlint-0.0.1/.githooks/pre-push +34 -0
  6. hanlint-0.0.1/.github/workflows/ci.yml +60 -0
  7. hanlint-0.0.1/.github/workflows/publish.yml +151 -0
  8. hanlint-0.0.1/.gitignore +44 -0
  9. hanlint-0.0.1/.pre-commit-hooks.yaml +13 -0
  10. hanlint-0.0.1/CHANGELOG.md +36 -0
  11. hanlint-0.0.1/LICENSE +21 -0
  12. hanlint-0.0.1/PKG-INFO +143 -0
  13. hanlint-0.0.1/README.md +121 -0
  14. hanlint-0.0.1/action.yml +30 -0
  15. hanlint-0.0.1/hanlint.toml +4 -0
  16. hanlint-0.0.1/hooks/tests/checkWriteGate.py +100 -0
  17. hanlint-0.0.1/hooks/writeGate.py +86 -0
  18. hanlint-0.0.1/npm/README.md +27 -0
  19. hanlint-0.0.1/npm/bin/hanlint.js +4 -0
  20. hanlint-0.0.1/npm/data/causalMarkers.txt +25 -0
  21. hanlint-0.0.1/npm/data/cliches.json +99 -0
  22. hanlint-0.0.1/npm/data/confusable.json +76 -0
  23. hanlint-0.0.1/npm/data/connectors.txt +18 -0
  24. hanlint-0.0.1/npm/data/countUnits.txt +6 -0
  25. hanlint-0.0.1/npm/data/coverageTypes.txt +7 -0
  26. hanlint-0.0.1/npm/data/deixis.txt +8 -0
  27. hanlint-0.0.1/npm/data/easyWords.json +340 -0
  28. hanlint-0.0.1/npm/data/emphasisWords.txt +11 -0
  29. hanlint-0.0.1/npm/data/endings.txt +9 -0
  30. hanlint-0.0.1/npm/data/euiNouns.txt +40 -0
  31. hanlint-0.0.1/npm/data/hedges.txt +15 -0
  32. hanlint-0.0.1/npm/data/hiddenDeps.txt +11 -0
  33. hanlint-0.0.1/npm/data/holeKinds.json +145 -0
  34. hanlint-0.0.1/npm/data/japaneseLoan.json +82 -0
  35. hanlint-0.0.1/npm/data/josa.txt +54 -0
  36. hanlint-0.0.1/npm/data/koreanNumbers.txt +22 -0
  37. hanlint-0.0.1/npm/data/passiveStems.txt +53 -0
  38. hanlint-0.0.1/npm/data/platformApis.txt +12 -0
  39. hanlint-0.0.1/npm/data/promiseMarkers.txt +7 -0
  40. hanlint-0.0.1/npm/data/pythonPackages.txt +35 -0
  41. hanlint-0.0.1/npm/data/pythonStdlib.txt +193 -0
  42. hanlint-0.0.1/npm/data/readerCalls.txt +14 -0
  43. hanlint-0.0.1/npm/data/recallMarkers.txt +6 -0
  44. hanlint-0.0.1/npm/data/redundantPair.json +70 -0
  45. hanlint-0.0.1/npm/data/ruleDocs.json +44 -0
  46. hanlint-0.0.1/npm/data/spacing.json +76 -0
  47. hanlint-0.0.1/npm/data/spelling.json +166 -0
  48. hanlint-0.0.1/npm/data/stopwords.txt +33 -0
  49. hanlint-0.0.1/npm/data/translationese.json +104 -0
  50. hanlint-0.0.1/npm/data/verbTails.txt +56 -0
  51. hanlint-0.0.1/npm/data/version.json +1 -0
  52. hanlint-0.0.1/npm/package.json +39 -0
  53. hanlint-0.0.1/npm/src/analysis/index.js +34 -0
  54. hanlint-0.0.1/npm/src/analysis/surface/splitSentences.js +36 -0
  55. hanlint-0.0.1/npm/src/analysis/surface/tokenize.js +162 -0
  56. hanlint-0.0.1/npm/src/cli/main.js +376 -0
  57. hanlint-0.0.1/npm/src/config/loadConfig.js +51 -0
  58. hanlint-0.0.1/npm/src/config/settings.js +80 -0
  59. hanlint-0.0.1/npm/src/config/toml.js +239 -0
  60. hanlint-0.0.1/npm/src/data/load.js +63 -0
  61. hanlint-0.0.1/npm/src/document/model.js +55 -0
  62. hanlint-0.0.1/npm/src/document/parseMarkdown.js +183 -0
  63. hanlint-0.0.1/npm/src/document/plainText.js +43 -0
  64. hanlint-0.0.1/npm/src/edit/applyFixes.js +0 -0
  65. hanlint-0.0.1/npm/src/fingerprint/build.js +284 -0
  66. hanlint-0.0.1/npm/src/fingerprint/dictionaries.js +118 -0
  67. hanlint-0.0.1/npm/src/fingerprint/markers.js +175 -0
  68. hanlint-0.0.1/npm/src/fingerprint/topics.js +42 -0
  69. hanlint-0.0.1/npm/src/index.js +68 -0
  70. hanlint-0.0.1/npm/src/regex.js +47 -0
  71. hanlint-0.0.1/npm/src/report/compactReport.js +9 -0
  72. hanlint-0.0.1/npm/src/report/fingerprintJson.js +126 -0
  73. hanlint-0.0.1/npm/src/report/githubReport.js +13 -0
  74. hanlint-0.0.1/npm/src/report/jsonReport.js +16 -0
  75. hanlint-0.0.1/npm/src/report/textReport.js +19 -0
  76. hanlint-0.0.1/npm/src/rules/code/duplicateBlock.js +48 -0
  77. hanlint-0.0.1/npm/src/rules/code/firstResultDistance.js +23 -0
  78. hanlint-0.0.1/npm/src/rules/code/inputFileSource.js +103 -0
  79. hanlint-0.0.1/npm/src/rules/code/installImport.js +126 -0
  80. hanlint-0.0.1/npm/src/rules/code/platformApi.js +36 -0
  81. hanlint-0.0.1/npm/src/rules/document/countMismatch.js +42 -0
  82. hanlint-0.0.1/npm/src/rules/document/keywordMissing.js +21 -0
  83. hanlint-0.0.1/npm/src/rules/document/noQuestion.js +11 -0
  84. hanlint-0.0.1/npm/src/rules/document/promiseRecall.js +12 -0
  85. hanlint-0.0.1/npm/src/rules/document/readerAbsent.js +11 -0
  86. hanlint-0.0.1/npm/src/rules/finding.js +53 -0
  87. hanlint-0.0.1/npm/src/rules/orthography/confusable.js +9 -0
  88. hanlint-0.0.1/npm/src/rules/orthography/spacing.js +9 -0
  89. hanlint-0.0.1/npm/src/rules/orthography/spelling.js +9 -0
  90. hanlint-0.0.1/npm/src/rules/paragraph/factListParagraph.js +27 -0
  91. hanlint-0.0.1/npm/src/rules/paragraph/paraFragment.js +29 -0
  92. hanlint-0.0.1/npm/src/rules/paragraph/topicBreak.js +25 -0
  93. hanlint-0.0.1/npm/src/rules/registry.js +147 -0
  94. hanlint-0.0.1/npm/src/rules/sentence/cliche.js +9 -0
  95. hanlint-0.0.1/npm/src/rules/sentence/connectorRepeat.js +23 -0
  96. hanlint-0.0.1/npm/src/rules/sentence/danglingDeixis.js +19 -0
  97. hanlint-0.0.1/npm/src/rules/sentence/dash.js +21 -0
  98. hanlint-0.0.1/npm/src/rules/sentence/deixis.js +19 -0
  99. hanlint-0.0.1/npm/src/rules/sentence/doubleNegative.js +28 -0
  100. hanlint-0.0.1/npm/src/rules/sentence/doublePassive.js +17 -0
  101. hanlint-0.0.1/npm/src/rules/sentence/ellipsis.js +17 -0
  102. hanlint-0.0.1/npm/src/rules/sentence/endingRepeat.js +25 -0
  103. hanlint-0.0.1/npm/src/rules/sentence/euiChain.js +18 -0
  104. hanlint-0.0.1/npm/src/rules/sentence/fillerOpener.js +18 -0
  105. hanlint-0.0.1/npm/src/rules/sentence/hardWord.js +10 -0
  106. hanlint-0.0.1/npm/src/rules/sentence/imperativePeriod.js +20 -0
  107. hanlint-0.0.1/npm/src/rules/sentence/japaneseLoan.js +9 -0
  108. hanlint-0.0.1/npm/src/rules/sentence/longSentence.js +20 -0
  109. hanlint-0.0.1/npm/src/rules/sentence/negationRedefine.js +22 -0
  110. hanlint-0.0.1/npm/src/rules/sentence/nounPile.js +20 -0
  111. hanlint-0.0.1/npm/src/rules/sentence/redundantPair.js +9 -0
  112. hanlint-0.0.1/npm/src/rules/sentence/translationese.js +9 -0
  113. hanlint-0.0.1/npm/src/rules/shared/codeBlocks.js +44 -0
  114. hanlint-0.0.1/npm/src/rules/shared/dictionaryRule.js +35 -0
  115. hanlint-0.0.1/npm/src/rules/shared/runs.js +22 -0
  116. hanlint-0.0.1/npm/src/rules/structure/emojiBullet.js +34 -0
  117. hanlint-0.0.1/npm/src/rules/structure/headingSentence.js +17 -0
  118. hanlint-0.0.1/npm/src/rules/structure/headingSkip.js +18 -0
  119. hanlint-0.0.1/npm/src/rules/structure/headingUniform.js +36 -0
  120. hanlint-0.0.1/npm/src/rules/structure/introLong.js +20 -0
  121. hanlint-0.0.1/npm/src/rules/structure/sectionNoProse.js +16 -0
  122. hanlint-0.0.1/npm/src/rules/structure/sectionResult.js +37 -0
  123. hanlint-0.0.1/npm/src/text.js +63 -0
  124. hanlint-0.0.1/npm/test/cli.test.js +126 -0
  125. hanlint-0.0.1/npm/test/config.test.js +68 -0
  126. hanlint-0.0.1/npm/test/edit.test.js +45 -0
  127. hanlint-0.0.1/npm/test/rules.test.js +52 -0
  128. hanlint-0.0.1/pyproject.toml +56 -0
  129. hanlint-0.0.1/scripts/__init__.py +0 -0
  130. hanlint-0.0.1/scripts/commitMessage.py +170 -0
  131. hanlint-0.0.1/scripts/exportData.py +71 -0
  132. hanlint-0.0.1/skills/README.md +56 -0
  133. hanlint-0.0.1/skills/SCHEMA.md +51 -0
  134. hanlint-0.0.1/skills/specs/operation/README.md +23 -0
  135. hanlint-0.0.1/skills/specs/operation/addingARule.md +59 -0
  136. hanlint-0.0.1/skills/specs/operation/codeStyle.md +116 -0
  137. hanlint-0.0.1/skills/specs/operation/moduleLayers.md +60 -0
  138. hanlint-0.0.1/skills/specs/operation/release.md +89 -0
  139. hanlint-0.0.1/skills/specs/operation/sourceControl.md +102 -0
  140. hanlint-0.0.1/skills/specs/operation/verify.md +86 -0
  141. hanlint-0.0.1/skills/specs/start/README.md +18 -0
  142. hanlint-0.0.1/skills/specs/start/hanlintSkillOs.md +55 -0
  143. hanlint-0.0.1/skills/specs/start/product.md +103 -0
  144. hanlint-0.0.1/skills/use-hanlint/SKILL.md +63 -0
  145. hanlint-0.0.1/src/hanlint/__init__.py +81 -0
  146. hanlint-0.0.1/src/hanlint/__main__.py +9 -0
  147. hanlint-0.0.1/src/hanlint/analysis/__init__.py +24 -0
  148. hanlint-0.0.1/src/hanlint/analysis/analyzer.py +34 -0
  149. hanlint-0.0.1/src/hanlint/analysis/kiwi/__init__.py +7 -0
  150. hanlint-0.0.1/src/hanlint/analysis/kiwi/kiwiAnalyzer.py +81 -0
  151. hanlint-0.0.1/src/hanlint/analysis/surface/__init__.py +7 -0
  152. hanlint-0.0.1/src/hanlint/analysis/surface/splitSentences.py +35 -0
  153. hanlint-0.0.1/src/hanlint/analysis/surface/surfaceAnalyzer.py +23 -0
  154. hanlint-0.0.1/src/hanlint/analysis/surface/tokenize.py +149 -0
  155. hanlint-0.0.1/src/hanlint/analysis/tags.py +23 -0
  156. hanlint-0.0.1/src/hanlint/audit/__init__.py +41 -0
  157. hanlint-0.0.1/src/hanlint/audit/density.py +31 -0
  158. hanlint-0.0.1/src/hanlint/audit/flow.py +24 -0
  159. hanlint-0.0.1/src/hanlint/audit/lexicon.py +41 -0
  160. hanlint-0.0.1/src/hanlint/audit/rhythm.py +46 -0
  161. hanlint-0.0.1/src/hanlint/audit/sectionShape.py +30 -0
  162. hanlint-0.0.1/src/hanlint/audit/shape.py +94 -0
  163. hanlint-0.0.1/src/hanlint/cli/__init__.py +7 -0
  164. hanlint-0.0.1/src/hanlint/cli/commands/__init__.py +1 -0
  165. hanlint-0.0.1/src/hanlint/cli/commands/audit.py +35 -0
  166. hanlint-0.0.1/src/hanlint/cli/commands/coverage.py +37 -0
  167. hanlint-0.0.1/src/hanlint/cli/commands/diff.py +30 -0
  168. hanlint-0.0.1/src/hanlint/cli/commands/explain.py +21 -0
  169. hanlint-0.0.1/src/hanlint/cli/commands/fix.py +58 -0
  170. hanlint-0.0.1/src/hanlint/cli/commands/init.py +79 -0
  171. hanlint-0.0.1/src/hanlint/cli/commands/lint.py +98 -0
  172. hanlint-0.0.1/src/hanlint/cli/commands/mapCommand.py +31 -0
  173. hanlint-0.0.1/src/hanlint/cli/commands/printFingerprint.py +31 -0
  174. hanlint-0.0.1/src/hanlint/cli/commands/profile.py +39 -0
  175. hanlint-0.0.1/src/hanlint/cli/commands/rules.py +25 -0
  176. hanlint-0.0.1/src/hanlint/cli/commands/shared.py +111 -0
  177. hanlint-0.0.1/src/hanlint/cli/main.py +84 -0
  178. hanlint-0.0.1/src/hanlint/config/__init__.py +8 -0
  179. hanlint-0.0.1/src/hanlint/config/loadConfig.py +42 -0
  180. hanlint-0.0.1/src/hanlint/config/settings.py +84 -0
  181. hanlint-0.0.1/src/hanlint/coverage/__init__.py +7 -0
  182. hanlint-0.0.1/src/hanlint/coverage/match.py +166 -0
  183. hanlint-0.0.1/src/hanlint/data/__init__.py +7 -0
  184. hanlint-0.0.1/src/hanlint/data/causalMarkers.txt +25 -0
  185. hanlint-0.0.1/src/hanlint/data/cliches.toml +97 -0
  186. hanlint-0.0.1/src/hanlint/data/confusable.toml +76 -0
  187. hanlint-0.0.1/src/hanlint/data/connectors.txt +18 -0
  188. hanlint-0.0.1/src/hanlint/data/countUnits.txt +6 -0
  189. hanlint-0.0.1/src/hanlint/data/coverageTypes.txt +7 -0
  190. hanlint-0.0.1/src/hanlint/data/deixis.txt +8 -0
  191. hanlint-0.0.1/src/hanlint/data/easyWords.toml +342 -0
  192. hanlint-0.0.1/src/hanlint/data/emphasisWords.txt +11 -0
  193. hanlint-0.0.1/src/hanlint/data/endings.txt +9 -0
  194. hanlint-0.0.1/src/hanlint/data/euiNouns.txt +40 -0
  195. hanlint-0.0.1/src/hanlint/data/hedges.txt +15 -0
  196. hanlint-0.0.1/src/hanlint/data/hiddenDeps.txt +11 -0
  197. hanlint-0.0.1/src/hanlint/data/holeKinds.toml +90 -0
  198. hanlint-0.0.1/src/hanlint/data/japaneseLoan.toml +80 -0
  199. hanlint-0.0.1/src/hanlint/data/josa.txt +54 -0
  200. hanlint-0.0.1/src/hanlint/data/koreanNumbers.txt +22 -0
  201. hanlint-0.0.1/src/hanlint/data/load.py +39 -0
  202. hanlint-0.0.1/src/hanlint/data/passiveStems.txt +53 -0
  203. hanlint-0.0.1/src/hanlint/data/platformApis.txt +12 -0
  204. hanlint-0.0.1/src/hanlint/data/promiseMarkers.txt +7 -0
  205. hanlint-0.0.1/src/hanlint/data/pythonPackages.txt +35 -0
  206. hanlint-0.0.1/src/hanlint/data/pythonStdlib.txt +193 -0
  207. hanlint-0.0.1/src/hanlint/data/readerCalls.txt +14 -0
  208. hanlint-0.0.1/src/hanlint/data/recallMarkers.txt +6 -0
  209. hanlint-0.0.1/src/hanlint/data/redundantPair.toml +67 -0
  210. hanlint-0.0.1/src/hanlint/data/spacing.toml +76 -0
  211. hanlint-0.0.1/src/hanlint/data/spelling.toml +167 -0
  212. hanlint-0.0.1/src/hanlint/data/stopwords.txt +33 -0
  213. hanlint-0.0.1/src/hanlint/data/translationese.toml +104 -0
  214. hanlint-0.0.1/src/hanlint/data/verbTails.txt +56 -0
  215. hanlint-0.0.1/src/hanlint/document/__init__.py +9 -0
  216. hanlint-0.0.1/src/hanlint/document/model.py +84 -0
  217. hanlint-0.0.1/src/hanlint/document/parseMarkdown.py +163 -0
  218. hanlint-0.0.1/src/hanlint/document/plainText.py +43 -0
  219. hanlint-0.0.1/src/hanlint/edit/__init__.py +7 -0
  220. hanlint-0.0.1/src/hanlint/edit/applyFixes.py +93 -0
  221. hanlint-0.0.1/src/hanlint/fingerprint/__init__.py +22 -0
  222. hanlint-0.0.1/src/hanlint/fingerprint/build.py +177 -0
  223. hanlint-0.0.1/src/hanlint/fingerprint/dictionaries.py +91 -0
  224. hanlint-0.0.1/src/hanlint/fingerprint/documentPrint.py +44 -0
  225. hanlint-0.0.1/src/hanlint/fingerprint/markers.py +134 -0
  226. hanlint-0.0.1/src/hanlint/fingerprint/paragraphPrint.py +35 -0
  227. hanlint-0.0.1/src/hanlint/fingerprint/sectionPrint.py +32 -0
  228. hanlint-0.0.1/src/hanlint/fingerprint/sentencePrint.py +58 -0
  229. hanlint-0.0.1/src/hanlint/fingerprint/topics.py +43 -0
  230. hanlint-0.0.1/src/hanlint/profile/__init__.py +12 -0
  231. hanlint-0.0.1/src/hanlint/profile/build.py +54 -0
  232. hanlint-0.0.1/src/hanlint/profile/compare.py +120 -0
  233. hanlint-0.0.1/src/hanlint/profile/metrics.py +49 -0
  234. hanlint-0.0.1/src/hanlint/report/__init__.py +27 -0
  235. hanlint-0.0.1/src/hanlint/report/auditReport.py +50 -0
  236. hanlint-0.0.1/src/hanlint/report/compactReport.py +15 -0
  237. hanlint-0.0.1/src/hanlint/report/diffReport.py +78 -0
  238. hanlint-0.0.1/src/hanlint/report/fingerprintJson.py +129 -0
  239. hanlint-0.0.1/src/hanlint/report/githubReport.py +14 -0
  240. hanlint-0.0.1/src/hanlint/report/holeKinds.py +42 -0
  241. hanlint-0.0.1/src/hanlint/report/jsonReport.py +26 -0
  242. hanlint-0.0.1/src/hanlint/report/mapHtml.py +110 -0
  243. hanlint-0.0.1/src/hanlint/report/mapText.py +100 -0
  244. hanlint-0.0.1/src/hanlint/report/textReport.py +24 -0
  245. hanlint-0.0.1/src/hanlint/rules/__init__.py +8 -0
  246. hanlint-0.0.1/src/hanlint/rules/code/__init__.py +4 -0
  247. hanlint-0.0.1/src/hanlint/rules/code/duplicateBlock.py +59 -0
  248. hanlint-0.0.1/src/hanlint/rules/code/firstResultDistance.py +40 -0
  249. hanlint-0.0.1/src/hanlint/rules/code/inputFileSource.py +115 -0
  250. hanlint-0.0.1/src/hanlint/rules/code/installImport.py +137 -0
  251. hanlint-0.0.1/src/hanlint/rules/code/platformApi.py +50 -0
  252. hanlint-0.0.1/src/hanlint/rules/document/__init__.py +1 -0
  253. hanlint-0.0.1/src/hanlint/rules/document/countMismatch.py +63 -0
  254. hanlint-0.0.1/src/hanlint/rules/document/keywordMissing.py +41 -0
  255. hanlint-0.0.1/src/hanlint/rules/document/noQuestion.py +33 -0
  256. hanlint-0.0.1/src/hanlint/rules/document/promiseRecall.py +35 -0
  257. hanlint-0.0.1/src/hanlint/rules/document/readerAbsent.py +34 -0
  258. hanlint-0.0.1/src/hanlint/rules/finding.py +49 -0
  259. hanlint-0.0.1/src/hanlint/rules/orthography/__init__.py +1 -0
  260. hanlint-0.0.1/src/hanlint/rules/orthography/confusable.py +23 -0
  261. hanlint-0.0.1/src/hanlint/rules/orthography/spacing.py +24 -0
  262. hanlint-0.0.1/src/hanlint/rules/orthography/spelling.py +24 -0
  263. hanlint-0.0.1/src/hanlint/rules/paragraph/__init__.py +1 -0
  264. hanlint-0.0.1/src/hanlint/rules/paragraph/factListParagraph.py +48 -0
  265. hanlint-0.0.1/src/hanlint/rules/paragraph/paraFragment.py +45 -0
  266. hanlint-0.0.1/src/hanlint/rules/paragraph/topicBreak.py +39 -0
  267. hanlint-0.0.1/src/hanlint/rules/registry.py +81 -0
  268. hanlint-0.0.1/src/hanlint/rules/sentence/__init__.py +1 -0
  269. hanlint-0.0.1/src/hanlint/rules/sentence/cliche.py +24 -0
  270. hanlint-0.0.1/src/hanlint/rules/sentence/connectorRepeat.py +36 -0
  271. hanlint-0.0.1/src/hanlint/rules/sentence/danglingDeixis.py +38 -0
  272. hanlint-0.0.1/src/hanlint/rules/sentence/dash.py +38 -0
  273. hanlint-0.0.1/src/hanlint/rules/sentence/deixis.py +38 -0
  274. hanlint-0.0.1/src/hanlint/rules/sentence/doubleNegative.py +49 -0
  275. hanlint-0.0.1/src/hanlint/rules/sentence/doublePassive.py +32 -0
  276. hanlint-0.0.1/src/hanlint/rules/sentence/ellipsis.py +35 -0
  277. hanlint-0.0.1/src/hanlint/rules/sentence/endingRepeat.py +40 -0
  278. hanlint-0.0.1/src/hanlint/rules/sentence/euiChain.py +37 -0
  279. hanlint-0.0.1/src/hanlint/rules/sentence/fillerOpener.py +38 -0
  280. hanlint-0.0.1/src/hanlint/rules/sentence/hardWord.py +25 -0
  281. hanlint-0.0.1/src/hanlint/rules/sentence/imperativePeriod.py +39 -0
  282. hanlint-0.0.1/src/hanlint/rules/sentence/japaneseLoan.py +23 -0
  283. hanlint-0.0.1/src/hanlint/rules/sentence/longSentence.py +32 -0
  284. hanlint-0.0.1/src/hanlint/rules/sentence/negationRedefine.py +39 -0
  285. hanlint-0.0.1/src/hanlint/rules/sentence/nounPile.py +34 -0
  286. hanlint-0.0.1/src/hanlint/rules/sentence/redundantPair.py +22 -0
  287. hanlint-0.0.1/src/hanlint/rules/sentence/translationese.py +24 -0
  288. hanlint-0.0.1/src/hanlint/rules/shared/__init__.py +9 -0
  289. hanlint-0.0.1/src/hanlint/rules/shared/codeBlocks.py +48 -0
  290. hanlint-0.0.1/src/hanlint/rules/shared/dictionaryRule.py +34 -0
  291. hanlint-0.0.1/src/hanlint/rules/shared/runs.py +19 -0
  292. hanlint-0.0.1/src/hanlint/rules/structure/__init__.py +1 -0
  293. hanlint-0.0.1/src/hanlint/rules/structure/emojiBullet.py +41 -0
  294. hanlint-0.0.1/src/hanlint/rules/structure/headingSentence.py +34 -0
  295. hanlint-0.0.1/src/hanlint/rules/structure/headingSkip.py +34 -0
  296. hanlint-0.0.1/src/hanlint/rules/structure/headingUniform.py +38 -0
  297. hanlint-0.0.1/src/hanlint/rules/structure/introLong.py +36 -0
  298. hanlint-0.0.1/src/hanlint/rules/structure/sectionNoProse.py +35 -0
  299. hanlint-0.0.1/src/hanlint/rules/structure/sectionResult.py +56 -0
  300. hanlint-0.0.1/tests/__init__.py +0 -0
  301. hanlint-0.0.1/tests/analysis/__init__.py +0 -0
  302. hanlint-0.0.1/tests/analysis/testAnalyzers.py +71 -0
  303. hanlint-0.0.1/tests/audit/__init__.py +0 -0
  304. hanlint-0.0.1/tests/audit/testAudit.py +55 -0
  305. hanlint-0.0.1/tests/cli/__init__.py +0 -0
  306. hanlint-0.0.1/tests/cli/testCli.py +176 -0
  307. hanlint-0.0.1/tests/config/__init__.py +0 -0
  308. hanlint-0.0.1/tests/config/testConfig.py +43 -0
  309. hanlint-0.0.1/tests/conftest.py +52 -0
  310. hanlint-0.0.1/tests/coverage/testCoverage.py +48 -0
  311. hanlint-0.0.1/tests/data/__init__.py +0 -0
  312. hanlint-0.0.1/tests/data/testLoad.py +65 -0
  313. hanlint-0.0.1/tests/document/__init__.py +0 -0
  314. hanlint-0.0.1/tests/document/testParseMarkdown.py +103 -0
  315. hanlint-0.0.1/tests/edit/testApplyFixes.py +52 -0
  316. hanlint-0.0.1/tests/fingerprint/__init__.py +0 -0
  317. hanlint-0.0.1/tests/fingerprint/testBuild.py +121 -0
  318. hanlint-0.0.1/tests/fixtures/README.md +23 -0
  319. hanlint-0.0.1/tests/fixtures/rules/cliche.json +17 -0
  320. hanlint-0.0.1/tests/fixtures/rules/confusable.json +25 -0
  321. hanlint-0.0.1/tests/fixtures/rules/connectorRepeat.json +5 -0
  322. hanlint-0.0.1/tests/fixtures/rules/countMismatch.json +13 -0
  323. hanlint-0.0.1/tests/fixtures/rules/danglingDeixis.json +13 -0
  324. hanlint-0.0.1/tests/fixtures/rules/dash.json +11 -0
  325. hanlint-0.0.1/tests/fixtures/rules/deixis.json +16 -0
  326. hanlint-0.0.1/tests/fixtures/rules/doubleNegative.json +5 -0
  327. hanlint-0.0.1/tests/fixtures/rules/doublePassive.json +5 -0
  328. hanlint-0.0.1/tests/fixtures/rules/duplicateBlock.json +11 -0
  329. hanlint-0.0.1/tests/fixtures/rules/ellipsis.json +10 -0
  330. hanlint-0.0.1/tests/fixtures/rules/emojiBullet.json +5 -0
  331. hanlint-0.0.1/tests/fixtures/rules/endingRepeat.json +12 -0
  332. hanlint-0.0.1/tests/fixtures/rules/euiChain.json +10 -0
  333. hanlint-0.0.1/tests/fixtures/rules/factListParagraph.json +11 -0
  334. hanlint-0.0.1/tests/fixtures/rules/fillerOpener.json +5 -0
  335. hanlint-0.0.1/tests/fixtures/rules/firstResultDistance.json +10 -0
  336. hanlint-0.0.1/tests/fixtures/rules/hardWord.json +22 -0
  337. hanlint-0.0.1/tests/fixtures/rules/headingSentence.json +5 -0
  338. hanlint-0.0.1/tests/fixtures/rules/headingSkip.json +11 -0
  339. hanlint-0.0.1/tests/fixtures/rules/headingUniform.json +8 -0
  340. hanlint-0.0.1/tests/fixtures/rules/imperativePeriod.json +5 -0
  341. hanlint-0.0.1/tests/fixtures/rules/inputFileSource.json +16 -0
  342. hanlint-0.0.1/tests/fixtures/rules/installImport.json +14 -0
  343. hanlint-0.0.1/tests/fixtures/rules/introLong.json +8 -0
  344. hanlint-0.0.1/tests/fixtures/rules/japaneseLoan.json +5 -0
  345. hanlint-0.0.1/tests/fixtures/rules/keywordMissing.json +10 -0
  346. hanlint-0.0.1/tests/fixtures/rules/longSentence.json +11 -0
  347. hanlint-0.0.1/tests/fixtures/rules/negationRedefine.json +5 -0
  348. hanlint-0.0.1/tests/fixtures/rules/noQuestion.json +9 -0
  349. hanlint-0.0.1/tests/fixtures/rules/nounPile.json +13 -0
  350. hanlint-0.0.1/tests/fixtures/rules/paraFragment.json +9 -0
  351. hanlint-0.0.1/tests/fixtures/rules/platformApi.json +13 -0
  352. hanlint-0.0.1/tests/fixtures/rules/promiseRecall.json +8 -0
  353. hanlint-0.0.1/tests/fixtures/rules/readerAbsent.json +9 -0
  354. hanlint-0.0.1/tests/fixtures/rules/redundantPair.json +5 -0
  355. hanlint-0.0.1/tests/fixtures/rules/sectionNoProse.json +5 -0
  356. hanlint-0.0.1/tests/fixtures/rules/sectionResult.json +12 -0
  357. hanlint-0.0.1/tests/fixtures/rules/spacing.json +32 -0
  358. hanlint-0.0.1/tests/fixtures/rules/spelling.json +46 -0
  359. hanlint-0.0.1/tests/fixtures/rules/topicBreak.json +9 -0
  360. hanlint-0.0.1/tests/fixtures/rules/translationese.json +18 -0
  361. hanlint-0.0.1/tests/gates/__init__.py +0 -0
  362. hanlint-0.0.1/tests/gates/layerContract.py +34 -0
  363. hanlint-0.0.1/tests/gates/testCommitMessage.py +79 -0
  364. hanlint-0.0.1/tests/gates/testDash.py +46 -0
  365. hanlint-0.0.1/tests/gates/testLayers.py +243 -0
  366. hanlint-0.0.1/tests/gates/testNaming.py +189 -0
  367. hanlint-0.0.1/tests/gates/testNpmData.py +12 -0
  368. hanlint-0.0.1/tests/gates/testNpmParity.py +98 -0
  369. hanlint-0.0.1/tests/gates/testSelfLint.py +39 -0
  370. hanlint-0.0.1/tests/profile/testProfile.py +69 -0
  371. hanlint-0.0.1/tests/report/__init__.py +0 -0
  372. hanlint-0.0.1/tests/report/testReports.py +127 -0
  373. hanlint-0.0.1/tests/rules/__init__.py +0 -0
  374. hanlint-0.0.1/tests/rules/testInlineControl.py +50 -0
  375. hanlint-0.0.1/tests/rules/testRegistry.py +45 -0
  376. hanlint-0.0.1/tests/rules/testRules.py +48 -0
  377. hanlint-0.0.1/vscode/.vscodeignore +2 -0
  378. hanlint-0.0.1/vscode/LICENSE +21 -0
  379. hanlint-0.0.1/vscode/README.md +9 -0
  380. hanlint-0.0.1/vscode/extension.js +122 -0
  381. hanlint-0.0.1/vscode/package.json +40 -0
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-settings.json",
3
+ "hooks": {
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Write|Edit|NotebookEdit",
7
+ "hooks": [
8
+ {
9
+ "type": "command",
10
+ "command": "python -X utf8 \"${CLAUDE_PROJECT_DIR:-.}/hooks/writeGate.py\" pre"
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,4 @@
1
+ # 줄 끝은 LF 하나다. 윈도우 체크아웃에서도 바꾸지 않는다.
2
+ * text=auto eol=lf
3
+ *.png binary
4
+ *.jpg binary
@@ -0,0 +1,22 @@
1
+ #!/bin/sh
2
+ # 커밋 메시지 규칙 집행. 판정 정본은 scripts/commitMessage.py, 규칙 문장은 skills/specs/operation/sourceControl.md.
3
+ # 이 훅은 얇게 유지한다. 판정을 sh 와 파이썬에 이중화하면 두 판정이 표류한다.
4
+ message_file="$1"
5
+
6
+ if [ -z "$message_file" ] || [ ! -f "$message_file" ]; then
7
+ echo "blocked: 커밋 메시지 파일이 없다" >&2
8
+ exit 1
9
+ fi
10
+
11
+ repo_root="$(git rev-parse --show-toplevel)"
12
+ python_bin="$repo_root/.venv/Scripts/python.exe"
13
+ if [ ! -x "$python_bin" ]; then
14
+ python_bin="$(command -v python || command -v python3)"
15
+ fi
16
+ # 실패는 닫는 방향으로 한다. 파이썬 없이 통과시키면 검사 없는 커밋이 조용히 들어온다.
17
+ if [ -z "$python_bin" ]; then
18
+ echo "blocked: 커밋 메시지 규칙을 판정할 python 이 없다" >&2
19
+ exit 1
20
+ fi
21
+
22
+ exec "$python_bin" -X utf8 "$repo_root/scripts/commitMessage.py" "$message_file"
@@ -0,0 +1,47 @@
1
+ #!/bin/sh
2
+ # staged 파일에서 em 대시, en 대시, 제어 문자, snake_case 파일 이름을 막는다.
3
+ # 규칙 정본은 skills/specs/operation/codeStyle.md 와 sourceControl.md. 검사 범위는 tests/gates/testDash.py 와 같다.
4
+ # printf 로 실제 문자를 만든다. 정규식 안에 리터럴로 쓰면 이 파일이 자기 게이트에 걸린다.
5
+
6
+ em_dash="$(printf '\342\200\224')"
7
+ en_dash="$(printf '\342\200\223')"
8
+ control="$(printf '[\001-\010\013\014\016-\037\177]')"
9
+ blocked=0
10
+
11
+ for file in $(git diff --cached --name-only --diff-filter=ACM); do
12
+ case "$file" in
13
+ *.py|*.js|*.md|*.toml|*.json|*.txt|*.sh|*.yml|*.yaml) ;;
14
+ .githooks/*) ;;
15
+ *) continue ;;
16
+ esac
17
+ if git show ":$file" | grep -q "$em_dash"; then
18
+ echo "blocked: em 대시 (U+2014) 가 있다: $file" >&2
19
+ blocked=1
20
+ fi
21
+ if git show ":$file" | grep -q "$en_dash"; then
22
+ echo "blocked: en 대시 (U+2013) 가 있다: $file" >&2
23
+ blocked=1
24
+ fi
25
+ if git show ":$file" | LC_ALL=C grep -q "$control"; then
26
+ echo "blocked: 제어 문자가 있다 (이스케이프를 날것으로 적었을 때 생긴다): $file" >&2
27
+ blocked=1
28
+ fi
29
+ # 코드 폴더의 파일 이름은 camelCase 다. 던더 파일과 conftest.py 만 파이썬이 정한 이름이라 예외. npm 의 js 도 같다.
30
+ case "$file" in
31
+ src/*.py|tests/*.py|hooks/*.py|scripts/*.py|npm/*.js)
32
+ base="$(basename "$file")"
33
+ case "$base" in
34
+ __init__.py|__main__.py|conftest.py) ;;
35
+ *_*)
36
+ echo "blocked: snake_case 파일 이름이다. camelCase 로 바꾼다: $file" >&2
37
+ blocked=1
38
+ ;;
39
+ esac
40
+ ;;
41
+ esac
42
+ done
43
+
44
+ if [ "$blocked" -ne 0 ]; then
45
+ echo "blocked: 부연은 마침표로 끊고 범위는 물결표로 쓴다. 이름은 camelCase 다 (operation.codeStyle)" >&2
46
+ exit 1
47
+ fi
@@ -0,0 +1,34 @@
1
+ #!/bin/sh
2
+ # main 이 아닌 ref 의 push 를 막고, main 을 올리기 전에 게이트를 다시 돈다.
3
+ # 커밋 메시지의 검증 줄은 주장이고 이 훅이 확인이다. 규칙 정본은 skills/specs/operation/sourceControl.md.
4
+
5
+ while read -r local_ref local_sha remote_ref remote_sha; do
6
+ case "$remote_ref" in
7
+ refs/heads/main) ;;
8
+ "") ;;
9
+ *)
10
+ echo "blocked: main 만 push 한다: $remote_ref" >&2
11
+ exit 1
12
+ ;;
13
+ esac
14
+ done
15
+
16
+ repo_root="$(git rev-parse --show-toplevel)"
17
+ if [ ! -d "$repo_root/tests" ]; then
18
+ exit 0
19
+ fi
20
+
21
+ python_bin="$repo_root/.venv/Scripts/python.exe"
22
+ if [ ! -x "$python_bin" ]; then
23
+ python_bin="$(command -v python || command -v python3)"
24
+ fi
25
+ if [ -z "$python_bin" ]; then
26
+ echo "blocked: 게이트를 돌릴 python 이 없다" >&2
27
+ exit 1
28
+ fi
29
+
30
+ if ! "$python_bin" -X utf8 -B -m pytest -q "$repo_root/tests" >/dev/null 2>&1; then
31
+ echo "blocked: pytest 가 red 다. 고치고 다시 push 한다" >&2
32
+ "$python_bin" -X utf8 -B -m pytest -q "$repo_root/tests" 2>&1 | tail -20 >&2
33
+ exit 1
34
+ fi
@@ -0,0 +1,60 @@
1
+ # 게이트 전부. push 와 PR 에서 돌고 publish.yml 이 같은 것을 재사용한다 (복사하면 조용히 뒤처진다).
2
+ # 정본 절차는 skills/specs/operation/verify.md (여기는 집행만).
3
+ name: ci
4
+
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+ workflow_call:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ defaults:
15
+ run:
16
+ shell: bash
17
+
18
+ jobs:
19
+ gates:
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ include:
24
+ - os: ubuntu-latest
25
+ python: "3.11"
26
+ - os: ubuntu-latest
27
+ python: "3.13"
28
+ - os: windows-latest
29
+ python: "3.12"
30
+ runs-on: ${{ matrix.os }}
31
+ steps:
32
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
33
+ - uses: actions/setup-python@v6
34
+ with:
35
+ python-version: ${{ matrix.python }}
36
+ # npm 동등성 게이트 (testNpmParity) 와 npm 자체 테스트에 node 가 필요하다.
37
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
38
+ with:
39
+ node-version: 22
40
+ - run: python -X utf8 -B -m pip install --quiet ".[dev]"
41
+ - run: python -X utf8 -B scripts/exportData.py --check
42
+ - run: python -X utf8 -B -m ruff check src tests hooks scripts
43
+ - run: python -X utf8 -B -m ruff format --check src tests hooks scripts
44
+ - run: python -X utf8 -B -m pytest -q
45
+ - run: node --test "npm/test/*.test.js"
46
+ - run: python -X utf8 -B hooks/tests/checkWriteGate.py
47
+
48
+ # 루트 action.yml 을 자기 문서에 실제로 써서 액션 표면이 살아 있는지 본다.
49
+ action:
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
53
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
54
+ with:
55
+ node-version: 22
56
+ - uses: ./
57
+ with:
58
+ files: README.md npm/README.md
59
+ errors-only: "true"
60
+ command: node npm/bin/hanlint.js
@@ -0,0 +1,151 @@
1
+ # 배포: 버전 태그 (v0.0.x) 푸시가 트리거. PyPI 와 npm 에 같은 버전을 같은 날 올린다.
2
+ # 정본 절차는 skills/specs/operation/release.md (여기는 집행만).
3
+ #
4
+ # 인증은 둘 다 Trusted Publishing (OIDC) 이다. 장수 토큰 시크릿을 두지 않는다. 러너가 GitHub OIDC 로
5
+ # 자기 신원을 증명하고 레지스트리가 단기 자격을 발급한다. 사전 1회 설정:
6
+ # PyPI: pypi.org > Account > Publishing > Add pending publisher
7
+ # (project hanlint, owner eddmpython, repository hanlint, workflow publish.yml, environment pypi)
8
+ # GitHub: Settings > Environments > pypi
9
+ # npm: 첫 게시 (0.0.1) 는 운영자 기계에서 npm publish 로 한다. 패키지가 생긴 뒤
10
+ # npmjs.com > hanlint > Settings > Trusted Publisher > GitHub Actions
11
+ # (repository eddmpython/hanlint, workflow publish.yml) 을 등록하면 다음 태그부터 여기서 올라간다.
12
+ name: publish
13
+
14
+ on:
15
+ push:
16
+ tags: ["v*"]
17
+ # 수동 실행. 태그가 워크플로보다 먼저 나갔거나 게시가 실패해 재시도할 때. 버전은 체크아웃한 ref 의 것이다.
18
+ workflow_dispatch:
19
+
20
+ permissions:
21
+ contents: read
22
+
23
+ defaults:
24
+ run:
25
+ shell: bash
26
+
27
+ jobs:
28
+ # 게시는 되돌릴 수 없다 (버전 번호 재사용 불가). ci 의 게이트 전부를 통과해야 한다.
29
+ gates:
30
+ permissions:
31
+ contents: read
32
+ uses: ./.github/workflows/ci.yml
33
+
34
+ build:
35
+ needs: gates
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
39
+ with:
40
+ fetch-depth: 0
41
+ - uses: actions/setup-python@v6
42
+ with:
43
+ python-version: "3.12"
44
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
45
+ with:
46
+ node-version: 22
47
+
48
+ # 릴리즈 정책: pyproject.toml, npm/package.json, 태그는 항상 같은 값이다. 어긋나면 여기서 멈춘다.
49
+ - name: 세 버전 일치 검증
50
+ run: |
51
+ set -eu
52
+ pyVersion="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
53
+ npmVersion="$(node -p "require('./npm/package.json').version")"
54
+ echo "pyproject=$pyVersion package.json=$npmVersion"
55
+ test "$pyVersion" = "$npmVersion"
56
+ case "$GITHUB_REF" in
57
+ refs/tags/*)
58
+ echo "tag=${GITHUB_REF_NAME#v}"
59
+ test "${GITHUB_REF_NAME#v}" = "$pyVersion"
60
+ ;;
61
+ *)
62
+ echo "dispatch: HEAD 태그 목록:"
63
+ git tag --points-at HEAD
64
+ git tag --points-at HEAD | grep -Fxq "v$pyVersion"
65
+ ;;
66
+ esac
67
+
68
+ - run: python -X utf8 -B -m pip install --quiet build
69
+ - run: python -X utf8 -B -m build
70
+
71
+ # PyPI 에 올라갈 바로 그 wheel 을 격리 venv 에 설치해 본다. 의존성 0, 데이터 동봉, 자기 문서 통과.
72
+ - name: wheel 격리 설치 검증
73
+ run: |
74
+ set -eu
75
+ python -m venv "$RUNNER_TEMP/smoke"
76
+ "$RUNNER_TEMP/smoke/bin/pip" install --quiet dist/*.whl
77
+ "$RUNNER_TEMP/smoke/bin/pip" show hanlint | grep -E '^Requires: ?$'
78
+ "$RUNNER_TEMP/smoke/bin/hanlint" --version
79
+ "$RUNNER_TEMP/smoke/bin/hanlint" README.md
80
+
81
+ - uses: actions/upload-artifact@v7
82
+ with:
83
+ name: dist
84
+ path: dist
85
+
86
+ publish-pypi:
87
+ needs: build
88
+ runs-on: ubuntu-latest
89
+ environment: pypi
90
+ permissions:
91
+ contents: read
92
+ id-token: write # PyPI Trusted Publishing OIDC 는 게시 job 에만 연다.
93
+ steps:
94
+ - uses: actions/download-artifact@v8
95
+ with:
96
+ name: dist
97
+ path: dist
98
+ # 재실행 (workflow_dispatch) 때 이미 올라간 버전이면 실패하지 않고 지나간다.
99
+ - uses: pypa/gh-action-pypi-publish@release/v1
100
+ with:
101
+ skip-existing: true
102
+
103
+ publish-npm:
104
+ needs: build
105
+ runs-on: ubuntu-latest
106
+ permissions:
107
+ contents: read
108
+ id-token: write # npm Trusted Publishing OIDC 는 게시 job 에만 연다.
109
+ steps:
110
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
111
+ - uses: actions/setup-python@v6
112
+ with:
113
+ python-version: "3.12"
114
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
115
+ with:
116
+ node-version: 22.19.0
117
+ registry-url: https://registry.npmjs.org
118
+ # trusted publishing 은 npm >= 11.5.1 에서만 동작한다 (node 22 번들은 10.x).
119
+ - run: npm install -g npm@11.19.0
120
+ # npm/data 는 파이썬 정본의 투영이다. 낡은 투영을 올리지 않는다.
121
+ - run: python -X utf8 -B scripts/exportData.py --check
122
+ - run: node --test "npm/test/*.test.js"
123
+ # 첫 판 (0.0.1) 은 운영자 기계에서 올라간다 (Trusted Publisher 등록에 패키지가 먼저 있어야 한다).
124
+ # 그 뒤 같은 태그를 밀어도 이미 있는 버전은 건너뛴다. PyPI 의 skip-existing 과 같은 뜻이다.
125
+ - name: npm 게시 (이미 있는 버전은 건너뛴다)
126
+ working-directory: npm
127
+ run: |
128
+ set -eu
129
+ version="$(node -p "require('./package.json').version")"
130
+ if npm view "hanlint@$version" version >/dev/null 2>&1; then
131
+ echo "hanlint@$version 은 이미 npm 에 있다. 건너뛴다"
132
+ else
133
+ npm publish
134
+ fi
135
+
136
+ release:
137
+ needs: [publish-pypi, publish-npm]
138
+ runs-on: ubuntu-latest
139
+ permissions:
140
+ contents: write
141
+ steps:
142
+ - uses: actions/download-artifact@v8
143
+ with:
144
+ name: dist
145
+ path: dist
146
+ - uses: softprops/action-gh-release@v3
147
+ with:
148
+ generate_release_notes: true
149
+ files: |
150
+ dist/*.whl
151
+ dist/*.tar.gz
@@ -0,0 +1,44 @@
1
+ # 로컬 하네스. 공개 저장소에 올리지 않는다. 경로를 루트에 고정한다.
2
+ /CLAUDE.md
3
+ /AGENTS.md
4
+ # .claude/settings.json 만 예외다. 훅 등록은 로컬 상태가 아니라 이 저장소의 강행 계약이라 클론에도 따라간다.
5
+ .claude/*
6
+ !.claude/settings.json
7
+ .codex/
8
+ .cursor/
9
+ .idea/
10
+ .vscode/
11
+ *.local.json
12
+
13
+ # 끝나지 않은 이니셔티브. 폴더 하나가 기획 하나다. 로컬 전용
14
+ mainPlan/
15
+
16
+ # 세션 간 메모리. 로컬 전용
17
+ /memory/
18
+
19
+ # 비밀
20
+ .env
21
+ .env.*
22
+ !.env.example
23
+ *.pem
24
+ *.key
25
+
26
+ # 파이썬
27
+ .venv/
28
+ __pycache__/
29
+ *.py[cod]
30
+ *.egg-info/
31
+ .pytest_cache/
32
+ .ruff_cache/
33
+ .mypy_cache/
34
+ dist/
35
+ build/
36
+
37
+ # npm. 의존성 0 이라 node_modules 는 원래 없다. pack 산출물도 저장소 밖으로
38
+ node_modules/
39
+ *.tgz
40
+
41
+ # 운영체제
42
+ .DS_Store
43
+ Thumbs.db
44
+ Desktop.ini
@@ -0,0 +1,13 @@
1
+ # pre-commit 표면. 소비 저장소의 .pre-commit-config.yaml 에서 이 저장소를 repo 로 가리키면 커밋마다 마크다운을 검사한다.
2
+ #
3
+ # - repo: https://github.com/eddmpython/hanlint
4
+ # rev: v0.0.1
5
+ # hooks:
6
+ # - id: hanlint
7
+ - id: hanlint
8
+ name: hanlint
9
+ description: 한국어 마크다운에서 반복되는 결함을 결정적으로 잡는다
10
+ entry: hanlint
11
+ language: python
12
+ types: [markdown]
13
+ args: [--errors-only, --quiet]
@@ -0,0 +1,36 @@
1
+ # Changelog
2
+
3
+ hanlint 의 눈에 띄는 변경을 이 파일에 적는다. 형식은 [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) 를
4
+ 따르고 버전은 [Semantic Versioning](https://semver.org/spec/v2.0.0.html) 이다. 변경은 [Unreleased] 에 쌓고
5
+ 릴리즈 커밋에서 버전 절로 내린다.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.0.1] - 2026-08-27
10
+
11
+ 첫 공개판이다. 한국어 마크다운에서 반복되는 결함을 결정적으로 잡는 린터이자 글쓴이의 글짓기 도구다.
12
+ 의존성 0 의 파이썬이 정본이고 npm 판은 같은 규칙, 같은 fixture 로 글자 단위로 같은 출력을 낸다
13
+ (testNpmParity 게이트). 점수와 등급은 내지 않는다. 세어서 확정할 수 있는 결함만 자리와 이유와 함께 짚는다.
14
+
15
+ ### Added
16
+
17
+ - 규칙 42개. sentence (상투어, 번역투, 겹말, 일본어투, 지시어, 이중 피동, 관형격 연쇄, 명사 쌓기, 종결어미
18
+ 반복, 긴 문장, 어려운 말 등), paragraph (조각난 문단, 사실 나열, 화제 끊김), structure (제목 어미 통일,
19
+ 헤딩 건너뜀, 설명글 없는 절, 확인할 결과 없는 절), document (수 약속 불일치, 미회수 예고, 독자 부재),
20
+ orthography (맞춤법 27, 띄어쓰기 12, 헷갈리는 말 12항목. 국립국어원 조항 근거), code (읽는 파일의 출처,
21
+ 설치 줄과 import 대조, 중복 블록, 첫 실행까지 거리, 플랫폼 전용 API)
22
+ - `hanlint fix`: 고친 표기가 확정된 지적을 원문에 적용하고 줄마다 보여 준다. `--dry-run` 은 보여 주기만 한다
23
+ - `hanlint audit` 와 `hanlint map --format html`: 지문 지도 (색이 구멍 종류) 와 문장 길이, 종결어미, 어휘,
24
+ 접속사 분포
25
+ - `hanlint print --layer`: 문장, 문단, 절, 글 지문을 JSON 으로
26
+ - `hanlint profile`: 승인된 글들의 문체 분포를 만들고 새 글의 편차 구간을 짚는다
27
+ - `hanlint coverage`: 사람 평가자 지적과 hanlint 지적의 자리 겹침 비율과 못 집은 유형 목록
28
+ - `hanlint diff`: 두 초안의 짜임, 리듬, 규칙별 지적 수의 변화
29
+ - 입출력: stdin (`-`, `--path`), `--format compact|json|github`, `--errors-only`, `--severity`, 설정 파일
30
+ `hanlint.toml` 과 인라인 제어 주석. 따옴표와 백틱 안은 인용으로 보고 사전 규칙이 건너뛴다
31
+ - 표면: GitHub Action (`action.yml`), pre-commit 훅 (`.pre-commit-hooks.yaml`), VS Code 확장 (`vscode/`,
32
+ 저장 시 진단과 quick fix), AI 스킬 (`skills/use-hanlint/SKILL.md`)
33
+ - 형태소 정밀 모드 (`pip install hanlint[kiwi]`) 는 선택이고 기본은 표층 근사다
34
+
35
+ [Unreleased]: https://github.com/eddmpython/hanlint/compare/v0.0.1...HEAD
36
+ [0.0.1]: https://github.com/eddmpython/hanlint/releases/tag/v0.0.1
hanlint-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 eddmpython
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
hanlint-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,143 @@
1
+ Metadata-Version: 2.5
2
+ Name: hanlint
3
+ Version: 0.0.1
4
+ Summary: 한국어 글에서 AI 와 사람이 반복해서 어기는 결함을 결정적으로 잡는 린터
5
+ Project-URL: Homepage, https://github.com/eddmpython/hanlint
6
+ Project-URL: Issues, https://github.com/eddmpython/hanlint/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: fingerprint,korean,lint,readability,writing
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Natural Language :: Korean
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Topic :: Text Processing :: Linguistic
15
+ Requires-Python: >=3.11
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=8; extra == 'dev'
18
+ Requires-Dist: ruff>=0.5; extra == 'dev'
19
+ Provides-Extra: kiwi
20
+ Requires-Dist: kiwipiepy>=0.20; extra == 'kiwi'
21
+ Description-Content-Type: text/markdown
22
+
23
+ # hanlint
24
+
25
+ 한국어 글에서 반복되는 결함을 결정적으로 잡는 린터이자 글쓴이의 글짓기 도구다. 의존성이 없다.
26
+
27
+ 번역투와 상투어, 자주 틀리는 맞춤법과 띄어쓰기 (`됬`, `않 해`, `할수 있다`), 로서/로써 같은 헷갈리는 말,
28
+ 지시어와 명사 쌓기, 조각난 문단, 도입과 결말의 개수 불일치, 그리고 코드 튜토리얼이라면 만들지 않은
29
+ 파일을 읽는 자리와 설치 줄에 없는 import 까지 집는다. 항목마다 국립국어원 조항이나 실측 근거가 붙어
30
+ 있고 고친 표기가 확정된 자리는 `hanlint fix` 가 바로 바꾼다.
31
+
32
+ 글의 좋고 나쁨은 판정하지 않는다. 세어서 확정할 수 있는 결함만 집어서 자리와 이유를 돌려준다.
33
+ 좋은 글인지는 사람과 LLM 평가자가 그 위에서 판단한다. hanlint 는 그 판단이 셈에 시간을 쓰지
34
+ 않게 바닥을 깔아 주는 도구다.
35
+
36
+ ## 설치
37
+
38
+ pip 하나면 된다. 형태소 분석기로 더 정밀하게 보려면 `hanlint[kiwi]` 를 따로 받는다. Node 쪽은 npm 에
39
+ 같은 이름으로 있고 설치 없이 `npx hanlint 글.md` 로 바로 돈다.
40
+
41
+ ```powershell
42
+ pip install hanlint
43
+ pip install hanlint[kiwi]
44
+ npx hanlint 글.md
45
+ ```
46
+
47
+ 두 구현은 같은 규칙, 같은 fixture, 같은 출력이다. 지문 지도와 프로파일과 kiwi 정밀 모드는 파이썬 쪽에만 있다.
48
+
49
+ ## 사용
50
+
51
+ 마크다운 파일 하나를 주면 된다.
52
+
53
+ ```powershell
54
+ hanlint 글.md
55
+ ```
56
+
57
+ 지적마다 규칙 이름, 줄 번호, 인용 문장, 왜 문제인지가 붙는다. 기계가 고칠 수 있는 것은 고친
58
+ 문장이 같이 온다. 첫 줄은 어느 설정을 읽었는지다. 종료 코드는 지적이 없으면 0, error 가 있으면 1 이라
59
+ 발행 게이트에 그대로 물린다.
60
+
61
+ ```text
62
+ 설정: hanlint.toml
63
+
64
+ 글.md:12 [doublePassive] 결과가 저장되어집니다.
65
+ 되어지 는 이중 피동이다. 피동 하나로 줄인다
66
+ 고친 뒤: 결과가 저장됩니다.
67
+ ```
68
+
69
+ 기계가 확실히 고칠 수 있는 자리는 `hanlint fix 글.md` 가 원문에 적용하고 무엇을 바꿨는지 줄마다 보여 준다.
70
+ `--dry-run` 은 보여 주기만 한다. 다른 명령은 표에 있다.
71
+
72
+ | 명령 | 무엇 |
73
+ |---|---|
74
+ | `hanlint 글.md --format compact --errors-only` | 한 줄에 지적 하나, error 만. AI 와 스크립트가 쓴다 |
75
+ | `hanlint - --path 초안.md` | stdin 으로 넣은 글을 그 이름으로 검사한다 |
76
+ | `hanlint 글.md --format json` | 기계가 읽는 꼴. `github` 은 GitHub Actions 주석 |
77
+ | `hanlint fix 글.md` | 번역투, 명령형 뒤 마침표, 이중 부정처럼 확실한 자리를 고친다 |
78
+ | `hanlint audit 글.md` | 지문 지도와 분포. 색이 있는 자리가 구멍이다 |
79
+ | `hanlint map 글.md --format html` | 지도를 단일 HTML 로 |
80
+ | `hanlint print 글.md --layer sentences` | 문장, 문단, 절, 글의 지문을 JSON 으로. 층 하나만 고를 수 있다 |
81
+ | `hanlint rules` | 규칙 목록 |
82
+ | `hanlint explain <규칙>` | 규칙의 기술서. 왜, 어디서, 고치기, 안 잡는 것 |
83
+ | `hanlint init` | 주석 달린 `hanlint.toml` |
84
+ | `hanlint profile build 글들/` | 승인된 글의 문체 분포. `--profile` 로 새 글을 견준다 |
85
+ | `hanlint diff 전.md 후.md` | 두 초안의 짜임, 리듬, 지적 수의 변화 |
86
+ | `hanlint coverage review.json 글.md` | 사람 평가자의 지적 가운데 hanlint 가 같은 자리를 집은 비율 |
87
+
88
+ 백틱과 따옴표 안은 인용이라 사전 규칙과 지시어 규칙이 건너뛴다. 상투어를 인용하며 설명하는 글이 잡히지 않는다.
89
+
90
+ ## 규칙을 끄기
91
+
92
+ 글 전체에서 끄려면 `hanlint init` 이 만드는 `hanlint.toml` 의 `disable` 에 이름을 넣는다. 한 자리에서만
93
+ 끄려면 마크다운 주석을 쓴다. 상투어를 인용하는 문단처럼 규칙이 맞지만 그 자리만 예외일 때다.
94
+
95
+ ```markdown
96
+ <!-- hanlint-disable cliche -->
97
+
98
+ AI 가 자주 쓰는 표현은 `핵심은`, `결국 중요한 것은` 처럼 눈에 띄는 것부터 지웁니다.
99
+
100
+ <!-- hanlint-enable cliche -->
101
+ ```
102
+
103
+ `hanlint-disable-next` 는 다음 블록 하나만 끈다. 규칙 이름을 안 적으면 전부 끈다.
104
+
105
+ ## 파이썬에서
106
+
107
+ 같은 일을 함수로 한다.
108
+
109
+ ```python
110
+ from hanlint import lintText
111
+
112
+ for finding in lintText(text):
113
+ print(finding.line, finding.rule, finding.why)
114
+ ```
115
+
116
+ `lintFile`, `auditText`, `fingerprint` 도 같은 자리에 있다.
117
+
118
+ ## 커밋과 PR 에서
119
+
120
+ pre-commit 훅과 GitHub Action 이 저장소 루트에 있다. 훅은 `.pre-commit-config.yaml` 에서 이 저장소를
121
+ 가리키면 되고, 액션은 지적을 PR 의 줄 주석으로 단다. VS Code 확장 (`vscode/`) 은 저장할 때 검사해
122
+ 밑줄로 보여 주고 확정된 자리는 quick fix 로 고친다.
123
+
124
+ ```yaml
125
+ - uses: eddmpython/hanlint@main
126
+ with:
127
+ files: docs/글.md
128
+ errors-only: "true"
129
+ ```
130
+
131
+ ## AI 에게 시키기
132
+
133
+ `skills/use-hanlint/SKILL.md` 를 에이전트의 스킬 폴더에 두면 AI 가 글을 쓴 직후 스스로 검사하고
134
+ 지적이 0 이 될 때까지 고친다.
135
+
136
+ ## 무엇을 잡나
137
+
138
+ 잡는 것과 잡지 않는 것은 [skills/specs/start/product.md](skills/specs/start/product.md) 에 있다.
139
+ 규칙 하나는 파일 하나이고 자기 기술서를 docstring 으로 든다.
140
+
141
+ ## 라이선스
142
+
143
+ MIT 다.