chaffjs 0.24.0 → 0.26.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 (418) hide show
  1. package/README.md +179 -42
  2. package/dist/adapter-import.d.ts +2 -0
  3. package/dist/adapter-import.d.ts.map +1 -0
  4. package/dist/adapter-import.js +11 -0
  5. package/dist/adapter-import.js.map +1 -0
  6. package/dist/adapter-load.d.ts.map +1 -1
  7. package/dist/adapter-load.js +2 -9
  8. package/dist/adapter-load.js.map +1 -1
  9. package/dist/ai-score/human-shares.d.ts +4 -0
  10. package/dist/ai-score/human-shares.d.ts.map +1 -0
  11. package/dist/ai-score/human-shares.js +205 -0
  12. package/dist/ai-score/human-shares.js.map +1 -0
  13. package/dist/ai-score/lint-line.d.ts +4 -0
  14. package/dist/ai-score/lint-line.d.ts.map +1 -0
  15. package/dist/ai-score/lint-line.js +9 -0
  16. package/dist/ai-score/lint-line.js.map +1 -0
  17. package/dist/ai-score/of-document.d.ts +7 -0
  18. package/dist/ai-score/of-document.d.ts.map +1 -0
  19. package/dist/ai-score/of-document.js +40 -0
  20. package/dist/ai-score/of-document.js.map +1 -0
  21. package/dist/ai-score/render.d.ts +24 -0
  22. package/dist/ai-score/render.d.ts.map +1 -0
  23. package/dist/ai-score/render.js +100 -0
  24. package/dist/ai-score/render.js.map +1 -0
  25. package/dist/ai-score/score.d.ts +77 -0
  26. package/dist/ai-score/score.d.ts.map +1 -0
  27. package/dist/ai-score/score.js +52 -0
  28. package/dist/ai-score/score.js.map +1 -0
  29. package/dist/ai-score/signals.d.ts +19 -0
  30. package/dist/ai-score/signals.d.ts.map +1 -0
  31. package/dist/ai-score/signals.js +30 -0
  32. package/dist/ai-score/signals.js.map +1 -0
  33. package/dist/ai-score/text.d.ts +37 -0
  34. package/dist/ai-score/text.d.ts.map +1 -0
  35. package/dist/ai-score/text.js +59 -0
  36. package/dist/ai-score/text.js.map +1 -0
  37. package/dist/ai-score/view.d.ts +5 -0
  38. package/dist/ai-score/view.d.ts.map +1 -0
  39. package/dist/ai-score/view.js +19 -0
  40. package/dist/ai-score/view.js.map +1 -0
  41. package/dist/backends/anthropic.d.ts +0 -1
  42. package/dist/backends/anthropic.d.ts.map +1 -1
  43. package/dist/backends/anthropic.js +0 -1
  44. package/dist/backends/anthropic.js.map +1 -1
  45. package/dist/backends/default-model.d.ts +3 -0
  46. package/dist/backends/default-model.d.ts.map +1 -0
  47. package/dist/backends/default-model.js +5 -0
  48. package/dist/backends/default-model.js.map +1 -0
  49. package/dist/backends/openai.d.ts +0 -1
  50. package/dist/backends/openai.d.ts.map +1 -1
  51. package/dist/backends/openai.js +0 -1
  52. package/dist/backends/openai.js.map +1 -1
  53. package/dist/browser/adapter-import.d.ts +2 -0
  54. package/dist/browser/adapter-import.d.ts.map +1 -0
  55. package/dist/browser/adapter-import.js +17 -0
  56. package/dist/browser/adapter-import.js.map +1 -0
  57. package/dist/browser/bounded-match.d.ts +3 -0
  58. package/dist/browser/bounded-match.d.ts.map +1 -0
  59. package/dist/browser/bounded-match.js +4 -0
  60. package/dist/browser/bounded-match.js.map +1 -0
  61. package/dist/browser/check-text.d.ts +43 -0
  62. package/dist/browser/check-text.d.ts.map +1 -0
  63. package/dist/browser/check-text.js +50 -0
  64. package/dist/browser/check-text.js.map +1 -0
  65. package/dist/browser/detectors.d.ts +4 -0
  66. package/dist/browser/detectors.d.ts.map +1 -0
  67. package/dist/browser/detectors.js +22 -0
  68. package/dist/browser/detectors.js.map +1 -0
  69. package/dist/browser/package-files.d.ts +5 -0
  70. package/dist/browser/package-files.d.ts.map +1 -0
  71. package/dist/browser/package-files.js +8 -0
  72. package/dist/browser/package-files.js.map +1 -0
  73. package/dist/browser/path.d.ts +9 -0
  74. package/dist/browser/path.d.ts.map +1 -0
  75. package/dist/browser/path.js +70 -0
  76. package/dist/browser/path.js.map +1 -0
  77. package/dist/browser/working-dir.d.ts +2 -0
  78. package/dist/browser/working-dir.d.ts.map +1 -0
  79. package/dist/browser/working-dir.js +3 -0
  80. package/dist/browser/working-dir.js.map +1 -0
  81. package/dist/browser-files.d.ts +21 -0
  82. package/dist/browser-files.d.ts.map +1 -0
  83. package/dist/browser-files.js +65 -0
  84. package/dist/browser-files.js.map +1 -0
  85. package/dist/browser.d.ts +9 -0
  86. package/dist/browser.d.ts.map +1 -0
  87. package/dist/browser.js +13 -0
  88. package/dist/browser.js.map +1 -0
  89. package/dist/cli-text.d.ts.map +1 -1
  90. package/dist/cli-text.js +2 -0
  91. package/dist/cli-text.js.map +1 -1
  92. package/dist/cli.d.ts.map +1 -1
  93. package/dist/cli.js +4 -1
  94. package/dist/cli.js.map +1 -1
  95. package/dist/commands/ai-score.d.ts +8 -0
  96. package/dist/commands/ai-score.d.ts.map +1 -0
  97. package/dist/commands/ai-score.js +59 -0
  98. package/dist/commands/ai-score.js.map +1 -0
  99. package/dist/commands/document-commands.d.ts.map +1 -1
  100. package/dist/commands/document-commands.js +2 -0
  101. package/dist/commands/document-commands.js.map +1 -1
  102. package/dist/config/by-path-problems.d.ts +5 -0
  103. package/dist/config/by-path-problems.d.ts.map +1 -0
  104. package/dist/config/by-path-problems.js +11 -0
  105. package/dist/config/by-path-problems.js.map +1 -0
  106. package/dist/config/load.d.ts +9 -2
  107. package/dist/config/load.d.ts.map +1 -1
  108. package/dist/config/load.js +21 -8
  109. package/dist/config/load.js.map +1 -1
  110. package/dist/config/read.d.ts +2 -0
  111. package/dist/config/read.d.ts.map +1 -1
  112. package/dist/config/read.js +4 -1
  113. package/dist/config/read.js.map +1 -1
  114. package/dist/config/warnings.d.ts +1 -1
  115. package/dist/config/warnings.d.ts.map +1 -1
  116. package/dist/config/warnings.js +3 -1
  117. package/dist/config/warnings.js.map +1 -1
  118. package/dist/config/working-dir.d.ts +2 -0
  119. package/dist/config/working-dir.d.ts.map +1 -0
  120. package/dist/config/working-dir.js +3 -0
  121. package/dist/config/working-dir.js.map +1 -0
  122. package/dist/custom/bounded-match.d.ts +1 -12
  123. package/dist/custom/bounded-match.d.ts.map +1 -1
  124. package/dist/custom/bounded-match.js +1 -11
  125. package/dist/custom/bounded-match.js.map +1 -1
  126. package/dist/custom/pattern-timeout.d.ts +13 -0
  127. package/dist/custom/pattern-timeout.d.ts.map +1 -0
  128. package/dist/custom/pattern-timeout.js +13 -0
  129. package/dist/custom/pattern-timeout.js.map +1 -0
  130. package/dist/detect.d.ts.map +1 -1
  131. package/dist/detect.js +4 -5
  132. package/dist/detect.js.map +1 -1
  133. package/dist/detectors/connective-hierarchy.d.ts +16 -0
  134. package/dist/detectors/connective-hierarchy.d.ts.map +1 -0
  135. package/dist/detectors/connective-hierarchy.js +68 -0
  136. package/dist/detectors/connective-hierarchy.js.map +1 -0
  137. package/dist/detectors/date-year.d.ts +10 -2
  138. package/dist/detectors/date-year.d.ts.map +1 -1
  139. package/dist/detectors/date-year.js +44 -8
  140. package/dist/detectors/date-year.js.map +1 -1
  141. package/dist/detectors/defined-term-form.d.ts +5 -0
  142. package/dist/detectors/defined-term-form.d.ts.map +1 -1
  143. package/dist/detectors/defined-term-form.js +22 -5
  144. package/dist/detectors/defined-term-form.js.map +1 -1
  145. package/dist/detectors/missing-final-period.d.ts +17 -0
  146. package/dist/detectors/missing-final-period.d.ts.map +1 -0
  147. package/dist/detectors/missing-final-period.js +62 -0
  148. package/dist/detectors/missing-final-period.js.map +1 -0
  149. package/dist/detectors/missing-space-after-comma.d.ts +10 -0
  150. package/dist/detectors/missing-space-after-comma.d.ts.map +1 -0
  151. package/dist/detectors/missing-space-after-comma.js +42 -0
  152. package/dist/detectors/missing-space-after-comma.js.map +1 -0
  153. package/dist/detectors/orthographic-variant.d.ts.map +1 -1
  154. package/dist/detectors/orthographic-variant.js +44 -17
  155. package/dist/detectors/orthographic-variant.js.map +1 -1
  156. package/dist/detectors/party-role-name.d.ts +3 -0
  157. package/dist/detectors/party-role-name.d.ts.map +1 -0
  158. package/dist/detectors/party-role-name.js +35 -0
  159. package/dist/detectors/party-role-name.js.map +1 -0
  160. package/dist/detectors/quote-without-source.d.ts +17 -0
  161. package/dist/detectors/quote-without-source.d.ts.map +1 -0
  162. package/dist/detectors/quote-without-source.js +64 -0
  163. package/dist/detectors/quote-without-source.js.map +1 -0
  164. package/dist/detectors/reference-text.js +1 -1
  165. package/dist/detectors/reference-text.js.map +1 -1
  166. package/dist/detectors/registry/connective-hierarchy.d.ts +3 -0
  167. package/dist/detectors/registry/connective-hierarchy.d.ts.map +1 -0
  168. package/dist/detectors/registry/connective-hierarchy.js +3 -0
  169. package/dist/detectors/registry/connective-hierarchy.js.map +1 -0
  170. package/dist/detectors/registry/missing-final-period.d.ts +3 -0
  171. package/dist/detectors/registry/missing-final-period.d.ts.map +1 -0
  172. package/dist/detectors/registry/missing-final-period.js +3 -0
  173. package/dist/detectors/registry/missing-final-period.js.map +1 -0
  174. package/dist/detectors/registry/missing-space-after-comma.d.ts +3 -0
  175. package/dist/detectors/registry/missing-space-after-comma.d.ts.map +1 -0
  176. package/dist/detectors/registry/missing-space-after-comma.js +3 -0
  177. package/dist/detectors/registry/missing-space-after-comma.js.map +1 -0
  178. package/dist/detectors/registry/party-role-name.d.ts +3 -0
  179. package/dist/detectors/registry/party-role-name.d.ts.map +1 -0
  180. package/dist/detectors/registry/party-role-name.js +3 -0
  181. package/dist/detectors/registry/party-role-name.js.map +1 -0
  182. package/dist/detectors/registry/quote-without-source.d.ts +3 -0
  183. package/dist/detectors/registry/quote-without-source.d.ts.map +1 -0
  184. package/dist/detectors/registry/quote-without-source.js +3 -0
  185. package/dist/detectors/registry/quote-without-source.js.map +1 -0
  186. package/dist/detectors/registry/vague-deadline.d.ts +3 -0
  187. package/dist/detectors/registry/vague-deadline.d.ts.map +1 -0
  188. package/dist/detectors/registry/vague-deadline.js +3 -0
  189. package/dist/detectors/registry/vague-deadline.js.map +1 -0
  190. package/dist/detectors/run-in-label.d.ts +7 -0
  191. package/dist/detectors/run-in-label.d.ts.map +1 -0
  192. package/dist/detectors/run-in-label.js +53 -0
  193. package/dist/detectors/run-in-label.js.map +1 -0
  194. package/dist/detectors/sentence-counts.d.ts +3 -1
  195. package/dist/detectors/sentence-counts.d.ts.map +1 -1
  196. package/dist/detectors/sentence-counts.js +11 -4
  197. package/dist/detectors/sentence-counts.js.map +1 -1
  198. package/dist/detectors/sentence-ending.d.ts +1 -1
  199. package/dist/detectors/sentence-ending.d.ts.map +1 -1
  200. package/dist/detectors/sentence-ending.js +2 -1
  201. package/dist/detectors/sentence-ending.js.map +1 -1
  202. package/dist/detectors/structure-tree.d.ts +4 -1
  203. package/dist/detectors/structure-tree.d.ts.map +1 -1
  204. package/dist/detectors/structure-tree.js +21 -2
  205. package/dist/detectors/structure-tree.js.map +1 -1
  206. package/dist/detectors/unbalanced-bracket.d.ts +1 -1
  207. package/dist/detectors/unbalanced-bracket.d.ts.map +1 -1
  208. package/dist/detectors/unbalanced-bracket.js +23 -11
  209. package/dist/detectors/unbalanced-bracket.js.map +1 -1
  210. package/dist/detectors/vague-deadline.d.ts +25 -0
  211. package/dist/detectors/vague-deadline.d.ts.map +1 -0
  212. package/dist/detectors/vague-deadline.js +97 -0
  213. package/dist/detectors/vague-deadline.js.map +1 -0
  214. package/dist/furigana.d.ts +7 -0
  215. package/dist/furigana.d.ts.map +1 -0
  216. package/dist/furigana.js +16 -0
  217. package/dist/furigana.js.map +1 -0
  218. package/dist/genre-load.d.ts.map +1 -1
  219. package/dist/genre-load.js +5 -7
  220. package/dist/genre-load.js.map +1 -1
  221. package/dist/grade/ai-score.d.ts +16 -0
  222. package/dist/grade/ai-score.d.ts.map +1 -0
  223. package/dist/grade/ai-score.js +17 -0
  224. package/dist/grade/ai-score.js.map +1 -0
  225. package/dist/grade/finding.d.ts +6 -0
  226. package/dist/grade/finding.d.ts.map +1 -0
  227. package/dist/grade/finding.js +13 -0
  228. package/dist/grade/finding.js.map +1 -0
  229. package/dist/grade/grade-item.d.ts.map +1 -1
  230. package/dist/grade/grade-item.js +4 -11
  231. package/dist/grade/grade-item.js.map +1 -1
  232. package/dist/grade/render-variants.d.ts.map +1 -1
  233. package/dist/grade/render-variants.js +3 -0
  234. package/dist/grade/render-variants.js.map +1 -1
  235. package/dist/grade/render.d.ts.map +1 -1
  236. package/dist/grade/render.js +1 -0
  237. package/dist/grade/render.js.map +1 -1
  238. package/dist/grade/result.d.ts +3 -0
  239. package/dist/grade/result.d.ts.map +1 -1
  240. package/dist/grade/results-read.d.ts.map +1 -1
  241. package/dist/grade/results-read.js +13 -2
  242. package/dist/grade/results-read.js.map +1 -1
  243. package/dist/grade/summary.d.ts +3 -0
  244. package/dist/grade/summary.d.ts.map +1 -1
  245. package/dist/grade/summary.js +6 -0
  246. package/dist/grade/summary.js.map +1 -1
  247. package/dist/grade/text.d.ts +2 -0
  248. package/dist/grade/text.d.ts.map +1 -1
  249. package/dist/grade/text.js +2 -0
  250. package/dist/grade/text.js.map +1 -1
  251. package/dist/grade/variants-text.d.ts +3 -0
  252. package/dist/grade/variants-text.d.ts.map +1 -1
  253. package/dist/grade/variants-text.js +4 -0
  254. package/dist/grade/variants-text.js.map +1 -1
  255. package/dist/grade/variants.d.ts +3 -0
  256. package/dist/grade/variants.d.ts.map +1 -1
  257. package/dist/grade/variants.js +1 -0
  258. package/dist/grade/variants.js.map +1 -1
  259. package/dist/grade-api.d.ts +1 -0
  260. package/dist/grade-api.d.ts.map +1 -1
  261. package/dist/grade-api.js +2 -1
  262. package/dist/grade-api.js.map +1 -1
  263. package/dist/judge.d.ts +0 -1
  264. package/dist/judge.d.ts.map +1 -1
  265. package/dist/judge.js +0 -1
  266. package/dist/judge.js.map +1 -1
  267. package/dist/known-genres.js +2 -2
  268. package/dist/known-genres.js.map +1 -1
  269. package/dist/package-files.d.ts +6 -0
  270. package/dist/package-files.d.ts.map +1 -0
  271. package/dist/package-files.js +11 -0
  272. package/dist/package-files.js.map +1 -0
  273. package/dist/profile/load.d.ts.map +1 -1
  274. package/dist/profile/load.js +5 -6
  275. package/dist/profile/load.js.map +1 -1
  276. package/dist/rule-load.d.ts.map +1 -1
  277. package/dist/rule-load.js +5 -6
  278. package/dist/rule-load.js.map +1 -1
  279. package/dist/rule-offs.js +3 -3
  280. package/dist/rule-offs.js.map +1 -1
  281. package/dist/run.js +1 -1
  282. package/dist/run.js.map +1 -1
  283. package/dist/structure/date-range.d.ts +4 -0
  284. package/dist/structure/date-range.d.ts.map +1 -1
  285. package/dist/structure/date-range.js +23 -3
  286. package/dist/structure/date-range.js.map +1 -1
  287. package/dist/structure/issues.d.ts +17 -1
  288. package/dist/structure/issues.d.ts.map +1 -1
  289. package/dist/structure/issues.js +54 -5
  290. package/dist/structure/issues.js.map +1 -1
  291. package/dist/structure/party-role.d.ts +30 -0
  292. package/dist/structure/party-role.d.ts.map +1 -0
  293. package/dist/structure/party-role.js +58 -0
  294. package/dist/structure/party-role.js.map +1 -0
  295. package/dist/structure/prose-total.d.ts +28 -0
  296. package/dist/structure/prose-total.d.ts.map +1 -0
  297. package/dist/structure/prose-total.js +99 -0
  298. package/dist/structure/prose-total.js.map +1 -0
  299. package/dist/structure/reference-text.d.ts +5 -0
  300. package/dist/structure/reference-text.d.ts.map +1 -1
  301. package/dist/structure/reference-text.js +17 -4
  302. package/dist/structure/reference-text.js.map +1 -1
  303. package/dist/structure/term-prefix.d.ts +18 -0
  304. package/dist/structure/term-prefix.d.ts.map +1 -0
  305. package/dist/structure/term-prefix.js +40 -0
  306. package/dist/structure/term-prefix.js.map +1 -0
  307. package/dist/structure/total.d.ts +2 -0
  308. package/dist/structure/total.d.ts.map +1 -1
  309. package/dist/structure/total.js +2 -0
  310. package/dist/structure/total.js.map +1 -1
  311. package/dist/structure-shape/baseline-load.d.ts.map +1 -1
  312. package/dist/structure-shape/baseline-load.js +4 -5
  313. package/dist/structure-shape/baseline-load.js.map +1 -1
  314. package/dist/style-load.d.ts.map +1 -1
  315. package/dist/style-load.js +5 -6
  316. package/dist/style-load.js.map +1 -1
  317. package/package.json +24 -3
  318. package/rules/cliche.yaml +28 -8
  319. package/rules/connective-hierarchy.yaml +62 -0
  320. package/rules/date-without-year.yaml +28 -3
  321. package/rules/defined-term-form.yaml +9 -6
  322. package/rules/demonstrative-opener-run.yaml +24 -7
  323. package/rules/missing-final-period.yaml +76 -0
  324. package/rules/missing-space-after-comma.yaml +57 -0
  325. package/rules/no-mixed-desumasu.yaml +2 -2
  326. package/rules/orthographic-variant.yaml +2 -2
  327. package/rules/party-role-name.yaml +94 -0
  328. package/rules/quote-without-source.yaml +61 -0
  329. package/rules/total-mismatch.yaml +8 -8
  330. package/rules/vague-deadline.yaml +95 -0
  331. package/rules/weasel-word.yaml +25 -7
  332. package/skills/chaff/SKILL.md +32 -5
  333. package/src/adapter-import.ts +3 -0
  334. package/src/adapter-load.ts +2 -1
  335. package/src/ai-score/human-shares.ts +207 -0
  336. package/src/ai-score/lint-line.ts +10 -0
  337. package/src/ai-score/of-document.ts +46 -0
  338. package/src/ai-score/render.ts +124 -0
  339. package/src/ai-score/score.ts +121 -0
  340. package/src/ai-score/signals.ts +39 -0
  341. package/src/ai-score/text.ts +103 -0
  342. package/src/ai-score/view.ts +21 -0
  343. package/src/backends/anthropic.ts +0 -2
  344. package/src/backends/default-model.ts +9 -0
  345. package/src/backends/openai.ts +0 -2
  346. package/src/browser/adapter-import.ts +19 -0
  347. package/src/browser/bounded-match.ts +9 -0
  348. package/src/browser/check-text.ts +106 -0
  349. package/src/browser/detectors.ts +40 -0
  350. package/src/browser/import-meta-glob.d.ts +5 -0
  351. package/src/browser/package-files.ts +13 -0
  352. package/src/browser/path.ts +73 -0
  353. package/src/browser/working-dir.ts +3 -0
  354. package/src/browser-files.ts +87 -0
  355. package/src/browser.ts +19 -0
  356. package/src/cli-text.ts +2 -0
  357. package/src/cli.ts +4 -1
  358. package/src/commands/ai-score.ts +77 -0
  359. package/src/commands/document-commands.ts +2 -0
  360. package/src/config/by-path-problems.ts +17 -0
  361. package/src/config/load.ts +21 -8
  362. package/src/config/read.ts +5 -1
  363. package/src/config/warnings.ts +3 -1
  364. package/src/config/working-dir.ts +3 -0
  365. package/src/custom/bounded-match.ts +1 -16
  366. package/src/custom/pattern-timeout.ts +17 -0
  367. package/src/detect.ts +4 -5
  368. package/src/detectors/connective-hierarchy.ts +99 -0
  369. package/src/detectors/date-year.ts +51 -12
  370. package/src/detectors/defined-term-form.ts +28 -6
  371. package/src/detectors/missing-final-period.ts +79 -0
  372. package/src/detectors/missing-space-after-comma.ts +54 -0
  373. package/src/detectors/orthographic-variant.ts +59 -18
  374. package/src/detectors/party-role-name.ts +44 -0
  375. package/src/detectors/quote-without-source.ts +90 -0
  376. package/src/detectors/reference-text.ts +1 -1
  377. package/src/detectors/registry/connective-hierarchy.ts +4 -0
  378. package/src/detectors/registry/missing-final-period.ts +4 -0
  379. package/src/detectors/registry/missing-space-after-comma.ts +4 -0
  380. package/src/detectors/registry/party-role-name.ts +4 -0
  381. package/src/detectors/registry/quote-without-source.ts +4 -0
  382. package/src/detectors/registry/vague-deadline.ts +4 -0
  383. package/src/detectors/run-in-label.ts +60 -0
  384. package/src/detectors/sentence-counts.ts +13 -4
  385. package/src/detectors/sentence-ending.ts +3 -2
  386. package/src/detectors/structure-tree.ts +28 -6
  387. package/src/detectors/unbalanced-bracket.ts +25 -10
  388. package/src/detectors/vague-deadline.ts +132 -0
  389. package/src/furigana.ts +20 -0
  390. package/src/genre-load.ts +5 -7
  391. package/src/grade/ai-score.ts +32 -0
  392. package/src/grade/finding.ts +16 -0
  393. package/src/grade/grade-item.ts +4 -13
  394. package/src/grade/render-variants.ts +5 -0
  395. package/src/grade/render.ts +1 -0
  396. package/src/grade/result.ts +3 -0
  397. package/src/grade/results-read.ts +17 -2
  398. package/src/grade/summary.ts +9 -0
  399. package/src/grade/text.ts +6 -0
  400. package/src/grade/variants-text.ts +7 -0
  401. package/src/grade/variants.ts +4 -0
  402. package/src/grade-api.ts +3 -1
  403. package/src/judge.ts +0 -2
  404. package/src/known-genres.ts +2 -2
  405. package/src/package-files.ts +15 -0
  406. package/src/profile/load.ts +5 -6
  407. package/src/rule-load.ts +5 -6
  408. package/src/rule-offs.ts +3 -3
  409. package/src/run.ts +1 -1
  410. package/src/structure/date-range.ts +28 -3
  411. package/src/structure/issues.ts +61 -6
  412. package/src/structure/party-role.ts +94 -0
  413. package/src/structure/prose-total.ts +135 -0
  414. package/src/structure/reference-text.ts +17 -4
  415. package/src/structure/term-prefix.ts +54 -0
  416. package/src/structure/total.ts +4 -0
  417. package/src/structure-shape/baseline-load.ts +4 -5
  418. package/src/style-load.ts +5 -6
@@ -0,0 +1,132 @@
1
+ // A time limit a contract leaves to the reader (速やかに, 遅滞なく, promptly, within a reasonable time) where the same
2
+ // contract writes most of its limits as numbers (7日以内, within thirty days): who decides whether 速やかに was met?
3
+ // Which way to write limits is the drafter's choice; only the minority in one document is pointed at. A sentence that
4
+ // also gives a number of days (速やかに、遅くとも7日以内に) has a deadline, and a sentence that grants a right (may
5
+ // promptly suspend, 速やかに解除することができる) sets no deadline. The words come from the language package's word
6
+ // lists: vague-deadline, concrete-deadline (a stated limit: 日以内, "within … days") and deadline-permission. Pure.
7
+ import { quoteAt } from "./structure-tree.ts";
8
+ import type { Detector, Finding, LexiconEntry, ProseDocument } from "../plugin.ts";
9
+
10
+ export type VagueDeadline = { readonly offset: number; readonly word: string };
11
+
12
+ type Text = { readonly start: number; readonly text: string };
13
+
14
+ type Hit = { readonly at: number; readonly word: string };
15
+
16
+ /** What the three word lists give the decision. */
17
+ export type DeadlineWords = {
18
+ readonly vague: readonly string[];
19
+ /** A phrase, or a frame "within … days": the second part within FRAME_REACH characters after the first. */
20
+ readonly concrete: readonly string[];
21
+ /** A right, not a duty: a word anywhere in the sentence, or (position: after) the sentence's ending. */
22
+ readonly permissions: readonly LexiconEntry[];
23
+ };
24
+
25
+ const LATIN = /\p{Script=Latin}/u;
26
+ const LETTER = /\p{L}/u;
27
+ const FRAME = "…";
28
+ /** How far after "within" the unit of a frame may come: "within fourteen (14) business days". */
29
+ const FRAME_REACH = 30;
30
+ const SENTENCE_MARKS = new Set(["。", ".", ".", "!", "?", "!", "?"]);
31
+
32
+ /** The sentence without its closing marks and spaces. */
33
+ const withoutEnding = (text: string): string => {
34
+ const chars = [...text.trimEnd()];
35
+ const last = chars.findLastIndex((char) => !SENTENCE_MARKS.has(char) && char.trim() !== "");
36
+ return chars.slice(0, last + 1).join("");
37
+ };
38
+
39
+ /** An English word is not read inside a longer one (promptly in unpromptly, may in mayor). */
40
+ const isWordAt = (text: string, word: string, at: number): boolean =>
41
+ !LATIN.test(word) || (!LETTER.test(text.charAt(at - 1)) && !LETTER.test(text.charAt(at + word.length)));
42
+
43
+ const indexesOf = (text: string, word: string): number[] => {
44
+ const found: number[] = [];
45
+ if (word === "") return found;
46
+ let at = text.indexOf(word);
47
+ while (at !== -1) {
48
+ found.push(at);
49
+ at = text.indexOf(word, at + word.length);
50
+ }
51
+ return found;
52
+ };
53
+
54
+ const wordIn = (lower: string, word: string): boolean => indexesOf(lower, word.toLowerCase()).some((at) => isWordAt(lower, word, at));
55
+
56
+ /** A phrase, or a frame whose second part follows its first within reach. */
57
+ const concreteIn = (lower: string, entry: string): boolean => {
58
+ const [lead = "", tail, ...rest] = entry
59
+ .toLowerCase()
60
+ .split(FRAME)
61
+ .map((part) => part.trim());
62
+ if (rest.length > 0) return false;
63
+ if (tail === undefined) return lead !== "" && lower.includes(lead);
64
+ return (
65
+ lead !== "" &&
66
+ tail !== "" &&
67
+ indexesOf(lower, lead).some((at) => isWordAt(lower, lead, at) && lower.slice(at + lead.length, at + lead.length + FRAME_REACH).includes(tail))
68
+ );
69
+ };
70
+
71
+ const statesConcrete = (text: Text, concrete: readonly string[]): boolean => {
72
+ const lower = text.text.toLowerCase();
73
+ return concrete.some((entry) => concreteIn(lower, entry));
74
+ };
75
+
76
+ const grantsRight = (text: Text, permissions: readonly LexiconEntry[]): boolean => {
77
+ const lower = text.text.toLowerCase();
78
+ const ending = withoutEnding(lower);
79
+ return permissions
80
+ .filter((entry) => entry.pattern !== "")
81
+ .some((entry) => (entry.position === "after" ? ending.endsWith(entry.pattern.toLowerCase()) : wordIn(lower, entry.pattern)));
82
+ };
83
+
84
+ /** A hit inside a longer hit at the same place (速やかに in 可及的速やかに) is that longer word. */
85
+ const isInsideLonger = (hit: Hit, hits: readonly Hit[]): boolean =>
86
+ hits.some((other) => other.word.length > hit.word.length && other.at <= hit.at && hit.at + hit.word.length <= other.at + other.word.length);
87
+
88
+ /** The vague limits in one sentence, in order. Matching ignores case. */
89
+ const vagueIn = (text: Text, vague: readonly string[]): VagueDeadline[] => {
90
+ const lower = text.text.toLowerCase();
91
+ const hits = vague
92
+ .filter((word) => word !== "")
93
+ .flatMap((word) =>
94
+ indexesOf(lower, word.toLowerCase())
95
+ .filter((at) => isWordAt(lower, word, at))
96
+ .map((at) => ({ at, word })),
97
+ );
98
+ return hits
99
+ .filter((hit) => !isInsideLonger(hit, hits))
100
+ .toSorted((left, right) => left.at - right.at)
101
+ .map((hit) => ({ offset: text.start + hit.at, word: text.text.slice(hit.at, hit.at + hit.word.length) }));
102
+ };
103
+
104
+ /**
105
+ * Each vague limit in a sentence that states no concrete one and grants no right, when more sentences of the document
106
+ * state a concrete one than hold such a vague one: the vague limits are then the document's minority.
107
+ */
108
+ export const vagueDeadlines = (texts: readonly Text[], words: DeadlineWords): VagueDeadline[] => {
109
+ const perSentence = texts.map((text) => (statesConcrete(text, words.concrete) || grantsRight(text, words.permissions) ? [] : vagueIn(text, words.vague)));
110
+ const vagueSentences = perSentence.filter((hits) => hits.length > 0).length;
111
+ const concreteSentences = texts.filter((text) => statesConcrete(text, words.concrete)).length;
112
+ return concreteSentences > vagueSentences ? perSentence.flat() : [];
113
+ };
114
+
115
+ const patternsOf = (doc: ProseDocument, lexicon: string): string[] => (doc.lexicons[lexicon] ?? []).map((entry) => entry.pattern);
116
+
117
+ export const vagueDeadline: Detector = (doc): Finding[] =>
118
+ vagueDeadlines(
119
+ doc.sentences.map((sentence) => ({ start: sentence.span.start, text: sentence.text })),
120
+ {
121
+ vague: patternsOf(doc, "vague-deadline"),
122
+ concrete: patternsOf(doc, "concrete-deadline"),
123
+ permissions: doc.lexicons["deadline-permission"] ?? [],
124
+ },
125
+ ).map((hit) => ({
126
+ rule: "vague-deadline",
127
+ severity: "info",
128
+ line: 0,
129
+ column: 0,
130
+ quote: quoteAt(doc.source, hit.offset),
131
+ values: { word: hit.word, offset: hit.offset },
132
+ }));
@@ -0,0 +1,20 @@
1
+ import type { Span } from "./plugin.ts";
2
+
3
+ /** ひらがなと、読みの中に書く長音・中黒・空白だけの括弧書き。 */
4
+ const KANA_ASIDE = /[((][\p{Script=Hiragana}ー・ \u3000]+[))]/gu;
5
+
6
+ const HIRAGANA = /\p{Script=Hiragana}/u;
7
+ /** 読みを付ける語の終わりの字。ひらがなの後ろの括弧書きは読みではなく挟んだ言葉(「これは(たぶん)」)。 */
8
+ const WORD_END = /[\p{L}\p{N}]/u;
9
+
10
+ /**
11
+ * text の中の、語の直後のふりがなの括弧(「HTTP(えいちてぃーてぃーぴー)」「脆弱性(ぜいじゃくせい)」)の範囲。括弧を含む。
12
+ * 語の読みであって、語の書き方ではない。位置は text の先頭を 0 とする。
13
+ */
14
+ export const furiganaSpans = (text: string): Span[] =>
15
+ [...text.matchAll(KANA_ASIDE)]
16
+ .filter((match) => {
17
+ const before = text.charAt(match.index - 1);
18
+ return HIRAGANA.test(match[0]) && WORD_END.test(before) && !HIRAGANA.test(before);
19
+ })
20
+ .map((match) => ({ start: match.index, end: match.index + match[0].length }));
package/src/genre-load.ts CHANGED
@@ -1,13 +1,11 @@
1
- import { readFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { fileURLToPath } from "node:url";
1
+ import { join } from "node:path";
4
2
  import { parse } from "yaml";
5
3
  import { parseGenres, presetLevelsOf, presetProfileOf, withRuleOffs, type GenreData, type PresetLevels } from "./genre-parse.ts";
6
4
  import { loadRuleOffs } from "./rule-offs.ts";
5
+ import { PACKAGE_DIR, readText } from "./package-files.ts";
7
6
 
8
- const PACKAGE = join(dirname(fileURLToPath(import.meta.url)), "..");
9
- const GENRES_FILE = join(PACKAGE, "genres.yaml");
10
- const RULES_DIR = join(PACKAGE, "rules");
7
+ const GENRES_FILE = join(PACKAGE_DIR, "genres.yaml");
8
+ const RULES_DIR = join(PACKAGE_DIR, "rules");
11
9
 
12
10
  const loaded: { value: GenreData | undefined } = { value: undefined };
13
11
 
@@ -15,7 +13,7 @@ const loaded: { value: GenreData | undefined } = { value: undefined };
15
13
  export const loadGenres = (): GenreData => {
16
14
  if (loaded.value !== undefined) return loaded.value;
17
15
  try {
18
- loaded.value = withRuleOffs(parseGenres(parse(readFileSync(GENRES_FILE, "utf8"))), loadRuleOffs(RULES_DIR));
16
+ loaded.value = withRuleOffs(parseGenres(parse(readText(GENRES_FILE))), loadRuleOffs(RULES_DIR));
19
17
  } catch (error) {
20
18
  throw new Error(`cannot read ${GENRES_FILE}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
21
19
  }
@@ -0,0 +1,32 @@
1
+ import { shownSignsOf, type AiLevel, type AiScore, type NotScored } from "../ai-score/score.ts";
2
+
3
+ // The AI-likeness quick score as one output's result carries it, and its levels counted over many outputs. Pure.
4
+
5
+ /** One output's quick score: its level (null when not scored, and why), the signs, and which showed. */
6
+ export type GradeAiScore = {
7
+ readonly level: AiLevel | null;
8
+ readonly notScored: NotScored["reason"] | null;
9
+ readonly signs: number;
10
+ readonly compared: number;
11
+ /** The signs that counted: rule ids, and structure measures as structure:<id>. */
12
+ readonly shown: readonly string[];
13
+ };
14
+
15
+ /** How many outputs reached each level, and how many were not scored. */
16
+ export type AiLevelCounts = Readonly<Record<AiLevel | "notScored", number>>;
17
+
18
+ export const gradeAiScoreOf = (score: AiScore): GradeAiScore => ({
19
+ level: score.level ?? null,
20
+ notScored: score.notScored?.reason ?? null,
21
+ signs: score.signs,
22
+ compared: score.compared,
23
+ shown: shownSignsOf(score),
24
+ });
25
+
26
+ /** The levels of the outputs that carry a score, or undefined when none does (results written before the score existed). */
27
+ export const aiLevelCountsOf = (scores: readonly (GradeAiScore | undefined)[]): AiLevelCounts | undefined => {
28
+ const scored = scores.filter((score) => score !== undefined);
29
+ if (scored.length === 0) return undefined;
30
+ const count = (level: AiLevel): number => scored.filter((score) => score.level === level).length;
31
+ return { low: count("low"), medium: count("medium"), high: count("high"), notScored: scored.filter((score) => score.level === null).length };
32
+ };
@@ -0,0 +1,16 @@
1
+ import type { SourceCheck } from "../check-source.ts";
2
+ import type { Finding } from "../plugin.ts";
3
+ import { messageOf } from "../render/text.ts";
4
+ import type { GradeFinding } from "./result.ts";
5
+
6
+ /** One finding as `chaff grade` writes it: the rule, the level, where, and the message the command line prints. */
7
+ export const findingOf = (finding: Finding, check: SourceCheck): GradeFinding => {
8
+ const rule = check.rules.find((entry) => entry.id === finding.rule);
9
+ return {
10
+ rule: finding.rule,
11
+ level: finding.severity,
12
+ line: finding.line,
13
+ column: finding.column,
14
+ message: rule === undefined ? finding.rule : messageOf(rule, finding, check.language),
15
+ };
16
+ };
@@ -1,9 +1,8 @@
1
1
  import { checkSource, type SourceCheck } from "../check-source.ts";
2
2
  import type { Allowed } from "../compare/outcome.ts";
3
3
  import { missingSections } from "../detectors/team.ts";
4
- import { messageOf } from "../render/text.ts";
5
4
  import { uiLanguageOf, type UiLanguage } from "../ui.ts";
6
- import type { Finding } from "../plugin.ts";
5
+ import { findingOf } from "./finding.ts";
7
6
  import { citationsAgainst, factsAgainst, OUTPUT_PATH, type CitationsRead, type ItemReading } from "./checks.ts";
8
7
  import { contextsAgainst, type ContextsRead } from "./contexts.ts";
9
8
  import type { GradeItem } from "./item.ts";
@@ -13,22 +12,13 @@ import type { Rubric } from "./rubric.ts";
13
12
  import { rubricVerdict } from "./rubric-verdict.ts";
14
13
  import type { GradeSettings } from "./stamp.ts";
15
14
  import { GRADE_TEXT } from "./text.ts";
15
+ import { aiScoreOfDocument } from "../ai-score/of-document.ts";
16
+ import { gradeAiScoreOf } from "./ai-score.ts";
16
17
  import { defaultVerdict, type Verdict } from "./verdict.ts";
17
18
 
18
19
  /** One run's settings, its `grade:` rubric when chaff.yaml has one, and its stamp, which every result carries. */
19
20
  export type GradeSetup = GradeSettings & { readonly stamp: Stamp; readonly rubric?: Rubric | undefined };
20
21
 
21
- const findingOf = (finding: Finding, check: SourceCheck): GradeFinding => {
22
- const rule = check.rules.find((entry) => entry.id === finding.rule);
23
- return {
24
- rule: finding.rule,
25
- level: finding.severity,
26
- line: finding.line,
27
- column: finding.column,
28
- message: rule === undefined ? finding.rule : messageOf(rule, finding, check.language),
29
- };
30
- };
31
-
32
22
  /** The fact kinds the rubric lets change (`allow_dropped`, `allow_added`), as compare's --allow-dropped and --allow-added. */
33
23
  const allowedBy = (rubric: Rubric | undefined): Allowed => ({
34
24
  dropped: new Set(rubric?.facts?.allowDropped ?? []),
@@ -126,6 +116,7 @@ export const gradeItem = async (item: GradeItem, setup: GradeSetup): Promise<Gra
126
116
  citations: citationsOf(checked),
127
117
  ...(checked.contexts === undefined ? {} : { contexts: checked.contexts.contexts }),
128
118
  ...(score === undefined ? {} : { score }),
119
+ aiScore: gradeAiScoreOf(aiScoreOfDocument(checked.check.doc, checked.check.rules, checked.check.genre.genre)),
129
120
  pass: verdict.pass,
130
121
  failedBecause: verdict.failedBecause,
131
122
  stamp: setup.stamp,
@@ -14,11 +14,15 @@ type Rows = readonly (readonly string[])[];
14
14
  const passedCell = (column: VariantColumn, text: VariantText): string =>
15
15
  text.passedCell(column.passed, column.outputs, column.passRate === undefined ? "–" : `${String(column.passRate)}%`);
16
16
 
17
+ const aiScoreCellOf = (column: VariantColumn, text: VariantText): string =>
18
+ column.aiScore === undefined ? "–" : text.aiScoreCell(column.aiScore.low, column.aiScore.medium, column.aiScore.high, column.aiScore.notScored);
19
+
17
20
  /** The header and one row per measure, a column per variant. */
18
21
  const measureRows = (comparison: VariantComparison, text: VariantText): Rows => {
19
22
  const { columns } = comparison;
20
23
  const scored = columns.some((column) => column.penalty !== undefined);
21
24
  const grounded = columns.some((column) => column.contexts !== undefined);
25
+ const aiScored = columns.some((column) => column.aiScore !== undefined);
22
26
  return [
23
27
  ["", ...comparison.variants],
24
28
  [text.passed, ...columns.map((column) => passedCell(column, text))],
@@ -29,6 +33,7 @@ const measureRows = (comparison: VariantComparison, text: VariantText): Rows =>
29
33
  ? [[text.unsupportedFacts, ...columns.map((column) => text.citationsCell(column.contexts?.unsupported ?? 0, column.contexts?.checked ?? 0))]]
30
34
  : []),
31
35
  ...(scored ? [[text.penalty, ...columns.map((column) => String(column.penalty ?? 0))]] : []),
36
+ ...(aiScored ? [[text.aiScore, ...columns.map((column) => aiScoreCellOf(column, text))]] : []),
32
37
  ];
33
38
  };
34
39
 
@@ -65,6 +65,7 @@ export const renderSummary = (path: string, summary: GradeSummary, text: GradeTe
65
65
  text.citations(summary.citations.checked, summary.citations.failed),
66
66
  ...contextsLine(summary, text),
67
67
  ...(summary.penalty === undefined ? [] : [text.penalty(summary.penalty)]),
68
+ ...(summary.aiScore === undefined ? [] : [text.aiScore(summary.aiScore.low, summary.aiScore.medium, summary.aiScore.high, summary.aiScore.notScored)]),
68
69
  ...block(text.notRunHeading(summary.notRun.length), table(summary.notRun.map((entry) => [entry.rule, text.inOutputs(entry.outputs), entry.reason]))),
69
70
  ...stampLines(summary, text),
70
71
  ].join("\n");
@@ -2,6 +2,7 @@ import type { AtomKind } from "../compare/atom.ts";
2
2
  import type { CitationStatus } from "../structure/cite.ts";
3
3
  import type { Severity } from "../plugin.ts";
4
4
  import type { OutputSize } from "./rates.ts";
5
+ import type { GradeAiScore } from "./ai-score.ts";
5
6
 
6
7
  // The shape of one line of `chaff grade --out`, and of what `grade()` returns. Spec §29.3.
7
8
 
@@ -75,6 +76,8 @@ export type GradeResult = {
75
76
  readonly contexts?: GradeContexts | undefined;
76
77
  /** Only with a `grade:` rubric. */
77
78
  readonly score?: GradeScore | undefined;
79
+ /** The AI-likeness quick score. Absent only in results written before it existed. Never part of pass or fail. */
80
+ readonly aiScore?: GradeAiScore | undefined;
78
81
  readonly pass: boolean;
79
82
  /** Each condition the output failed, with how far: `facts.dropped 3 > 0`. */
80
83
  readonly failedBecause: readonly string[];
@@ -52,6 +52,19 @@ const isScoreItem = (value: unknown): boolean => isRecord(value) && isNumber(val
52
52
 
53
53
  const isScore = (value: unknown): boolean => value === undefined || (isRecord(value) && isNumber(value["penalty"]) && isArrayOf(value["items"], isScoreItem));
54
54
 
55
+ const AI_LEVELS: ReadonlySet<unknown> = new Set(["low", "medium", "high", null]);
56
+ const NOT_SCORED: ReadonlySet<unknown> = new Set(["too-short", "no-baseline", null]);
57
+
58
+ /** Absent in results written before the quick score existed. */
59
+ const isAiScore = (value: unknown): boolean =>
60
+ value === undefined ||
61
+ (isRecord(value) &&
62
+ AI_LEVELS.has(value["level"]) &&
63
+ NOT_SCORED.has(value["notScored"]) &&
64
+ isNumber(value["signs"]) &&
65
+ isNumber(value["compared"]) &&
66
+ isArrayOf(value["shown"], isString));
67
+
55
68
  const isNotRun = (value: unknown): boolean => isRecord(value) && isString(value["rule"]) && isString(value["reason"]);
56
69
 
57
70
  const isRates = (value: unknown): boolean => isRecord(value) && Object.values(value).every(isNumber);
@@ -59,6 +72,9 @@ const isRates = (value: unknown): boolean => isRecord(value) && Object.values(va
59
72
  /** The id, and the variant when the output had one. */
60
73
  const isLabel = (value: Record<string, unknown>): boolean => isString(value["id"]) && (value["variant"] === undefined || isString(value["variant"]));
61
74
 
75
+ /** The parts a result carries only sometimes: contexts, a rubric's score, the quick score. Each is checked when present. */
76
+ const hasOptionalParts = (value: Record<string, unknown>): boolean => isContexts(value["contexts"]) && isScore(value["score"]) && isAiScore(value["aiScore"]);
77
+
62
78
  /** A line `--out` writes, every field checked: a line that only looks like one would be compared as if it had been graded. */
63
79
  export const isGradeResult = (value: unknown): value is GradeResult =>
64
80
  isRecord(value) &&
@@ -71,8 +87,7 @@ export const isGradeResult = (value: unknown): value is GradeResult =>
71
87
  isArrayOf(value["notRun"], isNotRun) &&
72
88
  isFacts(value["facts"]) &&
73
89
  isCitations(value["citations"]) &&
74
- isContexts(value["contexts"]) &&
75
- isScore(value["score"]) &&
90
+ hasOptionalParts(value) &&
76
91
  typeof value["pass"] === "boolean" &&
77
92
  isArrayOf(value["failedBecause"], isString) &&
78
93
  isStamp(value["stamp"]);
@@ -2,6 +2,7 @@ import type { LengthUnit } from "../plugin.ts";
2
2
  import { compareText, sortedByKey, tally } from "./order.ts";
3
3
  import { rateOf } from "./rates.ts";
4
4
  import type { GradeFact, GradeResult, NotRunEntry, Stamp } from "./result.ts";
5
+ import { aiLevelCountsOf, type AiLevelCounts } from "./ai-score.ts";
5
6
 
6
7
  // The summary of a run (spec §29.3), from the per-output results alone, so the same results always sum the same way.
7
8
 
@@ -23,6 +24,8 @@ export type GradeSummary = {
23
24
  readonly notRun: readonly (NotRunEntry & { readonly outputs: number })[];
24
25
  /** The penalty points of every output, added up. Only with a `grade:` rubric. */
25
26
  readonly penalty?: number | undefined;
27
+ /** How many outputs reached each level of the AI-likeness quick score. Absent when no result carries one. */
28
+ readonly aiScore?: AiLevelCounts | undefined;
26
29
  readonly stamp: Stamp | undefined;
27
30
  };
28
31
 
@@ -86,6 +89,11 @@ const contextsOf = (results: readonly GradeResult[]): Pick<GradeSummary, "contex
86
89
  };
87
90
  };
88
91
 
92
+ const aiScoreOf = (results: readonly GradeResult[]): Pick<GradeSummary, "aiScore"> => {
93
+ const counts = aiLevelCountsOf(results.map((result) => result.aiScore));
94
+ return counts === undefined ? {} : { aiScore: counts };
95
+ };
96
+
89
97
  export const summaryOf = (results: readonly GradeResult[]): GradeSummary => {
90
98
  const sizes = sizesOf(results);
91
99
  return {
@@ -107,6 +115,7 @@ export const summaryOf = (results: readonly GradeResult[]): GradeSummary => {
107
115
  ...contextsOf(results),
108
116
  notRun: notRunOf(results),
109
117
  ...penaltyOf(results),
118
+ ...aiScoreOf(results),
110
119
  stamp: results[0]?.stamp,
111
120
  };
112
121
  };
package/src/grade/text.ts CHANGED
@@ -13,6 +13,8 @@ export type GradeText = {
13
13
  readonly unknownRule: string;
14
14
  readonly rubricProblem: (problem: RubricProblem) => string;
15
15
  readonly penalty: (total: number) => string;
16
+ /** The outputs at each level of the AI-likeness quick score. */
17
+ readonly aiScore: (low: number, medium: number, high: number, notScored: number) => string;
16
18
  readonly noStructure: (source: string, language: string) => string;
17
19
  readonly unreadable: (path: string, why: string) => string;
18
20
  readonly problem: (problem: ItemProblem) => string;
@@ -110,6 +112,8 @@ export const GRADE_TEXT: Texts<GradeText> = {
110
112
  unknownRule: "grade: に書かれているが、chaff の知らないルール",
111
113
  rubricProblem: (problem) => `chaff.yaml の ${problem.path} は${EXPECTED_JA[problem.expected]}で書いてください(書かれていたのは ${problem.written})`,
112
114
  penalty: (total) => `減点の和: ${String(total)}`,
115
+ aiScore: (low, medium, high, notScored) =>
116
+ `AI らしさ: 低 ${String(low)}、中 ${String(medium)}、高 ${String(high)}、測っていない ${String(notScored)}(書いたのが AI かどうかの判定ではありません。合否には入りません)`,
113
117
  noStructure: (source, language) => `原文 ${source} の言語 ${language} のパッケージは文書の構造を読めない`,
114
118
  unreadable: (path, why) => `${path} を読めませんでした: ${why}`,
115
119
  problem: (problem) => PROBLEM_JA[problem.kind](problem),
@@ -142,6 +146,8 @@ export const GRADE_TEXT: Texts<GradeText> = {
142
146
  unknownRule: "named under grade: but not a rule chaff knows",
143
147
  rubricProblem: (problem) => `chaff.yaml: ${problem.path} must be ${EXPECTED_EN[problem.expected]} (found ${problem.written})`,
144
148
  penalty: (total) => `Penalty points: ${String(total)}`,
149
+ aiScore: (low, medium, high, notScored) =>
150
+ `AI-likeness: low ${String(low)}, medium ${String(medium)}, high ${String(high)}, not scored ${String(notScored)} (not a verdict on whether AI wrote them; not part of pass or fail)`,
145
151
  noStructure: (source, language) => `the ${language} package cannot read the structure of source ${source}`,
146
152
  unreadable: (path, why) => `Could not read ${path}: ${why}`,
147
153
  problem: (problem) => PROBLEM_EN[problem.kind](problem),
@@ -10,6 +10,9 @@ export type VariantText = {
10
10
  readonly citationsCell: (failed: number, checked: number) => string;
11
11
  readonly unsupportedFacts: string;
12
12
  readonly penalty: string;
13
+ /** The row of the AI-likeness quick score, and its cell: outputs at low / medium / high, then (not scored). */
14
+ readonly aiScore: string;
15
+ readonly aiScoreCell: (low: number, medium: number, high: number, notScored: number) => string;
13
16
  readonly ratesHeading: (unit: string) => string;
14
17
  readonly unit: Readonly<Record<"char" | "word", string>>;
15
18
  readonly disagreeHeading: (count: number) => string;
@@ -40,6 +43,8 @@ export const VARIANT_TEXT: Texts<VariantText> = {
40
43
  citationsCell: (failed, checked) => `${String(failed)}/${String(checked)}`,
41
44
  unsupportedFacts: "一節に無い事実",
42
45
  penalty: "減点の和",
46
+ aiScore: "AI らしさ 低/中/高",
47
+ aiScoreCell: (low, medium, high, notScored) => `${String(low)}/${String(medium)}/${String(high)}(${String(notScored)})`,
43
48
  ratesHeading: (unit) => `ルールごとの率(1,000 ${unit}あたり)`,
44
49
  unit: { char: "字", word: "語" },
45
50
  disagreeHeading: (count) => `合否が分かれた出力 ${String(count)} 件`,
@@ -68,6 +73,8 @@ export const VARIANT_TEXT: Texts<VariantText> = {
68
73
  citationsCell: (failed, checked) => `${String(failed)}/${String(checked)}`,
69
74
  unsupportedFacts: "Unsupported facts",
70
75
  penalty: "Penalty points",
76
+ aiScore: "AI low/med/high",
77
+ aiScoreCell: (low, medium, high, notScored) => `${String(low)}/${String(medium)}/${String(high)} (${String(notScored)})`,
71
78
  ratesHeading: (unit) => `Rule rates (per 1,000 ${unit})`,
72
79
  unit: { char: "characters", word: "words" },
73
80
  disagreeHeading: (count) => `${String(count)} ${count === 1 ? "output" : "outputs"} where pass or fail differs`,
@@ -1,6 +1,7 @@
1
1
  import type { LengthUnit } from "../plugin.ts";
2
2
  import { rateRows, sameReading } from "./pairing.ts";
3
3
  import type { GradeResult } from "./result.ts";
4
+ import type { AiLevelCounts } from "./ai-score.ts";
4
5
  import { summaryOf } from "./summary.ts";
5
6
  import { variantGroupsOf, type VariantGroups } from "./variants-input.ts";
6
7
 
@@ -21,6 +22,8 @@ export type VariantColumn = {
21
22
  readonly contexts?: { readonly checked: number; readonly unsupported: number } | undefined;
22
23
  /** Only with a `grade:` rubric. */
23
24
  readonly penalty?: number | undefined;
25
+ /** The outputs at each level of the AI-likeness quick score. Absent when no result carries one. */
26
+ readonly aiScore?: AiLevelCounts | undefined;
24
27
  };
25
28
 
26
29
  /** One rule's rate per 1,000 units in each variant, by variant label; 0 where the variant had no finding of it. */
@@ -66,6 +69,7 @@ const columnOf = (variant: string, results: readonly GradeResult[]): VariantColu
66
69
  citations: summary.citations,
67
70
  ...(summary.contexts === undefined ? {} : { contexts: { checked: summary.contexts.checked, unsupported: sum(summary.contexts.unsupported) } }),
68
71
  ...(summary.penalty === undefined ? {} : { penalty: summary.penalty }),
72
+ ...(summary.aiScore === undefined ? {} : { aiScore: summary.aiScore }),
69
73
  };
70
74
  };
71
75
 
package/src/grade-api.ts CHANGED
@@ -5,7 +5,8 @@ import { statSync } from "node:fs";
5
5
  import { resolve } from "node:path";
6
6
  import { packageFor } from "./adapter-load.ts";
7
7
  import { CLI_TEXT } from "./cli-text.ts";
8
- import { EMPTY, loadConfig, type Config } from "./config/load.ts";
8
+ import { EMPTY, type Config } from "./config/load.ts";
9
+ import { loadConfig } from "./config/read.ts";
9
10
  import { withStyle } from "./config/style.ts";
10
11
  import { withExtensions } from "./extension/load.ts";
11
12
  import { GENRES } from "./genre.ts";
@@ -35,6 +36,7 @@ export type {
35
36
  SupportedFact,
36
37
  } from "./grade/result.ts";
37
38
  export type { OutputSize } from "./grade/rates.ts";
39
+ export type { AiLevelCounts, GradeAiScore } from "./grade/ai-score.ts";
38
40
  export { toScorer, type ChaffScore } from "./grade/scorer.ts";
39
41
  export type { Disagreement, VariantColumn, VariantComparison, VariantRates } from "./grade/variants.ts";
40
42
  export type { VariantInput } from "./grade/variants-input.ts";
package/src/judge.ts CHANGED
@@ -78,8 +78,6 @@ export type JudgeOptions = {
78
78
  const judgeFor = (options: JudgeOptions): Judge =>
79
79
  options.backend === "openai" ? openai.judge(options.openaiClient) : anthropic.judge(options.anthropicClient);
80
80
 
81
- export const defaultModel = (backend: BackendName): string => (backend === "openai" ? openai.DEFAULT_MODEL : anthropic.DEFAULT_MODEL);
82
-
83
81
  export const hasCredentials = (backend: BackendName): boolean => (backend === "openai" ? openai.hasCredentials() : anthropic.hasCredentials());
84
82
 
85
83
  export const isAuthFailure = (backend: BackendName, error: unknown): boolean =>
@@ -1,6 +1,6 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
1
  import { join } from "node:path";
3
2
  import { parse } from "yaml";
3
+ import { exists, readText } from "./package-files.ts";
4
4
  import { loadGenres } from "./genre-load.ts";
5
5
  import { parseGenres, type GenreData } from "./genre-parse.ts";
6
6
 
@@ -15,5 +15,5 @@ export const knownGenres = (): string[] => idsOf(loadGenres());
15
15
  */
16
16
  export const genresBeside = (rulesDir: string): string[] => {
17
17
  const file = join(rulesDir, "..", "genres.yaml");
18
- return existsSync(file) ? idsOf(parseGenres(parse(readFileSync(file, "utf8")))) : knownGenres();
18
+ return exists(file) ? idsOf(parseGenres(parse(readText(file)))) : knownGenres();
19
19
  };
@@ -0,0 +1,15 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ // Every read of a file chaff ships (rules, genres, profiles, styles) goes through here, so a build without a file
6
+ // system can put its own reads in this module's place. The functions take any path, as the callers always have.
7
+
8
+ /** The package's folder: src/ and dist/ both sit right under it. */
9
+ export const PACKAGE_DIR = join(dirname(fileURLToPath(import.meta.url)), "..");
10
+
11
+ export const readText = (path: string): string => readFileSync(path, "utf8");
12
+
13
+ export const readDir = (path: string): string[] => readdirSync(path);
14
+
15
+ export const exists = (path: string): boolean => existsSync(path);
@@ -1,22 +1,21 @@
1
- import { readFileSync, readdirSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
- import { fileURLToPath } from "node:url";
1
+ import { join } from "node:path";
4
2
  import { parse } from "yaml";
3
+ import { PACKAGE_DIR, readDir, readText } from "../package-files.ts";
5
4
  import { parseProfile, type ProfileDefinition } from "./parse.ts";
6
5
 
7
- const PROFILES_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "profiles");
6
+ const PROFILES_DIR = join(PACKAGE_DIR, "profiles");
8
7
 
9
8
  const loaded: { value: readonly ProfileDefinition[] | undefined } = { value: undefined };
10
9
 
11
10
  /** 同梱の profiles/*.yaml。一度だけ読む。 */
12
11
  export const loadProfiles = (): readonly ProfileDefinition[] => {
13
- loaded.value ??= readdirSync(PROFILES_DIR)
12
+ loaded.value ??= readDir(PROFILES_DIR)
14
13
  .filter((file) => file.endsWith(".yaml"))
15
14
  .toSorted((left, right) => left.localeCompare(right))
16
15
  .flatMap((file) => {
17
16
  const path = join(PROFILES_DIR, file);
18
17
  try {
19
- const definition = parseProfile(parse(readFileSync(path, "utf8")));
18
+ const definition = parseProfile(parse(readText(path)));
20
19
  return definition === undefined ? [] : [definition];
21
20
  } catch (error) {
22
21
  throw new Error(`cannot read the profile ${path}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
package/src/rule-load.ts CHANGED
@@ -1,6 +1,4 @@
1
- import { readFileSync, readdirSync } from "node:fs";
2
- import { fileURLToPath } from "node:url";
3
- import { dirname, join } from "node:path";
1
+ import { join } from "node:path";
4
2
  import { parse } from "yaml";
5
3
  import type { LanguageLevels, LevelSets, LevelTable, RuleDefinition, Severity } from "./plugin.ts";
6
4
  import { rankOfSeverity, severityAt } from "./levels.ts";
@@ -8,8 +6,9 @@ import { optionsOf } from "./rule-options.ts";
8
6
  import { ruleGuideOf } from "./rule-guide.ts";
9
7
  import { fieldProblemSentence, fieldProblems } from "./rule-fields.ts";
10
8
  import { genresBeside } from "./known-genres.ts";
9
+ import { PACKAGE_DIR, readDir, readText } from "./package-files.ts";
11
10
 
12
- const RULES_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "rules");
11
+ const RULES_DIR = join(PACKAGE_DIR, "rules");
13
12
 
14
13
  const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
15
14
 
@@ -191,7 +190,7 @@ const ruleOf = (raw: Record<string, unknown>, levels: LevelTable, levelSets: Lev
191
190
  */
192
191
  const parseRule = (dir: string, file: string): unknown => {
193
192
  try {
194
- return parse(readFileSync(join(dir, file), "utf8"));
193
+ return parse(readText(join(dir, file)));
195
194
  } catch (error) {
196
195
  throw new Error(`${file} を読めません: ${error instanceof Error ? error.message.split("\n")[0] : String(error)}`, { cause: error });
197
196
  }
@@ -199,7 +198,7 @@ const parseRule = (dir: string, file: string): unknown => {
199
198
 
200
199
  export const loadRules = (language: string, dir: string = RULES_DIR): RuleDefinition[] => {
201
200
  const genres = genresBeside(dir);
202
- return readdirSync(dir)
201
+ return readDir(dir)
203
202
  .filter((file) => file.endsWith(".yaml"))
204
203
  .toSorted((left, right) => left.localeCompare(right, "en"))
205
204
  .map((file) => toRule(parseRule(dir, file), language, file, genres));
package/src/rule-offs.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { readFileSync, readdirSync } from "node:fs";
2
1
  import { join } from "node:path";
3
2
  import { parse } from "yaml";
3
+ import { readDir, readText } from "./package-files.ts";
4
4
 
5
5
  /** One rule a group or genre turns off by default, from the rule file's off_for, with why. */
6
6
  export type RuleOff = { readonly rule: string; readonly target: string; readonly reason: string };
@@ -34,7 +34,7 @@ const offForBlock = (rule: string, text: string): string | undefined => {
34
34
  /** The offs of one rule file. The rule is named by the file, which is named by the rule's id. */
35
35
  const offsInFile = (dir: string, file: string): RuleOff[] => {
36
36
  const rule = file.replace(/\.yaml$/u, "");
37
- const block = offForBlock(rule, readFileSync(join(dir, file), "utf8").replaceAll("\r\n", "\n"));
37
+ const block = offForBlock(rule, readText(join(dir, file)).replaceAll("\r\n", "\n"));
38
38
  const parsed: unknown = block === undefined ? undefined : parse(block);
39
39
  return ruleOffsOf(rule, isRecord(parsed) ? parsed["off_for"] : undefined);
40
40
  };
@@ -44,7 +44,7 @@ const offsInFile = (dir: string, file: string): RuleOff[] => {
44
44
  * at startup, before any rule is.
45
45
  */
46
46
  export const loadRuleOffs = (dir: string): RuleOff[] =>
47
- readdirSync(dir)
47
+ readDir(dir)
48
48
  .filter((file) => file.endsWith(".yaml"))
49
49
  .toSorted((left, right) => left.localeCompare(right, "en"))
50
50
  .flatMap((file) => offsInFile(dir, file));