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
@@ -1,6 +1,7 @@
1
1
  import { severityOf, type UserCheck } from "./checks.ts";
2
2
  import { ask, type JudgeOptions } from "./judge.ts";
3
3
  import { FILTERS, type Candidate } from "./semantic.ts";
4
+ import { narrow, type Narrowing } from "./look-at.ts";
4
5
  import { localized } from "./render/text.ts";
5
6
  import type { Finding, Level, ProseDocument, RuleDefinition, Severity } from "./plugin.ts";
6
7
  import { lineStarts, placeOf } from "./position.ts";
@@ -10,15 +11,18 @@ export type SemanticResult = {
10
11
  readonly skipped: readonly { rule: string; why: string }[];
11
12
  readonly asked: number;
12
13
  readonly sentencesSeen: number;
14
+ /** 利用者の検査が、どこまで絞り込めたか。絞り込めていないものを黙って通さない。 */
15
+ readonly narrowed: readonly { readonly name: string; readonly narrowing: Narrowing }[];
13
16
  };
14
17
 
15
- type Job = {
18
+ export type Job = {
16
19
  readonly rule: string;
17
20
  readonly name: string;
18
21
  readonly rubric: string;
19
22
  readonly howToFix: string;
20
23
  readonly severity: Severity;
21
24
  readonly candidates: readonly Candidate[];
25
+ readonly narrowing?: Narrowing;
22
26
  };
23
27
 
24
28
  const filterFor = (name: string | undefined): (typeof FILTERS)[string] => FILTERS[name ?? "whole-document"] ?? FILTERS["whole-document"] ?? (() => []);
@@ -52,15 +56,19 @@ const builtInJobs = (doc: ProseDocument, rules: readonly RuleDefinition[], setti
52
56
  const userJobs = (doc: ProseDocument, checks: readonly UserCheck[], genre: string): Job[] =>
53
57
  checks
54
58
  .filter((check) => check.level !== "off" && check.use_for.some((target) => genre.startsWith(target)))
55
- .map((check) => ({
56
- rule: check.id,
57
- name: check.name,
58
- // look_at は自然文なので、まだ絞り込みに変換できない。文書全体を渡す。spec §26-7。
59
- rubric: check.look_at === undefined ? check.check : `${check.check}\n\n見るところ: ${check.look_at}`,
60
- howToFix: check.how_to_fix,
61
- severity: severityOf(check.level),
62
- candidates: filterFor("whole-document")(doc),
63
- }));
59
+ .map((check) => {
60
+ // look_at の括弧の中を取り出して絞り込む。取り出せなければ文書全体。spec §14。
61
+ const { candidates, narrowing } = narrow(doc, check.look_at, filterFor("whole-document"));
62
+ return {
63
+ rule: check.id,
64
+ name: check.name,
65
+ rubric: check.look_at === undefined ? check.check : `${check.check}\n\n見るところ: ${check.look_at}`,
66
+ howToFix: check.how_to_fix,
67
+ severity: severityOf(check.level),
68
+ candidates,
69
+ narrowing,
70
+ };
71
+ });
64
72
 
65
73
  const findingOf = (job: Job, candidate: Candidate, reason: string, confidence: number, threshold: number, starts: readonly number[]): Finding => {
66
74
  const at = placeOf(starts, candidate.offset);
@@ -78,6 +86,18 @@ const findingOf = (job: Job, candidate: Candidate, reason: string, confidence: n
78
86
 
79
87
  export type SemanticOptions = JudgeOptions & { readonly confidenceThreshold: number };
80
88
 
89
+ /**
90
+ * 何を問い合わせるかを決めるところまで。API を呼ばない。
91
+ * 鍵が無くても「何が送られるのか」を確かめられるようにするため、run から切り出してある。
92
+ */
93
+ export const planSemantic = (
94
+ doc: ProseDocument,
95
+ rules: readonly RuleDefinition[],
96
+ checks: readonly UserCheck[],
97
+ settings: Readonly<Record<string, Level>>,
98
+ genre: string,
99
+ ): Job[] => [...builtInJobs(doc, rules, settings, genre), ...userJobs(doc, checks, genre)];
100
+
81
101
  export const runSemantic = async (
82
102
  doc: ProseDocument,
83
103
  rules: readonly RuleDefinition[],
@@ -86,7 +106,7 @@ export const runSemantic = async (
86
106
  genre: string,
87
107
  options: SemanticOptions,
88
108
  ): Promise<SemanticResult> => {
89
- const jobs = [...builtInJobs(doc, rules, settings, genre), ...userJobs(doc, checks, genre)];
109
+ const jobs = planSemantic(doc, rules, checks, settings, genre);
90
110
  const starts = lineStarts(doc.source);
91
111
  const skipped = jobs.filter((job) => job.candidates.length === 0).map((job) => ({ rule: job.rule, why: "見るところが無かったため" }));
92
112
  const live = jobs.filter((job) => job.candidates.length > 0);
@@ -103,5 +123,6 @@ export const runSemantic = async (
103
123
  skipped,
104
124
  asked: live.reduce((sum, job) => sum + job.candidates.length, 0),
105
125
  sentencesSeen: doc.sentences.length,
126
+ narrowed: jobs.flatMap((job) => (job.narrowing === undefined ? [] : [{ name: job.name, narrowing: job.narrowing }])),
106
127
  };
107
128
  };
package/src/run.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DETECTORS } from "./detectors/index.ts";
2
2
  import { resolve } from "./levels.ts";
3
- import type { Finding, Level, ProseDocument, RuleDefinition } from "./plugin.ts";
3
+ import type { AdapterNeeds, Finding, Level, ProseDocument, RuleDefinition } from "./plugin.ts";
4
4
  import { lineStarts, placeOf } from "./position.ts";
5
5
 
6
6
  export type Skipped = { readonly rule: string; readonly why: string };
@@ -22,6 +22,37 @@ const levelFor = (rule: RuleDefinition, settings: Settings, experimental: boolea
22
22
  return "normal";
23
23
  };
24
24
 
25
+ const CAPABILITY_NAME: Readonly<Record<string, string>> = { pos: "品詞解析", lemma: "原形" };
26
+
27
+ /** 知らない要求は満たされていないものとして扱う。黙って無視すると、要求なしで動いてしまう。 */
28
+ const has = (capabilities: ProseDocument["capabilities"], need: string): boolean => {
29
+ if (need === "pos") return capabilities.pos;
30
+ if (need === "lemma") return capabilities.lemma;
31
+ return false;
32
+ };
33
+
34
+ /** 要求を満たさない rule は動かせない。満たさないまま動かすと「指摘 0 件」が保証に見える。 */
35
+ const unmet = (rule: RuleDefinition, doc: ProseDocument): string | undefined => {
36
+ if (rule.languages !== undefined && !rule.languages.includes(doc.language)) return `${doc.language} 向けの rule ではないため`;
37
+ const missing = rule.requires.find((need) => !has(doc.capabilities, need));
38
+ if (missing === undefined) return undefined;
39
+ return `この言語では${CAPABILITY_NAME[missing] ?? missing}が使えないため`;
40
+ };
41
+
42
+ const forGenre = (rules: readonly RuleDefinition[], genre: string): RuleDefinition[] =>
43
+ rules.filter((rule) => rule.use_for.some((target) => genre.startsWith(target)));
44
+
45
+ /**
46
+ * 解析器の初期化に払う代金を決める。動く rule が 1 本も要求しないなら読み込まない。
47
+ * capabilities は「払えばできる」の宣言なので、ここでは見ない。
48
+ */
49
+ export const neededBy = (rules: readonly RuleDefinition[], settings: Settings, experimental: boolean, genre: string, language: string): AdapterNeeds => ({
50
+ pos: forGenre(rules, genre)
51
+ .filter((rule) => rule.layer !== "L4" && levelFor(rule, settings, experimental) !== "off")
52
+ .filter((rule) => rule.languages === undefined || rule.languages.includes(language))
53
+ .some((rule) => rule.requires.includes("pos") || rule.requires.includes("lemma")),
54
+ });
55
+
25
56
  const place = (starts: readonly number[], finding: Finding): Finding => {
26
57
  const offset = finding.values["offset"];
27
58
  const at = placeOf(starts, typeof offset === "number" ? offset : 0);
@@ -30,7 +61,7 @@ const place = (starts: readonly number[], finding: Finding): Finding => {
30
61
 
31
62
  export const runRules = (doc: ProseDocument, rules: readonly RuleDefinition[], settings: Settings, experimental: boolean, genre: string): RunResult => {
32
63
  const starts = lineStarts(doc.source);
33
- const applicable = rules.filter((rule) => rule.use_for.some((target) => genre.startsWith(target)));
64
+ const applicable = forGenre(rules, genre);
34
65
  const forced = applicable
35
66
  .filter((rule) => rule.status === "experimental" && settings[rule.id] !== undefined && settings[rule.id] !== "off")
36
67
  .map((rule) => rule.id);
@@ -40,6 +71,8 @@ export const runRules = (doc: ProseDocument, rules: readonly RuleDefinition[], s
40
71
  if (rule.layer === "L4") {
41
72
  return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: "意味を読む検査のため(npx chaff test で動きます)" }] };
42
73
  }
74
+ const blocked = unmet(rule, doc);
75
+ if (blocked !== undefined) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: blocked }] };
43
76
  const level = levelFor(rule, settings, experimental);
44
77
  if (level === "off") {
45
78
  const why = rule.status === "experimental" && settings[rule.id] === undefined ? "まだ試験中のため" : "設定で止めているため";
@@ -48,7 +81,7 @@ export const runRules = (doc: ProseDocument, rules: readonly RuleDefinition[], s
48
81
  const detector = DETECTORS[rule.how_to_find];
49
82
  if (detector === undefined) return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: `検出器 ${rule.how_to_find} がないため` }] };
50
83
  const options = {
51
- limit: resolve(rule, level).limit,
84
+ limit: resolve(rule, level, genre).limit,
52
85
  lexicon: rule.word_list === undefined ? undefined : doc.lexicons[rule.word_list],
53
86
  where: rule.where,
54
87
  };
@@ -0,0 +1,22 @@
1
+ import type { Sentence, Token } from "./plugin.ts";
2
+
3
+ /**
4
+ * 「これは文か」の判定を 1 箇所に置く。
5
+ *
6
+ * 段落として解析されるものには、見出しの下の名前だけの行(`OpenAI`)、引用の出典行、
7
+ * 表題行が混ざる。文として数えると、文書全体を分母にする rule がそこで狂う。
8
+ * 終止符で終わることを条件にすると、そのほとんどが落ちる。
9
+ */
10
+ const ENDS = /[。..!?!?]["))」』\s]*$/u;
11
+
12
+ export const isClosed = (sentence: Sentence): boolean => ENDS.test(sentence.text);
13
+
14
+ const PREDICATE = new Set(["AUX", "VERB", "ADJ"]);
15
+
16
+ export const hasPredicate = (sentence: Sentence): boolean => (sentence.tokens ?? []).some((token) => PREDICATE.has(token.pos));
17
+
18
+ export const hasParticle = (sentence: Sentence): boolean => (sentence.tokens ?? []).some((token) => token.pos === "ADP" || token.pos === "SCONJ");
19
+
20
+ const SKIP = new Set(["PUNCT", "PART", "SYM"]);
21
+
22
+ export const lastContent = (sentence: Sentence): Token | undefined => [...(sentence.tokens ?? [])].reverse().find((token) => !SKIP.has(token.pos));