chaffjs 0.2.0 → 0.4.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.
- package/dist/backends/anthropic.d.ts +24 -0
- package/dist/backends/anthropic.d.ts.map +1 -0
- package/dist/backends/anthropic.js +44 -0
- package/dist/backends/anthropic.js.map +1 -0
- package/dist/backends/openai.d.ts +29 -0
- package/dist/backends/openai.d.ts.map +1 -0
- package/dist/backends/openai.js +33 -0
- package/dist/backends/openai.js.map +1 -0
- package/dist/backends/types.d.ts +32 -0
- package/dist/backends/types.d.ts.map +1 -0
- package/dist/backends/types.js +18 -0
- package/dist/backends/types.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +9 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +5 -2
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +62 -16
- package/dist/commands/test.js.map +1 -1
- package/dist/config/load.d.ts +8 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +16 -3
- package/dist/config/load.js.map +1 -1
- package/dist/detectors/agentless-passive.d.ts +3 -0
- package/dist/detectors/agentless-passive.d.ts.map +1 -0
- package/dist/detectors/agentless-passive.js +31 -0
- package/dist/detectors/agentless-passive.js.map +1 -0
- package/dist/detectors/char-shape.d.ts +12 -0
- package/dist/detectors/char-shape.d.ts.map +1 -0
- package/dist/detectors/char-shape.js +39 -0
- package/dist/detectors/char-shape.js.map +1 -0
- package/dist/detectors/en-shape.d.ts +19 -0
- package/dist/detectors/en-shape.d.ts.map +1 -0
- package/dist/detectors/en-shape.js +176 -0
- package/dist/detectors/en-shape.js.map +1 -0
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +37 -0
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/lexicon.d.ts +15 -0
- package/dist/detectors/lexicon.d.ts.map +1 -0
- package/dist/detectors/lexicon.js +109 -0
- package/dist/detectors/lexicon.js.map +1 -0
- package/dist/detectors/repeated-head.d.ts.map +1 -1
- package/dist/detectors/repeated-head.js +17 -5
- package/dist/detectors/repeated-head.js.map +1 -1
- package/dist/detectors/sentence-ending.d.ts +3 -0
- package/dist/detectors/sentence-ending.d.ts.map +1 -0
- package/dist/detectors/sentence-ending.js +34 -0
- package/dist/detectors/sentence-ending.js.map +1 -0
- package/dist/detectors/signals.d.ts +6 -0
- package/dist/detectors/signals.d.ts.map +1 -0
- package/dist/detectors/signals.js +183 -0
- package/dist/detectors/signals.js.map +1 -0
- package/dist/detectors/structure.d.ts +24 -0
- package/dist/detectors/structure.d.ts.map +1 -0
- package/dist/detectors/structure.js +111 -0
- package/dist/detectors/structure.js.map +1 -0
- package/dist/detectors/team.d.ts +17 -0
- package/dist/detectors/team.d.ts.map +1 -0
- package/dist/detectors/team.js +87 -0
- package/dist/detectors/team.js.map +1 -0
- package/dist/detectors/token-shape.d.ts +4 -0
- package/dist/detectors/token-shape.d.ts.map +1 -0
- package/dist/detectors/token-shape.js +62 -0
- package/dist/detectors/token-shape.js.map +1 -0
- package/dist/document.d.ts +11 -1
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +85 -10
- package/dist/document.js.map +1 -1
- package/dist/env.d.ts +12 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +20 -0
- package/dist/env.js.map +1 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +9 -6
- package/dist/init.js.map +1 -1
- package/dist/judge.d.ts +14 -16
- package/dist/judge.d.ts.map +1 -1
- package/dist/judge.js +0 -0
- package/dist/judge.js.map +1 -1
- package/dist/levels.d.ts +1 -9
- package/dist/levels.d.ts.map +1 -1
- package/dist/levels.js +15 -3
- package/dist/levels.js.map +1 -1
- package/dist/look-at.d.ts +18 -0
- package/dist/look-at.d.ts.map +1 -0
- package/dist/look-at.js +43 -0
- package/dist/look-at.js.map +1 -0
- package/dist/plugin.d.ts +47 -2
- package/dist/plugin.d.ts.map +1 -1
- package/dist/render/explain.d.ts +1 -1
- package/dist/render/explain.d.ts.map +1 -1
- package/dist/render/explain.js +12 -4
- package/dist/render/explain.js.map +1 -1
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +13 -5
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/semantic.d.ts +2 -1
- package/dist/render/semantic.d.ts.map +1 -1
- package/dist/render/semantic.js +35 -0
- package/dist/render/semantic.js.map +1 -1
- package/dist/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +27 -1
- package/dist/rule-load.js.map +1 -1
- package/dist/run-semantic.d.ts +22 -1
- package/dist/run-semantic.d.ts.map +1 -1
- package/dist/run-semantic.js +21 -10
- package/dist/run-semantic.js.map +1 -1
- package/dist/run.d.ts +6 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +34 -2
- package/dist/run.js.map +1 -1
- package/dist/sentence-shape.d.ts +6 -0
- package/dist/sentence-shape.d.ts.map +1 -0
- package/dist/sentence-shape.js +15 -0
- package/dist/sentence-shape.js.map +1 -0
- package/package.json +4 -3
- package/rules/adverb-overuse.yaml +28 -0
- package/rules/agentless-passive.yaml +28 -0
- package/rules/ai-tell.yaml +27 -0
- package/rules/concrete-evidence-density.yaml +25 -0
- package/rules/cushion-phrase-density.yaml +26 -0
- package/rules/double-keigo.yaml +27 -0
- package/rules/emoji-density.yaml +26 -0
- package/rules/excessive-hedging.yaml +26 -0
- package/rules/expletive-construction.yaml +27 -0
- package/rules/hiragana-fukushi.yaml +27 -0
- package/rules/internal-jargon.yaml +27 -0
- package/rules/max-kanji-continuous.yaml +26 -0
- package/rules/max-paragraph-length.yaml +25 -0
- package/rules/max-sentence-length.yaml +22 -0
- package/rules/ngram-repetition.yaml +25 -0
- package/rules/no-doubled-joshi.yaml +28 -0
- package/rules/no-mixed-desumasu.yaml +29 -0
- package/rules/no-nakaguro-parallel.yaml +27 -0
- package/rules/oxford-comma-consistency.yaml +26 -0
- package/rules/paragraph-length-variance.yaml +25 -0
- package/rules/preamble-length.yaml +25 -0
- package/rules/proper-noun-density.yaml +26 -0
- package/rules/repeated-conjunction.yaml +26 -0
- package/rules/required-sections.yaml +26 -0
- package/rules/rule-of-three.yaml +25 -0
- package/rules/sasete-itadaku.yaml +27 -0
- package/rules/section-length-uniformity.yaml +25 -0
- package/rules/sentence-initial-conjunction-run.yaml +27 -0
- package/rules/taigen-dome-in-prose.yaml +27 -0
- package/rules/title-case-consistency.yaml +26 -0
- package/rules/undefined-acronym.yaml +25 -0
- package/rules/unqualified-superlative.yaml +26 -0
- package/src/backends/anthropic.ts +61 -0
- package/src/backends/openai.ts +46 -0
- package/src/backends/types.ts +38 -0
- package/src/cli.ts +9 -5
- package/src/commands/eval.ts +5 -2
- package/src/commands/test.ts +74 -16
- package/src/config/load.ts +22 -3
- package/src/detectors/agentless-passive.ts +34 -0
- package/src/detectors/char-shape.ts +47 -0
- package/src/detectors/en-shape.ts +187 -0
- package/src/detectors/index.ts +37 -0
- package/src/detectors/lexicon.ts +124 -0
- package/src/detectors/repeated-head.ts +17 -6
- package/src/detectors/sentence-ending.ts +37 -0
- package/src/detectors/signals.ts +199 -0
- package/src/detectors/structure.ts +119 -0
- package/src/detectors/team.ts +90 -0
- package/src/detectors/token-shape.ts +81 -0
- package/src/document.ts +95 -11
- package/src/env.ts +20 -0
- package/src/init.ts +9 -6
- package/src/judge.ts +0 -0
- package/src/levels.ts +16 -4
- package/src/look-at.ts +53 -0
- package/src/plugin.ts +48 -4
- package/src/render/explain.ts +12 -4
- package/src/render/rules-json.ts +16 -5
- package/src/render/semantic.ts +38 -1
- package/src/rule-load.ts +28 -1
- package/src/run-semantic.ts +32 -11
- package/src/run.ts +36 -3
- package/src/sentence-shape.ts +22 -0
package/src/run-semantic.ts
CHANGED
|
@@ -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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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 =
|
|
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
|
|
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));
|