chaffjs 0.1.0 → 0.3.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 (217) hide show
  1. package/dist/backends/anthropic.d.ts +24 -0
  2. package/dist/backends/anthropic.d.ts.map +1 -0
  3. package/dist/backends/anthropic.js +44 -0
  4. package/dist/backends/anthropic.js.map +1 -0
  5. package/dist/backends/openai.d.ts +29 -0
  6. package/dist/backends/openai.d.ts.map +1 -0
  7. package/dist/backends/openai.js +33 -0
  8. package/dist/backends/openai.js.map +1 -0
  9. package/dist/backends/types.d.ts +32 -0
  10. package/dist/backends/types.d.ts.map +1 -0
  11. package/dist/backends/types.js +18 -0
  12. package/dist/backends/types.js.map +1 -0
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +21 -67
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/eval.d.ts +15 -0
  17. package/dist/commands/eval.d.ts.map +1 -0
  18. package/dist/commands/eval.js +47 -0
  19. package/dist/commands/eval.js.map +1 -0
  20. package/dist/commands/test.d.ts +21 -0
  21. package/dist/commands/test.d.ts.map +1 -0
  22. package/dist/commands/test.js +116 -0
  23. package/dist/commands/test.js.map +1 -0
  24. package/dist/config/by-path.d.ts +15 -0
  25. package/dist/config/by-path.d.ts.map +1 -0
  26. package/dist/config/by-path.js +41 -0
  27. package/dist/config/by-path.js.map +1 -0
  28. package/dist/config/load.d.ts +8 -1
  29. package/dist/config/load.d.ts.map +1 -1
  30. package/dist/config/load.js +31 -3
  31. package/dist/config/load.js.map +1 -1
  32. package/dist/detectors/agentless-passive.d.ts +3 -0
  33. package/dist/detectors/agentless-passive.d.ts.map +1 -0
  34. package/dist/detectors/agentless-passive.js +31 -0
  35. package/dist/detectors/agentless-passive.js.map +1 -0
  36. package/dist/detectors/char-shape.d.ts +12 -0
  37. package/dist/detectors/char-shape.d.ts.map +1 -0
  38. package/dist/detectors/char-shape.js +39 -0
  39. package/dist/detectors/char-shape.js.map +1 -0
  40. package/dist/detectors/count-per-section.d.ts +8 -1
  41. package/dist/detectors/count-per-section.d.ts.map +1 -1
  42. package/dist/detectors/count-per-section.js +26 -5
  43. package/dist/detectors/count-per-section.js.map +1 -1
  44. package/dist/detectors/en-shape.d.ts +19 -0
  45. package/dist/detectors/en-shape.d.ts.map +1 -0
  46. package/dist/detectors/en-shape.js +176 -0
  47. package/dist/detectors/en-shape.js.map +1 -0
  48. package/dist/detectors/index.d.ts.map +1 -1
  49. package/dist/detectors/index.js +33 -0
  50. package/dist/detectors/index.js.map +1 -1
  51. package/dist/detectors/lexicon.d.ts +15 -0
  52. package/dist/detectors/lexicon.d.ts.map +1 -0
  53. package/dist/detectors/lexicon.js +109 -0
  54. package/dist/detectors/lexicon.js.map +1 -0
  55. package/dist/detectors/repeated-head.d.ts.map +1 -1
  56. package/dist/detectors/repeated-head.js +17 -5
  57. package/dist/detectors/repeated-head.js.map +1 -1
  58. package/dist/detectors/sentence-ending.d.ts +3 -0
  59. package/dist/detectors/sentence-ending.d.ts.map +1 -0
  60. package/dist/detectors/sentence-ending.js +34 -0
  61. package/dist/detectors/sentence-ending.js.map +1 -0
  62. package/dist/detectors/sentence-length.d.ts.map +1 -1
  63. package/dist/detectors/sentence-length.js +3 -9
  64. package/dist/detectors/sentence-length.js.map +1 -1
  65. package/dist/detectors/sentence-rhythm.d.ts.map +1 -1
  66. package/dist/detectors/sentence-rhythm.js +3 -2
  67. package/dist/detectors/sentence-rhythm.js.map +1 -1
  68. package/dist/detectors/signals.d.ts +6 -0
  69. package/dist/detectors/signals.d.ts.map +1 -0
  70. package/dist/detectors/signals.js +183 -0
  71. package/dist/detectors/signals.js.map +1 -0
  72. package/dist/detectors/structure.d.ts +24 -0
  73. package/dist/detectors/structure.d.ts.map +1 -0
  74. package/dist/detectors/structure.js +111 -0
  75. package/dist/detectors/structure.js.map +1 -0
  76. package/dist/detectors/token-shape.d.ts +4 -0
  77. package/dist/detectors/token-shape.d.ts.map +1 -0
  78. package/dist/detectors/token-shape.js +62 -0
  79. package/dist/detectors/token-shape.js.map +1 -0
  80. package/dist/document.d.ts.map +1 -1
  81. package/dist/document.js +95 -12
  82. package/dist/document.js.map +1 -1
  83. package/dist/env.d.ts +12 -0
  84. package/dist/env.d.ts.map +1 -0
  85. package/dist/env.js +20 -0
  86. package/dist/env.js.map +1 -0
  87. package/dist/eval.d.ts +31 -0
  88. package/dist/eval.d.ts.map +1 -0
  89. package/dist/eval.js +62 -0
  90. package/dist/eval.js.map +1 -0
  91. package/dist/genre.d.ts.map +1 -1
  92. package/dist/genre.js +8 -1
  93. package/dist/genre.js.map +1 -1
  94. package/dist/init.d.ts.map +1 -1
  95. package/dist/init.js +11 -6
  96. package/dist/init.js.map +1 -1
  97. package/dist/judge.d.ts +14 -16
  98. package/dist/judge.d.ts.map +1 -1
  99. package/dist/judge.js +0 -0
  100. package/dist/judge.js.map +1 -1
  101. package/dist/levels.d.ts +1 -9
  102. package/dist/levels.d.ts.map +1 -1
  103. package/dist/levels.js +15 -3
  104. package/dist/levels.js.map +1 -1
  105. package/dist/look-at.d.ts +18 -0
  106. package/dist/look-at.d.ts.map +1 -0
  107. package/dist/look-at.js +43 -0
  108. package/dist/look-at.js.map +1 -0
  109. package/dist/measure.d.ts +15 -0
  110. package/dist/measure.d.ts.map +1 -0
  111. package/dist/measure.js +16 -0
  112. package/dist/measure.js.map +1 -0
  113. package/dist/plugin.d.ts +44 -1
  114. package/dist/plugin.d.ts.map +1 -1
  115. package/dist/render/eval.d.ts +7 -0
  116. package/dist/render/eval.d.ts.map +1 -0
  117. package/dist/render/eval.js +59 -0
  118. package/dist/render/eval.js.map +1 -0
  119. package/dist/render/explain.d.ts +1 -1
  120. package/dist/render/explain.d.ts.map +1 -1
  121. package/dist/render/explain.js +12 -4
  122. package/dist/render/explain.js.map +1 -1
  123. package/dist/render/rules-json.d.ts.map +1 -1
  124. package/dist/render/rules-json.js +13 -5
  125. package/dist/render/rules-json.js.map +1 -1
  126. package/dist/render/semantic.d.ts +2 -1
  127. package/dist/render/semantic.d.ts.map +1 -1
  128. package/dist/render/semantic.js +35 -0
  129. package/dist/render/semantic.js.map +1 -1
  130. package/dist/rule-load.d.ts.map +1 -1
  131. package/dist/rule-load.js +27 -1
  132. package/dist/rule-load.js.map +1 -1
  133. package/dist/run-semantic.d.ts +22 -1
  134. package/dist/run-semantic.d.ts.map +1 -1
  135. package/dist/run-semantic.js +21 -10
  136. package/dist/run-semantic.js.map +1 -1
  137. package/dist/run.d.ts +6 -1
  138. package/dist/run.d.ts.map +1 -1
  139. package/dist/run.js +34 -2
  140. package/dist/run.js.map +1 -1
  141. package/dist/sentence-shape.d.ts +6 -0
  142. package/dist/sentence-shape.d.ts.map +1 -0
  143. package/dist/sentence-shape.js +15 -0
  144. package/dist/sentence-shape.js.map +1 -0
  145. package/package.json +4 -3
  146. package/rules/adverb-overuse.yaml +28 -0
  147. package/rules/agentless-passive.yaml +28 -0
  148. package/rules/ai-tell.yaml +27 -0
  149. package/rules/bold-density.yaml +8 -6
  150. package/rules/concrete-evidence-density.yaml +25 -0
  151. package/rules/cushion-phrase-density.yaml +26 -0
  152. package/rules/double-keigo.yaml +27 -0
  153. package/rules/emoji-density.yaml +26 -0
  154. package/rules/empty-intensifier.yaml +1 -1
  155. package/rules/excessive-hedging.yaml +26 -0
  156. package/rules/expletive-construction.yaml +27 -0
  157. package/rules/heading-echo.yaml +1 -1
  158. package/rules/hiragana-fukushi.yaml +27 -0
  159. package/rules/max-kanji-continuous.yaml +26 -0
  160. package/rules/max-paragraph-length.yaml +25 -0
  161. package/rules/max-sentence-length.yaml +23 -1
  162. package/rules/ngram-repetition.yaml +25 -0
  163. package/rules/no-doubled-joshi.yaml +28 -0
  164. package/rules/no-mixed-desumasu.yaml +29 -0
  165. package/rules/no-nakaguro-parallel.yaml +27 -0
  166. package/rules/oxford-comma-consistency.yaml +26 -0
  167. package/rules/paragraph-length-variance.yaml +25 -0
  168. package/rules/preamble-length.yaml +25 -0
  169. package/rules/repeated-conjunction.yaml +26 -0
  170. package/rules/repeated-sentence-head.yaml +1 -1
  171. package/rules/rule-of-three.yaml +25 -0
  172. package/rules/sasete-itadaku.yaml +27 -0
  173. package/rules/section-length-uniformity.yaml +25 -0
  174. package/rules/sentence-initial-conjunction-run.yaml +27 -0
  175. package/rules/taigen-dome-in-prose.yaml +27 -0
  176. package/rules/title-case-consistency.yaml +26 -0
  177. package/rules/undefined-acronym.yaml +25 -0
  178. package/rules/unqualified-superlative.yaml +26 -0
  179. package/src/backends/anthropic.ts +61 -0
  180. package/src/backends/openai.ts +46 -0
  181. package/src/backends/types.ts +38 -0
  182. package/src/cli.ts +19 -78
  183. package/src/commands/eval.ts +67 -0
  184. package/src/commands/test.ts +147 -0
  185. package/src/config/by-path.ts +47 -0
  186. package/src/config/load.ts +36 -3
  187. package/src/detectors/agentless-passive.ts +34 -0
  188. package/src/detectors/char-shape.ts +47 -0
  189. package/src/detectors/count-per-section.ts +30 -6
  190. package/src/detectors/en-shape.ts +187 -0
  191. package/src/detectors/index.ts +33 -0
  192. package/src/detectors/lexicon.ts +124 -0
  193. package/src/detectors/repeated-head.ts +17 -6
  194. package/src/detectors/sentence-ending.ts +37 -0
  195. package/src/detectors/sentence-length.ts +4 -10
  196. package/src/detectors/sentence-rhythm.ts +3 -2
  197. package/src/detectors/signals.ts +199 -0
  198. package/src/detectors/structure.ts +119 -0
  199. package/src/detectors/token-shape.ts +81 -0
  200. package/src/document.ts +101 -13
  201. package/src/env.ts +20 -0
  202. package/src/eval.ts +80 -0
  203. package/src/genre.ts +8 -1
  204. package/src/init.ts +11 -6
  205. package/src/judge.ts +0 -0
  206. package/src/levels.ts +16 -4
  207. package/src/look-at.ts +53 -0
  208. package/src/measure.ts +21 -0
  209. package/src/plugin.ts +45 -3
  210. package/src/render/eval.ts +62 -0
  211. package/src/render/explain.ts +12 -4
  212. package/src/render/rules-json.ts +16 -5
  213. package/src/render/semantic.ts +38 -1
  214. package/src/rule-load.ts +28 -1
  215. package/src/run-semantic.ts +32 -11
  216. package/src/run.ts +36 -3
  217. package/src/sentence-shape.ts +22 -0
@@ -0,0 +1,28 @@
1
+ id: no-doubled-joshi
2
+ layer: L3
3
+ status: experimental
4
+ how_to_find: doubled-particle
5
+ word_list: nesting-particle
6
+ requires: [pos]
7
+ languages: [ja]
8
+ severity: warning
9
+
10
+ name:
11
+ ja: 同じ助詞が入れ子で続いている
12
+ en: Nested repeated particle
13
+
14
+ why:
15
+ ja: 「弊社の新製品の販売の計画」のように同じ助詞が読点を挟まずに続くと、どれがどれに係るのか読み手が追えなくなります。読点で区切られた並列は数えていません。
16
+ en: When the same particle repeats without a break, the reader cannot tell which phrase attaches to which. Items separated by punctuation are not counted.
17
+
18
+ message:
19
+ ja: 「{word}」が読点を挟まずに {count} 回続いています({limit} 回まで)
20
+ en: '"{word}" repeats {count} times with no break (limit {limit})'
21
+
22
+ how_to_fix:
23
+ ja: 文を割るか、係り先を言い換えてください。「弊社の新製品の販売の計画」を「弊社が新製品を販売する計画」に。
24
+ en: Split the sentence, or rephrase so the attachment is explicit.
25
+
26
+ levels: { strict: 1, normal: 2, relaxed: 3 }
27
+
28
+ use_for: [blog, business, technical]
@@ -0,0 +1,29 @@
1
+ id: no-mixed-desumasu
2
+ layer: L3
3
+ status: experimental
4
+ how_to_find: sentence-ending
5
+ word_list: polite-ending
6
+ requires: [pos]
7
+ languages: [ja]
8
+ severity: warning
9
+
10
+ name:
11
+ ja: 文末の調子が混ざっている
12
+ en: Mixed sentence-ending register
13
+
14
+ why:
15
+ ja: ですます調とである調が混ざると、読み手は「誰に向けた文書か」を掴み損ねます。どちらが正しいかではなく、1 つの文書で揃っているかの問題です。
16
+ en: Mixing polite and plain endings leaves the reader unsure who the document addresses. Neither register is wrong; consistency within one document is the point.
17
+
18
+ message:
19
+ ja: この文だけ他と文末の調子が違います(文書の中で {count} 文)
20
+ en: "This sentence's ending register differs from the rest ({count} in this document)"
21
+
22
+ how_to_fix:
23
+ ja: 多いほうに揃えてください。少数派のほうを直すのが、いちばん手数が少なくて済みます。
24
+ en: Match the majority. Rewriting the minority is the smallest change.
25
+
26
+ # 少数派が何文までなら「混在」と見るか。これを超えたら、混在ではなく別の文体と判断して黙る。
27
+ levels: { strict: 5, normal: 3, relaxed: 1 }
28
+
29
+ use_for: [blog, business, technical]
@@ -0,0 +1,27 @@
1
+ id: no-nakaguro-parallel
2
+ layer: L3
3
+ status: experimental
4
+ how_to_find: middle-dot
5
+ languages: [ja]
6
+ severity: info
7
+
8
+ name:
9
+ ja: 中黒で並べすぎている
10
+ en: Too many items joined by middle dots
11
+
12
+ why:
13
+ ja: 中黒の並列は 1 組なら読めますが、1 文に何組も入ると、どの項目がどの組に属するのか追えなくなります。実文書では 1 文 4 個までは普通に読めていました。
14
+ en: One run of middle dots reads fine. Several runs in one sentence make it impossible to tell which item belongs to which group.
15
+
16
+ message:
17
+ ja: この文に中黒が {count} 個あります({limit} 個まで)
18
+ en: "{count} middle dots in this sentence (limit {limit})"
19
+
20
+ how_to_fix:
21
+ ja: 文を割るか、箇条書きにしてください。組の切れ目がはっきりします。
22
+ en: Use commas, or turn it into a bullet list.
23
+
24
+ # 実文書の分布から。1 文 4 個までは読める。
25
+ levels: { strict: 3, normal: 5, relaxed: 8 }
26
+
27
+ use_for: [blog, business, technical]
@@ -0,0 +1,26 @@
1
+ id: oxford-comma-consistency
2
+ layer: L3
3
+ status: experimental
4
+ how_to_find: oxford-comma
5
+ languages: [en]
6
+ severity: info
7
+
8
+ name:
9
+ ja: 並列の読点が揃っていない
10
+ en: Oxford comma use is inconsistent
11
+
12
+ why:
13
+ ja: 最後の and の前に読点を打つかは、スタイルガイドで割れます。どちらが正しいかは決めません。1 つの文書で混ざっていると、読み手は項目の切れ目を毎回読み直すことになります。
14
+ en: Whether to put a comma before the final "and" is a style-guide split. Neither is wrong. Mixed within one document, the reader re-parses every list.
15
+
16
+ message:
17
+ ja: この文だけ並列の読点の打ちかたが他と違います(文書の中で {count} 文)
18
+ en: "This list punctuates differently from the rest ({count} in this document)"
19
+
20
+ how_to_fix:
21
+ ja: 多いほうに揃えてください。
22
+ en: Match the majority.
23
+
24
+ levels: { strict: 3, normal: 2, relaxed: 1 }
25
+
26
+ use_for: [blog, business, technical]
@@ -0,0 +1,25 @@
1
+ id: paragraph-length-variance
2
+ layer: L1
3
+ status: experimental
4
+ how_to_find: paragraph-variance
5
+ severity: info
6
+
7
+ name:
8
+ ja: 段落の長さが揃いすぎている
9
+ en: Paragraph lengths are too uniform
10
+
11
+ why:
12
+ ja: 人が書くと、言いたいことの重さで段落の長さは揺れます。揃いすぎは、埋めるために長さを合わせた跡です。
13
+ en: When a person writes, paragraph length follows what matters. Uniform length is the mark of filling a shape.
14
+
15
+ message:
16
+ ja: 段落の長さのばらつきが {variance}% しかありません({limit}% 以上ほしい)
17
+ en: Paragraph length varies by only {variance}% (want at least {limit}%)
18
+
19
+ how_to_fix:
20
+ ja: 短くできる段落を短くしてください。全部を同じ長さにしないこと。
21
+ en: Let the short ones be short.
22
+
23
+ levels: { strict: 40, normal: 30, relaxed: 20 }
24
+
25
+ use_for: [blog]
@@ -0,0 +1,25 @@
1
+ id: preamble-length
2
+ layer: L1
3
+ status: experimental
4
+ how_to_find: preamble-length
5
+ severity: info
6
+
7
+ name:
8
+ ja: 本題に入るまでが長い
9
+ en: Too long before the point
10
+
11
+ why:
12
+ ja: 業務文書の読み手は結論を探しに来ています。最初の見出しまでに段落が重なると、探す手間だけが増えます。
13
+ en: A reader of a business document is looking for the decision. Paragraphs before the first heading only delay it.
14
+
15
+ message:
16
+ ja: 最初の見出しまでに {count} 段落あります({limit} 段落まで)
17
+ en: "{count} paragraphs before the first heading (limit {limit})"
18
+
19
+ how_to_fix:
20
+ ja: 結論を先に書くか、前置きを見出しの下へ移してください。
21
+ en: Put the conclusion first, or move the preamble under a heading.
22
+
23
+ levels: { strict: 1, normal: 2, relaxed: 4 }
24
+
25
+ use_for: [business]
@@ -0,0 +1,26 @@
1
+ id: repeated-conjunction
2
+ layer: L2
3
+ status: experimental
4
+ how_to_find: repeated-conjunction
5
+ word_list: paragraph-opener
6
+ severity: warning
7
+
8
+ name:
9
+ ja: 段落が同じ調子で始まり続ける
10
+ en: Paragraphs keep opening the same way
11
+
12
+ why:
13
+ ja: 接続詞で始まる段落は 1 つなら流れを作りますが、続くとどの段落も前の付け足しに見えて、話がどこへ向かっているのか分からなくなります。
14
+ en: One paragraph opening with a conjunction carries the flow. A run of them makes every paragraph an afterthought.
15
+
16
+ message:
17
+ ja: 接続詞で始まる段落が {count} 続いています({limit} まで)
18
+ en: "{count} paragraphs in a row open with a conjunction (limit {limit})"
19
+
20
+ how_to_fix:
21
+ ja: どれか 1 つを残して、他は言いたいことから始めてください。
22
+ en: Keep one. Start the others with what you want to say.
23
+
24
+ levels: { strict: 1, normal: 2, relaxed: 4 }
25
+
26
+ use_for: [business, blog, technical]
@@ -25,4 +25,4 @@ levels:
25
25
  normal: 3
26
26
  relaxed: 4
27
27
 
28
- use_for: [blog, business]
28
+ use_for: [blog, business, technical]
@@ -0,0 +1,25 @@
1
+ id: rule-of-three
2
+ layer: L1
3
+ status: experimental
4
+ how_to_find: rule-of-three
5
+ severity: info
6
+
7
+ name:
8
+ ja: 3 項目の箇条書きばかり
9
+ en: Everything comes in threes
10
+
11
+ why:
12
+ ja: 3 つ並べると収まりがよいので、言いたいことが 2 つでも 4 つでも 3 つにされます。
13
+ en: Three items feel complete, so two become three and four become three.
14
+
15
+ message:
16
+ ja: 箇条書き {total} 個のうち {count} 個が 3 項目です({limit}% まで)
17
+ en: "{count} of {total} lists have exactly three items ({limit}% allowed)"
18
+
19
+ how_to_fix:
20
+ ja: 数を合わせるために足した項目を消してください。2 つで足りるなら 2 つで。
21
+ en: Drop the item you added to reach three.
22
+
23
+ levels: { strict: 50, normal: 70, relaxed: 90 }
24
+
25
+ use_for: [blog]
@@ -0,0 +1,27 @@
1
+ id: sasete-itadaku
2
+ layer: L3
3
+ status: experimental
4
+ how_to_find: phrase-match
5
+ word_list: sasete-itadaku
6
+ languages: [ja]
7
+ severity: info
8
+
9
+ name:
10
+ ja: 「させていただく」が多い
11
+ en: Overused humble form
12
+
13
+ why:
14
+ ja: 1 度なら丁寧ですが、重なると誰の判断で何をするのかが消えます。「検討させていただきます」は、検討するのかしないのかを言っていません。
15
+ en: Once it is polite. Repeated, it hides whose decision it is and what will actually happen.
16
+
17
+ message:
18
+ ja: 「{matched}」が文書内に {count} 回あります({limit} 回まで)
19
+ en: '"{matched}" appears {count} times in this document (limit {limit})'
20
+
21
+ how_to_fix:
22
+ ja: 主語を立てて言い切ってください。「検討させていただきます」を「来週までに結論を出します」に。
23
+ en: Name the actor and commit. "We will look into it" becomes "we will decide by Friday".
24
+
25
+ levels: { strict: 1, normal: 3, relaxed: 6 }
26
+
27
+ use_for: [business, blog]
@@ -0,0 +1,25 @@
1
+ id: section-length-uniformity
2
+ layer: L1
3
+ status: experimental
4
+ how_to_find: section-uniformity
5
+ severity: info
6
+
7
+ name:
8
+ ja: 節の長さが揃いすぎている
9
+ en: Section lengths are too uniform
10
+
11
+ why:
12
+ ja: 見出しごとに機械的に埋めると、節の長さが揃います。書くことの量は節ごとに違うはずです。
13
+ en: Filling every heading to the same size is what a template does, not what a writer does.
14
+
15
+ message:
16
+ ja: 節の長さのばらつきが {variance}% しかありません({limit}% 以上ほしい)
17
+ en: Section length varies by only {variance}% (want at least {limit}%)
18
+
19
+ how_to_fix:
20
+ ja: 書くことの少ない節は短いままにしてください。
21
+ en: Let the thin sections stay thin.
22
+
23
+ levels: { strict: 45, normal: 35, relaxed: 25 }
24
+
25
+ use_for: [blog]
@@ -0,0 +1,27 @@
1
+ id: sentence-initial-conjunction-run
2
+ layer: L3
3
+ status: experimental
4
+ how_to_find: conjunction-run
5
+ word_list: sentence-opener
6
+ languages: [en]
7
+ severity: info
8
+
9
+ name:
10
+ ja: 接続詞で始まる文が続いている
11
+ en: Run of sentences opening with a conjunction
12
+
13
+ why:
14
+ ja: 接続詞で始まる文は 1 つなら効きますが、続くとどの文も前の文の付け足しに見えて、何が主張なのかが分からなくなります。
15
+ en: One sentence opening with "And" or "But" lands. A run of them makes every sentence read as an afterthought.
16
+
17
+ message:
18
+ ja: 接続詞で始まる文が {count} 文続いています({limit} 文まで)
19
+ en: "{count} sentences in a row open with a conjunction (limit {limit})"
20
+
21
+ how_to_fix:
22
+ ja: どれか 1 つを残して、他は前の文に繋ぐか独立させてください。
23
+ en: Keep one. Join the others to the previous sentence, or let them stand alone.
24
+
25
+ levels: { strict: 1, normal: 2, relaxed: 4 }
26
+
27
+ use_for: [blog, business]
@@ -0,0 +1,27 @@
1
+ id: taigen-dome-in-prose
2
+ layer: L3
3
+ status: experimental
4
+ how_to_find: noun-ending
5
+ requires: [pos]
6
+ languages: [ja]
7
+ severity: info
8
+
9
+ name:
10
+ ja: 体言止めが続いている
11
+ en: Too many noun-ending sentences
12
+
13
+ why:
14
+ ja: 体言止めは 1 つなら効きますが、続くと文が途中で切れたように読めます。箇条書きでは普通の書きかたなので、本文だけを見ています。
15
+ en: One noun-ending sentence lands. Several in a row read as unfinished. Bullet lists are excluded, where the form is normal.
16
+
17
+ message:
18
+ ja: 本文に体言止めが {count} 文あります({limit} 文まで)
19
+ en: "{count} sentences end on a noun in the body text (limit {limit})"
20
+
21
+ how_to_fix:
22
+ ja: 述語を足してください。「〜が課題。」を「〜が課題です。」に。
23
+ en: Add the predicate.
24
+
25
+ levels: { strict: 2, normal: 4, relaxed: 8 }
26
+
27
+ use_for: [blog, business, technical]
@@ -0,0 +1,26 @@
1
+ id: title-case-consistency
2
+ layer: L3
3
+ status: experimental
4
+ how_to_find: title-case-mix
5
+ languages: [en]
6
+ severity: info
7
+
8
+ name:
9
+ ja: 見出しの大文字化が揃っていない
10
+ en: Heading capitalisation is inconsistent
11
+
12
+ why:
13
+ ja: Title Case と sentence case のどちらが正しいかは決めません。1 つの文書で混ざっていると、見出しの階層が変わったように見えます。
14
+ en: Neither Title Case nor sentence case is wrong. Mixed within one document, the headings look like they change level.
15
+
16
+ message:
17
+ ja: この見出しだけ他と大文字化の流儀が違います(文書の中で {count} 個)
18
+ en: "This heading's capitalisation differs from the rest ({count} in this document)"
19
+
20
+ how_to_fix:
21
+ ja: 多いほうに揃えてください。
22
+ en: Match the majority.
23
+
24
+ levels: { strict: 3, normal: 2, relaxed: 1 }
25
+
26
+ use_for: [blog, business, technical]
@@ -0,0 +1,25 @@
1
+ id: undefined-acronym
2
+ layer: L1
3
+ status: experimental
4
+ how_to_find: undefined-acronym
5
+ severity: info
6
+
7
+ name:
8
+ ja: 略語が説明なしで出てくる
9
+ en: Acronym never expanded
10
+
11
+ why:
12
+ ja: 略語は読む人によって指すものが違います。社内で通じても、半年後の自分や別の部署には通じません。
13
+ en: An acronym means different things to different readers. What is obvious inside one team is not obvious outside it.
14
+
15
+ message:
16
+ ja: 「{word}」が説明なしで使われています(説明のない略語 {count} 個)
17
+ en: '"{word}" is used without an expansion ({count} such acronyms)'
18
+
19
+ how_to_fix:
20
+ ja: 初出のところで括弧に入れて展開してください。「CI(継続的インテグレーション)」のように。
21
+ en: Expand it in brackets at first use.
22
+
23
+ levels: { strict: 1, normal: 3, relaxed: 6 }
24
+
25
+ use_for: [business, technical]
@@ -0,0 +1,26 @@
1
+ id: unqualified-superlative
2
+ layer: L2
3
+ status: experimental
4
+ how_to_find: unqualified-superlative
5
+ word_list: superlative
6
+ severity: warning
7
+
8
+ name:
9
+ ja: 比べる相手のない最上級
10
+ en: Superlative with nothing to compare
11
+
12
+ why:
13
+ ja: 「最も速い」だけでは、何と比べて最もなのかがありません。比較対象も条件も無い最上級は、読み手に確かめようがありません。
14
+ en: '"The fastest" without saying than what leaves the reader nothing to check.'
15
+
16
+ message:
17
+ ja: 「{matched}」に比べる相手がありません(そういう言い方が {count} 箇所)
18
+ en: '"{matched}" has nothing to compare against ({count} such claims)'
19
+
20
+ how_to_fix:
21
+ ja: 比較対象か条件を添えてください。「社内の 3 案のうち最も速い」のように。
22
+ en: Name the comparison. "The fastest of the three options we tried."
23
+
24
+ levels: { strict: 1, normal: 2, relaxed: 4 }
25
+
26
+ use_for: [business, blog]
@@ -0,0 +1,61 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import Anthropic from "@anthropic-ai/sdk";
5
+ import { toFailure } from "./types.ts";
6
+ import type { Failure, Judge, Prompt } from "./types.ts";
7
+
8
+ export type AnthropicResponse = { readonly content: readonly { readonly type: string; readonly text?: string | undefined }[] };
9
+
10
+ /** judge が使うのは messages.create だけ。最小の面だけを要求する。テストで差し替えられる。 */
11
+ export type AnthropicClient = { readonly messages: { readonly create: (params: Anthropic.MessageCreateParamsNonStreaming) => Promise<AnthropicResponse> } };
12
+
13
+ const isTextBlock = (block: unknown): block is { readonly text: string } =>
14
+ typeof block === "object" && block !== null && "text" in block && typeof block.text === "string";
15
+
16
+ export const DEFAULT_MODEL = "claude-opus-5";
17
+
18
+ /**
19
+ * 認証情報があるか。
20
+ *
21
+ * env var を見るだけでは足りない。SDK は ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN /
22
+ * `ant auth login` のプロファイル / Workload Identity の順に解決するので、env が
23
+ * 無くても通ることがある。逆に、無いまま呼ぶと SDK は型の無い素の Error を投げる
24
+ * (AnthropicError でも APIError でもない)ので、あとから種類で判別できない。
25
+ * だから先に確かめる。
26
+ */
27
+ const ENV_KEYS = ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "ANTHROPIC_IDENTITY_TOKEN", "ANTHROPIC_IDENTITY_TOKEN_FILE"];
28
+
29
+ export const configDir = (): string => process.env["ANTHROPIC_CONFIG_DIR"] ?? join(homedir(), ".config", "anthropic");
30
+
31
+ export const hasCredentials = (profileDir: string = configDir()): boolean =>
32
+ ENV_KEYS.some((name) => (process.env[name] ?? "").length > 0) || existsSync(profileDir);
33
+
34
+ export const isAuthFailure = (error: unknown): boolean =>
35
+ error instanceof Anthropic.AuthenticationError || (error instanceof Anthropic.APIError && error.status === 401);
36
+
37
+ /** API が返した失敗だけを拾う。ネットワーク断やコードの誤りは、握りつぶさずそのまま投げる。 */
38
+ export const describeFailure = (error: unknown): Failure | undefined => {
39
+ if (!(error instanceof Anthropic.APIError)) return undefined;
40
+ const raw: { status: unknown; message: unknown } = error;
41
+ return toFailure(raw);
42
+ };
43
+
44
+ export const judge =
45
+ (client?: AnthropicClient): Judge =>
46
+ async (prompt: Prompt): Promise<string> => {
47
+ const anthropic: AnthropicClient = client ?? new Anthropic();
48
+ const response = await anthropic.messages.create({
49
+ model: prompt.model,
50
+ max_tokens: 1024,
51
+ system: prompt.system,
52
+ output_config: { format: { type: "json_schema", schema: prompt.schema } },
53
+ messages: [{ role: "user", content: prompt.user }],
54
+ });
55
+ const body = response.content.map((block) => (isTextBlock(block) ? block.text : undefined)).find((text) => text !== undefined);
56
+ if (body === undefined) throw new Error("判定が返りませんでした");
57
+ return body;
58
+ };
59
+
60
+ /** 認証が無いときに人へ見せる案内。 */
61
+ export const SETUP_HINT = "ANTHROPIC_API_KEY を設定するか、ant auth login を実行してください。";
@@ -0,0 +1,46 @@
1
+ import OpenAI from "openai";
2
+ import { toFailure } from "./types.ts";
3
+ import type { Failure, Judge, Prompt } from "./types.ts";
4
+
5
+ export type OpenAIResponse = { readonly choices: readonly { readonly message: { readonly content: string | null } }[] };
6
+
7
+ /** judge が使うのは chat.completions.create だけ。最小の面だけを要求する。 */
8
+ export type OpenAIClient = {
9
+ readonly chat: { readonly completions: { readonly create: (params: OpenAI.ChatCompletionCreateParamsNonStreaming) => Promise<OpenAIResponse> } };
10
+ };
11
+
12
+ export const DEFAULT_MODEL = "gpt-5";
13
+
14
+ export const hasCredentials = (): boolean => (process.env["OPENAI_API_KEY"] ?? "").length > 0;
15
+
16
+ export const isAuthFailure = (error: unknown): boolean => error instanceof OpenAI.APIError && error.status === 401;
17
+
18
+ /** API が返した失敗だけを拾う。ネットワーク断やコードの誤りは、握りつぶさずそのまま投げる。 */
19
+ export const describeFailure = (error: unknown): Failure | undefined => {
20
+ if (!(error instanceof OpenAI.APIError)) return undefined;
21
+ const raw: { status: unknown; message: unknown } = error;
22
+ return toFailure(raw);
23
+ };
24
+
25
+ /**
26
+ * 同じ JSON schema をそのまま使う。包みかたが違うだけで、返させる形は provider で変えない。
27
+ * 変えると、provider を替えたときに判定の意味まで変わる。
28
+ */
29
+ export const judge =
30
+ (client?: OpenAIClient): Judge =>
31
+ async (prompt: Prompt): Promise<string> => {
32
+ const openai: OpenAIClient = client ?? new OpenAI();
33
+ const response = await openai.chat.completions.create({
34
+ model: prompt.model,
35
+ messages: [
36
+ { role: "system", content: prompt.system },
37
+ { role: "user", content: prompt.user },
38
+ ],
39
+ response_format: { type: "json_schema", json_schema: { name: "verdict", schema: prompt.schema, strict: true } },
40
+ });
41
+ const body = response.choices[0]?.message.content;
42
+ if (body === null || body === undefined) throw new Error("判定が返りませんでした");
43
+ return body;
44
+ };
45
+
46
+ export const SETUP_HINT = "OPENAI_API_KEY を設定してください。";
@@ -0,0 +1,38 @@
1
+ /**
2
+ * 判定役の面。ここまでが provider ごとに違い、ここから先は共通。
3
+ *
4
+ * 返すのは JSON の文字列で、Verdict への変換と検証は judge.ts が 1 箇所で行う。
5
+ * provider が増えても「返ってきた形の確かめかた」は増やさない。
6
+ */
7
+ export type JsonSchema = Readonly<Record<string, unknown>>;
8
+
9
+ export type Prompt = { readonly system: string; readonly user: string; readonly schema: JsonSchema; readonly model: string };
10
+
11
+ export type Judge = (prompt: Prompt) => Promise<string>;
12
+
13
+ export type BackendName = "anthropic" | "openai";
14
+
15
+ export const BACKENDS: readonly BackendName[] = ["anthropic", "openai"];
16
+
17
+ export const isBackend = (value: unknown): value is BackendName => BACKENDS.some((name) => name === value);
18
+
19
+ /** API が返した失敗。生のスタックトレースを人に見せないための、最小の分類。 */
20
+ export type Failure = { readonly kind: "auth" | "quota" | "api"; readonly status: number | undefined; readonly message: string };
21
+
22
+ const statusOf = (value: unknown): number | undefined => (typeof value === "number" ? value : undefined);
23
+
24
+ const messageOf = (value: unknown): string => (typeof value === "string" && value.length > 0 ? value : "原因は返ってきませんでした");
25
+
26
+ const kindOf = (status: number | undefined): Failure["kind"] => {
27
+ if (status === 401 || status === 403) return "auth";
28
+ return status === 429 ? "quota" : "api";
29
+ };
30
+
31
+ /**
32
+ * SDK の APIError から Failure を作る。分類は provider で変えない。
33
+ * status と message は SDK 上 any なので、ここで 1 度だけ形を確かめる。
34
+ */
35
+ export const toFailure = (error: { readonly status: unknown; readonly message: unknown }): Failure => {
36
+ const status = statusOf(error.status);
37
+ return { kind: kindOf(status), status, message: messageOf(error.message) };
38
+ };