chaffjs 0.15.0 → 0.17.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 (528) hide show
  1. package/README.md +27 -2
  2. package/dist/address-chain.js +1 -1
  3. package/dist/address-chain.js.map +1 -1
  4. package/dist/backends/anthropic.d.ts +2 -1
  5. package/dist/backends/anthropic.d.ts.map +1 -1
  6. package/dist/backends/anthropic.js +4 -1
  7. package/dist/backends/anthropic.js.map +1 -1
  8. package/dist/backends/openai.d.ts +2 -1
  9. package/dist/backends/openai.d.ts.map +1 -1
  10. package/dist/backends/openai.js +1 -1
  11. package/dist/backends/openai.js.map +1 -1
  12. package/dist/backends/types.d.ts +2 -1
  13. package/dist/backends/types.d.ts.map +1 -1
  14. package/dist/backends/types.js +1 -1
  15. package/dist/backends/types.js.map +1 -1
  16. package/dist/baseline.js +1 -1
  17. package/dist/baseline.js.map +1 -1
  18. package/dist/calendar-number.d.ts +31 -0
  19. package/dist/calendar-number.d.ts.map +1 -0
  20. package/dist/calendar-number.js +58 -0
  21. package/dist/calendar-number.js.map +1 -0
  22. package/dist/cli-args.d.ts +0 -5
  23. package/dist/cli-args.d.ts.map +1 -1
  24. package/dist/cli-args.js +2 -2
  25. package/dist/cli-args.js.map +1 -1
  26. package/dist/cli-text.d.ts +6 -2
  27. package/dist/cli-text.d.ts.map +1 -1
  28. package/dist/cli-text.js +19 -12
  29. package/dist/cli-text.js.map +1 -1
  30. package/dist/cli.d.ts.map +1 -1
  31. package/dist/cli.js +43 -37
  32. package/dist/cli.js.map +1 -1
  33. package/dist/closing-numbers.d.ts +20 -0
  34. package/dist/closing-numbers.d.ts.map +1 -0
  35. package/dist/closing-numbers.js +56 -0
  36. package/dist/closing-numbers.js.map +1 -0
  37. package/dist/commands/cite.d.ts.map +1 -1
  38. package/dist/commands/cite.js +4 -4
  39. package/dist/commands/cite.js.map +1 -1
  40. package/dist/commands/eval.js +1 -1
  41. package/dist/commands/eval.js.map +1 -1
  42. package/dist/commands/init-ask.d.ts +19 -0
  43. package/dist/commands/init-ask.d.ts.map +1 -0
  44. package/dist/commands/init-ask.js +43 -0
  45. package/dist/commands/init-ask.js.map +1 -0
  46. package/dist/commands/test.d.ts +8 -0
  47. package/dist/commands/test.d.ts.map +1 -1
  48. package/dist/commands/test.js +78 -44
  49. package/dist/commands/test.js.map +1 -1
  50. package/dist/commands/tree.d.ts +1 -8
  51. package/dist/commands/tree.d.ts.map +1 -1
  52. package/dist/commands/tree.js +7 -6
  53. package/dist/commands/tree.js.map +1 -1
  54. package/dist/config/by-path.d.ts.map +1 -1
  55. package/dist/config/by-path.js +10 -9
  56. package/dist/config/by-path.js.map +1 -1
  57. package/dist/config/load.d.ts +4 -0
  58. package/dist/config/load.d.ts.map +1 -1
  59. package/dist/config/load.js +19 -0
  60. package/dist/config/load.js.map +1 -1
  61. package/dist/config/name-problems.d.ts +5 -0
  62. package/dist/config/name-problems.d.ts.map +1 -0
  63. package/dist/config/name-problems.js +11 -0
  64. package/dist/config/name-problems.js.map +1 -0
  65. package/dist/date-stamp.js +1 -1
  66. package/dist/date-stamp.js.map +1 -1
  67. package/dist/detectors/acronym-context.d.ts +2 -0
  68. package/dist/detectors/acronym-context.d.ts.map +1 -1
  69. package/dist/detectors/acronym-context.js +13 -0
  70. package/dist/detectors/acronym-context.js.map +1 -1
  71. package/dist/detectors/acronym-expansion.d.ts +2 -0
  72. package/dist/detectors/acronym-expansion.d.ts.map +1 -1
  73. package/dist/detectors/acronym-expansion.js +104 -1
  74. package/dist/detectors/acronym-expansion.js.map +1 -1
  75. package/dist/detectors/agreement-slip.d.ts +32 -0
  76. package/dist/detectors/agreement-slip.d.ts.map +1 -0
  77. package/dist/detectors/agreement-slip.js +137 -0
  78. package/dist/detectors/agreement-slip.js.map +1 -0
  79. package/dist/detectors/char-shape.d.ts.map +1 -1
  80. package/dist/detectors/char-shape.js +16 -6
  81. package/dist/detectors/char-shape.js.map +1 -1
  82. package/dist/detectors/content-morphemes.d.ts +4 -0
  83. package/dist/detectors/content-morphemes.d.ts.map +1 -0
  84. package/dist/detectors/content-morphemes.js +12 -0
  85. package/dist/detectors/content-morphemes.js.map +1 -0
  86. package/dist/detectors/en-shape.d.ts +0 -1
  87. package/dist/detectors/en-shape.d.ts.map +1 -1
  88. package/dist/detectors/en-shape.js +2 -143
  89. package/dist/detectors/en-shape.js.map +1 -1
  90. package/dist/detectors/enumerated-runs.d.ts.map +1 -1
  91. package/dist/detectors/enumerated-runs.js +4 -2
  92. package/dist/detectors/enumerated-runs.js.map +1 -1
  93. package/dist/detectors/gram-place.js +1 -1
  94. package/dist/detectors/gram-place.js.map +1 -1
  95. package/dist/detectors/heading-case.d.ts.map +1 -1
  96. package/dist/detectors/heading-case.js +3 -1
  97. package/dist/detectors/heading-case.js.map +1 -1
  98. package/dist/detectors/heading-echo.d.ts.map +1 -1
  99. package/dist/detectors/heading-echo.js +12 -18
  100. package/dist/detectors/heading-echo.js.map +1 -1
  101. package/dist/detectors/heading-overlap.d.ts +13 -0
  102. package/dist/detectors/heading-overlap.d.ts.map +1 -0
  103. package/dist/detectors/heading-overlap.js +52 -0
  104. package/dist/detectors/heading-overlap.js.map +1 -0
  105. package/dist/detectors/index.d.ts.map +1 -1
  106. package/dist/detectors/index.js +10 -4
  107. package/dist/detectors/index.js.map +1 -1
  108. package/dist/detectors/letter-joined-name.d.ts +13 -0
  109. package/dist/detectors/letter-joined-name.d.ts.map +1 -0
  110. package/dist/detectors/letter-joined-name.js +11 -0
  111. package/dist/detectors/letter-joined-name.js.map +1 -0
  112. package/dist/detectors/lettered-index.d.ts +9 -0
  113. package/dist/detectors/lettered-index.d.ts.map +1 -0
  114. package/dist/detectors/lettered-index.js +54 -0
  115. package/dist/detectors/lettered-index.js.map +1 -0
  116. package/dist/detectors/lexicon.d.ts +5 -1
  117. package/dist/detectors/lexicon.d.ts.map +1 -1
  118. package/dist/detectors/lexicon.js +51 -5
  119. package/dist/detectors/lexicon.js.map +1 -1
  120. package/dist/detectors/orthography.d.ts.map +1 -1
  121. package/dist/detectors/orthography.js +24 -6
  122. package/dist/detectors/orthography.js.map +1 -1
  123. package/dist/detectors/oxford-comma.d.ts +3 -0
  124. package/dist/detectors/oxford-comma.d.ts.map +1 -0
  125. package/dist/detectors/oxford-comma.js +340 -0
  126. package/dist/detectors/oxford-comma.js.map +1 -0
  127. package/dist/detectors/phrase-match.d.ts +3 -1
  128. package/dist/detectors/phrase-match.d.ts.map +1 -1
  129. package/dist/detectors/phrase-match.js +20 -15
  130. package/dist/detectors/phrase-match.js.map +1 -1
  131. package/dist/detectors/repeated-head.d.ts.map +1 -1
  132. package/dist/detectors/repeated-head.js +5 -4
  133. package/dist/detectors/repeated-head.js.map +1 -1
  134. package/dist/detectors/signals.d.ts.map +1 -1
  135. package/dist/detectors/signals.js +45 -16
  136. package/dist/detectors/signals.js.map +1 -1
  137. package/dist/detectors/stacked-hedge.d.ts +16 -0
  138. package/dist/detectors/stacked-hedge.d.ts.map +1 -0
  139. package/dist/detectors/stacked-hedge.js +67 -0
  140. package/dist/detectors/stacked-hedge.js.map +1 -0
  141. package/dist/detectors/stray-space.d.ts +7 -0
  142. package/dist/detectors/stray-space.d.ts.map +1 -0
  143. package/dist/detectors/stray-space.js +34 -0
  144. package/dist/detectors/stray-space.js.map +1 -0
  145. package/dist/detectors/structure.d.ts +3 -1
  146. package/dist/detectors/structure.d.ts.map +1 -1
  147. package/dist/detectors/structure.js +4 -2
  148. package/dist/detectors/structure.js.map +1 -1
  149. package/dist/detectors/superlative-amount.d.ts +9 -0
  150. package/dist/detectors/superlative-amount.d.ts.map +1 -0
  151. package/dist/detectors/superlative-amount.js +20 -0
  152. package/dist/detectors/superlative-amount.js.map +1 -0
  153. package/dist/detectors/superlative-clause.d.ts +18 -0
  154. package/dist/detectors/superlative-clause.d.ts.map +1 -0
  155. package/dist/detectors/superlative-clause.js +61 -0
  156. package/dist/detectors/superlative-clause.js.map +1 -0
  157. package/dist/detectors/superlative-scope.d.ts +6 -0
  158. package/dist/detectors/superlative-scope.d.ts.map +1 -1
  159. package/dist/detectors/superlative-scope.js +3 -3
  160. package/dist/detectors/superlative-scope.js.map +1 -1
  161. package/dist/detectors/team.d.ts +0 -6
  162. package/dist/detectors/team.d.ts.map +1 -1
  163. package/dist/detectors/team.js +17 -1
  164. package/dist/detectors/team.js.map +1 -1
  165. package/dist/detectors/token-shape.d.ts.map +1 -1
  166. package/dist/detectors/token-shape.js +8 -6
  167. package/dist/detectors/token-shape.js.map +1 -1
  168. package/dist/document.d.ts +3 -0
  169. package/dist/document.d.ts.map +1 -1
  170. package/dist/document.js +33 -6
  171. package/dist/document.js.map +1 -1
  172. package/dist/eval.js +1 -1
  173. package/dist/eval.js.map +1 -1
  174. package/dist/feedback/draft.js +1 -1
  175. package/dist/feedback/draft.js.map +1 -1
  176. package/dist/feedback/excerpt.d.ts +0 -1
  177. package/dist/feedback/excerpt.d.ts.map +1 -1
  178. package/dist/feedback/excerpt.js +2 -2
  179. package/dist/feedback/excerpt.js.map +1 -1
  180. package/dist/file-header.d.ts +20 -0
  181. package/dist/file-header.d.ts.map +1 -0
  182. package/dist/file-header.js +21 -0
  183. package/dist/file-header.js.map +1 -0
  184. package/dist/files.js +3 -3
  185. package/dist/files.js.map +1 -1
  186. package/dist/genre-check.js +1 -1
  187. package/dist/genre-check.js.map +1 -1
  188. package/dist/genre-load.d.ts +6 -0
  189. package/dist/genre-load.d.ts.map +1 -0
  190. package/dist/genre-load.js +22 -0
  191. package/dist/genre-load.js.map +1 -0
  192. package/dist/genre-parse.d.ts +44 -0
  193. package/dist/genre-parse.d.ts.map +1 -0
  194. package/dist/genre-parse.js +117 -0
  195. package/dist/genre-parse.js.map +1 -0
  196. package/dist/genre-suggest.d.ts +15 -0
  197. package/dist/genre-suggest.d.ts.map +1 -0
  198. package/dist/genre-suggest.js +40 -0
  199. package/dist/genre-suggest.js.map +1 -0
  200. package/dist/genre.d.ts +1 -1
  201. package/dist/genre.d.ts.map +1 -1
  202. package/dist/genre.js +3 -13
  203. package/dist/genre.js.map +1 -1
  204. package/dist/init-choice.d.ts +12 -0
  205. package/dist/init-choice.d.ts.map +1 -0
  206. package/dist/init-choice.js +41 -0
  207. package/dist/init-choice.js.map +1 -0
  208. package/dist/init.d.ts.map +1 -1
  209. package/dist/init.js +16 -5
  210. package/dist/init.js.map +1 -1
  211. package/dist/judge.d.ts +2 -1
  212. package/dist/judge.d.ts.map +1 -1
  213. package/dist/judge.js +1 -1
  214. package/dist/judge.js.map +1 -1
  215. package/dist/line-paragraphs.d.ts +4 -0
  216. package/dist/line-paragraphs.d.ts.map +1 -0
  217. package/dist/line-paragraphs.js +71 -0
  218. package/dist/line-paragraphs.js.map +1 -0
  219. package/dist/mask.d.ts.map +1 -1
  220. package/dist/mask.js +2 -10
  221. package/dist/mask.js.map +1 -1
  222. package/dist/number-name.js +1 -1
  223. package/dist/number-name.js.map +1 -1
  224. package/dist/opening-line.d.ts +3 -0
  225. package/dist/opening-line.d.ts.map +1 -0
  226. package/dist/opening-line.js +14 -0
  227. package/dist/opening-line.js.map +1 -0
  228. package/dist/orthography.d.ts +5 -1
  229. package/dist/orthography.d.ts.map +1 -1
  230. package/dist/orthography.js +7 -1
  231. package/dist/orthography.js.map +1 -1
  232. package/dist/paragraph-length.d.ts +7 -0
  233. package/dist/paragraph-length.d.ts.map +1 -0
  234. package/dist/paragraph-length.js +10 -0
  235. package/dist/paragraph-length.js.map +1 -0
  236. package/dist/plugin.d.ts +12 -0
  237. package/dist/plugin.d.ts.map +1 -1
  238. package/dist/profile/check.d.ts +28 -0
  239. package/dist/profile/check.d.ts.map +1 -0
  240. package/dist/profile/check.js +12 -0
  241. package/dist/profile/check.js.map +1 -0
  242. package/dist/profile/for-file.d.ts +2 -2
  243. package/dist/profile/for-file.d.ts.map +1 -1
  244. package/dist/profile/for-file.js +9 -2
  245. package/dist/profile/for-file.js.map +1 -1
  246. package/dist/profile/load.js +1 -1
  247. package/dist/profile/load.js.map +1 -1
  248. package/dist/profile/select.d.ts +4 -2
  249. package/dist/profile/select.d.ts.map +1 -1
  250. package/dist/profile/select.js +8 -3
  251. package/dist/profile/select.js.map +1 -1
  252. package/dist/quoted-span.d.ts +13 -3
  253. package/dist/quoted-span.d.ts.map +1 -1
  254. package/dist/quoted-span.js +16 -11
  255. package/dist/quoted-span.js.map +1 -1
  256. package/dist/reasons.d.ts +1 -0
  257. package/dist/reasons.d.ts.map +1 -1
  258. package/dist/reasons.js +4 -1
  259. package/dist/reasons.js.map +1 -1
  260. package/dist/render/compact.d.ts.map +1 -1
  261. package/dist/render/compact.js +1 -2
  262. package/dist/render/compact.js.map +1 -1
  263. package/dist/render/eval.d.ts.map +1 -1
  264. package/dist/render/eval.js +4 -3
  265. package/dist/render/eval.js.map +1 -1
  266. package/dist/render/friendly.d.ts +2 -1
  267. package/dist/render/friendly.d.ts.map +1 -1
  268. package/dist/render/friendly.js +14 -6
  269. package/dist/render/friendly.js.map +1 -1
  270. package/dist/render/genres.d.ts +5 -0
  271. package/dist/render/genres.d.ts.map +1 -0
  272. package/dist/render/genres.js +28 -0
  273. package/dist/render/genres.js.map +1 -0
  274. package/dist/render/plural.d.ts +3 -0
  275. package/dist/render/plural.d.ts.map +1 -0
  276. package/dist/render/plural.js +5 -0
  277. package/dist/render/plural.js.map +1 -0
  278. package/dist/render/rules-json.d.ts.map +1 -1
  279. package/dist/render/rules-json.js +9 -3
  280. package/dist/render/rules-json.js.map +1 -1
  281. package/dist/render/semantic.d.ts +3 -2
  282. package/dist/render/semantic.d.ts.map +1 -1
  283. package/dist/render/semantic.js +94 -34
  284. package/dist/render/semantic.js.map +1 -1
  285. package/dist/render/summary.d.ts.map +1 -1
  286. package/dist/render/summary.js +4 -3
  287. package/dist/render/summary.js.map +1 -1
  288. package/dist/render/suppressions.d.ts.map +1 -1
  289. package/dist/render/suppressions.js +3 -2
  290. package/dist/render/suppressions.js.map +1 -1
  291. package/dist/render/text.d.ts +2 -0
  292. package/dist/render/text.d.ts.map +1 -1
  293. package/dist/render/text.js +11 -3
  294. package/dist/render/text.js.map +1 -1
  295. package/dist/rule-load.d.ts.map +1 -1
  296. package/dist/rule-load.js +12 -1
  297. package/dist/rule-load.js.map +1 -1
  298. package/dist/run-semantic.d.ts +3 -1
  299. package/dist/run-semantic.d.ts.map +1 -1
  300. package/dist/run-semantic.js +9 -4
  301. package/dist/run-semantic.js.map +1 -1
  302. package/dist/run.d.ts +4 -0
  303. package/dist/run.d.ts.map +1 -1
  304. package/dist/run.js +29 -16
  305. package/dist/run.js.map +1 -1
  306. package/dist/semantic.d.ts +1 -1
  307. package/dist/semantic.d.ts.map +1 -1
  308. package/dist/semantic.js +10 -3
  309. package/dist/semantic.js.map +1 -1
  310. package/dist/sentence-shape.d.ts +0 -1
  311. package/dist/sentence-shape.d.ts.map +1 -1
  312. package/dist/sentence-shape.js +3 -5
  313. package/dist/sentence-shape.js.map +1 -1
  314. package/dist/setting-problems.d.ts +5 -0
  315. package/dist/setting-problems.d.ts.map +1 -0
  316. package/dist/setting-problems.js +15 -0
  317. package/dist/setting-problems.js.map +1 -0
  318. package/dist/span-merge.d.ts +4 -0
  319. package/dist/span-merge.d.ts.map +1 -0
  320. package/dist/span-merge.js +12 -0
  321. package/dist/span-merge.js.map +1 -0
  322. package/dist/speaker-labels.d.ts +4 -0
  323. package/dist/speaker-labels.d.ts.map +1 -0
  324. package/dist/speaker-labels.js +106 -0
  325. package/dist/speaker-labels.js.map +1 -0
  326. package/dist/stray-space.d.ts +17 -0
  327. package/dist/stray-space.d.ts.map +1 -0
  328. package/dist/stray-space.js +66 -0
  329. package/dist/stray-space.js.map +1 -0
  330. package/dist/structure/build.js +5 -5
  331. package/dist/structure/build.js.map +1 -1
  332. package/dist/structure/cite.js +1 -1
  333. package/dist/structure/cite.js.map +1 -1
  334. package/dist/structure/issues.d.ts.map +1 -1
  335. package/dist/structure/issues.js +5 -5
  336. package/dist/structure/issues.js.map +1 -1
  337. package/dist/structure/markdown-path.d.ts.map +1 -1
  338. package/dist/structure/markdown-path.js +2 -2
  339. package/dist/structure/markdown-path.js.map +1 -1
  340. package/dist/structure/numbering.d.ts.map +1 -1
  341. package/dist/structure/numbering.js +1 -1
  342. package/dist/structure/numbering.js.map +1 -1
  343. package/dist/structure/relative-find.js +1 -1
  344. package/dist/structure/relative-find.js.map +1 -1
  345. package/dist/structure/relative-resolve.d.ts.map +1 -1
  346. package/dist/structure/relative-resolve.js +26 -20
  347. package/dist/structure/relative-resolve.js.map +1 -1
  348. package/dist/structure/sexp.js +1 -1
  349. package/dist/structure/sexp.js.map +1 -1
  350. package/dist/structure/unnumbered.js +2 -2
  351. package/dist/structure/unnumbered.js.map +1 -1
  352. package/dist/team-names.d.ts +9 -0
  353. package/dist/team-names.d.ts.map +1 -0
  354. package/dist/team-names.js +31 -0
  355. package/dist/team-names.js.map +1 -0
  356. package/dist/ui.d.ts +2 -0
  357. package/dist/ui.d.ts.map +1 -1
  358. package/dist/ui.js +6 -0
  359. package/dist/ui.js.map +1 -1
  360. package/dist/version.js +1 -1
  361. package/dist/version.js.map +1 -1
  362. package/dist/watch.d.ts +2 -2
  363. package/dist/watch.d.ts.map +1 -1
  364. package/dist/watch.js +5 -5
  365. package/dist/watch.js.map +1 -1
  366. package/genres.yaml +395 -0
  367. package/package.json +4 -3
  368. package/rules/adverb-overuse.yaml +2 -2
  369. package/rules/agentless-passive.yaml +1 -1
  370. package/rules/agreement-slip.yaml +36 -0
  371. package/rules/ai-generated-composite.yaml +2 -2
  372. package/rules/ai-tell.yaml +2 -2
  373. package/rules/bold-density.yaml +2 -2
  374. package/rules/closing-cliche.yaml +1 -1
  375. package/rules/concrete-evidence-density.yaml +2 -2
  376. package/rules/contraction-consistency.yaml +1 -1
  377. package/rules/cushion-phrase-density.yaml +1 -1
  378. package/rules/dangling-reference.yaml +1 -1
  379. package/rules/date-order.yaml +1 -1
  380. package/rules/date-weekday-mismatch.yaml +1 -1
  381. package/rules/double-keigo.yaml +1 -1
  382. package/rules/doubled-word.yaml +1 -1
  383. package/rules/duplicate-definition.yaml +1 -1
  384. package/rules/emoji-density.yaml +1 -1
  385. package/rules/empty-conclusion.yaml +3 -1
  386. package/rules/empty-intensifier.yaml +1 -1
  387. package/rules/excessive-hedging.yaml +16 -6
  388. package/rules/expletive-construction.yaml +2 -2
  389. package/rules/heading-echo.yaml +2 -1
  390. package/rules/hiragana-fukushi.yaml +1 -1
  391. package/rules/internal-jargon.yaml +2 -2
  392. package/rules/latin-spacing.yaml +5 -4
  393. package/rules/max-kanji-continuous.yaml +4 -3
  394. package/rules/max-paragraph-length.yaml +18 -4
  395. package/rules/max-sentence-length.yaml +18 -2
  396. package/rules/ngram-repetition.yaml +4 -4
  397. package/rules/no-doubled-joshi.yaml +2 -2
  398. package/rules/no-em-dash.yaml +2 -2
  399. package/rules/no-mixed-desumasu.yaml +1 -1
  400. package/rules/no-nakaguro-parallel.yaml +2 -2
  401. package/rules/numbering-gap.yaml +1 -1
  402. package/rules/oxford-comma-consistency.yaml +2 -1
  403. package/rules/padded-intro.yaml +2 -1
  404. package/rules/paragraph-length-variance.yaml +1 -1
  405. package/rules/preamble-length.yaml +2 -2
  406. package/rules/preferred-term.yaml +1 -1
  407. package/rules/proper-noun-density.yaml +3 -2
  408. package/rules/repeated-conjunction.yaml +2 -2
  409. package/rules/repeated-sentence-head.yaml +2 -2
  410. package/rules/required-sections.yaml +1 -1
  411. package/rules/rule-of-three.yaml +2 -2
  412. package/rules/sasete-itadaku.yaml +3 -3
  413. package/rules/section-length-uniformity.yaml +1 -1
  414. package/rules/sentence-initial-conjunction-run.yaml +2 -2
  415. package/rules/sentence-rhythm.yaml +1 -1
  416. package/rules/stray-space.yaml +38 -0
  417. package/rules/taigen-dome-in-prose.yaml +2 -2
  418. package/rules/title-case-consistency.yaml +1 -1
  419. package/rules/total-mismatch.yaml +1 -1
  420. package/rules/undefined-acronym.yaml +5 -4
  421. package/rules/unqualified-superlative.yaml +2 -2
  422. package/skills/chaff/SKILL.md +28 -9
  423. package/src/address-chain.ts +1 -1
  424. package/src/backends/anthropic.ts +5 -1
  425. package/src/backends/openai.ts +2 -1
  426. package/src/backends/types.ts +3 -2
  427. package/src/baseline.ts +1 -1
  428. package/src/calendar-number.ts +84 -0
  429. package/src/cli-args.ts +2 -2
  430. package/src/cli-text.ts +29 -15
  431. package/src/cli.ts +42 -41
  432. package/src/closing-numbers.ts +72 -0
  433. package/src/commands/cite.ts +4 -4
  434. package/src/commands/eval.ts +1 -1
  435. package/src/commands/init-ask.ts +53 -0
  436. package/src/commands/test.ts +118 -56
  437. package/src/commands/tree.ts +7 -6
  438. package/src/config/by-path.ts +8 -9
  439. package/src/config/load.ts +24 -0
  440. package/src/config/name-problems.ts +15 -0
  441. package/src/date-stamp.ts +1 -1
  442. package/src/detectors/acronym-context.ts +16 -0
  443. package/src/detectors/acronym-expansion.ts +132 -1
  444. package/src/detectors/agreement-slip.ts +191 -0
  445. package/src/detectors/char-shape.ts +21 -9
  446. package/src/detectors/content-morphemes.ts +19 -0
  447. package/src/detectors/en-shape.ts +2 -173
  448. package/src/detectors/enumerated-runs.ts +3 -2
  449. package/src/detectors/gram-place.ts +1 -1
  450. package/src/detectors/heading-case.ts +4 -1
  451. package/src/detectors/heading-echo.ts +12 -20
  452. package/src/detectors/heading-overlap.ts +65 -0
  453. package/src/detectors/index.ts +10 -4
  454. package/src/detectors/letter-joined-name.ts +15 -0
  455. package/src/detectors/lettered-index.ts +78 -0
  456. package/src/detectors/lexicon.ts +69 -8
  457. package/src/detectors/orthography.ts +31 -7
  458. package/src/detectors/oxford-comma.ts +402 -0
  459. package/src/detectors/phrase-match.ts +23 -12
  460. package/src/detectors/repeated-head.ts +3 -4
  461. package/src/detectors/signals.ts +47 -15
  462. package/src/detectors/stacked-hedge.ts +96 -0
  463. package/src/detectors/stray-space.ts +40 -0
  464. package/src/detectors/structure.ts +10 -2
  465. package/src/detectors/superlative-amount.ts +24 -0
  466. package/src/detectors/superlative-clause.ts +82 -0
  467. package/src/detectors/superlative-scope.ts +3 -3
  468. package/src/detectors/team.ts +18 -2
  469. package/src/detectors/token-shape.ts +8 -10
  470. package/src/document.ts +54 -7
  471. package/src/eval.ts +1 -1
  472. package/src/feedback/draft.ts +1 -1
  473. package/src/feedback/excerpt.ts +2 -2
  474. package/src/file-header.ts +31 -0
  475. package/src/files.ts +3 -3
  476. package/src/genre-check.ts +1 -1
  477. package/src/genre-load.ts +24 -0
  478. package/src/genre-parse.ts +151 -0
  479. package/src/genre-suggest.ts +50 -0
  480. package/src/genre.ts +4 -13
  481. package/src/init-choice.ts +47 -0
  482. package/src/init.ts +20 -6
  483. package/src/judge.ts +3 -1
  484. package/src/line-paragraphs.ts +86 -0
  485. package/src/mask.ts +2 -12
  486. package/src/number-name.ts +1 -1
  487. package/src/opening-line.ts +17 -0
  488. package/src/orthography.ts +10 -2
  489. package/src/paragraph-length.ts +9 -0
  490. package/src/plugin.ts +12 -0
  491. package/src/profile/check.ts +34 -0
  492. package/src/profile/for-file.ts +10 -3
  493. package/src/profile/load.ts +1 -1
  494. package/src/profile/select.ts +11 -4
  495. package/src/quoted-span.ts +26 -12
  496. package/src/reasons.ts +5 -1
  497. package/src/render/compact.ts +1 -3
  498. package/src/render/eval.ts +4 -3
  499. package/src/render/friendly.ts +21 -6
  500. package/src/render/genres.ts +31 -0
  501. package/src/render/plural.ts +6 -0
  502. package/src/render/rules-json.ts +10 -3
  503. package/src/render/semantic.ts +117 -34
  504. package/src/render/summary.ts +4 -3
  505. package/src/render/suppressions.ts +3 -2
  506. package/src/render/text.ts +18 -3
  507. package/src/rule-load.ts +14 -1
  508. package/src/run-semantic.ts +29 -21
  509. package/src/run.ts +33 -16
  510. package/src/semantic.ts +11 -3
  511. package/src/sentence-shape.ts +3 -7
  512. package/src/setting-problems.ts +24 -0
  513. package/src/span-merge.ts +13 -0
  514. package/src/speaker-labels.ts +129 -0
  515. package/src/stray-space.ts +81 -0
  516. package/src/structure/build.ts +5 -5
  517. package/src/structure/cite.ts +1 -1
  518. package/src/structure/issues.ts +6 -5
  519. package/src/structure/markdown-path.ts +2 -2
  520. package/src/structure/numbering.ts +1 -1
  521. package/src/structure/relative-find.ts +1 -1
  522. package/src/structure/relative-resolve.ts +34 -19
  523. package/src/structure/sexp.ts +1 -1
  524. package/src/structure/unnumbered.ts +2 -2
  525. package/src/team-names.ts +39 -0
  526. package/src/ui.ts +7 -0
  527. package/src/version.ts +1 -1
  528. package/src/watch.ts +5 -5
@@ -16,7 +16,7 @@ why:
16
16
 
17
17
  message:
18
18
  ja: 接続詞で始まる文が {count} 文続いています({limit} 文まで)
19
- en: "{count} sentences in a row open with a conjunction (limit {limit})"
19
+ en: "{count} {count|sentence|sentences} in a row {count|opens|open} with a conjunction (limit {limit})"
20
20
 
21
21
  how_to_fix:
22
22
  ja: どれか 1 つを残して、他は前の文に繋ぐか独立させてください。
@@ -24,4 +24,4 @@ how_to_fix:
24
24
 
25
25
  levels: { strict: 1, normal: 2, relaxed: 4 }
26
26
 
27
- use_for: [blog, business]
27
+ use_for: [blog, business, legal, docs, academic, literature, speech]
@@ -27,4 +27,4 @@ levels:
27
27
  normal: 30
28
28
  relaxed: 20
29
29
 
30
- use_for: [blog]
30
+ use_for: [blog, legal, docs, academic, literature, speech]
@@ -0,0 +1,38 @@
1
+ id: stray-space
2
+ layer: L3
3
+ status: experimental
4
+ how_to_find: stray-space
5
+ # どの境目が一続きの語句の中かは、語の品詞で決める。品詞が無ければ動かない。
6
+ requires: [pos]
7
+ languages: [ja]
8
+ severity: warning
9
+
10
+ name:
11
+ ja: 語句の途中に空白がある
12
+ en: Stray space inside a phrase
13
+
14
+ why:
15
+ ja: 「こころさんが 払った」「確認 しました」のように、一続きに読む語句の途中に空白が入ると、読み手はそこで区切れるのかと迷います。打ち直しや貼り付けで残りやすく、目で見ても気づきにくい空白です。chaff は二つの境目を別々に数えます。助詞の後ろ(「が 払った」、文節の切れ目)と、文節を始められない語の前(「アプリ が」の助詞、「行き ます」の助動詞、「確認 しました」の「する」)です。文節ごとに空けて書く分かち書き(子ども向けの文や、日本語を学ぶ人向けの文)は書き方の一つなので、chaff はどちらが正しいかを決めず、文書の中で空けたほうが少ないときだけ、その空白を指摘します。名詞どうしのあいだ(「担当 田中」の名札と値、「赤坂 文弥」の名前、「サムネイル キャッシュ」の複合語)は書き方として空けることが多いので数えません。句点で終わらない行(歌、名札と値、表のような行)、見出し、表、行頭の全角空白の字下げ、行頭の項目の記号(「イ 」)の後ろ、Markdown の改行、鉤括弧(「」『』)で引いたものの中、英字・数字との境目(latin-spacing が見ます)も数えません。
16
+ en: A space inside what reads as one phrase in Japanese (こころさんが 払った, 確認 しました) makes the reader wonder whether the phrase breaks there. It is typically left over from retyping or pasting and is easy to miss by eye. chaff counts two kinds of joint separately, after a particle (が 払った, the break between phrases) and before a word that cannot start a phrase (a particle as in アプリ が, an auxiliary as in 行き ます, or する after a noun as in 確認 しました). Spacing every phrase (分かち書き, as in text for children or for learners of Japanese) is a way of writing, so chaff does not decide which is right; it reports the spaces only when the document spaces less often than it does not. A space between two nouns (a label and its value as in 担当 田中, a name as in 赤坂 文弥, a compound as in サムネイル キャッシュ) is often written on purpose and is not counted. Neither are lines that do not end with a full stop (verse, labels and values, table-like lines), headings, tables, a full-width indent at the start of a line, the space after an item mark at the start of a line (イ ), a Markdown line break, the inside of a quotation in 「」 or 『』, or a boundary with Latin letters or digits (latin-spacing looks at those).
17
+
18
+ message:
19
+ ja: "「{before}」と「{after}」のあいだに空白があります(この文書では {of} 箇所のうち {count} 箇所が空いている)"
20
+ en: 'Space between "{before}" and "{after}" ({count} of {of} in this document are spaced)'
21
+
22
+ messages:
23
+ between-phrases:
24
+ ja: "助詞「{before}」と「{after}」のあいだに空白があります(この文書は文節のあいだをふつう詰めて書く。{of} 箇所のうち {count} 箇所が空いている)"
25
+ en: 'Space after the particle "{before}" before "{after}" (this document usually writes phrases without a space; {count} of {of} are spaced)'
26
+ inside-phrase:
27
+ ja: "「{before}」と「{after}」は一続きの語句ですが、あいだに空白があります({of} 箇所のうち {count} 箇所が空いている)"
28
+ en: '"{before}" and "{after}" read as one phrase but are separated by a space ({count} of {of} are spaced)'
29
+
30
+ how_to_fix:
31
+ ja: 空白を消して詰めてください。文節ごとに空ける書き方をしているなら、文書全体でそろえてください。
32
+ en: Delete the space. If the document spaces its phrases on purpose, do so throughout.
33
+
34
+ # 何箇所空いていれば指摘するか。1 は「1 箇所でも空いていれば」。
35
+ levels: { strict: 1, normal: 1, relaxed: 3 }
36
+
37
+ # 歌の行は句点で終わらないので、ジャンルを問わず数えない。
38
+ use_for: [business, blog, technical, legal, docs, academic, literature, speech]
@@ -16,7 +16,7 @@ why:
16
16
 
17
17
  message:
18
18
  ja: 本文に体言止めが {count} 文あります({limit} 文まで)
19
- en: "{count} sentences end on a noun in the body text (limit {limit})"
19
+ en: "{count} {count|sentence|sentences} {count|ends|end} on a noun in the body text (limit {limit})"
20
20
 
21
21
  how_to_fix:
22
22
  ja: 述語を足してください。「〜が課題。」を「〜が課題です。」に。
@@ -24,4 +24,4 @@ how_to_fix:
24
24
 
25
25
  levels: { strict: 2, normal: 4, relaxed: 8 }
26
26
 
27
- use_for: [blog, business, technical]
27
+ use_for: [blog, business, technical, legal, docs, academic, literature, speech]
@@ -23,4 +23,4 @@ how_to_fix:
23
23
 
24
24
  levels: { strict: 3, normal: 2, relaxed: 1 }
25
25
 
26
- use_for: [blog, business, technical]
26
+ use_for: [blog, business, technical, legal, docs, academic, literature, speech]
@@ -23,4 +23,4 @@ how_to_fix:
23
23
 
24
24
  levels: { strict: 1, normal: 1, relaxed: 1 }
25
25
 
26
- use_for: [business, technical, blog]
26
+ use_for: [business, technical, blog, legal, docs, academic, literature, speech]
@@ -14,6 +14,7 @@ extra_word_lists:
14
14
  emphasis-word,
15
15
  numbered-division,
16
16
  honorific,
17
+ name-title,
17
18
  date-time-unit,
18
19
  http-method,
19
20
  ]
@@ -30,12 +31,12 @@ why:
30
31
 
31
32
  message:
32
33
  ja: 「{word}」が説明なしで使われています(説明のない略語 {count} 個)
33
- en: '"{word}" is used without an expansion ({count} such acronyms)'
34
+ en: '"{word}" is used without an expansion ({count} such {count|acronym|acronyms})'
34
35
 
35
36
  how_to_fix:
36
- ja: 初出のところで括弧に入れて展開してください。「CI(継続的インテグレーション)」のように。
37
- en: Expand it in brackets at first use.
37
+ ja: 初出のところで括弧に入れて展開してください。「CI(継続的インテグレーション)」のように。読み手が名前として知っている略語(社名など)なら、chaff.yaml の names に並べると求めません。
38
+ en: Expand it in brackets at first use. If your readers know it as a name (a company, say), list it under names in chaff.yaml and it is not asked for.
38
39
 
39
40
  levels: { strict: 1, normal: 3, relaxed: 6 }
40
41
 
41
- use_for: [business, technical]
42
+ use_for: [business, technical, legal, docs, academic, literature, speech]
@@ -17,7 +17,7 @@ why:
17
17
 
18
18
  message:
19
19
  ja: 「{matched}」に比べる相手がありません(そういう言い方が {count} 箇所)
20
- en: '"{matched}" has nothing to compare against ({count} such claims)'
20
+ en: '"{matched}" has nothing to compare against ({count} such {count|claim|claims})'
21
21
 
22
22
  how_to_fix:
23
23
  ja: 比較対象か条件を添えてください。「社内の 3 案のうち最も速い」のように。
@@ -25,4 +25,4 @@ how_to_fix:
25
25
 
26
26
  levels: { strict: 1, normal: 2, relaxed: 4 }
27
27
 
28
- use_for: [business, blog]
28
+ use_for: [business, blog, legal, docs, academic, literature, speech]
@@ -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
 
@@ -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,84 @@
1
+ import { digitRunAround } from "./number-name.ts";
2
+ import type { ProseDocument, 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)。unit は数の後ろの単位(月)。 */
16
+ export type CalendarRun = { readonly run: Span; readonly place: "first" | "inner"; readonly unit: string };
17
+
18
+ /** 数の並びと、その後ろの単位の終わり(文の中の位置)。rank は chained の中の単位の順(無ければ -1)、anchored は単位だけで日付・時刻と分かるもの。 */
19
+ type Part = { readonly run: Span; readonly unit: string; 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
+ export 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, unit: unit.surface, 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", unit: part.unit })));
75
+ };
76
+
77
+ const patternList = (doc: ProseDocument, id: string): string[] => (doc.lexicons[id] ?? []).map((entry) => entry.pattern);
78
+
79
+ /** 文書の言語の、日付・時刻の単位の語彙表。表の無い言語では空で、日付・時刻の数は見つからない。 */
80
+ export const calendarUnitsOf = (doc: ProseDocument): CalendarUnits => ({
81
+ chained: patternList(doc, "date-time-unit"),
82
+ positional: new Set(patternList(doc, "calendar-unit")),
83
+ year: new Set(patternList(doc, "calendar-year-unit")),
84
+ });
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,7 +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";
4
5
  import type { GenreSetting } from "./genre-check.ts";
6
+ import type { ProfileSetting } from "./profile/check.ts";
5
7
 
6
8
  /** Where a file's genre came from, as the header names it. */
7
9
  export type GenreSource = "--genre" | "by_path" | "config" | "default" | "front-matter" | "path" | "content";
@@ -12,11 +14,11 @@ const USAGE_JA = `chaff — 文章の読みにくいところを見つけます
12
14
  chaff . この場所の Markdown を全部
13
15
  chaff test <file|dir>... 意味を読む検査も動かす(API key が要ります)
14
16
  判定役は chaff.yaml の ai_backend で選びます
15
- chaff init chaff.yaml を作る
17
+ chaff init chaff.yaml を作る(端末ならジャンルを尋ねる。--genre <ジャンル> でも選べる)
16
18
  chaff --version chaffjs と言語パッケージの版
17
19
  chaff eval <dir> 手元の文書で閾値を測り直す
18
20
  chaff explain <rule> そのルールの意図と根拠を読む
19
- chaff genres ジャンルの一覧
21
+ chaff genres ジャンル(文書の種類)の一覧と、それぞれ何向けか
20
22
  chaff tree <file> [--format sexp|json] 文書を番地の付いた木にする(条・項・定義・参照)
21
23
  chaff cite <原文> <引用.json> 回答の引用(番地と引用文)が原文にあるかを確かめる
22
24
  chaff rules --json いまの設定を JSON で出す(AI に渡す用)
@@ -46,11 +48,11 @@ const USAGE_EN = `chaff — finds what makes writing hard to read. It never rewr
46
48
  chaff . every Markdown file here
47
49
  chaff test <file|dir>... also run the checks that read meaning (needs an API key)
48
50
  the judge is chosen by ai_backend in chaff.yaml
49
- chaff init create chaff.yaml
51
+ chaff init create chaff.yaml (asks for the genre at a terminal; --genre <genre> chooses it)
50
52
  chaff --version the version of chaffjs and its language packages
51
53
  chaff eval <dir> re-measure the limits on your own documents
52
54
  chaff explain <rule> read what a rule is for and why
53
- chaff genres list the genres
55
+ chaff genres list the genres (kinds of document) and what each is for
54
56
  chaff tree <file> [--format sexp|json] the document as a tree of addresses (sections, clauses, definitions, references)
55
57
  chaff cite <source> <quotes.json> check that quoted passages (address and text) are in the source
56
58
  chaff rules --json the current settings as JSON (to give to an AI)
@@ -79,22 +81,25 @@ export type CliText = {
79
81
  readonly languageName: (language: string) => string;
80
82
  readonly genreSource: Readonly<Record<GenreSource, string>>;
81
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;
82
86
  readonly noMarkdown: (targets: string) => string;
83
87
  readonly noMarkdownHere: string;
84
88
  readonly noAdapter: (language: string) => string;
85
89
  readonly unknownGenre: (genre: string, where: string, known: readonly string[]) => string;
86
90
  readonly genreWhere: (where: GenreSetting, files: readonly string[]) => string;
87
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;
88
94
  readonly unknownRule: (id: string) => string;
89
95
  readonly unknownRuleWithList: (id: string, list: string) => string;
90
96
  readonly unnamed: string;
91
97
  readonly sarifWritten: (path: string, count: number) => string;
92
98
  readonly unchanged: string;
93
- readonly findingsUnit: string;
99
+ readonly findingsUnit: (count: number) => string;
94
100
  readonly watching: (files: number, findings: number) => string;
95
101
  readonly watchHint: string;
96
102
  readonly baselineDone: (files: number, entries: number, file: string) => readonly string[];
97
- readonly genres: (list: readonly string[]) => string;
98
103
  readonly unit: (ruleId: string, language: string) => string;
99
104
  };
100
105
 
@@ -115,6 +120,9 @@ export const CLI_TEXT: Texts<CliText> = {
115
120
  [`${path} ${genre} · ${language} ジャンルは${from}から`, shelved > 0 ? ` 棚上げ ${shelved} 件` : "", hushed > 0 ? ` stet ${hushed} 件` : ""].join(
116
121
  "",
117
122
  ),
123
+ suggested: (name, genre) => ` ${name}のようです。--genre ${genre} を試せます`,
124
+ suggestedNote: (name, genre, used) =>
125
+ ` ジャンルを決めていないので、${used} として見ました。${name}なら、--genre ${genre} でその種類の書き方に合わせて見ます(npx chaffjs genres で一覧)。`,
118
126
  noMarkdown: (targets) => `Markdown が 1 つも見つかりませんでした: ${targets}`,
119
127
  noMarkdownHere: "Markdown が 1 つも見つかりませんでした。",
120
128
  noAdapter: (language) => `言語 "${language}" のアダプタがありません。`,
@@ -126,12 +134,15 @@ export const CLI_TEXT: Texts<CliText> = {
126
134
  },
127
135
  unreadFrontMatterGenre: (path, genre, known) =>
128
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(", ")}`),
129
140
  unknownRule: (id) => `${id} というルールはありません。npx chaff rules --json で一覧が出ます。`,
130
141
  unknownRuleWithList: (id, list) => `${id} というルールはありません。\n一覧:\n${list}`,
131
142
  unnamed: "(名前なし)",
132
143
  sarifWritten: (path, count) => `\n SARIF を書きました: ${path}(${count} 件)`,
133
144
  unchanged: "変わりませんでした",
134
- findingsUnit: " 件",
145
+ findingsUnit: () => " 件",
135
146
  watching: (files, findings) => `\n ${files} ファイルを見ています。いまの指摘は ${findings} 件です。`,
136
147
  watchHint: " 保存するたびに、変わったところだけ出します。止めるには Ctrl-C。\n",
137
148
  baselineDone: (files, entries, file) => [
@@ -144,8 +155,6 @@ export const CLI_TEXT: Texts<CliText> = {
144
155
  ` ${file} を commit してください。`,
145
156
  "",
146
157
  ],
147
- genres: (list) =>
148
- ["", " 使えるジャンル:", ...list.map((genre) => ` ${genre}`), "", " chaff.yaml の genre に書くか、--genre で指定します。", ""].join("\n"),
149
158
  unit: (ruleId, language) => {
150
159
  if (ruleId === "bold-density") return "1000 字あたりの箇所数";
151
160
  if (ruleId !== "max-sentence-length") return "回";
@@ -166,6 +175,9 @@ export const CLI_TEXT: Texts<CliText> = {
166
175
  },
167
176
  header: (path, genre, language, from, shelved, hushed) =>
168
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).`,
169
181
  noMarkdown: (targets) => `No Markdown files found: ${targets}`,
170
182
  noMarkdownHere: "No Markdown files found.",
171
183
  noAdapter: (language) => `No language package for "${language}".`,
@@ -176,25 +188,27 @@ export const CLI_TEXT: Texts<CliText> = {
176
188
  },
177
189
  unreadFrontMatterGenre: (path, genre, known) =>
178
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(", ")}`),
179
194
  unknownRule: (id) => `There is no rule named ${id}. npx chaff rules --json lists them.`,
180
195
  unknownRuleWithList: (id, list) => `There is no rule named ${id}.\nRules:\n${list}`,
181
196
  unnamed: "(no name)",
182
- sarifWritten: (path, count) => `\n Wrote SARIF: ${path} (${count} finding${count === 1 ? "" : "s"})`,
197
+ sarifWritten: (path, count) => `\n Wrote SARIF: ${path} (${counted(count, "finding")})`,
183
198
  unchanged: "no change",
184
- findingsUnit: " findings",
185
- 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.`,
186
201
  watchHint: " Each save prints only what changed. Ctrl-C to stop.\n",
187
202
  baselineDone: (files, entries, file) => [
188
203
  "",
189
- ` Checked ${files} file${files === 1 ? "" : "s"}.`,
204
+ ` Checked ${counted(files, "file")}.`,
190
205
  "",
191
- ` Recorded ${entries} finding${entries === 1 ? "" : "s"} in ${file}.`,
206
+ ` Recorded ${counted(entries, "finding")} in ${file}.`,
192
207
  " They will not be reported again; only new ones will.",
193
208
  "",
194
209
  ` Commit ${file}.`,
195
210
  "",
196
211
  ],
197
- genres: (list) => ["", " Genres:", ...list.map((genre) => ` ${genre}`), "", " Set one with genre in chaff.yaml, or with --genre.", ""].join("\n"),
198
212
  unit: (ruleId, language) => {
199
213
  if (ruleId === "bold-density") return "places per 1000 characters";
200
214
  if (ruleId !== "max-sentence-length") return "times";