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,116 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { loadAdapter } from "../adapter-load.js";
4
+ import { applyByPath } from "../config/by-path.js";
5
+ import { buildDocument } from "../document.js";
6
+ import { guessLanguage } from "../detect.js";
7
+ import { collectTargets } from "../files.js";
8
+ import { loadRules } from "../rule-load.js";
9
+ import { CHECKS_FILE, loadChecks } from "../checks.js";
10
+ import { CACHE_DIR, credentialHint, describeFailure, hasCredentials } from "../judge.js";
11
+ import { ENV_FILE, loadEnvFile } from "../env.js";
12
+ import { planSemantic, runSemantic } from "../run-semantic.js";
13
+ import { MACHINE_BANNER, renderPlan, renderSemantic } from "../render/semantic.js";
14
+ const judgeAll = async (paths, config, checks, options, resolveGenre) => Promise.all(paths.map(async (path) => {
15
+ const source = await readFile(path, "utf8");
16
+ const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
17
+ const adapter = await loadAdapter(language);
18
+ const doc = buildDocument(path, source, adapter);
19
+ const { genre } = resolveGenre(path, source, config);
20
+ const rules = loadRules(language);
21
+ return { path, outcome: await runSemantic(doc, rules, checks, config.rules, genre, options), rules, language };
22
+ }));
23
+ /** API を呼ばずに、何が送られるかだけを出す。鍵が無くても二段構えの 1 段目を確かめられる。 */
24
+ const dryRun = async (paths, config, checks, resolveGenre, envFile) => {
25
+ const plans = await Promise.all(paths.map(async (path) => {
26
+ const source = await readFile(path, "utf8");
27
+ const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
28
+ const adapter = await loadAdapter(language);
29
+ const doc = buildDocument(path, source, adapter);
30
+ const { genre } = resolveGenre(path, source, config);
31
+ return { path, jobs: planSemantic(doc, loadRules(language), checks, config.rules, genre), sentences: doc.sentences.length };
32
+ }));
33
+ plans.forEach(({ path, jobs, sentences }) => console.log(renderPlan(path, jobs, sentences).join("\n")));
34
+ const total = plans.reduce((sum, plan) => sum + plan.jobs.reduce((count, job) => count + job.candidates.length, 0), 0);
35
+ const from = envFile === undefined ? "" : ` ${envFile} を読みました。`;
36
+ const where = `${config.aiBackend} / ${config.aiModel}(認証${hasCredentials(config.aiBackend) ? "あり" : "なし"})`;
37
+ console.log([
38
+ "",
39
+ "─".repeat(60),
40
+ "",
41
+ ` 合計 ${total} 箇所を送ります。--dry-run なので API は呼んでいません。`,
42
+ ` 送り先: ${where}`,
43
+ ...(from === "" ? [] : [from]),
44
+ "",
45
+ ].join("\n"));
46
+ };
47
+ /**
48
+ * 機械と AI を見出しで分けて出す。読む人が「これは揺れる判定か」を知らないと、
49
+ * AI の誤検知に振り回される。workflow spec §7、samples/README の軸 2。
50
+ */
51
+ export const runTest = async (targets, argv, context) => {
52
+ const paths = collectTargets(targets.length > 0 ? targets : ["."]);
53
+ if (paths.length === 0) {
54
+ console.error(`Markdown が 1 つも見つかりませんでした: ${targets.join(", ")}`);
55
+ return 1;
56
+ }
57
+ const { config, resolveGenre, inspect } = context;
58
+ // 鍵の置き場所で「設定したのに効かない」を起こさせない。シェルの環境変数のほうが勝つ。
59
+ const envFile = loadEnvFile(process.cwd());
60
+ const checks = loadChecks(join(process.cwd(), CHECKS_FILE));
61
+ const results = await Promise.all(paths.map((path) => inspect(path, config, argv)));
62
+ results.forEach((result) => {
63
+ console.log(result.text.replace(/\n/u, `\n${MACHINE_BANNER.join("\n")}`));
64
+ });
65
+ const machineOnly = results.some((result) => result.outcome.findings.some((finding) => finding.severity === "error")) ? 1 : 0;
66
+ const where = envFile === undefined ? `${ENV_FILE} に書いても読みます(いまは ${ENV_FILE} がありません)` : `${envFile} は読みました`;
67
+ const notRun = (why, what) => ["", ` 意味を読む検査は動かしていません。${why}`, ` ${what}`, ` ${where}。`, " 機械による判定はすべて動いています。", ""].join("\n");
68
+ const noCredentials = notRun(`${config.aiBackend} の認証情報がありません。`, credentialHint(config.aiBackend));
69
+ /**
70
+ * API が返した失敗を人の言葉にする。生のスタックトレースを人に見せない。
71
+ * 鍵はあるのに弾かれたときに「ありません」と言うと、設定済みの鍵をもう一度設定しに行かせる。
72
+ * 残高切れも同じで、「鍵を確かめてください」と言われても鍵は正しい。
73
+ */
74
+ const failed = (failure) => {
75
+ if (failure.kind === "auth")
76
+ return notRun(`${config.aiBackend} が鍵を受け付けませんでした(${failure.status ?? "?"})。`, "鍵が正しいか、その provider のものかを確かめてください。");
77
+ if (failure.kind === "quota")
78
+ return notRun(`${config.aiBackend} の残高か上限に達しました(${failure.status ?? "?"})。`, failure.message);
79
+ return notRun(`${config.aiBackend} が ${failure.status ?? "?"} を返しました。`, failure.message);
80
+ };
81
+ if (argv.includes("--dry-run")) {
82
+ await dryRun(paths, config, checks, resolveGenre, envFile);
83
+ return machineOnly;
84
+ }
85
+ // 呼んでから落ちるのを待たない。認証が無いときの SDK の例外は型で判別できない。
86
+ if (!hasCredentials(config.aiBackend)) {
87
+ console.log(noCredentials);
88
+ return machineOnly;
89
+ }
90
+ const options = {
91
+ model: config.aiModel,
92
+ backend: config.aiBackend,
93
+ cacheDir: join(process.cwd(), CACHE_DIR),
94
+ confidenceThreshold: config.confidenceThreshold,
95
+ };
96
+ const semantic = await judgeAll(paths, config, checks, options, resolveGenre).catch((error) => {
97
+ // API が返した失敗だけを受け止める。ネットワーク断やコードの誤りは握りつぶさない。
98
+ const failure = describeFailure(config.aiBackend, error);
99
+ if (failure === undefined)
100
+ throw error;
101
+ console.log(failed(failure));
102
+ return undefined;
103
+ });
104
+ if (semantic === undefined)
105
+ return machineOnly;
106
+ semantic.forEach((entry) => {
107
+ if (entry.outcome.findings.length === 0 && entry.outcome.asked === 0)
108
+ return;
109
+ console.log([`${entry.path}`, ...renderSemantic(entry.outcome, entry.rules, checks, entry.language)].join("\n"));
110
+ });
111
+ const machine = results.flatMap((result) => result.outcome.findings);
112
+ const ai = semantic.flatMap((entry) => entry.outcome.findings);
113
+ console.log(["", "─".repeat(60), "", ` 機械 ${machine.length} 件 / AI ${ai.length} 件`, " 文章は書き換えていません。直すのは書いた人です。", ""].join("\n"));
114
+ return [...machine, ...ai].some((finding) => finding.severity === "error") ? 1 : 0;
115
+ };
116
+ //# sourceMappingURL=test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test.js","sourceRoot":"","sources":["../../src/commands/test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export type PathRule = {
2
+ readonly files: readonly string[];
3
+ readonly genre: string | undefined;
4
+ readonly language: string | undefined;
5
+ };
6
+ export declare const matches: (glob: string, base: string, path: string) => boolean;
7
+ /**
8
+ * 後に書いたものが勝つ。上から順に当て、最後に当たったものを使う。
9
+ * 「全体はこう、ここだけは違う」と書けるようにするため。
10
+ */
11
+ export declare const applyByPath: (rules: readonly PathRule[], base: string, path: string) => {
12
+ genre?: string;
13
+ language?: string;
14
+ };
15
+ //# sourceMappingURL=by-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"by-path.d.ts","sourceRoot":"","sources":["../../src/config/by-path.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AA8BxI,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,KAAG,OAAsD,CAAC;AAE1H;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,SAAS,QAAQ,EAAE,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,KAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAQhH,CAAC"}
@@ -0,0 +1,41 @@
1
+ import { relative, sep } from "node:path";
2
+ /**
3
+ * glob を正規表現にする。使うのは 3 つだけ。
4
+ * **\/ 区切りをまたいでディレクトリ 0 個以上(docs/**\/*.md は docs/a.md にも当たる)
5
+ * * 区切りをまたがずに何文字でも
6
+ * ? 区切り以外の 1 文字
7
+ * これ以上を足すと、設定を書く人が覚えることが増える。
8
+ *
9
+ * 「ディレクトリ 0 個」を落とすと docs/**\/*.md が docs/a.md に当たらない。
10
+ * glob の典型的な落とし穴で、設定を書いた人は当たらない理由に気づけない。
11
+ */
12
+ const DIRS = "\u0000";
13
+ const ANY = "\u0001";
14
+ const toPattern = (glob) => {
15
+ const body = glob
16
+ .replace(/[.+^${}()|[\]\\]/gu, "\\$&")
17
+ .replace(/\*\*\//gu, DIRS)
18
+ .replace(/\*\*/gu, ANY)
19
+ .replace(/\*/gu, "[^/]*")
20
+ .replace(/\?/gu, "[^/]")
21
+ .replaceAll(DIRS, "(?:[^/]*/)*")
22
+ .replaceAll(ANY, ".*");
23
+ return new RegExp(`^${body}$`, "u");
24
+ };
25
+ /** 設定ファイルからの相対で照合する。実行した場所に左右されないため。 */
26
+ const normalize = (base, path) => relative(base, path).split(sep).join("/");
27
+ export const matches = (glob, base, path) => toPattern(glob).test(normalize(base, path));
28
+ /**
29
+ * 後に書いたものが勝つ。上から順に当て、最後に当たったものを使う。
30
+ * 「全体はこう、ここだけは違う」と書けるようにするため。
31
+ */
32
+ export const applyByPath = (rules, base, path) => rules.reduce((acc, rule) => {
33
+ if (!rule.files.some((glob) => matches(glob, base, path)))
34
+ return acc;
35
+ return {
36
+ ...acc,
37
+ ...(rule.genre === undefined ? {} : { genre: rule.genre }),
38
+ ...(rule.language === undefined ? {} : { language: rule.language }),
39
+ };
40
+ }, {});
41
+ //# sourceMappingURL=by-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"by-path.js","sourceRoot":"","sources":["../../src/config/by-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAI1C;;;;;;;;;GASG;AACH,MAAM,IAAI,GAAG,QAAQ,CAAC;AACtB,MAAM,GAAG,GAAG,QAAQ,CAAC;AAErB,MAAM,SAAS,GAAG,CAAC,IAAY,EAAU,EAAE;IACzC,MAAM,IAAI,GAAG,IAAI;SACd,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC;SACrC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC;SACzB,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC;SACtB,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC;SACxB,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,IAAI,EAAE,aAAa,CAAC;SAC/B,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACzB,OAAO,IAAI,MAAM,CAAC,IAAI,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF,wCAAwC;AACxC,MAAM,SAAS,GAAG,CAAC,IAAY,EAAE,IAAY,EAAU,EAAE,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEpG,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,IAAY,EAAE,IAAY,EAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;AAE1H;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAA0B,EAAE,IAAY,EAAE,IAAY,EAAyC,EAAE,CAC3H,KAAK,CAAC,MAAM,CAAwC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;IAChE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC;IACtE,OAAO;QACL,GAAG,GAAG;QACN,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QAC1D,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;KACpE,CAAC;AACJ,CAAC,EAAE,EAAE,CAAC,CAAC"}
@@ -1,16 +1,23 @@
1
+ import { type BackendName } from "../backends/types.ts";
1
2
  import type { Level } from "../plugin.ts";
3
+ import type { PathRule } from "./by-path.ts";
2
4
  export declare const CONFIG_FILE = "chaff.yaml";
3
5
  export type Config = {
4
6
  readonly genre: string | undefined;
7
+ readonly aiBackend: BackendName;
5
8
  readonly language: string | undefined;
6
9
  readonly rules: Readonly<Record<string, Level>>;
7
10
  readonly experimental: boolean;
8
11
  readonly path: string | undefined;
9
12
  readonly aiModel: string;
10
13
  readonly confidenceThreshold: number;
14
+ /** パスごとの上書き。設定ファイルのある場所からの相対で照合する。 */
15
+ readonly byPath: readonly PathRule[];
16
+ readonly baseDir: string;
11
17
  };
12
18
  /** 判定の質が誤検知に直結するので、既定は最上位のモデル。cost は絞り込みで削る。spec §14。 */
13
- export declare const DEFAULT_MODEL = "claude-opus-5";
19
+ /** 既定は Anthropic。openai にすると判定役だけが替わり、rule も判定の形も変わらない。 */
20
+ export declare const DEFAULT_BACKEND: BackendName;
14
21
  export declare const EMPTY: Config;
15
22
  /** 設定ファイルが無くても動く。あっても、既定から変えたものだけが書かれている。spec §18。 */
16
23
  export declare const loadConfig: (path: string) => Config;
@@ -1 +1 @@
1
- {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAE1C,eAAO,MAAM,WAAW,eAAe,CAAC;AAExC,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACtC,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAE7C,eAAO,MAAM,KAAK,EAAE,MAQnB,CAAC;AAWF,sDAAsD;AACtD,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,MAYzC,CAAC"}
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"AAKA,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,eAAO,MAAM,WAAW,eAAe,CAAC;AAExC,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,sCAAsC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,yDAAyD;AACzD,2DAA2D;AAC3D,eAAO,MAAM,eAAe,EAAE,WAAyB,CAAC;AAExD,eAAO,MAAM,KAAK,EAAE,MAWnB,CAAC;AA2BF,sDAAsD;AACtD,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,MAiBzC,CAAC"}
@@ -1,17 +1,24 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
2
3
  import { parse } from "yaml";
3
4
  import { isLevel } from "../levels.js";
5
+ import { defaultModel } from "../judge.js";
6
+ import { isBackend } from "../backends/types.js";
4
7
  export const CONFIG_FILE = "chaff.yaml";
5
8
  /** 判定の質が誤検知に直結するので、既定は最上位のモデル。cost は絞り込みで削る。spec §14。 */
6
- export const DEFAULT_MODEL = "claude-opus-5";
9
+ /** 既定は Anthropic。openai にすると判定役だけが替わり、rule も判定の形も変わらない。 */
10
+ export const DEFAULT_BACKEND = "anthropic";
7
11
  export const EMPTY = {
8
12
  genre: undefined,
9
13
  language: undefined,
10
14
  rules: {},
11
15
  experimental: false,
12
16
  path: undefined,
13
- aiModel: DEFAULT_MODEL,
17
+ aiBackend: DEFAULT_BACKEND,
18
+ aiModel: defaultModel(DEFAULT_BACKEND),
14
19
  confidenceThreshold: 0.7,
20
+ byPath: [],
21
+ baseDir: process.cwd(),
15
22
  };
16
23
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
17
24
  const rulesOf = (raw) => {
@@ -20,19 +27,40 @@ const rulesOf = (raw) => {
20
27
  return Object.entries(raw).reduce((acc, [id, value]) => (isLevel(value) ? { ...acc, [id]: value } : acc), {});
21
28
  };
22
29
  const str = (value) => (typeof value === "string" ? value : undefined);
30
+ /** files は 1 つの文字列でも配列でも書ける。 */
31
+ const globsOf = (value) => {
32
+ if (typeof value === "string")
33
+ return [value];
34
+ return Array.isArray(value) ? value.map((entry) => String(entry)) : [];
35
+ };
36
+ const toPathRule = (raw) => {
37
+ if (!isRecord(raw))
38
+ return undefined;
39
+ const files = raw["files"];
40
+ const globs = globsOf(files);
41
+ if (globs.length === 0)
42
+ return undefined;
43
+ return { files: globs, genre: str(raw["genre"]), language: str(raw["language"]) };
44
+ };
45
+ const byPathOf = (raw) => (Array.isArray(raw) ? raw.map(toPathRule).filter((rule) => rule !== undefined) : []);
23
46
  /** 設定ファイルが無くても動く。あっても、既定から変えたものだけが書かれている。spec §18。 */
24
47
  export const loadConfig = (path) => {
25
48
  const raw = parse(readFileSync(path, "utf8"));
26
49
  if (!isRecord(raw))
27
50
  return { ...EMPTY, path };
51
+ const declared = raw["ai_backend"];
52
+ const backend = isBackend(declared) ? declared : DEFAULT_BACKEND;
28
53
  return {
29
54
  genre: str(raw["genre"]),
30
55
  language: str(raw["language"]),
31
56
  rules: rulesOf(raw["rules"]),
32
57
  experimental: raw["experimental"] === true,
33
58
  path,
34
- aiModel: str(raw["ai_model"]) ?? DEFAULT_MODEL,
59
+ aiBackend: backend,
60
+ aiModel: str(raw["ai_model"]) ?? defaultModel(backend),
35
61
  confidenceThreshold: typeof raw["confidence_threshold"] === "number" ? raw["confidence_threshold"] : 0.7,
62
+ byPath: byPathOf(raw["by_path"]),
63
+ baseDir: dirname(path),
36
64
  };
37
65
  };
38
66
  //# sourceMappingURL=load.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,MAAM,CAAC,MAAM,WAAW,GAAG,YAAY,CAAC;AAYxC,yDAAyD;AACzD,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,MAAM,CAAC,MAAM,KAAK,GAAW;IAC3B,KAAK,EAAE,SAAS;IAChB,QAAQ,EAAE,SAAS;IACnB,KAAK,EAAE,EAAE;IACT,YAAY,EAAE,KAAK;IACnB,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,aAAa;IACtB,mBAAmB,EAAE,GAAG;CACzB,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAE5I,MAAM,OAAO,GAAG,CAAC,GAAY,EAAyB,EAAE;IACtD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9B,OAAO,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAwB,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;AACvI,CAAC,CAAC;AAEF,MAAM,GAAG,GAAG,CAAC,KAAc,EAAsB,EAAE,CAAC,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAEpG,sDAAsD;AACtD,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE;IACjD,MAAM,GAAG,GAAY,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC;IAC9C,OAAO;QACL,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACxB,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QAC9B,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5B,YAAY,EAAE,GAAG,CAAC,cAAc,CAAC,KAAK,IAAI;QAC1C,IAAI;QACJ,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,aAAa;QAC9C,mBAAmB,EAAE,OAAO,GAAG,CAAC,sBAAsB,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC,CAAC,GAAG;KACzG,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"load.js","sourceRoot":"","sources":["../../src/config/load.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Detector } from "../plugin.ts";
2
+ export declare const agentlessPassive: Detector;
3
+ //# sourceMappingURL=agentless-passive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentless-passive.d.ts","sourceRoot":"","sources":["../../src/detectors/agentless-passive.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAA4B,MAAM,cAAc,CAAC;AAevE,eAAO,MAAM,gBAAgB,EAAE,QAkB9B,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 受動のまま動作主を書かないと、誰がしたのかが文から消える。
3
+ * 「決定されました」は、決めた人を名指さずに決定を伝える。
4
+ *
5
+ * 受動かどうかはアダプタが Voice=Pass で印を付ける。日本語の「れる/られる」と
6
+ * 英語の be + 過去分詞は形が全く違うので、その判断は言語の側に置く。spec §12.1。
7
+ * 「名詞を修飾しているだけか」の判断も、語順が言語で逆になるため同じくアダプタ側。
8
+ * 動作主の語(によって / by)も言語ごとの語彙表から引く。detector は言語を知らない。
9
+ */
10
+ const isPassive = (token) => token.features?.["Voice"] === "Pass";
11
+ const hasAgent = (sentence, markers) => markers.some((marker) => sentence.text.includes(marker));
12
+ export const agentlessPassive = (doc, options) => {
13
+ const markers = (options.lexicon ?? []).map((entry) => entry.pattern);
14
+ return doc.sentences
15
+ .map((sentence) => ({ sentence, passives: (sentence.tokens ?? []).filter(isPassive) }))
16
+ .filter(({ sentence, passives }) => passives.length >= options.limit && !hasAgent(sentence, markers))
17
+ .map(({ sentence, passives }) => ({
18
+ rule: "agentless-passive",
19
+ severity: "warning",
20
+ line: 0,
21
+ column: 0,
22
+ quote: sentence.text.trim(),
23
+ values: {
24
+ count: passives.length,
25
+ limit: options.limit,
26
+ word: passives.map((token) => token.surface).join(" "),
27
+ offset: passives[0]?.span.start ?? sentence.span.start,
28
+ },
29
+ }));
30
+ };
31
+ //# sourceMappingURL=agentless-passive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentless-passive.js","sourceRoot":"","sources":["../../src/detectors/agentless-passive.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,SAAS,GAAG,CAAC,KAAY,EAAW,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,MAAM,CAAC;AAElF,MAAM,QAAQ,GAAG,CAAC,QAAkB,EAAE,OAA0B,EAAW,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AAEvI,MAAM,CAAC,MAAM,gBAAgB,GAAa,CAAC,GAAG,EAAE,OAAO,EAAa,EAAE;IACpE,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtE,OAAO,GAAG,CAAC,SAAS;SACjB,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;SACtF,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;SACpG,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;QAChC,IAAI,EAAE,mBAAmB;QACzB,QAAQ,EAAE,SAAS;QACnB,IAAI,EAAE,CAAC;QACP,MAAM,EAAE,CAAC;QACT,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE;QAC3B,MAAM,EAAE;YACN,KAAK,EAAE,QAAQ,CAAC,MAAM;YACtB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YACtD,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK;SACvD;KACF,CAAC,CAAC,CAAC;AACR,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { Detector } from "../plugin.ts";
2
+ /**
3
+ * 漢字が続くと、どこで語が切れるのか読み手が探すことになる。
4
+ * 「情報処理推進機構認定試験」は 12 字。ひらがなを 1 つ挟むだけで読める。
5
+ */
6
+ export declare const kanjiRun: Detector;
7
+ /**
8
+ * 中黒で並べると、どこまでが 1 つの項目か分からなくなる。
9
+ * 「企画・開発・運用・保守体制」は、保守体制が 1 つなのか保守と体制なのか読めない。
10
+ */
11
+ export declare const middleDot: Detector;
12
+ //# sourceMappingURL=char-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"char-shape.d.ts","sourceRoot":"","sources":["../../src/detectors/char-shape.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAqB,MAAM,cAAc,CAAC;AAYhE;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,QAWhB,CAAC;AAIR;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,QAWjB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { proseText } from "../measure.js";
2
+ /**
3
+ * 文字の並びだけを見る検出。品詞も語彙表も要らない。
4
+ * 覆った箇所は空白になっているので、proseText で潰してから数える。
5
+ */
6
+ const KANJI_RUN = /[一-鿿]+/gu;
7
+ const longestKanji = (sentence) => [...proseText(sentence).matchAll(KANJI_RUN)].reduce((longest, match) => (match[0].length > longest.length ? match[0] : longest), "");
8
+ /**
9
+ * 漢字が続くと、どこで語が切れるのか読み手が探すことになる。
10
+ * 「情報処理推進機構認定試験」は 12 字。ひらがなを 1 つ挟むだけで読める。
11
+ */
12
+ export const kanjiRun = (doc, options) => doc.sentences
13
+ .map((sentence) => ({ sentence, run: longestKanji(sentence) }))
14
+ .filter(({ run }) => run.length > options.limit)
15
+ .map(({ sentence, run }) => ({
16
+ rule: "max-kanji-continuous",
17
+ severity: "warning",
18
+ line: 0,
19
+ column: 0,
20
+ quote: sentence.text.trim(),
21
+ values: { word: run, count: run.length, limit: options.limit, offset: sentence.span.start },
22
+ }));
23
+ const MIDDLE_DOT = /・/gu;
24
+ /**
25
+ * 中黒で並べると、どこまでが 1 つの項目か分からなくなる。
26
+ * 「企画・開発・運用・保守体制」は、保守体制が 1 つなのか保守と体制なのか読めない。
27
+ */
28
+ export const middleDot = (doc, options) => doc.sentences
29
+ .map((sentence) => ({ sentence, count: [...proseText(sentence).matchAll(MIDDLE_DOT)].length }))
30
+ .filter(({ count }) => count > options.limit)
31
+ .map(({ sentence, count }) => ({
32
+ rule: "no-nakaguro-parallel",
33
+ severity: "info",
34
+ line: 0,
35
+ column: 0,
36
+ quote: sentence.text.trim(),
37
+ values: { count, limit: options.limit, offset: sentence.span.start },
38
+ }));
39
+ //# sourceMappingURL=char-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"char-shape.js","sourceRoot":"","sources":["../../src/detectors/char-shape.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE1C;;;GAGG;AACH,MAAM,SAAS,GAAG,UAAU,CAAC;AAE7B,MAAM,YAAY,GAAG,CAAC,QAAkB,EAAU,EAAE,CAClD,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;AAEvI;;;GAGG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAa,CAAC,GAAG,EAAE,OAAO,EAAa,EAAE,CAC5D,GAAG,CAAC,SAAS;KACV,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;KAC9D,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC;KAC/C,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,CAAC;IAC3B,IAAI,EAAE,sBAAsB;IAC5B,QAAQ,EAAE,SAAS;IACnB,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE;IAC3B,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE;CAC5F,CAAC,CAAC,CAAC;AAER,MAAM,UAAU,GAAG,KAAK,CAAC;AAEzB;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAa,CAAC,GAAG,EAAE,OAAO,EAAa,EAAE,CAC7D,GAAG,CAAC,SAAS;KACV,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;KAC9F,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;KAC5C,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7B,IAAI,EAAE,sBAAsB;IAC5B,QAAQ,EAAE,MAAM;IAChB,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE;IAC3B,MAAM,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE;CACrE,CAAC,CAAC,CAAC"}
@@ -1,4 +1,11 @@
1
1
  import type { Detector } from "../plugin.ts";
2
- /** 節あたりの強調の数。bold-density が使う。 */
2
+ /**
3
+ * 節あたりの「密度」を見る。件数ではない。
4
+ *
5
+ * 件数で数えると、節が長いほど当たる。実文書で測ると、1095 字に 2 箇所の節と
6
+ * 557 字に 9 箇所の節が同じ「多すぎる」になっていた。読者が受け取る印象は逆なのに。
7
+ *
8
+ * rule の名前(bold-density)が最初から密度と言っていた。件数で実装していたのが誤り。
9
+ */
3
10
  export declare const countPerSection: Detector;
4
11
  //# sourceMappingURL=count-per-section.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"count-per-section.d.ts","sourceRoot":"","sources":["../../src/detectors/count-per-section.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAW,MAAM,cAAc,CAAC;AAEtD,kCAAkC;AAClC,eAAO,MAAM,eAAe,EAAE,QAWvB,CAAC"}
1
+ {"version":3,"file":"count-per-section.d.ts","sourceRoot":"","sources":["../../src/detectors/count-per-section.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAoB,MAAM,cAAc,CAAC;AAS/D;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,EAAE,QAoBvB,CAAC"}
@@ -1,13 +1,34 @@
1
- /** 節あたりの強調の数。bold-density が使う。 */
1
+ import { charLength, proseText } from "../measure.js";
2
+ /** これより短い節では密度が暴れる。43 字に 1 箇所で「1000 字あたり 23」になる。 */
3
+ const MIN_CHARS = 200;
4
+ const PER = 1000;
5
+ const charsOf = (section) => section.sentences.reduce((sum, sentence) => sum + charLength(sentence), 0);
6
+ /**
7
+ * 節あたりの「密度」を見る。件数ではない。
8
+ *
9
+ * 件数で数えると、節が長いほど当たる。実文書で測ると、1095 字に 2 箇所の節と
10
+ * 557 字に 9 箇所の節が同じ「多すぎる」になっていた。読者が受け取る印象は逆なのに。
11
+ *
12
+ * rule の名前(bold-density)が最初から密度と言っていた。件数で実装していたのが誤り。
13
+ */
2
14
  export const countPerSection = (doc, options) => doc.sections
3
- .filter((section) => section.strongCount > options.limit)
4
- .map((section) => ({
15
+ .map((section) => ({ section, chars: charsOf(section) }))
16
+ .filter(({ chars }) => chars >= MIN_CHARS)
17
+ .map(({ section, chars }) => ({ section, chars, density: Math.round((section.strongCount / chars) * PER) }))
18
+ .filter(({ density }) => density > options.limit)
19
+ .map(({ section, chars, density }) => ({
5
20
  rule: "bold-density",
6
21
  severity: "warning",
7
22
  line: 0,
8
23
  column: 0,
9
24
  // 節全体を引くと段落をまたいで読めなくなる。最初の文だけ見せて場所を示す。
10
- quote: section.firstSentence?.text.trim() ?? "",
11
- values: { count: section.strongCount, limit: options.limit, offset: section.firstSentence?.span.start ?? section.span.start },
25
+ quote: section.firstSentence === undefined ? "" : proseText(section.firstSentence),
26
+ values: {
27
+ count: density,
28
+ limit: options.limit,
29
+ bolds: section.strongCount,
30
+ chars,
31
+ offset: section.firstSentence?.span.start ?? section.span.start,
32
+ },
12
33
  }));
13
34
  //# sourceMappingURL=count-per-section.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"count-per-section.js","sourceRoot":"","sources":["../../src/detectors/count-per-section.ts"],"names":[],"mappings":"AAEA,kCAAkC;AAClC,MAAM,CAAC,MAAM,eAAe,GAAa,CAAC,GAAG,EAAE,OAAO,EAAa,EAAE,CACnE,GAAG,CAAC,QAAQ;KACT,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC;KACxD,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,SAAS;IACnB,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,uCAAuC;IACvC,KAAK,EAAE,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;IAC/C,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE;CAC9H,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"count-per-section.js","sourceRoot":"","sources":["../../src/detectors/count-per-section.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAGtD,oDAAoD;AACpD,MAAM,SAAS,GAAG,GAAG,CAAC;AAEtB,MAAM,GAAG,GAAG,IAAI,CAAC;AAEjB,MAAM,OAAO,GAAG,CAAC,OAAgB,EAAU,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,QAAQ,EAAE,EAAE,CAAC,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;AAEzH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAa,CAAC,GAAG,EAAE,OAAO,EAAa,EAAE,CACnE,GAAG,CAAC,QAAQ;KACT,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;KACxD,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,IAAI,SAAS,CAAC;KACzC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,WAAW,GAAG,KAAK,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC;KAC3G,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC;KAChD,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IACrC,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,SAAS;IACnB,IAAI,EAAE,CAAC;IACP,MAAM,EAAE,CAAC;IACT,uCAAuC;IACvC,KAAK,EAAE,OAAO,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,aAAa,CAAC;IAClF,MAAM,EAAE;QACN,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,KAAK,EAAE,OAAO,CAAC,WAAW;QAC1B,KAAK;QACL,MAAM,EAAE,OAAO,CAAC,aAAa,EAAE,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK;KAChE;CACF,CAAC,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { Detector } from "../plugin.ts";
2
+ /**
3
+ * -ly 副詞は、動詞が弱いことの目印になる。「walked quickly」より「hurried」。
4
+ * 1 つずつは正しいので、件数ではなく密度で見る(bold-density と同じ)。
5
+ */
6
+ export declare const adverbDensity: Detector;
7
+ export declare const expletive: Detector;
8
+ /**
9
+ * 接続詞で始まる文が続く。1 つなら効くが、続くと文が前の文の付け足しに見えて、
10
+ * 何が主張なのかが分からなくなる。
11
+ */
12
+ export declare const conjunctionRun: Detector;
13
+ /**
14
+ * 見出しの大文字化が混ざっている。どちらの流儀が正しいかは決めない。spec §12.3。
15
+ * 見るのは文書の中で揃っているかだけで、少数派のほうを指摘する。
16
+ */
17
+ export declare const titleCaseMix: Detector;
18
+ export declare const oxfordComma: Detector;
19
+ //# sourceMappingURL=en-shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"en-shape.d.ts","sourceRoot":"","sources":["../../src/detectors/en-shape.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAoD,MAAM,cAAc,CAAC;AAW/F;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,QAgB3B,CAAC;AAoBF,eAAO,MAAM,SAAS,EAAE,QAcvB,CAAC;AAQF;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,QA2B5B,CAAC;AAqBF;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,QAgB1B,CAAC;AAoBF,eAAO,MAAM,WAAW,EAAE,QAmBzB,CAAC"}