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
@@ -1,12 +1,14 @@
1
1
  import { proseText } from "../measure.ts";
2
- import { wordsOf } from "./structure.ts";
2
+ import { dateSpans, wordsOf } from "./structure.ts";
3
3
  import { compacted, placeOf } from "./gram-place.ts";
4
4
  import { notAcronymSpansOf, type NotAcronymSpans } from "./acronym-context.ts";
5
- import { expansionAt, type ExpandedAt } from "./acronym-expansion.ts";
5
+ import { expansionAt, termEntryAcronyms, type ExpandedAt } from "./acronym-expansion.ts";
6
6
  import { isExplained } from "./acronym-compound.ts";
7
7
  import { conjugatedForms } from "./conjugated-form.ts";
8
8
  import { evidenceSpans, hasNumeral, startsWithin } from "./concrete-evidence.ts";
9
+ import { letteredIndexEntries } from "./lettered-index.ts";
9
10
  import { hasPredicateIn } from "./gram-predicate.ts";
11
+ import { nameSpans, touchesAny } from "../team-names.ts";
10
12
  import type { Detector, Finding, ProseDocument, Section, Sentence } from "../plugin.ts";
11
13
 
12
14
  const PER = 1000;
@@ -62,9 +64,12 @@ export const emojiDensity: Detector = (doc, options): Finding[] => {
62
64
  */
63
65
  const GRAM = { char: 8, word: 20 };
64
66
 
65
- const countGrams = (text: string, width: number): Map<string, number> => {
67
+ /** 並べた名前にかかる語句は数えない。名前が繰り返されれば、その前後(「the …」)も一緒に繰り返されるのは当たり前。 */
68
+ const countGrams = (text: string, width: number, names: readonly string[]): Map<string, number> => {
66
69
  const counts = new Map<string, number>();
70
+ const named = nameSpans(text, names);
67
71
  Array.from({ length: Math.max(0, text.length - width + 1) }).forEach((_, index) => {
72
+ if (touchesAny(named, index, index + width)) return;
68
73
  const gram = text.slice(index, index + width);
69
74
  counts.set(gram, (counts.get(gram) ?? 0) + 1);
70
75
  });
@@ -93,7 +98,8 @@ const gramsOf = (doc: ProseDocument): Map<string, number> => {
93
98
  const counts = new Map<string, number>();
94
99
  doc.sentences.forEach((sentence) => {
95
100
  const text = doc.lengthUnit === "char" ? proseText(sentence).replace(/\s+/gu, "") : proseText(sentence);
96
- countGrams(text, GRAM[doc.lengthUnit]).forEach((count, gram) => counts.set(gram, (counts.get(gram) ?? 0) + count));
101
+ const names = doc.lengthUnit === "char" ? (doc.names ?? []).map((name) => name.replace(/\s+/gu, "")) : (doc.names ?? []);
102
+ countGrams(text, GRAM[doc.lengthUnit], names).forEach((count, gram) => counts.set(gram, (counts.get(gram) ?? 0) + count));
97
103
  });
98
104
  return counts;
99
105
  };
@@ -117,7 +123,7 @@ export const ngramRepetition: Detector = (doc, options): Finding[] => {
117
123
  const sentenceWith = (gram: string): Sentence | undefined => doc.sentences.find((sentence) => gramText(sentence, doc.lengthUnit).includes(gram));
118
124
  const worst = [...gramsOf(doc).entries()]
119
125
  .filter(([gram, count]) => count > options.limit && isPhrasing(gram, doc.lengthUnit))
120
- .sort(([, left], [, right]) => right - left)
126
+ .toSorted(([, left], [, right]) => right - left)
121
127
  .map(([gram, count]) => ({ gram, count, at: sentenceWith(gram) }))
122
128
  .find(({ gram, at }) => at === undefined || hasPredicate(gram, at, doc.lengthUnit));
123
129
  if (worst === undefined) return [];
@@ -194,13 +200,35 @@ const spansOf = (text: string, notation: NotAcronymSpans): Span[] => [
194
200
 
195
201
  type AcronymHit = { readonly word: string; readonly hit: Hit };
196
202
 
203
+ /**
204
+ * 言語パッケージが、大文字で書いて強調した普通の語(will NEVER call)と読んだ語。辞書がその語を名前になり得ない品詞でしか
205
+ * 知らないときだけ、そう読む。品詞を持たない言語パッケージでは何も外れない。
206
+ */
207
+ const isEmphasised = (sentence: Sentence, offset: number): boolean =>
208
+ (sentence.tokens ?? []).some((token) => token.span.start === offset && token.features?.["Emph"] === "Yes");
209
+
210
+ /** チームが並べた名前の中の略語(NTT Docomo の NTT)は、名前の一部であって説明を待つ略語ではない。 */
197
211
  const acronymsOf = (doc: ProseDocument, notation: NotAcronymSpans): AcronymHit[] =>
198
212
  doc.sentences.flatMap((sentence) => {
199
- const excluded = spansOf(sentence.text, notation);
213
+ const excluded = [...spansOf(sentence.text, notation), ...nameSpans(sentence.text, doc.names ?? [])];
200
214
  return [...sentence.text.matchAll(ACRONYM)]
201
215
  .filter((match) => !excluded.some((span) => span.start <= match.index && match.index + match[0].length <= span.end))
202
- .map((match) => ({ word: match[0], hit: { sentence, offset: sentence.span.start + match.index } }));
216
+ .map((match) => ({ word: match[0], hit: { sentence, offset: sentence.span.start + match.index } }))
217
+ .filter(({ hit }) => !isEmphasised(hit.sentence, hit.offset));
218
+ });
219
+
220
+ /** 略語ごとの、最初に現れた所。 */
221
+ const firstHits = (hits: readonly AcronymHit[]): Map<string, Hit> => {
222
+ const seen = new Map<string, Hit>();
223
+ hits.forEach(({ word, hit }) => {
224
+ if (!seen.has(word)) seen.set(word, hit);
203
225
  });
226
+ return seen;
227
+ };
228
+
229
+ /** 日付の中の月(SEP 01, 2022)は略語ではない。 */
230
+ const outsideDates = (hits: readonly AcronymHit[], dates: readonly Span[]): AcronymHit[] =>
231
+ hits.filter(({ hit }) => !dates.some((span) => span.start <= hit.offset && hit.offset < span.end));
204
232
 
205
233
  /**
206
234
  * どこか 1 か所で展開してあればよい。初出が節の見出し代わりの語(「5.3. DPA.」)で、
@@ -220,6 +248,7 @@ const notationOf = (doc: ProseDocument): NotAcronymSpans =>
220
248
  emphasis: patternsOf(doc, "emphasis-word"),
221
249
  divisions: patternsOf(doc, "numbered-division"),
222
250
  honorifics: patternsOf(doc, "honorific"),
251
+ titles: patternsOf(doc, "name-title"),
223
252
  dateTimeUnits: patternsOf(doc, "date-time-unit"),
224
253
  });
225
254
 
@@ -232,14 +261,16 @@ const definitionVerbsOf = (doc: ProseDocument): string[] => {
232
261
  export const undefinedAcronym: Detector = (doc, options): Finding[] => {
233
262
  const body = bodyOf(doc);
234
263
  // HTTP のメソッド名(GET)は略語ではないので、通じる略語と同じく展開を求めない。
235
- const common = new Set([...(options.lexicon ?? []).map((entry) => entry.pattern), ...patternsOf(doc, "http-method")]);
236
- const seen = new Map<string, Hit>();
237
- acronymsOf(doc, notationOf(doc)).forEach(({ word, hit }) => {
238
- if (!seen.has(word)) seen.set(word, hit);
239
- });
264
+ // 並べた名前(JAXA)も、繋いだ略語(JAXA-ISAS)の片割れとして説明済みに数える。
265
+ const common = new Set([...(options.lexicon ?? []).map((entry) => entry.pattern), ...patternsOf(doc, "http-method"), ...(doc.names ?? [])]);
240
266
  const expandedAt = expansionAt({ markers: patternsOf(doc, "definition-marker"), verbs: definitionVerbsOf(doc) });
241
- const explainedAlone = (word: string): boolean => common.has(word) || isExpanded(body, word, expandedAt);
242
- const bare = [...seen.entries()].filter(([acronym]) => !isExplained(acronym, explainedAlone));
267
+ const entries = termEntryAcronyms(doc.source);
268
+ const explainedAlone = (word: string): boolean => common.has(word) || entries.has(word) || isExpanded(body, word, expandedAt);
269
+ const hits = acronymsOf(doc, notationOf(doc));
270
+ const unexplained = new Set([...firstHits(hits).keys()].filter((acronym) => !isExplained(acronym, explainedAlone)));
271
+ // 日付を読むには文書の木を作る。上限に届かない文書では作らない。
272
+ if (unexplained.size < options.limit) return [];
273
+ const bare = [...firstHits(outsideDates(hits, dateSpans(doc)))].filter(([acronym]) => unexplained.has(acronym));
243
274
  if (bare.length < options.limit) return [];
244
275
  return bare.map(([acronym, hit]) => ({
245
276
  rule: "undefined-acronym",
@@ -273,7 +304,8 @@ const openingOf = (sentence: Sentence | undefined, unit: ProseDocument["lengthUn
273
304
  const hasNumeralIn = (section: Section): boolean => section.sentences.some((sentence) => hasNumeral(sentence.tokens ?? []));
274
305
 
275
306
  export const concreteEvidence: Detector = (doc, options): Finding[] => {
276
- const sections = doc.sections.filter((section) => section.sentences.length >= 3);
307
+ const entries = letteredIndexEntries(doc.sections);
308
+ const sections = doc.sections.filter((section) => section.sentences.length >= 3 && !entries.has(section));
277
309
  const bare = sections.filter(
278
310
  (section) => !CONCRETE.test(doc.source.slice(section.span.start, section.span.end)) && !hasNumeralIn(section) && !startsWithin(section.span, doc.links),
279
311
  );
@@ -0,0 +1,96 @@
1
+ import type { Lexicon, Sentence, Span, Token } from "../plugin.ts";
2
+ import { entryRanges, type TokenRange } from "./lexicon-match.ts";
3
+ import { isWithinAny, quotedIn, QUOTATION_MARKS } from "../quoted-span.ts";
4
+
5
+ /** 重ねたと言うのに要る数。1 つなら慎重さで、2 つ目からは読み手が何を言い切ったのか分からなくなる。 */
6
+ const STACKED_AT = 2;
7
+
8
+ /**
9
+ * 語彙表 3 つ。hedges は逃げの表現そのもの、frames はそれだけでは逃げていない包む言い方(「という状況です」、may)、
10
+ * scope は hedges のうち、どれだけ確かかではなくどこで成り立つかを言うもの(「場合がある」、in some cases)。
11
+ */
12
+ export type HedgeWords = { readonly hedges: Lexicon; readonly frames: Lexicon; readonly scope: Lexicon };
13
+
14
+ type Device = { readonly range: TokenRange; readonly hedge: boolean };
15
+
16
+ export type StackedHedge = { readonly sentence: Sentence; readonly spans: readonly Span[] };
17
+
18
+ const rangesOf = (sentence: Sentence, lexicon: Lexicon): TokenRange[] => lexicon.flatMap((entry) => entryRanges(sentence, entry));
19
+
20
+ const contains = (outer: TokenRange, inner: TokenRange): boolean => outer.start <= inner.start && inner.end <= outer.end;
21
+
22
+ const sameRange = (left: TokenRange, right: TokenRange): boolean => left.start === right.start && left.end === right.end;
23
+
24
+ /**
25
+ * 同じ語に当たった見出しは 1 つに数える。「could」は「it could be」の一部。
26
+ * 同じ範囲に hedge と frame が両方当たったら hedge として残す(devices は hedge が先に並んでいる)。
27
+ */
28
+ const outermost = (devices: readonly Device[]): Device[] =>
29
+ devices.filter(
30
+ (device, index) =>
31
+ !devices.some((other, at) => at !== index && contains(other.range, device.range) && (!sameRange(other.range, device.range) || at < index)),
32
+ );
33
+
34
+ /** 語の並びの、文書全体の座標での範囲。 */
35
+ const spanOf = (tokens: readonly Token[], range: TokenRange): Span | undefined => {
36
+ const first = tokens[range.start];
37
+ const last = tokens[range.end - 1];
38
+ return first === undefined || last === undefined ? undefined : { start: first.span.start, end: last.span.end };
39
+ };
40
+
41
+ /** 句読点と等位接続詞(and、but、及び)は節を切る。別の節の逃げは、同じ主張を二重にぼかしていない。 */
42
+ const CLAUSE_BREAK = new Set(["PUNCT", "CCONJ"]);
43
+
44
+ /** 語ごとの、文の頭から数えた節の番号。 */
45
+ const clauseOf = (tokens: readonly Token[]): number[] =>
46
+ tokens.reduce<number[]>((clauses, token) => {
47
+ clauses.push((clauses.at(-1) ?? 0) + (CLAUSE_BREAK.has(token.pos) ? 1 : 0));
48
+ return clauses;
49
+ }, []);
50
+
51
+ const PREDICATE = new Set(["VERB", "AUX"]);
52
+
53
+ /** 見せる範囲。活用する語で終わる見出し(と考えられる)は、後ろの助動詞(ます)まで含めて書いたとおりに見せる。 */
54
+ const shownEnd = (tokens: readonly Token[], range: TokenRange): number => {
55
+ const last = tokens[range.end - 1];
56
+ if (last === undefined || !PREDICATE.has(last.pos)) return range.end;
57
+ const stop = tokens.findIndex((token, at) => at >= range.end && token.pos !== "AUX");
58
+ return stop === -1 ? tokens.length : stop;
59
+ };
60
+
61
+ /** 書き手の言葉で、どこで成り立つかを言う語ではない見出し。引用(「」『』“” ")の中は話し手の言葉。 */
62
+ const devicesIn = (sentence: Sentence, words: HedgeWords): Device[] => {
63
+ const tokens = sentence.tokens ?? [];
64
+ const quoted = quotedIn(sentence, QUOTATION_MARKS);
65
+ const scope = rangesOf(sentence, words.scope);
66
+ const hedges = rangesOf(sentence, words.hedges).filter((range) => !scope.some((outer) => contains(outer, range)));
67
+ const devices = [...hedges.map((range) => ({ range, hedge: true })), ...rangesOf(sentence, words.frames).map((range) => ({ range, hedge: false }))];
68
+ return outermost(devices).filter((device) => {
69
+ const span = spanOf(tokens, device.range);
70
+ return span !== undefined && !isWithinAny(quoted, span);
71
+ });
72
+ };
73
+
74
+ /** 1 つの節に 2 つ以上あり、そのうち 1 つ以上が逃げの表現そのもの。包む言い方だけ(「という状況です」)は逃げていない。 */
75
+ const stackedClause = (devices: readonly Device[], clauses: readonly number[]): Device[] => {
76
+ const clauseAt = (device: Device): number => clauses[device.range.start] ?? 0;
77
+ const groups = [...new Set(devices.map(clauseAt))].map((clause) => devices.filter((device) => clauseAt(device) === clause));
78
+ return groups.find((group) => group.length >= STACKED_AT && group.some((device) => device.hedge)) ?? [];
79
+ };
80
+
81
+ /**
82
+ * 1 つの文の 1 つの節に、逃げの表現を重ねているか。business-blog-harness-spec §7.2。見せる範囲を文の中の順で返す。重ねていなければ空。
83
+ * 品詞が無ければ語の位置が分からないので何も言わない。
84
+ */
85
+ const stackedHedgeIn = (sentence: Sentence, words: HedgeWords): Span[] => {
86
+ const tokens = sentence.tokens ?? [];
87
+ return stackedClause(devicesIn(sentence, words), clauseOf(tokens))
88
+ .toSorted((left, right) => left.range.start - right.range.start)
89
+ .flatMap((device) => spanOf(tokens, { start: device.range.start, end: shownEnd(tokens, device.range) }) ?? []);
90
+ };
91
+
92
+ export const stackedHedges = (sentences: readonly Sentence[], words: HedgeWords): StackedHedge[] =>
93
+ sentences.flatMap((sentence) => {
94
+ const spans = stackedHedgeIn(sentence, words);
95
+ return spans.length === 0 ? [] : [{ sentence, spans }];
96
+ });
@@ -0,0 +1,40 @@
1
+ import type { Detector, Finding } from "../plugin.ts";
2
+ import { minorityStyle } from "../orthography.ts";
3
+ import { isClosed } from "../sentence-shape.ts";
4
+ import { phraseJoints, type Joint, type JointKind } from "../stray-space.ts";
5
+
6
+ const KINDS: readonly JointKind[] = ["between-phrases", "inside-phrase"];
7
+
8
+ type Quoted = Joint & { readonly quote: string };
9
+
10
+ /**
11
+ * 空けたほうが少ないときだけ、その空白を指摘する。空けたほうが多い文書(文節ごとに空ける分かち書き)は、
12
+ * その書き方を選んでいるので何も言わない。詰めた境目を「空け忘れ」とは言わない。どこで空けるかは書き手が決める。
13
+ */
14
+ const strays = (joints: readonly Quoted[]): readonly Quoted[] => (minorityStyle(joints) === true ? joints.filter((joint) => joint.spaced) : []);
15
+
16
+ /**
17
+ * 日本語の語句の途中の空白(「こころさんが 払った」「確認 しました」)。句点で終わる文だけを見る。
18
+ * 句点の無い行(歌、名札と値、表のような行)の空白は区切りとして書かれている。
19
+ */
20
+ export const straySpace: Detector = (doc, options): Finding[] => {
21
+ const joints: Quoted[] = doc.sentences
22
+ .filter(isClosed)
23
+ .flatMap((sentence) =>
24
+ phraseJoints(sentence, doc.source.slice(sentence.span.start, sentence.span.end)).map((joint) => ({ ...joint, quote: sentence.text.trim() })),
25
+ );
26
+ return KINDS.flatMap((kind) => {
27
+ const ofKind = joints.filter((joint) => joint.kind === kind);
28
+ const odd = strays(ofKind);
29
+ if (odd.length < options.limit) return [];
30
+ return odd.map((joint) => ({
31
+ rule: "",
32
+ severity: "warning",
33
+ line: 0,
34
+ column: 0,
35
+ quote: joint.quote,
36
+ variant: kind,
37
+ values: { before: joint.before, after: joint.after, count: odd.length, of: ofKind.length, limit: options.limit, offset: joint.offset },
38
+ }));
39
+ });
40
+ };
@@ -3,6 +3,7 @@ import type { BulletList, Detector, Finding, Paragraph, ProseDocument, Section,
3
3
  import { dateStampIndexes, stampCandidates } from "../date-stamp.ts";
4
4
  import { inDocumentOrder } from "../structure/issues.ts";
5
5
  import { preambleParagraphs } from "../preamble-paragraphs.ts";
6
+ import { isTooLongParagraph } from "../paragraph-length.ts";
6
7
 
7
8
  /**
8
9
  * ばらつきは変動係数(標準偏差 ÷ 平均)で測る。
@@ -41,7 +42,13 @@ const uniformity = (doc: ProseDocument, values: readonly number[], span: { start
41
42
  /** 1 段落に文を詰めすぎると、読み手はどこで息継ぎしていいか分からなくなる。 */
42
43
  export const paragraphLength: Detector = (doc, options): Finding[] =>
43
44
  doc.paragraphs
44
- .filter((paragraph) => paragraph.sentences.length > options.limit)
45
+ .filter((paragraph) =>
46
+ isTooLongParagraph(
47
+ paragraph.sentences.map((sentence) => lengthOf(sentence, doc.lengthUnit)),
48
+ options.limit,
49
+ options.fullSentence,
50
+ ),
51
+ )
45
52
  .map((paragraph) => ({
46
53
  rule: "max-paragraph-length",
47
54
  severity: "warning",
@@ -93,7 +100,8 @@ export const ruleOfThree: Detector = (doc, options): Finding[] => {
93
100
  ];
94
101
  };
95
102
 
96
- const dateSpans = (doc: ProseDocument): Span[] =>
103
+ /** 言語パッケージが日付と読んだ範囲(April 1, 2024、SEP 01, 2022)。木を作るので、要るときだけ呼ぶ。 */
104
+ export const dateSpans = (doc: ProseDocument): Span[] =>
97
105
  doc.structure === undefined ? [] : inDocumentOrder(doc.structure).flatMap((node) => (node.kind === "date" ? [node.span] : []));
98
106
 
99
107
  /** 更新日の刻印(「最終更新日:」「2025年6月20日」)は前置きに数えない。木を作るのは、数えて上限を超えたときだけ。 */
@@ -0,0 +1,24 @@
1
+ import type { Lexicon, Token } from "../plugin.ts";
2
+ import type { TokenRange } from "./lexicon-match.ts";
3
+
4
+ /** 名詞の後ろにこれが続けば、その名詞は句の頭ではない。解析器は形容詞を名詞と読むことがある(the most frigid weather・the most cost-effective)。 */
5
+ const JOINED = new Set(["NOUN", "PROPN", "ADJ", "X"]);
6
+
7
+ const listed = (tokens: readonly Token[], range: TokenRange, amounts: Lexicon): boolean => {
8
+ const words = tokens
9
+ .slice(range.start, range.end)
10
+ .map((token) => token.surface.toLowerCase())
11
+ .join(" ");
12
+ return amounts.some((entry) => entry.pattern.toLowerCase() === words);
13
+ };
14
+
15
+ /**
16
+ * 量も言う最上級に、句の頭になる名詞がそのまま続くか(the most work・the most students・for the most part)。
17
+ * それは一番多いものを言っていて、何かが一番良いという主張ではない。形容詞を挟めば(the most powerful tool)主張のまま。
18
+ * どの最上級が量を言うかは言語パッケージの語彙表 superlative-amount が持つ。
19
+ */
20
+ export const namesAmount = (tokens: readonly Token[], range: TokenRange, amounts: Lexicon): boolean => {
21
+ const noun = tokens[range.end];
22
+ const after = tokens[range.end + 1];
23
+ return listed(tokens, range, amounts) && noun?.pos === "NOUN" && (after === undefined || !JOINED.has(after.pos));
24
+ };
@@ -0,0 +1,82 @@
1
+ import type { Lexicon, Token } from "../plugin.ts";
2
+ import type { TokenRange } from "./lexicon-match.ts";
3
+ import { PHRASE_END, PHRASE_START, phraseEnd } from "./superlative-scope.ts";
4
+
5
+ /** 最上級の名詞句を後ろから限る語。どちらも言語パッケージの語彙表が持つ。 */
6
+ export type Restrictors = {
7
+ /** 名詞の後ろで関係節を始める語(that・which)。空の言語は、名詞の後ろに節を置かない。 */
8
+ readonly relatives: Lexicon;
9
+ /** 最上級を手に入る範囲に限る形容詞(possible・available)。 */
10
+ readonly bounds: Lexicon;
11
+ /** 節の主語になる代名詞(we・they)。themselves のような再帰の代名詞は主語にならない。 */
12
+ readonly subjects: Lexicon;
13
+ };
14
+
15
+ const VERBAL = new Set(["VERB", "AUX"]);
16
+
17
+ const isWord = (token: Token | undefined, words: Lexicon): boolean =>
18
+ token !== undefined && words.some((entry) => entry.pattern.toLowerCase() === token.surface.toLowerCase());
19
+
20
+ const isVerbal = (token: Token | undefined): boolean => token !== undefined && VERBAL.has(token.pos);
21
+
22
+ /** 節の主語(we・managers・the team・this)を読み飛ばした先に動詞があるか。主語の無い節(that has defined)は動詞で始まる。 */
23
+ const verbAfterSubject = (tokens: readonly Token[], at: number, subjects: Lexicon): boolean => {
24
+ if (isVerbal(tokens[at])) return true;
25
+ if (isWord(tokens[at], subjects)) return isVerbal(tokens[at + 1]);
26
+ if (tokens[at]?.pos === "DET") return isVerbal(tokens[at + 1]) || isVerbal(tokens[phraseEnd(tokens, at + 1)]);
27
+ return isVerbal(tokens[phraseEnd(tokens, at)]);
28
+ };
29
+
30
+ const isArticle = (token: Token | undefined): boolean => token?.features?.["PronType"] === "Art";
31
+
32
+ /**
33
+ * 関係詞を省いた節の主語は、代名詞(the best we have measured)か冠詞で始まる名詞句(the best option the team selected)。
34
+ * each・all のような限定詞(The best teams all adapt)や、冠詞の無い名詞(The best teams adapt)は節の始まりと読まない。
35
+ */
36
+ const contactClause = (tokens: readonly Token[], at: number, subjects: Lexicon): boolean => {
37
+ if (isWord(tokens[at], subjects)) return isVerbal(tokens[at + 1]);
38
+ return isArticle(tokens[at]) && isVerbal(tokens[phraseEnd(tokens, at + 1)]);
39
+ };
40
+
41
+ /** 関係節(the threats that we have seen・the option which we chose)と、関係詞を省いた節。 */
42
+ const clause = (tokens: readonly Token[], at: number, restrictors: Restrictors): boolean =>
43
+ isWord(tokens[at], restrictors.relatives) ? verbAfterSubject(tokens, at + 1, restrictors.subjects) : contactClause(tokens, at, restrictors.subjects);
44
+
45
+ /** 分詞の後ろに文の動詞があるか。and を挟んだ動詞(The best option changed quickly and won)は分詞と並ぶ述語。 */
46
+ const mainVerbFollows = (tokens: readonly Token[], at: number): boolean => {
47
+ const rest = tokens.slice(at + 1);
48
+ const verb = rest.findIndex(isVerbal);
49
+ return verb !== -1 && !rest.slice(0, verb).some((token) => token.pos === "CCONJ");
50
+ };
51
+
52
+ /**
53
+ * 名詞の後ろの過去分詞(the most scalable option discussed)。名詞句が動詞の目的語か補語で(We chose the best option discussed)、
54
+ * または分詞の後ろに文の動詞がある(The best results obtained were poor)ときだけ。目的語を取る分詞(The best solution
55
+ * proposed a fix)と、ほかに述語の無い分詞(The best option changed.)は文の動詞。
56
+ */
57
+ const participle = (tokens: readonly Token[], range: TokenRange, at: number): boolean => {
58
+ const next = tokens[at + 1];
59
+ if (tokens[at]?.features?.["VerbForm"] !== "Part" || (next !== undefined && PHRASE_START.has(next.pos))) return false;
60
+ return isVerbal(tokens[range.start - 1]) || mainVerbFollows(tokens, at);
61
+ };
62
+
63
+ /** 最上級のすぐ後ろ(the best possible outcome)か、名詞句の後ろ(the best education possible)。 */
64
+ const bounded = (tokens: readonly Token[], range: TokenRange, at: number, bounds: Lexicon): boolean =>
65
+ isWord(tokens[range.end], bounds) || isWord(tokens[at], bounds);
66
+
67
+ /** 修飾の語は名詞の前に来るので、句の後ろで止まった分詞は名詞の後ろにある。 */
68
+ const clauseOrParticiple = (tokens: readonly Token[], range: TokenRange, at: number, restrictors: Restrictors): boolean => {
69
+ const last = tokens[at - 1];
70
+ const endsPhrase = at === range.end || (last !== undefined && PHRASE_END.has(last.pos));
71
+ return (endsPhrase && clause(tokens, at, restrictors)) || participle(tokens, range, at);
72
+ };
73
+
74
+ /**
75
+ * 最上級の名詞句を後ろの節か分詞か形容詞が限っているか(the best we have measured・the most scalable option discussed・
76
+ * the best education possible)。何の中で最もなのかを言っているので、限定の無い最上級ではない。
77
+ * 名詞の後ろに節を置かない言語は関係詞の語彙表が空で、節も分詞も読まない。
78
+ */
79
+ export const restricted = (tokens: readonly Token[], range: TokenRange, restrictors: Restrictors): boolean => {
80
+ const at = phraseEnd(tokens, range.end);
81
+ return bounded(tokens, range, at, restrictors.bounds) || (restrictors.relatives.length > 0 && clauseOrParticiple(tokens, range, at, restrictors));
82
+ };
@@ -21,14 +21,14 @@ export const scopeMarkersOf = (lexicon: Lexicon): ScopeMarkers => ({
21
21
  const NOMINAL = new Set(["NOUN", "PROPN"]);
22
22
 
23
23
  /** 名詞句の始まり。the world・Chicago・our products・the three・all。 */
24
- const PHRASE_START = new Set(["DET", "NOUN", "PROPN", "PRON", "NUM", "ADJ"]);
24
+ export const PHRASE_START = new Set(["DET", "NOUN", "PROPN", "PRON", "NUM", "ADJ"]);
25
25
 
26
26
  /** 名詞の前に来る修飾(most skilled・widely used・stuffed)。名詞が出た後は名詞の連なりだけが続く。 */
27
27
  const PREMODIFIER = new Set(["ADJ", "ADV", "VERB", "NOUN", "PROPN", "NUM"]);
28
28
  const NOUN_RUN = new Set(["NOUN", "PROPN", "NUM"]);
29
29
 
30
30
  /** 範囲の前に置ける語。修飾だけで終わった句(the best is in …)は範囲ではなく述語。 */
31
- const PHRASE_END = new Set(["NOUN", "PROPN", "NUM", "ADJ"]);
31
+ export const PHRASE_END = new Set(["NOUN", "PROPN", "NUM", "ADJ"]);
32
32
 
33
33
  const isMarker = (token: Token | undefined, markers: Lexicon): boolean =>
34
34
  token !== undefined && markers.some((entry) => entry.pattern.toLowerCase() === token.surface.toLowerCase());
@@ -47,7 +47,7 @@ const namedBefore = (tokens: readonly Token[], range: TokenRange, markers: Lexic
47
47
  isMarker(tokens[range.start - 1], markers) && isNamed(tokens[range.start - 2]);
48
48
 
49
49
  /** 最上級に続く名詞句の終わり。名詞が出るまでは修飾を、出た後は名詞の連なりを読む。 */
50
- const phraseEnd = (tokens: readonly Token[], at: number, nounSeen = false): number => {
50
+ export const phraseEnd = (tokens: readonly Token[], at: number, nounSeen = false): number => {
51
51
  const token = tokens[at];
52
52
  if (token === undefined || !(nounSeen ? NOUN_RUN : PREMODIFIER).has(token.pos)) return at;
53
53
  return phraseEnd(tokens, at + 1, nounSeen || NOMINAL.has(token.pos));
@@ -1,7 +1,8 @@
1
1
  import { proseText } from "../measure.ts";
2
2
  import { joinWords } from "./word-list.ts";
3
3
  import { wordsOf } from "./structure.ts";
4
- import type { Detector, Finding, Sentence, Token } from "../plugin.ts";
4
+ import { nameSpans } from "../team-names.ts";
5
+ import type { Detector, Finding, Sentence, Span, Token } from "../plugin.ts";
5
6
 
6
7
  /**
7
8
  * チームの言葉。社内でしか通じない語を、チームが chaff.yaml に自分で並べる。
@@ -76,8 +77,23 @@ const isProperNoun = (token: Token): boolean => token.pos === "PROPN";
76
77
  * spec は未知語率としていたが、**品詞解析があれば PROPN を数えるだけで足りる**。
77
78
  * 辞書を別に持つ必要はない。
78
79
  */
80
+ const within = (spans: readonly Span[], token: Token): Span | undefined => spans.find((span) => token.span.start >= span.start && token.span.end <= span.end);
81
+
82
+ /** 並べた名前は、何語に割れても 1 つの固有名詞(Bank of England は 2 つでなく 1 つ)。名前の中の 2 語目からを落とす。 */
83
+ const properNounsOf = (sentence: Sentence, names: readonly string[]): Token[] => {
84
+ const named = nameSpans(sentence.text, names).map((span) => ({ start: sentence.span.start + span.start, end: sentence.span.start + span.end }));
85
+ const counted = new Set<Span>();
86
+ return (sentence.tokens ?? []).filter(isProperNoun).filter((token) => {
87
+ const name = within(named, token);
88
+ if (name === undefined) return true;
89
+ if (counted.has(name)) return false;
90
+ counted.add(name);
91
+ return true;
92
+ });
93
+ };
94
+
79
95
  export const properNounDensity: Detector = (doc, options): Finding[] => {
80
- const hits = doc.sentences.flatMap((sentence) => (sentence.tokens ?? []).filter(isProperNoun).map((token) => ({ sentence, token })));
96
+ const hits = doc.sentences.flatMap((sentence) => properNounsOf(sentence, doc.names ?? []).map((token) => ({ sentence, token })));
81
97
  const length = wordsOf(doc);
82
98
  const rate = length === 0 ? 0 : Math.round((hits.length / length) * PER);
83
99
  const first = hits[0];
@@ -1,6 +1,6 @@
1
1
  import { hasParticle, isClosed, lastContent } from "../sentence-shape.ts";
2
- import { isWithinAny, quotedSpans } from "../quoted-span.ts";
3
- import type { Detector, Finding, Sentence, Span, Token } from "../plugin.ts";
2
+ import { isWithinAny, quotedIn } from "../quoted-span.ts";
3
+ import type { Detector, Finding, Span, Token } from "../plugin.ts";
4
4
 
5
5
  /**
6
6
  * 文末が名詞で終わる(体言止め)。箇条書きでは普通だが、本文では文が途中で切れて読める。
@@ -45,10 +45,11 @@ export const nounEnding: Detector = (doc, options): Finding[] => {
45
45
  */
46
46
  type Run = { readonly surface: string; readonly tokens: readonly Token[] };
47
47
 
48
- const extend = (runs: readonly Run[], token: Token): Run[] => {
48
+ const extend = (runs: Run[], token: Token): Run[] => {
49
49
  const last = runs.at(-1);
50
- if (last !== undefined && last.surface === token.surface) return [...runs.slice(0, -1), { surface: last.surface, tokens: [...last.tokens, token] }];
51
- return [...runs, { surface: token.surface, tokens: [token] }];
50
+ if (last !== undefined && last.surface === token.surface) runs[runs.length - 1] = { surface: last.surface, tokens: [...last.tokens, token] };
51
+ else runs.push({ surface: token.surface, tokens: [token] });
52
+ return runs;
52
53
  };
53
54
 
54
55
  const PARTICLE = new Set(["ADP", "SCONJ", "PART", "CCONJ"]);
@@ -66,13 +67,10 @@ const runsOf = (tokens: readonly Token[], nesting: readonly string[], quoted: re
66
67
  tokens.reduce<Run[]>((acc, token) => {
67
68
  if (isWithinAny(quoted, token.span)) return acc;
68
69
  if (nesting.includes(token.surface)) return extend(acc, token);
69
- return token.pos === "PUNCT" || PARTICLE.has(token.pos) ? [...acc, BREAK] : acc;
70
+ if (token.pos === "PUNCT" || PARTICLE.has(token.pos)) acc.push(BREAK);
71
+ return acc;
70
72
  }, []);
71
73
 
72
- /** 文の中の、鉤括弧で引いたものを括弧ごと、文書全体の座標で。 */
73
- const quotedIn = (sentence: Sentence): Span[] =>
74
- quotedSpans(sentence.text).map((span) => ({ start: sentence.span.start + span.start - 1, end: sentence.span.start + span.end + 1 }));
75
-
76
74
  export const doubledParticle: Detector = (doc, options): Finding[] => {
77
75
  const nesting = (options.lexicon ?? []).map((entry) => entry.pattern);
78
76
  return doc.sentences.flatMap((sentence) =>