chaffjs 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (602) hide show
  1. package/README.md +27 -2
  2. package/dist/address-chain.d.ts +4 -1
  3. package/dist/address-chain.d.ts.map +1 -1
  4. package/dist/address-chain.js +6 -3
  5. package/dist/address-chain.js.map +1 -1
  6. package/dist/backends/anthropic.d.ts +2 -1
  7. package/dist/backends/anthropic.d.ts.map +1 -1
  8. package/dist/backends/anthropic.js +4 -1
  9. package/dist/backends/anthropic.js.map +1 -1
  10. package/dist/backends/openai.d.ts +2 -1
  11. package/dist/backends/openai.d.ts.map +1 -1
  12. package/dist/backends/openai.js +1 -1
  13. package/dist/backends/openai.js.map +1 -1
  14. package/dist/backends/types.d.ts +2 -1
  15. package/dist/backends/types.d.ts.map +1 -1
  16. package/dist/backends/types.js +1 -1
  17. package/dist/backends/types.js.map +1 -1
  18. package/dist/baseline.js +1 -1
  19. package/dist/baseline.js.map +1 -1
  20. package/dist/calendar-number.d.ts +26 -0
  21. package/dist/calendar-number.d.ts.map +1 -0
  22. package/dist/calendar-number.js +51 -0
  23. package/dist/calendar-number.js.map +1 -0
  24. package/dist/cli-args.d.ts +0 -5
  25. package/dist/cli-args.d.ts.map +1 -1
  26. package/dist/cli-args.js +2 -2
  27. package/dist/cli-args.js.map +1 -1
  28. package/dist/cli-text.d.ts +10 -3
  29. package/dist/cli-text.d.ts.map +1 -1
  30. package/dist/cli-text.js +33 -14
  31. package/dist/cli-text.js.map +1 -1
  32. package/dist/cli.d.ts.map +1 -1
  33. package/dist/cli.js +53 -47
  34. package/dist/cli.js.map +1 -1
  35. package/dist/commands/cite.d.ts.map +1 -1
  36. package/dist/commands/cite.js +4 -4
  37. package/dist/commands/cite.js.map +1 -1
  38. package/dist/commands/eval.d.ts +4 -0
  39. package/dist/commands/eval.d.ts.map +1 -1
  40. package/dist/commands/eval.js +21 -7
  41. package/dist/commands/eval.js.map +1 -1
  42. package/dist/commands/feedback.d.ts.map +1 -1
  43. package/dist/commands/feedback.js +2 -1
  44. package/dist/commands/feedback.js.map +1 -1
  45. package/dist/commands/init-ask.d.ts +19 -0
  46. package/dist/commands/init-ask.d.ts.map +1 -0
  47. package/dist/commands/init-ask.js +43 -0
  48. package/dist/commands/init-ask.js.map +1 -0
  49. package/dist/commands/test.d.ts +8 -0
  50. package/dist/commands/test.d.ts.map +1 -1
  51. package/dist/commands/test.js +73 -40
  52. package/dist/commands/test.js.map +1 -1
  53. package/dist/commands/tree.d.ts +1 -8
  54. package/dist/commands/tree.d.ts.map +1 -1
  55. package/dist/commands/tree.js +9 -7
  56. package/dist/commands/tree.js.map +1 -1
  57. package/dist/config/by-path.d.ts.map +1 -1
  58. package/dist/config/by-path.js +10 -9
  59. package/dist/config/by-path.js.map +1 -1
  60. package/dist/config/load.d.ts +4 -0
  61. package/dist/config/load.d.ts.map +1 -1
  62. package/dist/config/load.js +19 -0
  63. package/dist/config/load.js.map +1 -1
  64. package/dist/config/name-problems.d.ts +5 -0
  65. package/dist/config/name-problems.d.ts.map +1 -0
  66. package/dist/config/name-problems.js +11 -0
  67. package/dist/config/name-problems.js.map +1 -0
  68. package/dist/date-stamp.js +1 -1
  69. package/dist/date-stamp.js.map +1 -1
  70. package/dist/detectors/acronym-compound.d.ts +8 -0
  71. package/dist/detectors/acronym-compound.d.ts.map +1 -0
  72. package/dist/detectors/acronym-compound.js +8 -0
  73. package/dist/detectors/acronym-compound.js.map +1 -0
  74. package/dist/detectors/acronym-context.d.ts +4 -0
  75. package/dist/detectors/acronym-context.d.ts.map +1 -1
  76. package/dist/detectors/acronym-context.js +17 -1
  77. package/dist/detectors/acronym-context.js.map +1 -1
  78. package/dist/detectors/acronym-expansion.d.ts +10 -0
  79. package/dist/detectors/acronym-expansion.d.ts.map +1 -1
  80. package/dist/detectors/acronym-expansion.js +63 -5
  81. package/dist/detectors/acronym-expansion.js.map +1 -1
  82. package/dist/detectors/char-shape.d.ts.map +1 -1
  83. package/dist/detectors/char-shape.js +47 -15
  84. package/dist/detectors/char-shape.js.map +1 -1
  85. package/dist/detectors/citation-key.d.ts +13 -0
  86. package/dist/detectors/citation-key.d.ts.map +1 -0
  87. package/dist/detectors/citation-key.js +9 -0
  88. package/dist/detectors/citation-key.js.map +1 -0
  89. package/dist/detectors/cited-name.d.ts +11 -0
  90. package/dist/detectors/cited-name.d.ts.map +1 -0
  91. package/dist/detectors/cited-name.js +15 -0
  92. package/dist/detectors/cited-name.js.map +1 -0
  93. package/dist/detectors/concrete-evidence.d.ts +7 -0
  94. package/dist/detectors/concrete-evidence.d.ts.map +1 -0
  95. package/dist/detectors/concrete-evidence.js +21 -0
  96. package/dist/detectors/concrete-evidence.js.map +1 -0
  97. package/dist/detectors/counter-edge.d.ts +8 -0
  98. package/dist/detectors/counter-edge.d.ts.map +1 -0
  99. package/dist/detectors/counter-edge.js +27 -0
  100. package/dist/detectors/counter-edge.js.map +1 -0
  101. package/dist/detectors/dotted-name.d.ts +13 -0
  102. package/dist/detectors/dotted-name.d.ts.map +1 -0
  103. package/dist/detectors/dotted-name.js +10 -0
  104. package/dist/detectors/dotted-name.js.map +1 -0
  105. package/dist/detectors/doubled-word.d.ts +6 -1
  106. package/dist/detectors/doubled-word.d.ts.map +1 -1
  107. package/dist/detectors/doubled-word.js +22 -2
  108. package/dist/detectors/doubled-word.js.map +1 -1
  109. package/dist/detectors/en-shape.d.ts +1 -1
  110. package/dist/detectors/en-shape.d.ts.map +1 -1
  111. package/dist/detectors/en-shape.js +112 -29
  112. package/dist/detectors/en-shape.js.map +1 -1
  113. package/dist/detectors/enumerated-runs.d.ts.map +1 -1
  114. package/dist/detectors/enumerated-runs.js +4 -2
  115. package/dist/detectors/enumerated-runs.js.map +1 -1
  116. package/dist/detectors/gram-place.d.ts +0 -4
  117. package/dist/detectors/gram-place.d.ts.map +1 -1
  118. package/dist/detectors/gram-place.js +7 -2
  119. package/dist/detectors/gram-place.js.map +1 -1
  120. package/dist/detectors/gram-predicate.d.ts +7 -0
  121. package/dist/detectors/gram-predicate.d.ts.map +1 -0
  122. package/dist/detectors/gram-predicate.js +44 -0
  123. package/dist/detectors/gram-predicate.js.map +1 -0
  124. package/dist/detectors/heading-case.d.ts +21 -0
  125. package/dist/detectors/heading-case.d.ts.map +1 -0
  126. package/dist/detectors/heading-case.js +40 -0
  127. package/dist/detectors/heading-case.js.map +1 -0
  128. package/dist/detectors/heading-echo.d.ts.map +1 -1
  129. package/dist/detectors/heading-echo.js +4 -1
  130. package/dist/detectors/heading-echo.js.map +1 -1
  131. package/dist/detectors/index.d.ts.map +1 -1
  132. package/dist/detectors/index.js +4 -3
  133. package/dist/detectors/index.js.map +1 -1
  134. package/dist/detectors/lettered-index.d.ts +9 -0
  135. package/dist/detectors/lettered-index.d.ts.map +1 -0
  136. package/dist/detectors/lettered-index.js +54 -0
  137. package/dist/detectors/lettered-index.js.map +1 -0
  138. package/dist/detectors/lexicon.d.ts +5 -1
  139. package/dist/detectors/lexicon.d.ts.map +1 -1
  140. package/dist/detectors/lexicon.js +51 -5
  141. package/dist/detectors/lexicon.js.map +1 -1
  142. package/dist/detectors/name-numeral.d.ts +16 -0
  143. package/dist/detectors/name-numeral.d.ts.map +1 -0
  144. package/dist/detectors/name-numeral.js +30 -0
  145. package/dist/detectors/name-numeral.js.map +1 -0
  146. package/dist/detectors/name-part.d.ts +8 -0
  147. package/dist/detectors/name-part.d.ts.map +1 -0
  148. package/dist/detectors/name-part.js +20 -0
  149. package/dist/detectors/name-part.js.map +1 -0
  150. package/dist/detectors/name-run.d.ts +8 -0
  151. package/dist/detectors/name-run.d.ts.map +1 -0
  152. package/dist/detectors/name-run.js +13 -0
  153. package/dist/detectors/name-run.js.map +1 -0
  154. package/dist/detectors/orthography.d.ts.map +1 -1
  155. package/dist/detectors/orthography.js +35 -7
  156. package/dist/detectors/orthography.js.map +1 -1
  157. package/dist/detectors/phrase-match.d.ts +3 -1
  158. package/dist/detectors/phrase-match.d.ts.map +1 -1
  159. package/dist/detectors/phrase-match.js +20 -15
  160. package/dist/detectors/phrase-match.js.map +1 -1
  161. package/dist/detectors/placeholder.d.ts +14 -0
  162. package/dist/detectors/placeholder.d.ts.map +1 -0
  163. package/dist/detectors/placeholder.js +24 -0
  164. package/dist/detectors/placeholder.js.map +1 -0
  165. package/dist/detectors/quoted-variant.d.ts +7 -0
  166. package/dist/detectors/quoted-variant.d.ts.map +1 -0
  167. package/dist/detectors/quoted-variant.js +18 -0
  168. package/dist/detectors/quoted-variant.js.map +1 -0
  169. package/dist/detectors/repeated-head.d.ts.map +1 -1
  170. package/dist/detectors/repeated-head.js +5 -4
  171. package/dist/detectors/repeated-head.js.map +1 -1
  172. package/dist/detectors/series-label.d.ts +13 -0
  173. package/dist/detectors/series-label.d.ts.map +1 -0
  174. package/dist/detectors/series-label.js +26 -0
  175. package/dist/detectors/series-label.js.map +1 -0
  176. package/dist/detectors/signals.d.ts.map +1 -1
  177. package/dist/detectors/signals.js +44 -29
  178. package/dist/detectors/signals.js.map +1 -1
  179. package/dist/detectors/stacked-hedge.d.ts +23 -0
  180. package/dist/detectors/stacked-hedge.d.ts.map +1 -0
  181. package/dist/detectors/stacked-hedge.js +64 -0
  182. package/dist/detectors/stacked-hedge.js.map +1 -0
  183. package/dist/detectors/structure-tree.js +1 -1
  184. package/dist/detectors/structure-tree.js.map +1 -1
  185. package/dist/detectors/superlative-amount.d.ts +9 -0
  186. package/dist/detectors/superlative-amount.d.ts.map +1 -0
  187. package/dist/detectors/superlative-amount.js +20 -0
  188. package/dist/detectors/superlative-amount.js.map +1 -0
  189. package/dist/detectors/superlative-clause.d.ts +18 -0
  190. package/dist/detectors/superlative-clause.d.ts.map +1 -0
  191. package/dist/detectors/superlative-clause.js +61 -0
  192. package/dist/detectors/superlative-clause.js.map +1 -0
  193. package/dist/detectors/superlative-name.d.ts +9 -0
  194. package/dist/detectors/superlative-name.d.ts.map +1 -0
  195. package/dist/detectors/superlative-name.js +24 -0
  196. package/dist/detectors/superlative-name.js.map +1 -0
  197. package/dist/detectors/superlative-scope.d.ts +6 -0
  198. package/dist/detectors/superlative-scope.d.ts.map +1 -1
  199. package/dist/detectors/superlative-scope.js +3 -3
  200. package/dist/detectors/superlative-scope.js.map +1 -1
  201. package/dist/detectors/team.d.ts +0 -6
  202. package/dist/detectors/team.d.ts.map +1 -1
  203. package/dist/detectors/team.js +17 -1
  204. package/dist/detectors/team.js.map +1 -1
  205. package/dist/detectors/token-shape.d.ts.map +1 -1
  206. package/dist/detectors/token-shape.js +15 -5
  207. package/dist/detectors/token-shape.js.map +1 -1
  208. package/dist/document.d.ts +3 -0
  209. package/dist/document.d.ts.map +1 -1
  210. package/dist/document.js +28 -7
  211. package/dist/document.js.map +1 -1
  212. package/dist/eval.js +1 -1
  213. package/dist/eval.js.map +1 -1
  214. package/dist/feedback/draft.js +1 -1
  215. package/dist/feedback/draft.js.map +1 -1
  216. package/dist/feedback/excerpt.d.ts +0 -1
  217. package/dist/feedback/excerpt.d.ts.map +1 -1
  218. package/dist/feedback/excerpt.js +2 -2
  219. package/dist/feedback/excerpt.js.map +1 -1
  220. package/dist/file-header.d.ts +20 -0
  221. package/dist/file-header.d.ts.map +1 -0
  222. package/dist/file-header.js +21 -0
  223. package/dist/file-header.js.map +1 -0
  224. package/dist/files.js +3 -3
  225. package/dist/files.js.map +1 -1
  226. package/dist/genre-check.d.ts +34 -0
  227. package/dist/genre-check.d.ts.map +1 -0
  228. package/dist/genre-check.js +21 -0
  229. package/dist/genre-check.js.map +1 -0
  230. package/dist/genre-load.d.ts +6 -0
  231. package/dist/genre-load.d.ts.map +1 -0
  232. package/dist/genre-load.js +22 -0
  233. package/dist/genre-load.js.map +1 -0
  234. package/dist/genre-parse.d.ts +40 -0
  235. package/dist/genre-parse.d.ts.map +1 -0
  236. package/dist/genre-parse.js +104 -0
  237. package/dist/genre-parse.js.map +1 -0
  238. package/dist/genre-suggest.d.ts +15 -0
  239. package/dist/genre-suggest.d.ts.map +1 -0
  240. package/dist/genre-suggest.js +26 -0
  241. package/dist/genre-suggest.js.map +1 -0
  242. package/dist/genre.d.ts +8 -1
  243. package/dist/genre.d.ts.map +1 -1
  244. package/dist/genre.js +19 -5
  245. package/dist/genre.js.map +1 -1
  246. package/dist/init-choice.d.ts +12 -0
  247. package/dist/init-choice.d.ts.map +1 -0
  248. package/dist/init-choice.js +41 -0
  249. package/dist/init-choice.js.map +1 -0
  250. package/dist/init.d.ts +0 -1
  251. package/dist/init.d.ts.map +1 -1
  252. package/dist/init.js +16 -17
  253. package/dist/init.js.map +1 -1
  254. package/dist/joined-view.d.ts +25 -0
  255. package/dist/joined-view.d.ts.map +1 -0
  256. package/dist/joined-view.js +76 -0
  257. package/dist/joined-view.js.map +1 -0
  258. package/dist/judge.d.ts +2 -1
  259. package/dist/judge.d.ts.map +1 -1
  260. package/dist/judge.js +1 -1
  261. package/dist/judge.js.map +1 -1
  262. package/dist/line-continues.d.ts +6 -0
  263. package/dist/line-continues.d.ts.map +1 -0
  264. package/dist/line-continues.js +33 -0
  265. package/dist/line-continues.js.map +1 -0
  266. package/dist/line-paragraphs.d.ts +4 -0
  267. package/dist/line-paragraphs.d.ts.map +1 -0
  268. package/dist/line-paragraphs.js +69 -0
  269. package/dist/line-paragraphs.js.map +1 -0
  270. package/dist/mask.d.ts.map +1 -1
  271. package/dist/mask.js +7 -6
  272. package/dist/mask.js.map +1 -1
  273. package/dist/measure.d.ts +1 -0
  274. package/dist/measure.d.ts.map +1 -1
  275. package/dist/measure.js +5 -1
  276. package/dist/measure.js.map +1 -1
  277. package/dist/number-name.d.ts +6 -3
  278. package/dist/number-name.d.ts.map +1 -1
  279. package/dist/number-name.js +31 -14
  280. package/dist/number-name.js.map +1 -1
  281. package/dist/orthography.d.ts +4 -0
  282. package/dist/orthography.d.ts.map +1 -1
  283. package/dist/orthography.js +11 -9
  284. package/dist/orthography.js.map +1 -1
  285. package/dist/plain-source.d.ts +6 -0
  286. package/dist/plain-source.d.ts.map +1 -0
  287. package/dist/plain-source.js +6 -0
  288. package/dist/plain-source.js.map +1 -0
  289. package/dist/plugin.d.ts +22 -1
  290. package/dist/plugin.d.ts.map +1 -1
  291. package/dist/profile/check.d.ts +28 -0
  292. package/dist/profile/check.d.ts.map +1 -0
  293. package/dist/profile/check.js +12 -0
  294. package/dist/profile/check.js.map +1 -0
  295. package/dist/profile/for-file.d.ts +2 -2
  296. package/dist/profile/for-file.d.ts.map +1 -1
  297. package/dist/profile/for-file.js +9 -2
  298. package/dist/profile/for-file.js.map +1 -1
  299. package/dist/profile/load.js +1 -1
  300. package/dist/profile/load.js.map +1 -1
  301. package/dist/profile/select.d.ts +4 -2
  302. package/dist/profile/select.d.ts.map +1 -1
  303. package/dist/profile/select.js +8 -3
  304. package/dist/profile/select.js.map +1 -1
  305. package/dist/quoted-span.d.ts +22 -0
  306. package/dist/quoted-span.d.ts.map +1 -0
  307. package/dist/quoted-span.js +25 -0
  308. package/dist/quoted-span.js.map +1 -0
  309. package/dist/reasons.d.ts +1 -0
  310. package/dist/reasons.d.ts.map +1 -1
  311. package/dist/reasons.js +4 -1
  312. package/dist/reasons.js.map +1 -1
  313. package/dist/render/compact.d.ts.map +1 -1
  314. package/dist/render/compact.js +1 -2
  315. package/dist/render/compact.js.map +1 -1
  316. package/dist/render/eval.d.ts +2 -1
  317. package/dist/render/eval.d.ts.map +1 -1
  318. package/dist/render/eval.js +85 -43
  319. package/dist/render/eval.js.map +1 -1
  320. package/dist/render/friendly.d.ts +2 -1
  321. package/dist/render/friendly.d.ts.map +1 -1
  322. package/dist/render/friendly.js +14 -6
  323. package/dist/render/friendly.js.map +1 -1
  324. package/dist/render/genres.d.ts +5 -0
  325. package/dist/render/genres.d.ts.map +1 -0
  326. package/dist/render/genres.js +28 -0
  327. package/dist/render/genres.js.map +1 -0
  328. package/dist/render/plural.d.ts +3 -0
  329. package/dist/render/plural.d.ts.map +1 -0
  330. package/dist/render/plural.js +5 -0
  331. package/dist/render/plural.js.map +1 -0
  332. package/dist/render/rules-json.d.ts.map +1 -1
  333. package/dist/render/rules-json.js +9 -3
  334. package/dist/render/rules-json.js.map +1 -1
  335. package/dist/render/semantic.d.ts +3 -2
  336. package/dist/render/semantic.d.ts.map +1 -1
  337. package/dist/render/semantic.js +94 -34
  338. package/dist/render/semantic.js.map +1 -1
  339. package/dist/render/summary.d.ts.map +1 -1
  340. package/dist/render/summary.js +4 -3
  341. package/dist/render/summary.js.map +1 -1
  342. package/dist/render/suppressions.d.ts.map +1 -1
  343. package/dist/render/suppressions.js +3 -2
  344. package/dist/render/suppressions.js.map +1 -1
  345. package/dist/render/text.d.ts +2 -0
  346. package/dist/render/text.d.ts.map +1 -1
  347. package/dist/render/text.js +11 -3
  348. package/dist/render/text.js.map +1 -1
  349. package/dist/resolve-genre.d.ts +11 -0
  350. package/dist/resolve-genre.d.ts.map +1 -0
  351. package/dist/resolve-genre.js +18 -0
  352. package/dist/resolve-genre.js.map +1 -0
  353. package/dist/rule-load.d.ts.map +1 -1
  354. package/dist/rule-load.js +4 -1
  355. package/dist/rule-load.js.map +1 -1
  356. package/dist/run-semantic.js +1 -1
  357. package/dist/run-semantic.js.map +1 -1
  358. package/dist/run.d.ts +2 -0
  359. package/dist/run.d.ts.map +1 -1
  360. package/dist/run.js +25 -15
  361. package/dist/run.js.map +1 -1
  362. package/dist/sentence-shape.d.ts +0 -1
  363. package/dist/sentence-shape.d.ts.map +1 -1
  364. package/dist/sentence-shape.js +3 -5
  365. package/dist/sentence-shape.js.map +1 -1
  366. package/dist/setting-problems.d.ts +5 -0
  367. package/dist/setting-problems.d.ts.map +1 -0
  368. package/dist/setting-problems.js +15 -0
  369. package/dist/setting-problems.js.map +1 -0
  370. package/dist/soft-break.d.ts +19 -0
  371. package/dist/soft-break.d.ts.map +1 -0
  372. package/dist/soft-break.js +78 -0
  373. package/dist/soft-break.js.map +1 -0
  374. package/dist/structure/build.d.ts.map +1 -1
  375. package/dist/structure/build.js +24 -14
  376. package/dist/structure/build.js.map +1 -1
  377. package/dist/structure/cite.js +1 -1
  378. package/dist/structure/cite.js.map +1 -1
  379. package/dist/structure/date-order.d.ts +3 -0
  380. package/dist/structure/date-order.d.ts.map +1 -1
  381. package/dist/structure/date-order.js +9 -1
  382. package/dist/structure/date-order.js.map +1 -1
  383. package/dist/structure/issues.d.ts +1 -1
  384. package/dist/structure/issues.d.ts.map +1 -1
  385. package/dist/structure/issues.js +19 -8
  386. package/dist/structure/issues.js.map +1 -1
  387. package/dist/structure/listed-tags.d.ts +3 -0
  388. package/dist/structure/listed-tags.d.ts.map +1 -0
  389. package/dist/structure/listed-tags.js +6 -0
  390. package/dist/structure/listed-tags.js.map +1 -0
  391. package/dist/structure/markdown-path.d.ts.map +1 -1
  392. package/dist/structure/markdown-path.js +2 -2
  393. package/dist/structure/markdown-path.js.map +1 -1
  394. package/dist/structure/number-in-sentence.d.ts +15 -0
  395. package/dist/structure/number-in-sentence.d.ts.map +1 -0
  396. package/dist/structure/number-in-sentence.js +13 -0
  397. package/dist/structure/number-in-sentence.js.map +1 -0
  398. package/dist/structure/numbering.d.ts.map +1 -1
  399. package/dist/structure/numbering.js +1 -1
  400. package/dist/structure/numbering.js.map +1 -1
  401. package/dist/structure/relative-find.js +1 -1
  402. package/dist/structure/relative-find.js.map +1 -1
  403. package/dist/structure/sexp.js +1 -1
  404. package/dist/structure/sexp.js.map +1 -1
  405. package/dist/structure/unit-word.d.ts +8 -0
  406. package/dist/structure/unit-word.d.ts.map +1 -0
  407. package/dist/structure/unit-word.js +10 -0
  408. package/dist/structure/unit-word.js.map +1 -0
  409. package/dist/structure/unnumbered.js +2 -2
  410. package/dist/structure/unnumbered.js.map +1 -1
  411. package/dist/structure/wrapped-tail.d.ts +18 -0
  412. package/dist/structure/wrapped-tail.d.ts.map +1 -0
  413. package/dist/structure/wrapped-tail.js +11 -0
  414. package/dist/structure/wrapped-tail.js.map +1 -0
  415. package/dist/team-names.d.ts +9 -0
  416. package/dist/team-names.d.ts.map +1 -0
  417. package/dist/team-names.js +34 -0
  418. package/dist/team-names.js.map +1 -0
  419. package/dist/ui.d.ts +2 -0
  420. package/dist/ui.d.ts.map +1 -1
  421. package/dist/ui.js +6 -0
  422. package/dist/ui.js.map +1 -1
  423. package/dist/version.js +1 -1
  424. package/dist/version.js.map +1 -1
  425. package/dist/watch.d.ts +2 -2
  426. package/dist/watch.d.ts.map +1 -1
  427. package/dist/watch.js +5 -5
  428. package/dist/watch.js.map +1 -1
  429. package/genres.yaml +363 -0
  430. package/package.json +4 -3
  431. package/rules/adverb-overuse.yaml +2 -2
  432. package/rules/agentless-passive.yaml +1 -1
  433. package/rules/ai-generated-composite.yaml +2 -2
  434. package/rules/ai-tell.yaml +2 -2
  435. package/rules/bold-density.yaml +2 -2
  436. package/rules/closing-cliche.yaml +1 -1
  437. package/rules/concrete-evidence-density.yaml +2 -2
  438. package/rules/contraction-consistency.yaml +1 -1
  439. package/rules/cushion-phrase-density.yaml +1 -1
  440. package/rules/dangling-reference.yaml +3 -3
  441. package/rules/date-order.yaml +3 -3
  442. package/rules/date-weekday-mismatch.yaml +1 -1
  443. package/rules/double-keigo.yaml +1 -1
  444. package/rules/doubled-word.yaml +1 -1
  445. package/rules/duplicate-definition.yaml +1 -1
  446. package/rules/emoji-density.yaml +1 -1
  447. package/rules/empty-conclusion.yaml +2 -1
  448. package/rules/empty-intensifier.yaml +1 -1
  449. package/rules/excessive-hedging.yaml +16 -6
  450. package/rules/expletive-construction.yaml +2 -2
  451. package/rules/heading-echo.yaml +1 -1
  452. package/rules/hiragana-fukushi.yaml +1 -1
  453. package/rules/internal-jargon.yaml +2 -2
  454. package/rules/latin-spacing.yaml +6 -3
  455. package/rules/max-kanji-continuous.yaml +5 -4
  456. package/rules/max-paragraph-length.yaml +9 -2
  457. package/rules/max-sentence-length.yaml +14 -2
  458. package/rules/ngram-repetition.yaml +4 -4
  459. package/rules/no-doubled-joshi.yaml +4 -4
  460. package/rules/no-em-dash.yaml +2 -2
  461. package/rules/no-mixed-desumasu.yaml +1 -1
  462. package/rules/no-nakaguro-parallel.yaml +2 -2
  463. package/rules/numbering-gap.yaml +1 -1
  464. package/rules/oxford-comma-consistency.yaml +2 -1
  465. package/rules/padded-intro.yaml +2 -1
  466. package/rules/paragraph-length-variance.yaml +1 -1
  467. package/rules/preamble-length.yaml +2 -2
  468. package/rules/preferred-term.yaml +1 -1
  469. package/rules/proper-noun-density.yaml +3 -2
  470. package/rules/repeated-conjunction.yaml +2 -2
  471. package/rules/repeated-sentence-head.yaml +2 -2
  472. package/rules/required-sections.yaml +1 -1
  473. package/rules/rule-of-three.yaml +2 -2
  474. package/rules/sasete-itadaku.yaml +3 -3
  475. package/rules/section-length-uniformity.yaml +1 -1
  476. package/rules/sentence-initial-conjunction-run.yaml +2 -2
  477. package/rules/sentence-rhythm.yaml +1 -1
  478. package/rules/taigen-dome-in-prose.yaml +2 -2
  479. package/rules/title-case-consistency.yaml +3 -3
  480. package/rules/total-mismatch.yaml +1 -1
  481. package/rules/undefined-acronym.yaml +18 -5
  482. package/rules/unqualified-superlative.yaml +3 -3
  483. package/skills/chaff/SKILL.md +28 -9
  484. package/src/address-chain.ts +6 -3
  485. package/src/backends/anthropic.ts +5 -1
  486. package/src/backends/openai.ts +2 -1
  487. package/src/backends/types.ts +3 -2
  488. package/src/baseline.ts +1 -1
  489. package/src/calendar-number.ts +75 -0
  490. package/src/cli-args.ts +2 -2
  491. package/src/cli-text.ts +48 -18
  492. package/src/cli.ts +51 -47
  493. package/src/commands/cite.ts +4 -4
  494. package/src/commands/eval.ts +27 -6
  495. package/src/commands/feedback.ts +2 -1
  496. package/src/commands/init-ask.ts +53 -0
  497. package/src/commands/test.ts +105 -47
  498. package/src/commands/tree.ts +9 -7
  499. package/src/config/by-path.ts +8 -9
  500. package/src/config/load.ts +24 -0
  501. package/src/config/name-problems.ts +15 -0
  502. package/src/date-stamp.ts +1 -1
  503. package/src/detectors/acronym-compound.ts +9 -0
  504. package/src/detectors/acronym-context.ts +22 -1
  505. package/src/detectors/acronym-expansion.ts +72 -5
  506. package/src/detectors/char-shape.ts +56 -16
  507. package/src/detectors/citation-key.ts +13 -0
  508. package/src/detectors/cited-name.ts +23 -0
  509. package/src/detectors/concrete-evidence.ts +31 -0
  510. package/src/detectors/counter-edge.ts +29 -0
  511. package/src/detectors/dotted-name.ts +14 -0
  512. package/src/detectors/doubled-word.ts +25 -2
  513. package/src/detectors/en-shape.ts +123 -38
  514. package/src/detectors/enumerated-runs.ts +3 -2
  515. package/src/detectors/gram-place.ts +9 -2
  516. package/src/detectors/gram-predicate.ts +55 -0
  517. package/src/detectors/heading-case.ts +44 -0
  518. package/src/detectors/heading-echo.ts +5 -1
  519. package/src/detectors/index.ts +4 -3
  520. package/src/detectors/lettered-index.ts +78 -0
  521. package/src/detectors/lexicon.ts +68 -8
  522. package/src/detectors/name-numeral.ts +37 -0
  523. package/src/detectors/name-part.ts +24 -0
  524. package/src/detectors/name-run.ts +16 -0
  525. package/src/detectors/orthography.ts +43 -10
  526. package/src/detectors/phrase-match.ts +23 -12
  527. package/src/detectors/placeholder.ts +38 -0
  528. package/src/detectors/quoted-variant.ts +20 -0
  529. package/src/detectors/repeated-head.ts +3 -4
  530. package/src/detectors/series-label.ts +33 -0
  531. package/src/detectors/signals.ts +49 -35
  532. package/src/detectors/stacked-hedge.ts +93 -0
  533. package/src/detectors/structure-tree.ts +2 -2
  534. package/src/detectors/superlative-amount.ts +24 -0
  535. package/src/detectors/superlative-clause.ts +82 -0
  536. package/src/detectors/superlative-name.ts +30 -0
  537. package/src/detectors/superlative-scope.ts +3 -3
  538. package/src/detectors/team.ts +18 -2
  539. package/src/detectors/token-shape.ts +13 -6
  540. package/src/document.ts +41 -8
  541. package/src/eval.ts +1 -1
  542. package/src/feedback/draft.ts +1 -1
  543. package/src/feedback/excerpt.ts +2 -2
  544. package/src/file-header.ts +31 -0
  545. package/src/files.ts +3 -3
  546. package/src/genre-check.ts +46 -0
  547. package/src/genre-load.ts +24 -0
  548. package/src/genre-parse.ts +126 -0
  549. package/src/genre-suggest.ts +37 -0
  550. package/src/genre.ts +25 -5
  551. package/src/init-choice.ts +47 -0
  552. package/src/init.ts +20 -19
  553. package/src/joined-view.ts +96 -0
  554. package/src/judge.ts +3 -1
  555. package/src/line-continues.ts +43 -0
  556. package/src/line-paragraphs.ts +83 -0
  557. package/src/mask.ts +5 -6
  558. package/src/measure.ts +9 -1
  559. package/src/number-name.ts +35 -13
  560. package/src/orthography.ts +15 -9
  561. package/src/plain-source.ts +5 -0
  562. package/src/plugin.ts +27 -2
  563. package/src/profile/check.ts +34 -0
  564. package/src/profile/for-file.ts +10 -3
  565. package/src/profile/load.ts +1 -1
  566. package/src/profile/select.ts +11 -4
  567. package/src/quoted-span.ts +39 -0
  568. package/src/reasons.ts +5 -1
  569. package/src/render/compact.ts +1 -3
  570. package/src/render/eval.ts +102 -38
  571. package/src/render/friendly.ts +21 -6
  572. package/src/render/genres.ts +31 -0
  573. package/src/render/plural.ts +6 -0
  574. package/src/render/rules-json.ts +10 -3
  575. package/src/render/semantic.ts +117 -34
  576. package/src/render/summary.ts +4 -3
  577. package/src/render/suppressions.ts +3 -2
  578. package/src/render/text.ts +18 -3
  579. package/src/resolve-genre.ts +20 -0
  580. package/src/rule-load.ts +6 -1
  581. package/src/run-semantic.ts +1 -1
  582. package/src/run.ts +28 -15
  583. package/src/sentence-shape.ts +3 -7
  584. package/src/setting-problems.ts +24 -0
  585. package/src/soft-break.ts +94 -0
  586. package/src/structure/build.ts +24 -15
  587. package/src/structure/cite.ts +1 -1
  588. package/src/structure/date-order.ts +16 -1
  589. package/src/structure/issues.ts +24 -8
  590. package/src/structure/listed-tags.ts +7 -0
  591. package/src/structure/markdown-path.ts +2 -2
  592. package/src/structure/number-in-sentence.ts +20 -0
  593. package/src/structure/numbering.ts +1 -1
  594. package/src/structure/relative-find.ts +1 -1
  595. package/src/structure/sexp.ts +1 -1
  596. package/src/structure/unit-word.ts +11 -0
  597. package/src/structure/unnumbered.ts +2 -2
  598. package/src/structure/wrapped-tail.ts +22 -0
  599. package/src/team-names.ts +39 -0
  600. package/src/ui.ts +7 -0
  601. package/src/version.ts +1 -1
  602. package/src/watch.ts +5 -5
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: chaff
3
- description: "Check prose with chaff (npx chaffjs): run it on Markdown or text, read each finding, fix the text or record why not, and tune chaff.yaml with rules --json and relax --why. Use when asked to lint, proofread or tighten documents, or to set up a team's writing rules."
3
+ description: "Check prose with chaff (npx chaffjs): pick the genre preset for the kind of document (contract, statute, manual, FAQ, paper, fiction, transcript…), run it on Markdown or text, read each finding, fix the text or record why not, and tune chaff.yaml with rules --json and relax --why. Use when asked to lint, proofread or tighten documents, or to set up a team's writing rules."
4
4
  ---
5
5
 
6
6
  # Checking writing with chaff
@@ -9,12 +9,30 @@ chaff finds what makes writing hard to read — long sentences, padding, mixed s
9
9
  contract — and never rewrites anything. Reading the findings and changing the text is your part. Japanese and
10
10
  English documents both work; the language is detected per file.
11
11
 
12
+ ## Pick the genre first
13
+
14
+ A genre is a preset for one kind of document: which rules run, where their limits are, and how the document is
15
+ read. A contract checked as a blog post gets wrong findings, so choose the genre before reading any finding.
16
+
17
+ 1. Name the kind from the document itself. `npx chaffjs genres` lists every genre with what it is for:
18
+ `legal/contract`, `legal/statute`, `docs/manual`, `docs/faq`, `academic/paper`, `literature/fiction`,
19
+ `speech/transcript`, `business/report`, `technical/spec`, …
20
+ 2. Unsure? Run once without `--genre`. When the file looks like another kind, the line under the header says so
21
+ (`Looks like: Contract and terms. Try --genre legal/contract`); use that genre if it fits.
22
+ 3. Run with it: `npx chaffjs --genre legal/contract contract.md`. For a folder of one kind,
23
+ `npx chaffjs init --genre legal/contract` writes it into `chaff.yaml` (always pass `--genre`: without it,
24
+ `init` asks at a terminal).
25
+ 4. The header's first line shows the genre used and where it came from. Check it before trusting the findings.
26
+
27
+ A rule the genre turns off is listed under "did not run" with the genre as the reason. That is the preset's
28
+ choice, not a gap; turn it back on (`rules:` in `chaff.yaml`) only when the person asks.
29
+
12
30
  ## Run it
13
31
 
14
32
  ```sh
15
- npx chaffjs <file|dir|glob>... # human-readable, one block per finding (a folder or glob picks up .md / .markdown / .mdx; name a .txt file directly)
16
- npx chaffjs . --compact # two lines per finding: line:col severity message, then the rule id
17
- npx chaffjs <file> --experimental # also the experimental rules
33
+ npx chaffjs --genre <genre> <file|dir|glob>... # human-readable, one block per finding (a folder or glob picks up .md / .markdown / .mdx; name a .txt file directly)
34
+ npx chaffjs . --compact # two lines per finding: line:col severity message, then the rule id
35
+ npx chaffjs <file> --experimental # also the experimental rules
18
36
  ```
19
37
 
20
38
  A run exits 1 when a finding is an `error`. It also exits 1 when it could not check anything: no file to
@@ -39,15 +57,16 @@ relaxing a rule.
39
57
 
40
58
  ## Tune chaff.yaml
41
59
 
42
- - `npx chaffjs init` writes a commented `chaff.yaml` when there is none (an existing one is left as it is), and
43
- adds `.chaff-cache/` and `.env*` to `.gitignore`.
44
- - `npx chaffjs rules --json` prints every rule with its levels, the setting in effect (`now`) and why a rule is
45
- off. Read it instead of guessing rule names or limits.
60
+ - `npx chaffjs init --genre <genre>` writes a commented `chaff.yaml` for that genre when there is none (an
61
+ existing one is left as it is), and adds `.chaff-cache/` and `.env*` to `.gitignore`.
62
+ - `npx chaffjs rules --json [--genre <genre>]` prints every rule with its levels, the setting in effect (`now`,
63
+ the genre's preset included) and why a rule is off. Read it instead of guessing rule names or limits.
46
64
  - A level is `strict` / `normal` / `relaxed` / `off`; when even `relaxed` is too tight, a positive number is the
47
65
  limit itself (`max-sentence-length: 260`).
48
66
  - `prefer:` maps spellings to the team's (`e-mail: email`); `preferred-term: normal` turns the rule on.
49
67
  - Unknown rule names and unreadable values are reported on stderr — read stderr after editing `chaff.yaml`.
50
- - `genre:` sets the kind of document (`npx chaffjs genres` lists them); `--genre` overrides it for one run.
68
+ - `genre:` sets the kind of document; `by_path` sets one per folder; `--genre` overrides both for one run.
69
+ `rules:` wins over the genre's preset.
51
70
 
52
71
  ## Structured documents
53
72
 
@@ -78,7 +78,7 @@ const chainAt = (patterns: Compiled, text: string, start: number): { readonly sp
78
78
  const firstEnd = matchEnd(patterns.address, text, start);
79
79
  if (firstEnd === undefined) return undefined;
80
80
  const { spans, cuts } = readChain(patterns, text, { start, end: firstEnd });
81
- const cut = [...cuts].reverse().find((candidate) => endsHere(patterns, text, candidate.end));
81
+ const cut = cuts.findLast((candidate) => endsHere(patterns, text, candidate.end));
82
82
  return cut === undefined ? undefined : { spans: spans.slice(0, cut.addresses), end: cut.end };
83
83
  };
84
84
 
@@ -102,12 +102,15 @@ export const addressSpans = (text: string, profile: DocumentProfile | undefined)
102
102
  return spans;
103
103
  };
104
104
 
105
- /** 番地を空白にする。空白が連なりを切るので、番地は数えられず、前後は別の連なりになる。 */
105
+ /**
106
+ * 番地を同じ長さの空白にする。空白が連なりを切るので、番地は数えられず、前後は別の連なりになる。
107
+ * 長さを保つので、覆った後の位置は元の文字列の位置のまま。
108
+ */
106
109
  export const maskAddresses = (text: string, profile: DocumentProfile | undefined): string => {
107
110
  const spans = addressSpans(text, profile);
108
111
  if (spans.length === 0) return text;
109
112
  const pieces: string[] = [];
110
- spans.forEach((span, index) => pieces.push(text.slice(spans[index - 1]?.end ?? 0, span.start), " "));
113
+ spans.forEach((span, index) => pieces.push(text.slice(spans[index - 1]?.end ?? 0, span.start), " ".repeat(span.end - span.start)));
111
114
  pieces.push(text.slice(spans.at(-1)?.end ?? 0));
112
115
  return pieces.join("");
113
116
  };
@@ -4,6 +4,7 @@ import { join } from "node:path";
4
4
  import Anthropic from "@anthropic-ai/sdk";
5
5
  import { toFailure } from "./types.ts";
6
6
  import type { Failure, Judge, Prompt } from "./types.ts";
7
+ import type { Texts } from "../ui.ts";
7
8
 
8
9
  type AnthropicResponse = { readonly content: readonly { readonly type: string; readonly text?: string | undefined }[] };
9
10
 
@@ -58,4 +59,7 @@ export const judge =
58
59
  };
59
60
 
60
61
  /** 認証が無いときに人へ見せる案内。 */
61
- export const SETUP_HINT = "ANTHROPIC_API_KEY を設定するか、ant auth login を実行してください。";
62
+ export const SETUP_HINT: Texts<string> = {
63
+ ja: "ANTHROPIC_API_KEY を設定するか、ant auth login を実行してください。",
64
+ en: "Set ANTHROPIC_API_KEY, or run ant auth login.",
65
+ };
@@ -1,6 +1,7 @@
1
1
  import OpenAI from "openai";
2
2
  import { toFailure } from "./types.ts";
3
3
  import type { Failure, Judge, Prompt } from "./types.ts";
4
+ import type { Texts } from "../ui.ts";
4
5
 
5
6
  type OpenAIResponse = { readonly choices: readonly { readonly message: { readonly content: string | null } }[] };
6
7
 
@@ -43,4 +44,4 @@ export const judge =
43
44
  return body;
44
45
  };
45
46
 
46
- export const SETUP_HINT = "OPENAI_API_KEY を設定してください。";
47
+ export const SETUP_HINT: Texts<string> = { ja: "OPENAI_API_KEY を設定してください。", en: "Set OPENAI_API_KEY." };
@@ -17,11 +17,12 @@ const BACKENDS: readonly BackendName[] = ["anthropic", "openai"];
17
17
  export const isBackend = (value: unknown): value is BackendName => BACKENDS.some((name) => name === value);
18
18
 
19
19
  /** API が返した失敗。生のスタックトレースを人に見せないための、最小の分類。 */
20
- export type Failure = { readonly kind: "auth" | "quota" | "api"; readonly status: number | undefined; readonly message: string };
20
+ /** message is the provider's own words, undefined when it gave none: the screen says so in the reader's language. */
21
+ export type Failure = { readonly kind: "auth" | "quota" | "api"; readonly status: number | undefined; readonly message: string | undefined };
21
22
 
22
23
  const statusOf = (value: unknown): number | undefined => (typeof value === "number" ? value : undefined);
23
24
 
24
- const messageOf = (value: unknown): string => (typeof value === "string" && value.length > 0 ? value : "原因は返ってきませんでした");
25
+ const messageOf = (value: unknown): string | undefined => (typeof value === "string" && value.length > 0 ? value : undefined);
25
26
 
26
27
  const kindOf = (status: number | undefined): Failure["kind"] => {
27
28
  if (status === 401 || status === 403) return "auth";
package/src/baseline.ts CHANGED
@@ -29,7 +29,7 @@ export const readBaseline = (path: string): Baseline | undefined => {
29
29
  };
30
30
 
31
31
  export const writeBaseline = (path: string, entries: readonly string[]): void => {
32
- const sorted = [...entries].sort((left, right) => left.localeCompare(right, "en"));
32
+ const sorted = entries.toSorted((left, right) => left.localeCompare(right, "en"));
33
33
  const body = { version: 1, created: new Date().toISOString().slice(0, 10), entries: sorted };
34
34
  writeFileSync(path, `${JSON.stringify(body, null, 2)}\n`, "utf8");
35
35
  };
@@ -0,0 +1,75 @@
1
+ import { digitRunAround } from "./number-name.ts";
2
+ import type { Span, Token } from "./plugin.ts";
3
+
4
+ /**
5
+ * 日付・時刻として書かれた数(9月、3時、2026年、10月1日、10時5分)。数を詰めて書くのが決まりで、空け方の好みではない。
6
+ * 長さ(3ヶ月、3時間、3日間)は別の語で書くので、ここには入らない。
7
+ *
8
+ * 単位は語彙表から渡す。chained は日付・時刻の単位を大きい順に(年月日時分秒)。大きい単位の後ろに小さい単位を続けて書けば
9
+ * 一つの日付・時刻になる。「2008年 2年連続」のように同じ単位や大きい単位が続けば、別のものの始まり。
10
+ * positional は数の後ろに置くだけで位置を言う単位(月、時)。year は 4 桁の数の後ろで暦の年を言う単位(年、年度)。
11
+ * 「5日」「5分」「3年」のように、単位だけでは長さと見分けのつかないものは、位置を言う単位と続けて書いたときだけ日付・時刻と読む。
12
+ */
13
+ export type CalendarUnits = { readonly chained: readonly string[]; readonly positional: ReadonlySet<string>; readonly year: ReadonlySet<string> };
14
+
15
+ /** 日付・時刻の数の並び。first は日付の頭の数、inner は前の単位に続く数(2026年9月の 9)。 */
16
+ export type CalendarRun = { readonly run: Span; readonly place: "first" | "inner" };
17
+
18
+ /** 数の並びと、その後ろの単位の終わり(文の中の位置)。rank は chained の中の単位の順(無ければ -1)、anchored は単位だけで日付・時刻と分かるもの。 */
19
+ type Part = { readonly run: Span; readonly unitEnd: number; readonly rank: number; readonly anchored: boolean };
20
+
21
+ const DIGIT = /\d/u;
22
+ const CALENDAR_YEAR = /^\d{4}$/u;
23
+
24
+ /** text の中の数の並び(数字・小数点・ハイフン)を左から。 */
25
+ const digitRuns = (text: string): Span[] => {
26
+ const runs: Span[] = [];
27
+ let at = 0;
28
+ while (at < text.length) {
29
+ const run = DIGIT.test(text[at] ?? "") ? digitRunAround(text, at) : undefined;
30
+ if (run !== undefined) runs.push(run);
31
+ at = Math.max(run?.end ?? 0, at + 1);
32
+ }
33
+ return runs;
34
+ };
35
+
36
+ /** 空白 1 つを越えた位置。 */
37
+ const pastSpace = (text: string, at: number): number => (text[at] === " " ? at + 1 : at);
38
+
39
+ const isUnit = (surface: string, units: CalendarUnits): boolean => units.chained.includes(surface) || units.positional.has(surface) || units.year.has(surface);
40
+
41
+ const isAnchor = (digits: string, surface: string, units: CalendarUnits): boolean =>
42
+ units.positional.has(surface) || (units.year.has(surface) && CALENDAR_YEAR.test(digits));
43
+
44
+ /** 数のすぐ後ろ(空白 1 つまで)の語が日付・時刻の単位なら、その組。語の切れ目が合わなければ組にしない。 */
45
+ const partOf = (text: string, run: Span, tokens: readonly Token[], base: number, units: CalendarUnits): Part | undefined => {
46
+ const unit = tokens.find((token) => token.span.start === base + pastSpace(text, run.end));
47
+ if (unit === undefined || !isUnit(unit.surface, units)) return undefined;
48
+ const rank = units.chained.indexOf(unit.surface);
49
+ return { run, unitEnd: unit.span.end - base, rank, anchored: isAnchor(text.slice(run.start, run.end), unit.surface, units) };
50
+ };
51
+
52
+ /** 前の組の単位のすぐ後ろ(空白 1 つまで)から始まり、単位が前より小さい組か。 */
53
+ const continues = (text: string, previous: Part | undefined, part: Part): boolean =>
54
+ previous !== undefined && previous.rank >= 0 && part.rank > previous.rank && part.run.start === pastSpace(text, previous.unitEnd);
55
+
56
+ /** 前の組に続く組を、同じ日付・時刻にまとめる。 */
57
+ const chainsOf = (text: string, parts: readonly Part[]): Part[][] =>
58
+ parts.reduce<Part[][]>((chains, part) => {
59
+ const chain = chains.at(-1);
60
+ if (chain !== undefined && continues(text, chain.at(-1), part)) chain.push(part);
61
+ else chains.push([part]);
62
+ return chains;
63
+ }, []);
64
+
65
+ /**
66
+ * 文 text の中の、日付・時刻の数の並び。tokens は文書全体の座標で、base は text の先頭の位置。
67
+ * 品詞が無ければ判断せず、何も返さない。
68
+ */
69
+ export const calendarRuns = (text: string, tokens: readonly Token[] | undefined, base: number, units: CalendarUnits): CalendarRun[] => {
70
+ if (tokens === undefined) return [];
71
+ const parts = digitRuns(text).flatMap((run) => partOf(text, run, tokens, base, units) ?? []);
72
+ return chainsOf(text, parts)
73
+ .filter((chain) => chain.some((part) => part.anchored))
74
+ .flatMap((chain) => chain.map((part, index): CalendarRun => ({ run: part.run, place: index === 0 ? "first" : "inner" })));
75
+ };
package/src/cli-args.ts CHANGED
@@ -2,8 +2,8 @@
2
2
  * Options of the commands that take files to check, whose value is the next argument. That value is not a file.
3
3
  * `--why` (relax), `--format` and `--language` (tree) belong to commands that read their own arguments.
4
4
  */
5
- export const VALUE_FLAGS: readonly string[] = ["--sarif", "--genre", "--rule", "--line"];
5
+ const VALUE_FLAGS: ReadonlySet<string> = new Set(["--sarif", "--genre", "--rule", "--line"]);
6
6
 
7
7
  /** The arguments that name what to check: everything that is neither an option nor an option's value. */
8
8
  export const targetsOf = (args: readonly string[]): string[] =>
9
- args.filter((arg, index) => !arg.startsWith("--") && !(index > 0 && VALUE_FLAGS.includes(args[index - 1] ?? "")));
9
+ args.filter((arg, index) => !arg.startsWith("--") && !(index > 0 && VALUE_FLAGS.has(args[index - 1] ?? "")));
package/src/cli-text.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  // What the command line says, in Japanese and English. Per-file text follows the file's language; the rest
2
2
  // follows hostLanguage (chaff.yaml's language, else the terminal's locale).
3
3
  import type { Texts } from "./ui.ts";
4
+ import { counted, formFor } from "./render/plural.ts";
5
+ import type { GenreSetting } from "./genre-check.ts";
6
+ import type { ProfileSetting } from "./profile/check.ts";
4
7
 
5
8
  /** Where a file's genre came from, as the header names it. */
6
9
  export type GenreSource = "--genre" | "by_path" | "config" | "default" | "front-matter" | "path" | "content";
@@ -11,11 +14,11 @@ const USAGE_JA = `chaff — 文章の読みにくいところを見つけます
11
14
  chaff . この場所の Markdown を全部
12
15
  chaff test <file|dir>... 意味を読む検査も動かす(API key が要ります)
13
16
  判定役は chaff.yaml の ai_backend で選びます
14
- chaff init chaff.yaml を作る
17
+ chaff init chaff.yaml を作る(端末ならジャンルを尋ねる。--genre <ジャンル> でも選べる)
15
18
  chaff --version chaffjs と言語パッケージの版
16
19
  chaff eval <dir> 手元の文書で閾値を測り直す
17
20
  chaff explain <rule> そのルールの意図と根拠を読む
18
- chaff genres ジャンルの一覧
21
+ chaff genres ジャンル(文書の種類)の一覧と、それぞれ何向けか
19
22
  chaff tree <file> [--format sexp|json] 文書を番地の付いた木にする(条・項・定義・参照)
20
23
  chaff cite <原文> <引用.json> 回答の引用(番地と引用文)が原文にあるかを確かめる
21
24
  chaff rules --json いまの設定を JSON で出す(AI に渡す用)
@@ -45,11 +48,11 @@ const USAGE_EN = `chaff — finds what makes writing hard to read. It never rewr
45
48
  chaff . every Markdown file here
46
49
  chaff test <file|dir>... also run the checks that read meaning (needs an API key)
47
50
  the judge is chosen by ai_backend in chaff.yaml
48
- chaff init create chaff.yaml
51
+ chaff init create chaff.yaml (asks for the genre at a terminal; --genre <genre> chooses it)
49
52
  chaff --version the version of chaffjs and its language packages
50
53
  chaff eval <dir> re-measure the limits on your own documents
51
54
  chaff explain <rule> read what a rule is for and why
52
- chaff genres list the genres
55
+ chaff genres list the genres (kinds of document) and what each is for
53
56
  chaff tree <file> [--format sexp|json] the document as a tree of addresses (sections, clauses, definitions, references)
54
57
  chaff cite <source> <quotes.json> check that quoted passages (address and text) are in the source
55
58
  chaff rules --json the current settings as JSON (to give to an AI)
@@ -78,20 +81,25 @@ export type CliText = {
78
81
  readonly languageName: (language: string) => string;
79
82
  readonly genreSource: Readonly<Record<GenreSource, string>>;
80
83
  readonly header: (path: string, genre: string, language: string, from: string, shelved: number, hushed: number) => string;
84
+ readonly suggested: (name: string, genre: string) => string;
85
+ readonly suggestedNote: (name: string, genre: string, used: string) => string;
81
86
  readonly noMarkdown: (targets: string) => string;
82
87
  readonly noMarkdownHere: string;
83
88
  readonly noAdapter: (language: string) => string;
84
- readonly unknownGenre: (genre: string) => string;
89
+ readonly unknownGenre: (genre: string, where: string, known: readonly string[]) => string;
90
+ readonly genreWhere: (where: GenreSetting, files: readonly string[]) => string;
91
+ readonly unreadFrontMatterGenre: (path: string, genre: string, known: readonly string[]) => string;
92
+ readonly unknownProfile: (profile: string, where: string, known: readonly string[]) => string;
93
+ readonly profileWhere: (where: ProfileSetting, files: readonly string[]) => string;
85
94
  readonly unknownRule: (id: string) => string;
86
95
  readonly unknownRuleWithList: (id: string, list: string) => string;
87
96
  readonly unnamed: string;
88
97
  readonly sarifWritten: (path: string, count: number) => string;
89
98
  readonly unchanged: string;
90
- readonly findingsUnit: string;
99
+ readonly findingsUnit: (count: number) => string;
91
100
  readonly watching: (files: number, findings: number) => string;
92
101
  readonly watchHint: string;
93
102
  readonly baselineDone: (files: number, entries: number, file: string) => readonly string[];
94
- readonly genres: (list: readonly string[]) => string;
95
103
  readonly unit: (ruleId: string, language: string) => string;
96
104
  };
97
105
 
@@ -112,16 +120,29 @@ export const CLI_TEXT: Texts<CliText> = {
112
120
  [`${path} ${genre} · ${language} ジャンルは${from}から`, shelved > 0 ? ` 棚上げ ${shelved} 件` : "", hushed > 0 ? ` stet ${hushed} 件` : ""].join(
113
121
  "",
114
122
  ),
123
+ suggested: (name, genre) => ` ${name}のようです。--genre ${genre} を試せます`,
124
+ suggestedNote: (name, genre, used) =>
125
+ ` ジャンルを決めていないので、${used} として見ました。${name}なら、--genre ${genre} でその種類の書き方に合わせて見ます(npx chaffjs genres で一覧)。`,
115
126
  noMarkdown: (targets) => `Markdown が 1 つも見つかりませんでした: ${targets}`,
116
127
  noMarkdownHere: "Markdown が 1 つも見つかりませんでした。",
117
128
  noAdapter: (language) => `言語 "${language}" のアダプタがありません。`,
118
- unknownGenre: (genre) => `ジャンル "${genre}" はありません。npx chaff genres で一覧が出ます。`,
129
+ unknownGenre: (genre, where, known) =>
130
+ `ジャンル "${genre}" はありません(${where})。使えるのは ${known.join("、")} です。npx chaff genres で一覧が出ます。`,
131
+ genreWhere: (where, files) => {
132
+ if (where === "config") return "chaff.yaml の genre";
133
+ return where === "by_path" ? `chaff.yaml の by_path、files: ${files.join(", ")}` : "--genre";
134
+ },
135
+ unreadFrontMatterGenre: (path, genre, known) =>
136
+ `${path}: front matter の genre "${genre}" はジャンルではないので読みませんでした。使えるのは ${known.join("、")} です。`,
137
+ unknownProfile: (profile, where, known) =>
138
+ `文書の種類 "${profile}" はありません(${where})。使えるのは ${known.join("、")} と、種類を使わない none です。`,
139
+ profileWhere: (where, files) => (where === "config" ? "chaff.yaml の profile" : `chaff.yaml の by_path、files: ${files.join(", ")}`),
119
140
  unknownRule: (id) => `${id} というルールはありません。npx chaff rules --json で一覧が出ます。`,
120
141
  unknownRuleWithList: (id, list) => `${id} というルールはありません。\n一覧:\n${list}`,
121
142
  unnamed: "(名前なし)",
122
143
  sarifWritten: (path, count) => `\n SARIF を書きました: ${path}(${count} 件)`,
123
144
  unchanged: "変わりませんでした",
124
- findingsUnit: " 件",
145
+ findingsUnit: () => " 件",
125
146
  watching: (files, findings) => `\n ${files} ファイルを見ています。いまの指摘は ${findings} 件です。`,
126
147
  watchHint: " 保存するたびに、変わったところだけ出します。止めるには Ctrl-C。\n",
127
148
  baselineDone: (files, entries, file) => [
@@ -134,8 +155,6 @@ export const CLI_TEXT: Texts<CliText> = {
134
155
  ` ${file} を commit してください。`,
135
156
  "",
136
157
  ],
137
- genres: (list) =>
138
- ["", " 使えるジャンル:", ...list.map((genre) => ` ${genre}`), "", " chaff.yaml の genre に書くか、--genre で指定します。", ""].join("\n"),
139
158
  unit: (ruleId, language) => {
140
159
  if (ruleId === "bold-density") return "1000 字あたりの箇所数";
141
160
  if (ruleId !== "max-sentence-length") return "回";
@@ -156,29 +175,40 @@ export const CLI_TEXT: Texts<CliText> = {
156
175
  },
157
176
  header: (path, genre, language, from, shelved, hushed) =>
158
177
  [`${path} ${genre} · ${language} genre from ${from}`, shelved > 0 ? ` ${shelved} shelved` : "", hushed > 0 ? ` ${hushed} stet` : ""].join(""),
178
+ suggested: (name, genre) => ` Looks like: ${name}. Try --genre ${genre}`,
179
+ suggestedNote: (name, genre, used) =>
180
+ ` No genre was set, so this was checked as ${used}. If it is ${name}, --genre ${genre} checks it the way that kind is written (npx chaffjs genres lists them).`,
159
181
  noMarkdown: (targets) => `No Markdown files found: ${targets}`,
160
182
  noMarkdownHere: "No Markdown files found.",
161
183
  noAdapter: (language) => `No language package for "${language}".`,
162
- unknownGenre: (genre) => `There is no genre "${genre}". npx chaff genres lists them.`,
184
+ unknownGenre: (genre, where, known) => `There is no genre "${genre}" (${where}). The genres are ${known.join(", ")}. npx chaff genres lists them.`,
185
+ genreWhere: (where, files) => {
186
+ if (where === "config") return "genre in chaff.yaml";
187
+ return where === "by_path" ? `by_path in chaff.yaml, files: ${files.join(", ")}` : "--genre";
188
+ },
189
+ unreadFrontMatterGenre: (path, genre, known) =>
190
+ `${path}: the front matter's genre "${genre}" is not a genre, so it was not used. The genres are ${known.join(", ")}.`,
191
+ unknownProfile: (profile, where, known) =>
192
+ `There is no document profile "${profile}" (${where}). The profiles are ${known.join(", ")}; none uses no profile.`,
193
+ profileWhere: (where, files) => (where === "config" ? "profile in chaff.yaml" : `by_path in chaff.yaml, files: ${files.join(", ")}`),
163
194
  unknownRule: (id) => `There is no rule named ${id}. npx chaff rules --json lists them.`,
164
195
  unknownRuleWithList: (id, list) => `There is no rule named ${id}.\nRules:\n${list}`,
165
196
  unnamed: "(no name)",
166
- sarifWritten: (path, count) => `\n Wrote SARIF: ${path} (${count} finding${count === 1 ? "" : "s"})`,
197
+ sarifWritten: (path, count) => `\n Wrote SARIF: ${path} (${counted(count, "finding")})`,
167
198
  unchanged: "no change",
168
- findingsUnit: " findings",
169
- watching: (files, findings) => `\n Watching ${files} file${files === 1 ? "" : "s"}. ${findings} finding${findings === 1 ? "" : "s"} now.`,
199
+ findingsUnit: (count) => ` ${formFor(count, "finding", "findings")}`,
200
+ watching: (files, findings) => `\n Watching ${counted(files, "file")}. ${counted(findings, "finding")} now.`,
170
201
  watchHint: " Each save prints only what changed. Ctrl-C to stop.\n",
171
202
  baselineDone: (files, entries, file) => [
172
203
  "",
173
- ` Checked ${files} file${files === 1 ? "" : "s"}.`,
204
+ ` Checked ${counted(files, "file")}.`,
174
205
  "",
175
- ` Recorded ${entries} finding${entries === 1 ? "" : "s"} in ${file}.`,
206
+ ` Recorded ${counted(entries, "finding")} in ${file}.`,
176
207
  " They will not be reported again; only new ones will.",
177
208
  "",
178
209
  ` Commit ${file}.`,
179
210
  "",
180
211
  ],
181
- genres: (list) => ["", " Genres:", ...list.map((genre) => ` ${genre}`), "", " Set one with genre in chaff.yaml, or with --genre.", ""].join("\n"),
182
212
  unit: (ruleId, language) => {
183
213
  if (ruleId === "bold-density") return "places per 1000 characters";
184
214
  if (ruleId !== "max-sentence-length") return "times";
package/src/cli.ts CHANGED
@@ -7,6 +7,7 @@ import { applyByPath } from "./config/by-path.ts";
7
7
  import { applyLevel } from "./config/write.ts";
8
8
  import { buildDocument, teamRules } from "./document.ts";
9
9
  import { guessLanguage } from "./detect.ts";
10
+ import { plainSource } from "./plain-source.ts";
10
11
  import { collectTargets } from "./files.ts";
11
12
  import { BASELINE_FILE, fingerprint, readBaseline, splitByBaseline, writeBaseline } from "./baseline.ts";
12
13
  import { applySuppressions } from "./stet.ts";
@@ -14,13 +15,18 @@ import { renderSuppressions, type PerFile } from "./render/suppressions.ts";
14
15
  import { clock, describeChange, snapshotOf, watchPaths, type Snapshot } from "./watch.ts";
15
16
  import { runEval } from "./commands/eval.ts";
16
17
  import { runTest } from "./commands/test.ts";
17
- import { frontMatterGenre, guessGenre } from "./genre.ts";
18
- import { GENRES, runInit } from "./init.ts";
18
+ import { GENRES } from "./genre.ts";
19
+ import { resolveGenre } from "./resolve-genre.ts";
20
+ import { runInit } from "./init.ts";
21
+ import { initGenre } from "./commands/init-ask.ts";
19
22
  import { targetsOf } from "./cli-args.ts";
20
23
  import { loadRules } from "./rule-load.ts";
21
24
  import { renderCompact } from "./render/compact.ts";
22
25
  import { renderExplain } from "./render/explain.ts";
23
26
  import { renderFriendly } from "./render/friendly.ts";
27
+ import { renderGenres } from "./render/genres.ts";
28
+ import { loadGenres, presetLevels } from "./genre-load.ts";
29
+ import { fileHeader } from "./file-header.ts";
24
30
  import { rulesJson } from "./render/rules-json.ts";
25
31
  import { renderSarif } from "./render/sarif.ts";
26
32
  import { VERSION, VERSION_LINES } from "./version.ts";
@@ -30,29 +36,20 @@ import { runSkill } from "./commands/skill.ts";
30
36
  import { runFeedback, settingsOf } from "./commands/feedback.ts";
31
37
  import { homedir } from "node:os";
32
38
  import { ruleProblems } from "./config/rule-problems.ts";
39
+ import { nameProblems } from "./config/name-problems.ts";
33
40
  import { renderSummary, type FileOutcome } from "./render/summary.ts";
34
41
  import { neededBy, runRules } from "./run.ts";
35
42
  import type { Level, RuleDefinition } from "./plugin.ts";
36
43
  import { CLI_TEXT, type CliText, type GenreSource } from "./cli-text.ts";
37
- import { hostLanguage, uiLanguageOf, type UiLanguage } from "./ui.ts";
44
+ import { hostLanguage, sharedLanguage, uiLanguageOf, type UiLanguage } from "./ui.ts";
38
45
  import { profileFor } from "./profile/for-file.ts";
46
+ import { settingProblems } from "./setting-problems.ts";
39
47
 
40
48
  /** Text for output that is not about one document. */
41
49
  const hostText = (config: Config): CliText => CLI_TEXT[hostLanguage(config.language, process.env)];
42
50
 
43
51
  const readConfig = (): Config => (existsSync(join(process.cwd(), CONFIG_FILE)) ? loadConfig(join(process.cwd(), CONFIG_FILE)) : EMPTY);
44
52
 
45
- const resolveGenre = (path: string, source: string, config: Config, cliGenre?: string): { genre: string; from: GenreSource } => {
46
- // コマンドで指定したものが最優先。その実行だけの指定だから、設定より強い。
47
- if (cliGenre !== undefined) return { genre: cliGenre, from: "--genre" };
48
- // パスごとの上書きが次。「全体はこう、ここだけは違う」を書けるようにする。
49
- const override = applyByPath(config.byPath, config.baseDir, path);
50
- if (override.genre !== undefined) return { genre: override.genre, from: "by_path" };
51
- if (config.genre !== undefined) return { genre: config.genre, from: "config" };
52
- const guess = guessGenre(path, source, frontMatterGenre(source));
53
- return guess === undefined ? { genre: "blog/tech", from: "default" } : { genre: guess.genre, from: guess.from };
54
- };
55
-
56
53
  /** resolveGenre with this run's --genre, for the commands that take it as a dependency. */
57
54
  const genreFrom =
58
55
  (argv: readonly string[]) =>
@@ -90,21 +87,16 @@ type Inspected = {
90
87
  readonly all: readonly string[];
91
88
  };
92
89
 
93
- /** The first line for a file, in the file's language. */
94
- const headerFor = (path: string, genre: string, from: GenreSource, language: string, shelved: number, hushed: number): string => {
95
- const text = CLI_TEXT[uiLanguageOf(language)];
96
- return text.header(path, genre, text.languageName(language), text.genreSource[from], shelved, hushed);
97
- };
98
-
99
90
  const inspect = async (path: string, config: Config, argv: readonly string[]): Promise<Inspected> => {
100
- const source = await readFile(path, "utf8");
91
+ const source = plainSource(await readFile(path, "utf8"));
101
92
  const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
102
93
  const adapter = await loadAdapter(language);
103
- const { genre, from } = resolveGenre(path, source, config, flag(argv, "--genre"));
94
+ const { genre, from, unread } = resolveGenre(path, source, config, flag(argv, "--genre"));
95
+ if (unread !== undefined) console.error(`chaff: ${CLI_TEXT[uiLanguageOf(language)].unreadFrontMatterGenre(path, unread, GENRES)}`);
104
96
  const rules = loadRules(language);
105
97
  const experimental = config.experimental || argv.includes("--experimental");
106
98
  await adapter.prepare?.(neededBy(rules, config.rules, experimental, genre, language));
107
- const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language));
99
+ const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language, genre));
108
100
  const raw = runRules(doc, rules, config.rules, experimental, genre, config.limits);
109
101
  // 応答は 3 つ。stet で黙らせたものは、ここで落とす。
110
102
  const applied = applySuppressions(
@@ -115,8 +107,8 @@ const inspect = async (path: string, config: Config, argv: readonly string[]): P
115
107
  const baseline = argv.includes("--show-baseline") ? undefined : readBaseline(join(process.cwd(), BASELINE_FILE));
116
108
  const split = splitByBaseline(path, applied.kept, baseline);
117
109
  const result = { ...raw, findings: split.fresh };
118
- const header = headerFor(path, genre, from, language, split.shelved, applied.suppressed.length);
119
- const text = argv.includes("--compact") ? renderCompact(header, result, rules, language) : renderFriendly(header, result, rules, language);
110
+ const { header, notes } = fileHeader(path, source, language, { genre, from, unread }, { shelved: split.shelved, hushed: applied.suppressed.length });
111
+ const text = argv.includes("--compact") ? renderCompact(header, result, rules, language) : renderFriendly(header, result, rules, language, notes);
120
112
  return {
121
113
  text,
122
114
  rules,
@@ -146,14 +138,14 @@ const writeSarif = (results: readonly Inspected[], argv: readonly string[]): voi
146
138
 
147
139
  /** 効いていない設定は、結果の前に一度だけ言う。標準エラーに出すので、JSON や SARIF の出力は汚さない。 */
148
140
  const warnRuleProblems = (config: Config, language: string): void => {
149
- ruleProblems(config, loadRules(language), hostLanguage(config.language, process.env)).forEach((problem) => console.error(`chaff: ${problem}`));
141
+ const ui = hostLanguage(config.language, process.env);
142
+ [...ruleProblems(config, loadRules(language), ui), ...nameProblems(config, ui)].forEach((problem) => console.error(`chaff: ${problem}`));
150
143
  };
151
144
 
152
145
  /** Several files end with one summary: in their language when they share one, else the host's. */
153
146
  const summaryLanguage = (results: readonly Inspected[], config: Config): UiLanguage => {
154
- const languages = new Set(results.map((result) => uiLanguageOf(result.language)));
155
- const [only] = [...languages];
156
- return languages.size === 1 && only !== undefined ? only : hostLanguage(config.language, process.env);
147
+ const languages = results.map((result) => result.language);
148
+ return sharedLanguage(languages, hostLanguage(config.language, process.env));
157
149
  };
158
150
 
159
151
  const lint = async (targets: readonly string[], argv: readonly string[]): Promise<number> => {
@@ -215,8 +207,10 @@ const runWatch = async (targets: readonly string[], argv: readonly string[]): Pr
215
207
  return new Promise(() => undefined);
216
208
  };
217
209
 
218
- const explain = (ruleId: string | undefined): number => {
210
+ /** genreFlag: --genre, which wins over chaff.yaml's genre here as it does in a run. */
211
+ const explain = (ruleId: string | undefined, genreFlag: string | undefined): number => {
219
212
  const config = readConfig();
213
+ const genre = genreFlag ?? config.genre;
220
214
  // The rule's limits differ by language (characters for Japanese, words for English): explain in the one being written.
221
215
  const language = config.language ?? hostLanguage(undefined, process.env);
222
216
  const text = CLI_TEXT[uiLanguageOf(language)];
@@ -227,8 +221,9 @@ const explain = (ruleId: string | undefined): number => {
227
221
  console.error(text.unknownRuleWithList(ruleId ?? text.unnamed, list));
228
222
  return 1;
229
223
  }
230
- const current = config.rules[rule.id] ?? (rule.status === "experimental" && !config.experimental ? "off" : "normal");
231
- console.log(renderExplain(rule, current, language, text.unit(rule.id, language), config.genre));
224
+ const preset = genre === undefined ? {} : presetLevels(genre);
225
+ const current = config.rules[rule.id] ?? preset[rule.id] ?? (rule.status === "experimental" && !config.experimental ? "off" : "normal");
226
+ console.log(renderExplain(rule, current, language, text.unit(rule.id, language), genre));
232
227
  return 0;
233
228
  };
234
229
 
@@ -265,16 +260,16 @@ type Handler = (argv: readonly string[]) => number | Promise<number>;
265
260
  /** `--` で始まらない引数。対象のパス。 */
266
261
  const positional = (argv: readonly string[]): string[] => targetsOf(argv.slice(1));
267
262
 
268
- const showRules = (): number => {
263
+ const showRules = (genreFlag: string | undefined): number => {
269
264
  const config = readConfig();
270
265
  const language = config.language ?? hostLanguage(undefined, process.env);
271
266
  warnRuleProblems(config, language);
272
- console.log(rulesJson(loadRules(language), config, language, config.genre ?? "blog/tech"));
267
+ console.log(rulesJson(loadRules(language), config, language, genreFlag ?? config.genre ?? "blog/tech"));
273
268
  return 0;
274
269
  };
275
270
 
276
271
  const showGenres = (): number => {
277
- console.log(hostText(readConfig()).genres(GENRES));
272
+ console.log(renderGenres(loadGenres(), hostLanguage(readConfig().language, process.env)));
278
273
  return 0;
279
274
  };
280
275
 
@@ -283,19 +278,28 @@ const treeContext = (): TreeContext => {
283
278
  return { config, flag, ui: hostLanguage(config.language, process.env) };
284
279
  };
285
280
 
281
+ /** What eval and test share: the settings, this run's genre, and the language for what is not about one document. */
282
+ const measureContext = (argv: readonly string[]): { config: Config; resolveGenre: ReturnType<typeof genreFrom>; ui: UiLanguage } => {
283
+ const config = readConfig();
284
+ return { config, resolveGenre: genreFrom(argv), ui: hostLanguage(config.language, process.env) };
285
+ };
286
+
286
287
  /** 分岐を数珠つなぎにせず表にする。足すときに main を太らせない。 */
287
288
  const HANDLERS: Readonly<Record<string, Handler>> = {
288
- init: (argv) => {
289
- runInit(process.cwd(), flag(argv, "--genre") ?? "blog/tech", hostLanguage(readConfig().language, process.env)).forEach((line) => console.log(line));
290
- return 0;
289
+ init: async (argv) => {
290
+ const ui = hostLanguage(readConfig().language, process.env);
291
+ const chosen = await initGenre(flag(argv, "--genre"), ui, process.cwd());
292
+ if ("error" in chosen) console.error(chosen.error);
293
+ else runInit(process.cwd(), chosen.genre, ui).forEach((line) => console.log(line));
294
+ return "error" in chosen ? 1 : 0;
291
295
  },
292
296
  genres: showGenres,
293
- rules: showRules,
294
- explain: (argv) => explain(argv[1]),
295
- eval: (argv) => runEval(positional(argv), argv, { config: readConfig(), resolveGenre: genreFrom(argv), flag }),
297
+ rules: (argv) => showRules(flag(argv, "--genre")),
298
+ explain: (argv) => explain(argv[1], flag(argv, "--genre")),
299
+ eval: (argv) => runEval(positional(argv), argv, { ...measureContext(argv), flag }),
296
300
  tree: (argv) => runTree(treeTargets(argv), argv, treeContext()),
297
301
  cite: (argv) => runCite(citeTargets(argv), argv, treeContext()),
298
- test: (argv) => runTest(positional(argv), argv, { config: readConfig(), resolveGenre: genreFrom(argv), inspect }),
302
+ test: (argv) => runTest(positional(argv), argv, { ...measureContext(argv), inspect }),
299
303
  baseline: (argv) => runBaseline(positional(argv), argv),
300
304
  suppressions: (argv) => runSuppressions(positional(argv), argv),
301
305
  relax: (argv) => changeSetting("relaxed", argv[1], flag(argv, "--why")),
@@ -332,11 +336,11 @@ export const main = async (argv: readonly string[]): Promise<number> => {
332
336
  console.log(VERSION_LINES.join("\n"));
333
337
  return 0;
334
338
  }
335
- const cliGenre = flag(argv, "--genre");
336
- if (cliGenre !== undefined && !GENRES.includes(cliGenre)) {
337
- console.error(hostText(readConfig()).unknownGenre(cliGenre));
338
- return 1;
339
- }
339
+ // 知らないジャンルではどの rule も当たらず、知らない文書の種類では種類の知識が外れる。どちらも素通りに見えるので、何かする前に止める。
340
+ const config = readConfig();
341
+ const problems = settingProblems(first, flag(argv, "--genre"), config, hostText(config));
342
+ problems.forEach((problem) => console.error(problem));
343
+ if (problems.length > 0) return 1;
340
344
  const handler = HANDLERS[first];
341
345
  if (handler !== undefined) return handler(argv);
342
346
  const targets = targetsOf(first === "lint" ? argv.slice(1) : argv);