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
@@ -2,6 +2,7 @@ import type { Finding, RuleDefinition } from "../plugin.ts";
2
2
  import type { RunResult } from "../run.ts";
3
3
  import { MARK, localized, messageOf } from "./text.ts";
4
4
  import { tally } from "./summary.ts";
5
+ import { counted } from "./plural.ts";
5
6
  import { uiLanguageOf, type Texts } from "../ui.ts";
6
7
 
7
8
  const RULE = 60;
@@ -13,6 +14,7 @@ const TEXT: Texts<{
13
14
  readonly deterministic: string;
14
15
  readonly untouched: string;
15
16
  readonly forced: (n: number, ids: string) => string;
17
+ readonly presetOn: (n: number, ids: string) => string;
16
18
  readonly notRun: (n: number) => string;
17
19
  readonly because: (why: string) => string;
18
20
  }> = {
@@ -23,6 +25,7 @@ const TEXT: Texts<{
23
25
  deterministic: " (同じ文章なら何度実行しても同じ結果になります)",
24
26
  untouched: "文章は書き換えていません。直すのは書いた人です。",
25
27
  forced: (n, ids) => `試験中の rule を ${n} 件、設定により有効にしています: ${ids}`,
28
+ presetOn: (n, ids) => `試験中の rule を ${n} 件、ジャンルの既定で有効にしています: ${ids}`,
26
29
  notRun: (n) => `${n} 件の rule は動いていません:`,
27
30
  because: (why) => `(${why})`,
28
31
  },
@@ -32,8 +35,9 @@ const TEXT: Texts<{
32
35
  machine: "All judged by machine",
33
36
  deterministic: " (the same text gives the same result every time)",
34
37
  untouched: "The text was not changed. Fixing it is the writer's job.",
35
- forced: (n, ids) => `${n} experimental rule${n === 1 ? "" : "s"} turned on in the settings: ${ids}`,
36
- notRun: (n) => `${n} rule${n === 1 ? "" : "s"} did not run:`,
38
+ forced: (n, ids) => `${counted(n, "experimental rule")} turned on in the settings: ${ids}`,
39
+ presetOn: (n, ids) => `${counted(n, "experimental rule")} turned on by the genre: ${ids}`,
40
+ notRun: (n) => `${counted(n, "rule")} did not run:`,
37
41
  because: (why) => ` (${why})`,
38
42
  },
39
43
  };
@@ -69,14 +73,21 @@ const block = (finding: Finding, rule: RuleDefinition, language: string): string
69
73
  "",
70
74
  ];
71
75
 
72
- export const renderFriendly = (header: string, result: RunResult, rules: readonly RuleDefinition[], language: string): string => {
76
+ /** notes: lines after the not-run list (the genre chaff suggests when none is set). */
77
+ export const renderFriendly = (
78
+ header: string,
79
+ result: RunResult,
80
+ rules: readonly RuleDefinition[],
81
+ language: string,
82
+ notes: readonly string[] = [],
83
+ ): string => {
73
84
  const byId = new Map(rules.map((rule) => [rule.id, rule]));
74
85
  const blocks = result.findings.flatMap((finding) => {
75
86
  const rule = byId.get(finding.rule);
76
87
  return rule === undefined ? [] : block(finding, rule, language);
77
88
  });
78
89
  const text = TEXT[uiLanguageOf(language)];
79
- const notes = [
90
+ const closing = [
80
91
  "",
81
92
  "─".repeat(RULE),
82
93
  "",
@@ -85,10 +96,14 @@ export const renderFriendly = (header: string, result: RunResult, rules: readonl
85
96
  "",
86
97
  ` ${text.untouched}`,
87
98
  ];
88
- const forced = result.forcedExperimental.length > 0 ? ["", ` ${text.forced(result.forcedExperimental.length, result.forcedExperimental.join(", "))}`] : [];
99
+ const forced = [
100
+ ...(result.forcedExperimental.length > 0 ? ["", ` ${text.forced(result.forcedExperimental.length, result.forcedExperimental.join(", "))}`] : []),
101
+ ...(result.presetExperimental.length > 0 ? ["", ` ${text.presetOn(result.presetExperimental.length, result.presetExperimental.join(", "))}`] : []),
102
+ ];
89
103
  const skipped =
90
104
  result.skipped.length > 0
91
105
  ? ["", ` ${text.notRun(result.skipped.length)}`, ...result.skipped.map((entry) => ` ${entry.rule}${text.because(entry.why)}`)]
92
106
  : [];
93
- return ["", header, ...blocks, ...notes, ...forced, ...skipped, ""].join("\n");
107
+ const after = notes.flatMap((note) => ["", note]);
108
+ return ["", header, ...blocks, ...closing, ...forced, ...skipped, ...after, ""].join("\n");
94
109
  };
@@ -0,0 +1,31 @@
1
+ import type { GenreData } from "../genre-parse.ts";
2
+ import type { Texts, UiLanguage } from "../ui.ts";
3
+
4
+ const TEXT: Texts<{ readonly heading: string; readonly usage: readonly string[] }> = {
5
+ ja: {
6
+ heading: " ジャンル(文書の種類)を選ぶと、その種類の書き方に合わせて見ます:",
7
+ usage: [
8
+ " この実行だけ: npx chaffjs --genre legal/contract 契約書.md",
9
+ " この場所に決める: npx chaffjs init --genre legal/contract (chaff.yaml の genre に書きます)",
10
+ ],
11
+ },
12
+ en: {
13
+ heading: " Pick the genre (the kind of document) and chaff checks it the way that kind is written:",
14
+ usage: [
15
+ " For one run: npx chaffjs --genre legal/contract contract.md",
16
+ " For this folder: npx chaffjs init --genre legal/contract (writes genre in chaff.yaml)",
17
+ ],
18
+ },
19
+ };
20
+
21
+ /** `chaff genres`: every genre under its group, with what it is for, in the output language. */
22
+ export const renderGenres = (data: GenreData, ui: UiLanguage): string => {
23
+ const width = Math.max(...data.genres.map((genre) => genre.id.length)) + 2;
24
+ const groups = data.groups.flatMap((group) => {
25
+ const members = data.genres.filter((genre) => genre.id.startsWith(`${group.id}/`));
26
+ return members.length === 0
27
+ ? []
28
+ : ["", ` ${group.name[ui] ?? group.id}`, ...members.map((genre) => ` ${genre.id.padEnd(width)}${genre.summary[ui] ?? ""}`)];
29
+ });
30
+ return ["", TEXT[ui].heading, ...groups, "", ...TEXT[ui].usage, ""].join("\n");
31
+ };
@@ -0,0 +1,6 @@
1
+ /** English agreement: only exactly one takes the singular ("0 findings", "1 finding", "1.5 findings"). */
2
+ const isOne = (count: number | string): boolean => String(count) === "1";
3
+
4
+ export const formFor = (count: number | string, singular: string, plural: string): string => (isOne(count) ? singular : plural);
5
+
6
+ export const counted = (count: number, singular: string, plural = `${singular}s`): string => `${String(count)} ${formFor(count, singular, plural)}`;
@@ -2,9 +2,12 @@ import { definedLevels, resolve } from "../levels.ts";
2
2
  import type { Config } from "../config/load.ts";
3
3
  import type { RuleDefinition } from "../plugin.ts";
4
4
  import { uiLanguageOf, type Texts } from "../ui.ts";
5
+ import { presetLevels } from "../genre-load.ts";
6
+ import type { PresetLevels } from "../genre-parse.ts";
5
7
 
6
8
  const TEXT: Texts<{
7
9
  readonly offBySetting: string;
10
+ readonly offByGenre: (genre: string) => string;
8
11
  readonly offExperimental: string;
9
12
  readonly valuesNote: string;
10
13
  readonly reason: string;
@@ -12,6 +15,7 @@ const TEXT: Texts<{
12
15
  }> = {
13
16
  ja: {
14
17
  offBySetting: "設定で止めている",
18
+ offByGenre: (genre) => `ジャンル ${genre} では見ない`,
15
19
  offExperimental: "experimental な rule は既定で動かさない",
16
20
  valuesNote: "この 4 語のかわりに数字を直接書いてもよい。4 語のほうを勧める。",
17
21
  reason: "<理由>",
@@ -19,6 +23,7 @@ const TEXT: Texts<{
19
23
  },
20
24
  en: {
21
25
  offBySetting: "turned off in the settings",
26
+ offByGenre: (genre) => `the ${genre} genre does not check it`,
22
27
  offExperimental: "experimental rules do not run by default",
23
28
  valuesNote: "A number may be written instead of these four words. The words are recommended.",
24
29
  reason: "<reason>",
@@ -26,8 +31,9 @@ const TEXT: Texts<{
26
31
  },
27
32
  };
28
33
 
29
- const now = (rule: RuleDefinition, config: Config, genre: string, text: (typeof TEXT)["ja"]): Record<string, unknown> => {
30
- const explicit = config.rules[rule.id];
34
+ const now = (rule: RuleDefinition, config: Config, genre: string, text: (typeof TEXT)["ja"], preset: PresetLevels): Record<string, unknown> => {
35
+ const explicit = config.rules[rule.id] ?? preset[rule.id];
36
+ if (explicit === "off" && config.rules[rule.id] === undefined) return { level: "off", why_off: text.offByGenre(genre) };
31
37
  if (explicit === "off") return { level: "off", why_off: text.offBySetting };
32
38
  const limit = rule.layer === "L4" ? undefined : config.limits[rule.id];
33
39
  if (limit !== undefined) return { level: "normal", limit, set_as: "number" };
@@ -59,6 +65,7 @@ const levelsOf = (rule: RuleDefinition, genre: string): Record<string, unknown>
59
65
  /** AI に設定を書かせるときの入口。推測せずに書けるだけの情報を 1 つに入れる。spec §19.3。 */
60
66
  export const rulesJson = (rules: readonly RuleDefinition[], config: Config, language: string, genre: string): string => {
61
67
  const text = TEXT[uiLanguageOf(language)];
68
+ const preset = presetLevels(genre);
62
69
  return JSON.stringify(
63
70
  {
64
71
  schema_version: 1,
@@ -77,7 +84,7 @@ export const rulesJson = (rules: readonly RuleDefinition[], config: Config, lang
77
84
  ...levelsOf(rule, genre),
78
85
  levels_you_can_set: definedLevels(rule),
79
86
  your_setting: yourSetting(rule, config),
80
- now: now(rule, config, genre, text),
87
+ now: now(rule, config, genre, text, preset),
81
88
  })),
82
89
  how_to_change: {
83
90
  by_command: ["relax", "strict", "off"].map((command) => `npx chaff ${command} <rule-id> --why "${text.reason}"`),
@@ -1,35 +1,113 @@
1
1
  import type { Finding, RuleDefinition } from "../plugin.ts";
2
2
  import type { UserCheck } from "../checks.ts";
3
+ import type { Narrowing } from "../look-at.ts";
3
4
  import type { Job as Plan, SemanticResult } from "../run-semantic.ts";
5
+ import { uiLanguageOf, type Texts, type UiLanguage } from "../ui.ts";
4
6
  import { MARK, localized } from "./text.ts";
7
+ import { counted, formFor } from "./plural.ts";
5
8
 
6
9
  const RULE = 60;
7
10
 
11
+ const TEXT: Texts<{
12
+ readonly machineBanner: readonly string[];
13
+ readonly aiTitle: string;
14
+ readonly aiNote: readonly string[];
15
+ readonly read: (asked: number, seen: number) => string;
16
+ readonly wholeRead: (name: string) => readonly string[];
17
+ readonly word: (word: string) => string;
18
+ readonly number: string;
19
+ readonly narrowedRead: (name: string, what: string, kept: number, total: number) => string;
20
+ readonly line: (line: number) => string;
21
+ readonly confidence: (value: string) => string;
22
+ readonly disagree: readonly string[];
23
+ readonly silence: (rule: string) => string;
24
+ readonly relax: (rule: string) => string;
25
+ readonly notRunChecks: (count: number) => string;
26
+ readonly byMachine: string;
27
+ readonly whole: string;
28
+ readonly containing: (parts: string) => string;
29
+ readonly planLine: (count: number, name: string, how: string) => string;
30
+ readonly firstSample: string;
31
+ readonly planHead: (path: string, sentences: number, asked: number) => string;
32
+ }> = {
33
+ ja: {
34
+ machineBanner: ["", `═══ 機械による判定 ${"═".repeat(RULE - 18)}`, " 同じ文章なら何度実行しても同じ結果になります", ""],
35
+ aiTitle: `═══ AI による判定 ${"═".repeat(RULE - 17)}`,
36
+ aiNote: [" 文章の意味を読んでいます。実行するたび結果が変わることが", " あります。おかしいと思ったら、そのまま無視して構いません。"],
37
+ read: (asked, seen) => ` ${seen} 文のうち ${asked} 箇所を読みました(残りは機械が対象外と判断)`,
38
+ wholeRead: (name) => [` ${name}: 見るところを絞れず、全文を読みました`, ` look_at に「」で語を書くと、その語を含む文だけになります`],
39
+ word: (word) => `「${word}」`,
40
+ number: "数字",
41
+ narrowedRead: (name, what, kept, total) => ` ${name}: ${what} を含む ${kept} 文だけを読みました(全 ${total} 文)`,
42
+ line: (line) => `${line} 行目`,
43
+ confidence: (value) => `確からしさ ${value}`,
44
+ disagree: [" この指摘が違うと思ったら:"],
45
+ silence: (rule) => ` この箇所だけ黙らせる <!-- stet: ${rule} — 理由 -->`,
46
+ relax: (rule) => ` ルールごとゆるめる npx chaff relax ${rule}`,
47
+ notRunChecks: (count) => ` ${count} 件の検査は、見るところが無いため動いていません。`,
48
+ byMachine: "機械で絞り込み済み",
49
+ whole: "絞り込めず全文",
50
+ containing: (parts) => `${parts} を含む文`,
51
+ planLine: (count, name, how) => ` ${String(count).padStart(3)} 箇所 ${name} (${how})`,
52
+ firstSample: " 最初の 1 件に送る文章:",
53
+ planHead: (path, sentences, asked) => `${path} 全 ${sentences} 文のうち ${asked} 箇所を送ります(API は呼んでいません)`,
54
+ },
55
+ en: {
56
+ machineBanner: ["", `═══ Judged by machine ${"═".repeat(RULE - 21)}`, " The same text gives the same result every time", ""],
57
+ aiTitle: `═══ Judged by AI ${"═".repeat(RULE - 16)}`,
58
+ aiNote: [" These read the meaning of the text. The result can change", " from run to run. If one looks wrong, feel free to ignore it."],
59
+ read: (asked, seen) => ` Read ${counted(asked, "passage")} out of ${counted(seen, "sentence")} (the machine ruled out the rest)`,
60
+ wholeRead: (name) => [
61
+ ` ${name}: could not narrow what to read, so read the whole text`,
62
+ ` Put words in quotes in look_at ("like this") to read only the sentences that contain them`,
63
+ ],
64
+ word: (word) => `"${word}"`,
65
+ number: "a number",
66
+ narrowedRead: (name, what, kept, total) => ` ${name}: read only the ${counted(kept, "sentence")} containing ${what} (of ${total})`,
67
+ line: (line) => `line ${line}`,
68
+ confidence: (value) => `confidence ${value}`,
69
+ disagree: [" If you think this finding is wrong:"],
70
+ silence: (rule) => ` Silence this spot only <!-- stet: ${rule} — reason -->`,
71
+ relax: (rule) => ` Relax the whole rule npx chaff relax ${rule}`,
72
+ notRunChecks: (count) => ` ${counted(count, "check")} did not run: there was nothing for ${formFor(count, "it", "them")} to look at.`,
73
+ byMachine: "narrowed by machine",
74
+ whole: "not narrowed: the whole text",
75
+ containing: (parts) => `sentences containing ${parts}`,
76
+ planLine: (count, name, how) => ` ${String(count).padStart(3)} to send ${name} (${how})`,
77
+ firstSample: " The text sent for the first one:",
78
+ planHead: (path, sentences, asked) => `${path} sends ${counted(asked, "passage")} out of ${counted(sentences, "sentence")} (the API was not called)`,
79
+ },
80
+ };
81
+
8
82
  const indent = (text: string, pad: string): string[] => text.split("\n").map((line) => `${pad}${line}`);
9
83
 
10
- export const MACHINE_BANNER = ["", `═══ 機械による判定 ${"═".repeat(RULE - 18)}`, " 同じ文章なら何度実行しても同じ結果になります", ""];
84
+ /** The banner over a file's machine findings, in the file's language. */
85
+ export const machineBanner = (language: string): readonly string[] => TEXT[uiLanguageOf(language)].machineBanner;
86
+
87
+ /** The words a narrowing looks for, as the screen names them. */
88
+ const lookedFor = (narrowing: Narrowing, language: UiLanguage): string[] => {
89
+ const text = TEXT[language];
90
+ return [...narrowing.words.map((word) => text.word(word)), ...(narrowing.needsNumber ? [text.number] : [])];
91
+ };
11
92
 
12
93
  /**
13
94
  * 自分の書いた検査が、どこまで絞り込めたかを見せる。
14
95
  * 絞り込めないまま全文を読んでいることに気づけないと、遅さの原因が分からない。
15
96
  */
16
- const narrowingLines = (result: SemanticResult): string[] =>
97
+ const narrowingLines = (result: SemanticResult, language: UiLanguage): string[] =>
17
98
  result.narrowed.flatMap(({ name, narrowing }) => {
18
- if (narrowing.words.length === 0 && !narrowing.needsNumber) {
19
- return [` ${name}: 見るところを絞れず、全文を読みました`, ` look_at に「」で語を書くと、その語を含む文だけになります`];
20
- }
21
- const what = [...narrowing.words.map((word) => `「${word}」`), ...(narrowing.needsNumber ? ["数字"] : [])].join(" ");
22
- return [` ${name}: ${what} を含む ${narrowing.kept} 文だけを読みました(全 ${narrowing.total} 文)`];
99
+ if (narrowing.words.length === 0 && !narrowing.needsNumber) return [...TEXT[language].wholeRead(name)];
100
+ const what = lookedFor(narrowing, language).join(language === "ja" ? " " : ", ");
101
+ return [TEXT[language].narrowedRead(name, what, narrowing.kept, narrowing.total)];
23
102
  });
24
103
 
25
- const aiBanner = (result: SemanticResult): string[] => [
104
+ const aiBanner = (result: SemanticResult, language: UiLanguage): string[] => [
26
105
  "",
27
- `═══ AI による判定 ${"═".repeat(RULE - 17)}`,
28
- " 文章の意味を読んでいます。実行するたび結果が変わることが",
29
- " あります。おかしいと思ったら、そのまま無視して構いません。",
106
+ TEXT[language].aiTitle,
107
+ ...TEXT[language].aiNote,
30
108
  "",
31
- ` ${result.sentencesSeen} 文のうち ${result.asked} 箇所を読みました(残りは機械が対象外と判断)`,
32
- ...narrowingLines(result),
109
+ TEXT[language].read(result.asked, result.sentencesSeen),
110
+ ...narrowingLines(result, language),
33
111
  "",
34
112
  ];
35
113
 
@@ -46,32 +124,37 @@ const nameOf = (rule: string, rules: readonly RuleDefinition[], checks: readonly
46
124
  * AI の指摘にだけ確からしさを出し、納得できないときの逃げ道を 2 つ添える。
47
125
  * 揺れる判定だと読む人が知らないと、誤検知に振り回される。
48
126
  */
49
- const block = (finding: Finding, named: Named): string[] => {
127
+ const block = (finding: Finding, named: Named, language: UiLanguage): string[] => {
128
+ const text = TEXT[language];
50
129
  const confidence = finding.values["confidence"];
51
130
  const reason = String(finding.values["reason"] ?? "");
52
- const sure = confidence === undefined ? "" : ` 確からしさ ${String(confidence)}`;
131
+ const sure = confidence === undefined ? "" : ` ${text.confidence(String(confidence))}`;
53
132
  const fix = named.howToFix.length > 0 ? ["", ...indent(`→ ${named.howToFix}`, " ")] : [];
54
133
  return [
55
134
  "",
56
- `─── ${finding.line} 行目 ${"─".repeat(Math.max(0, RULE - String(finding.line).length - 8))}`,
135
+ // "12 行目" and "line 12" take the same width on a terminal, so the rule ends in the same column in both.
136
+ `─── ${text.line(finding.line)} ${"─".repeat(Math.max(0, RULE - String(finding.line).length - 8))}`,
57
137
  "",
58
138
  ` ${MARK[finding.severity] ?? "·"} ${named.name}${sure}`,
59
139
  "",
60
140
  ...indent(reason, " "),
61
141
  ...fix,
62
142
  "",
63
- " この指摘が違うと思ったら:",
64
- ` この箇所だけ黙らせる <!-- stet: ${finding.rule} — 理由 -->`,
65
- ` ルールごとゆるめる npx chaff relax ${finding.rule}`,
143
+ ...text.disagree,
144
+ text.silence(finding.rule),
145
+ text.relax(finding.rule),
66
146
  "",
67
147
  ];
68
148
  };
69
149
 
70
- export const renderSemantic = (result: SemanticResult, rules: readonly RuleDefinition[], checks: readonly UserCheck[], language: string): string[] => [
71
- ...aiBanner(result),
72
- ...result.findings.flatMap((finding) => block(finding, nameOf(finding.rule, rules, checks, language))),
73
- ...(result.skipped.length > 0 ? [` ${result.skipped.length} 件の検査は、見るところが無いため動いていません。`, ""] : []),
74
- ];
150
+ export const renderSemantic = (result: SemanticResult, rules: readonly RuleDefinition[], checks: readonly UserCheck[], language: string): string[] => {
151
+ const ui = uiLanguageOf(language);
152
+ return [
153
+ ...aiBanner(result, ui),
154
+ ...result.findings.flatMap((finding) => block(finding, nameOf(finding.rule, rules, checks, language), ui)),
155
+ ...(result.skipped.length > 0 ? [TEXT[ui].notRunChecks(result.skipped.length), ""] : []),
156
+ ];
157
+ };
75
158
 
76
159
  /**
77
160
  * 何が送られるのかを、API を呼ばずに見せる。`chaff test --dry-run`。
@@ -79,19 +162,19 @@ export const renderSemantic = (result: SemanticResult, rules: readonly RuleDefin
79
162
  * 鍵が無い環境でも二段構えの 1 段目まで確かめられる。全文が送られようとしていることに
80
163
  * 請求書で気づくのでは遅い。spec §14。
81
164
  */
82
- const howNarrowed = (job: Plan): string => {
165
+ const howNarrowed = (job: Plan, language: UiLanguage): string => {
83
166
  const narrowing = job.narrowing;
84
- if (narrowing === undefined) return "機械で絞り込み済み";
85
- const quoted = narrowing.words.map((word) => `「${word}」`);
86
- const parts = narrowing.needsNumber ? [...quoted, "数字"] : quoted;
87
- if (parts.length === 0) return "絞り込めず全文";
88
- return `${parts.join(" ")} を含む文`;
167
+ if (narrowing === undefined) return TEXT[language].byMachine;
168
+ const parts = lookedFor(narrowing, language);
169
+ if (parts.length === 0) return TEXT[language].whole;
170
+ return TEXT[language].containing(parts.join(language === "ja" ? " " : ", "));
89
171
  };
90
172
 
91
- export const renderPlan = (path: string, jobs: readonly Plan[], sentences: number): string[] => {
173
+ export const renderPlan = (path: string, jobs: readonly Plan[], sentences: number, language: string): string[] => {
174
+ const text = TEXT[uiLanguageOf(language)];
92
175
  const asked = jobs.reduce((sum, job) => sum + job.candidates.length, 0);
93
- const lines = jobs.map((job) => ` ${String(job.candidates.length).padStart(3)} 箇所 ${job.name} (${howNarrowed(job)})`);
176
+ const lines = jobs.map((job) => text.planLine(job.candidates.length, job.name, howNarrowed(job, uiLanguageOf(language))));
94
177
  const first = jobs.find((job) => job.candidates.length > 0);
95
- const sample = first === undefined ? [] : ["", " 最初の 1 件に送る文章:", "", ...indent(String(first.candidates[0]?.text ?? "").slice(0, 300), " ")];
96
- return ["", `${path} 全 ${sentences} 文のうち ${asked} 箇所を送ります(API は呼んでいません)`, "", ...lines, ...sample, ""];
178
+ const sample = first === undefined ? [] : ["", text.firstSample, "", ...indent(String(first.candidates[0]?.text ?? "").slice(0, 300), " ")];
179
+ return ["", text.planHead(path, sentences, asked), "", ...lines, ...sample, ""];
97
180
  };
@@ -1,5 +1,6 @@
1
1
  import type { Finding } from "../plugin.ts";
2
2
  import type { Texts, UiLanguage } from "../ui.ts";
3
+ import { counted } from "./plural.ts";
3
4
 
4
5
  export type FileOutcome = { readonly path: string; readonly findings: readonly Finding[]; readonly notRun: number };
5
6
 
@@ -21,9 +22,9 @@ const TEXT: Texts<{
21
22
  },
22
23
  en: {
23
24
  counts: {
24
- error: (n) => `${n} error${n === 1 ? "" : "s"}`,
25
- warning: (n) => `${n} warning${n === 1 ? "" : "s"}`,
26
- info: (n) => `${n} note${n === 1 ? "" : "s"}`,
25
+ error: (n) => counted(n, "error"),
26
+ warning: (n) => counted(n, "warning"),
27
+ info: (n) => counted(n, "note"),
27
28
  },
28
29
  join: ", ",
29
30
  none: "No findings",
@@ -1,5 +1,6 @@
1
1
  import type { Suppressed, Suppression } from "../stet.ts";
2
2
  import type { Texts, UiLanguage } from "../ui.ts";
3
+ import { counted } from "./plural.ts";
3
4
 
4
5
  const TEXT: Texts<{
5
6
  readonly nudge: string;
@@ -23,7 +24,7 @@ const TEXT: Texts<{
23
24
  },
24
25
  en: {
25
26
  nudge: " <- consider changing the setting instead",
26
- more: (n) => ` and ${n} more file${n === 1 ? "" : "s"}`,
27
+ more: (n) => ` and ${counted(n, "more file")}`,
27
28
  count: (n) => `${String(n).padStart(3)}`,
28
29
  reasons: (list) => `reasons: ${list}`,
29
30
  relaxAll: (rule) => `relax the whole rule: npx chaff relax ${rule} --why "..."`,
@@ -53,7 +54,7 @@ const groupByRule = (files: readonly PerFile[]): Group[] => {
53
54
  reasons: [...new Set(mine.flatMap((row) => (row.reason === undefined ? [] : [row.reason])))],
54
55
  };
55
56
  })
56
- .sort((left, right) => right.count - left.count);
57
+ .toSorted((left, right) => right.count - left.count);
57
58
  };
58
59
 
59
60
  const groupLines = (group: Group, ui: UiLanguage): string[] => {
@@ -1,13 +1,28 @@
1
1
  import type { Finding, Localized, RuleDefinition } from "../plugin.ts";
2
+ import { formFor } from "./plural.ts";
2
3
 
3
4
  export const localized = (field: Localized, language: string): string => field[language] ?? field["en"] ?? Object.values(field)[0] ?? "";
4
5
 
6
+ /** `{count}` is the value; `{count|word|words}` is the form that agrees with it (English only; Japanese has no plural). */
7
+ const PLACEHOLDER = /\{(\w+)(?:\|([^|{}]*)\|([^|{}]*))?\}/gu;
8
+
5
9
  const fill = (template: string, values: Readonly<Record<string, string | number>>): string =>
6
- template.replace(/\{(\w+)\}/gu, (whole, key: string) => {
10
+ template.replace(PLACEHOLDER, (whole, key: string, singular: string | undefined, plural: string | undefined) => {
7
11
  const value = values[key];
8
- return value === undefined ? whole : String(value);
12
+ if (value === undefined) return whole;
13
+ return singular === undefined || plural === undefined ? String(value) : formFor(value, singular, plural);
9
14
  });
10
15
 
11
- export const messageOf = (rule: RuleDefinition, finding: Finding, language: string): string => fill(localized(rule.message, language), finding.values);
16
+ /** A template as the rule reference shows it: each agreeing form as its plural ("{count} such words"). */
17
+ export const templateForReading = (template: string): string =>
18
+ template.replace(PLACEHOLDER, (whole, _key: string, singular: string | undefined, plural: string | undefined) =>
19
+ singular === undefined || plural === undefined ? whole : plural,
20
+ );
21
+
22
+ const templateOf = (rule: RuleDefinition, finding: Finding): Localized =>
23
+ (finding.variant === undefined ? undefined : rule.messages[finding.variant]) ?? rule.message;
24
+
25
+ export const messageOf = (rule: RuleDefinition, finding: Finding, language: string): string =>
26
+ fill(localized(templateOf(rule, finding), language), finding.values);
12
27
 
13
28
  export const MARK: Readonly<Record<string, string>> = { error: "✖", warning: "⚠", info: "·" };
@@ -0,0 +1,20 @@
1
+ import { applyByPath } from "./config/by-path.ts";
2
+ import type { Config } from "./config/load.ts";
3
+ import type { GenreSource } from "./cli-text.ts";
4
+ import { frontMatterGenre, GENRES, guessGenre } from "./genre.ts";
5
+ import { unknownFrontMatterGenre } from "./genre-check.ts";
6
+
7
+ /** unread: a genre the front matter wrote that chaff does not know, when the front matter was looked at. */
8
+ type ResolvedGenre = { readonly genre: string; readonly from: GenreSource; readonly unread?: string | undefined };
9
+
10
+ export const resolveGenre = (path: string, source: string, config: Config, cliGenre?: string): ResolvedGenre => {
11
+ // コマンドで指定したものが最優先。その実行だけの指定だから、設定より強い。
12
+ if (cliGenre !== undefined) return { genre: cliGenre, from: "--genre" };
13
+ // パスごとの上書きが次。「全体はこう、ここだけは違う」を書けるようにする。
14
+ const override = applyByPath(config.byPath, config.baseDir, path);
15
+ if (override.genre !== undefined) return { genre: override.genre, from: "by_path" };
16
+ if (config.genre !== undefined) return { genre: config.genre, from: "config" };
17
+ const guess = guessGenre(path, source, frontMatterGenre(source));
18
+ const unread = unknownFrontMatterGenre(source, GENRES);
19
+ return guess === undefined ? { genre: "blog/tech", from: "default", unread } : { genre: guess.genre, from: guess.from, unread };
20
+ };
package/src/rule-load.ts CHANGED
@@ -68,6 +68,10 @@ const missingFields = (raw: Record<string, unknown>, levels: LevelTable | undefi
68
68
 
69
69
  const localizedOf = (value: unknown): Record<string, string> => (isLocalized(value) ? value : {});
70
70
 
71
+ /** 見つけ方ごとの message。読めないものは捨てる。 */
72
+ const messagesOf = (value: unknown): Record<string, Record<string, string>> =>
73
+ isRecord(value) ? Object.fromEntries(Object.entries(value).flatMap(([variant, text]) => (isLocalized(text) ? [[variant, text]] : []))) : {};
74
+
71
75
  /**
72
76
  * severity も言語別に書ける。levels と同じ畳みかた。
73
77
  * 記号の許容度は言語で大きく違う(ダッシュは英語では普通、日本語の組版では扱いが難しい)。
@@ -95,6 +99,7 @@ const toRule = (raw: unknown, language: string, file: string): RuleDefinition =>
95
99
  why: localizedOf(raw["why"]),
96
100
  how_to_fix: localizedOf(raw["how_to_fix"]),
97
101
  message: localizedOf(raw["message"]),
102
+ messages: messagesOf(raw["messages"]),
98
103
  levels,
99
104
  by_genre: genreTables(raw["by_genre"], language),
100
105
  how_to_find: String(raw["how_to_find"]),
@@ -126,5 +131,5 @@ const parseRule = (dir: string, file: string): unknown => {
126
131
  export const loadRules = (language: string, dir: string = RULES_DIR): RuleDefinition[] =>
127
132
  readdirSync(dir)
128
133
  .filter((file) => file.endsWith(".yaml"))
129
- .sort((left, right) => left.localeCompare(right, "en"))
134
+ .toSorted((left, right) => left.localeCompare(right, "en"))
130
135
  .map((file) => toRule(parseRule(dir, file), language, file));
@@ -129,7 +129,7 @@ export const runSemantic = async (
129
129
  ),
130
130
  );
131
131
  return {
132
- findings: answers.filter((finding) => finding !== undefined).sort((left, right) => left.line - right.line),
132
+ findings: answers.filter((finding) => finding !== undefined).toSorted((left, right) => left.line - right.line),
133
133
  skipped,
134
134
  asked: live.reduce((sum, job) => sum + job.candidates.length, 0),
135
135
  sentencesSeen: doc.sentences.length,
package/src/run.ts CHANGED
@@ -10,6 +10,7 @@ import { isMarkdownPath } from "./structure/markdown-path.ts";
10
10
  import { maskSpans } from "./mask.ts";
11
11
  import { textOutline } from "./page-furniture.ts";
12
12
  import { uiLanguageOf } from "./ui.ts";
13
+ import { presetLevels } from "./genre-load.ts";
13
14
 
14
15
  export type Skipped = { readonly rule: string; readonly why: string };
15
16
 
@@ -18,18 +19,29 @@ export type RunResult = {
18
19
  readonly skipped: readonly Skipped[];
19
20
  /** 設定で明示的に有効にした experimental な rule。実行ごとに一度報告する。spec §18.4。 */
20
21
  readonly forcedExperimental: readonly string[];
22
+ /** The experimental rules the genre's preset turns on. Reported apart from forcedExperimental: chaff.yaml did not name them. */
23
+ readonly presetExperimental: readonly string[];
21
24
  };
22
25
 
23
26
  export type Settings = Readonly<Record<string, Level>>;
24
27
 
25
- const levelFor = (rule: RuleDefinition, settings: Settings, experimental: boolean): Level => {
28
+ const levelFor = (rule: RuleDefinition, settings: Settings, experimental: boolean, preset: Settings): Level => {
26
29
  const explicit = settings[rule.id];
27
30
  // 明示設定は status の既定に勝つ。名指しで有効にしたものを黙って無効にしない。
28
31
  if (explicit !== undefined) return explicit;
32
+ // ジャンルの既定は chaff.yaml より弱く、status の既定より強い。--experimental でも、ジャンルが止めたものは止めたまま。
33
+ const fromPreset = preset[rule.id];
34
+ if (fromPreset !== undefined) return fromPreset;
29
35
  if (rule.status === "experimental" && !experimental) return "off";
30
36
  return "normal";
31
37
  };
32
38
 
39
+ /** Why a rule at off did not run: chaff.yaml turned it off, the genre's preset did, or it is experimental. */
40
+ const offReason = (rule: RuleDefinition, settings: Settings, preset: Settings, genre: string, reasons: Reasons): string => {
41
+ if (settings[rule.id] !== undefined) return reasons.turnedOff;
42
+ return preset[rule.id] === undefined ? reasons.experimental : reasons.presetOff(genre);
43
+ };
44
+
33
45
  const reasonsFor = (doc: ProseDocument): Reasons => REASONS[uiLanguageOf(doc.language)];
34
46
 
35
47
  /**
@@ -41,11 +53,11 @@ const reasonsFor = (doc: ProseDocument): Reasons => REASONS[uiLanguageOf(doc.lan
41
53
  * 読むので、番号を読めなかった文書でも動く。どちらも capability ではなく、adapter が木を作れるかで決まる。
42
54
  */
43
55
  const TREE_NEEDS: ReadonlySet<string> = new Set(["structure", "dates", "quantities"]);
44
- const VALUE_NEEDS: readonly string[] = ["dates", "quantities"];
56
+ const VALUE_NEEDS: ReadonlySet<string> = new Set(["dates", "quantities"]);
45
57
 
46
58
  const treeNeed = (rule: RuleDefinition, doc: ProseDocument): string | undefined => {
47
59
  if (rule.requires.includes("structure")) return treeProblem(doc);
48
- return rule.requires.some((need) => VALUE_NEEDS.includes(need)) && doc.structure === undefined ? reasonsFor(doc).noStructure(doc.language) : undefined;
60
+ return rule.requires.some((need) => VALUE_NEEDS.has(need)) && doc.structure === undefined ? reasonsFor(doc).noStructure(doc.language) : undefined;
49
61
  };
50
62
 
51
63
  const treeProblem = (doc: ProseDocument): string | undefined => {
@@ -106,7 +118,7 @@ const forGenre = (rules: readonly RuleDefinition[], genre: string): RuleDefiniti
106
118
  */
107
119
  export const neededBy = (rules: readonly RuleDefinition[], settings: Settings, experimental: boolean, genre: string, language: string): AdapterNeeds => ({
108
120
  pos: forGenre(rules, genre)
109
- .filter((rule) => rule.layer !== "L4" && levelFor(rule, settings, experimental) !== "off")
121
+ .filter((rule) => rule.layer !== "L4" && levelFor(rule, settings, experimental, presetLevels(genre)) !== "off")
110
122
  .filter((rule) => rule.languages === undefined || rule.languages.includes(language))
111
123
  .some((rule) => [...rule.requires, ...rule.uses].some((need) => need === "pos" || need === "lemma")),
112
124
  });
@@ -153,11 +165,12 @@ export const runRules = (
153
165
  genre: string,
154
166
  limits: Limits = {},
155
167
  ): RunResult => {
168
+ const preset = presetLevels(genre);
156
169
  const starts = lineStarts(doc.source);
157
170
  const applicable = forGenre(rules, genre);
158
- const forced = applicable
159
- .filter((rule) => rule.status === "experimental" && settings[rule.id] !== undefined && settings[rule.id] !== "off")
160
- .map((rule) => rule.id);
171
+ const experimentalOn = applicable.filter((rule) => rule.status === "experimental" && levelFor(rule, settings, experimental, preset) !== "off");
172
+ const forced = experimentalOn.filter((rule) => settings[rule.id] !== undefined).map((rule) => rule.id);
173
+ const presetOn = experimentalOn.filter((rule) => settings[rule.id] === undefined && preset[rule.id] !== undefined).map((rule) => rule.id);
161
174
  const outcome = applicable.reduce<{ findings: Finding[]; skipped: Skipped[] }>(
162
175
  (acc, rule) => {
163
176
  // L4 は意味を読む検査。chaff test が扱う。ここで「検出器が無い」と言わせない。
@@ -166,11 +179,9 @@ export const runRules = (
166
179
  }
167
180
  const blocked = unmet(rule, doc);
168
181
  if (blocked !== undefined) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: blocked }] };
169
- const level = levelFor(rule, settings, experimental);
170
- if (level === "off") {
171
- const why = rule.status === "experimental" && settings[rule.id] === undefined ? reasonsFor(doc).experimental : reasonsFor(doc).turnedOff;
172
- return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why }] };
173
- }
182
+ const level = levelFor(rule, settings, experimental, preset);
183
+ if (level === "off")
184
+ return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: offReason(rule, settings, preset, genre, reasonsFor(doc)) }] };
174
185
  const noTags = untagged(rule, doc);
175
186
  if (noTags !== undefined) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: noTags }] };
176
187
  // 木は capability ではなく、adapter が structure を持つかで決まる。持たない言語で動かすと「参照先が無い」が 0 件に見える。
@@ -197,8 +208,10 @@ export const runRules = (
197
208
  { findings: [], skipped: [] },
198
209
  );
199
210
  const composites = applicable
200
- .filter((rule) => rule.from.length > 0 && levelFor(rule, settings, experimental) !== "off")
201
- .flatMap((rule) => compositeOf(rule, outcome.findings, limitFor(rule, levelFor(rule, settings, experimental), genre, limits), starts, doc.language));
211
+ .filter((rule) => rule.from.length > 0 && levelFor(rule, settings, experimental, preset) !== "off")
212
+ .flatMap((rule) =>
213
+ compositeOf(rule, outcome.findings, limitFor(rule, levelFor(rule, settings, experimental, preset), genre, limits), starts, doc.language),
214
+ );
202
215
  const all = [...outcome.findings, ...composites];
203
- return { ...outcome, findings: [...all].sort((left, right) => left.line - right.line), forcedExperimental: forced };
216
+ return { ...outcome, findings: all.toSorted((left, right) => left.line - right.line), forcedExperimental: forced, presetExperimental: presetOn };
204
217
  };