chaffjs 0.0.1 → 0.1.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/checks.d.ts +20 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +58 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +63 -0
- package/dist/cli.js.map +1 -1
- package/dist/config/load.d.ts +4 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +13 -1
- package/dist/config/load.js.map +1 -1
- package/dist/judge.d.ts +40 -0
- package/dist/judge.d.ts.map +1 -0
- package/dist/judge.js +0 -0
- package/dist/judge.js.map +1 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/render/semantic.d.ts +7 -0
- package/dist/render/semantic.d.ts.map +1 -0
- package/dist/render/semantic.js +50 -0
- package/dist/render/semantic.js.map +1 -0
- package/dist/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +12 -3
- package/dist/rule-load.js.map +1 -1
- package/dist/run-semantic.d.ts +17 -0
- package/dist/run-semantic.d.ts.map +1 -0
- package/dist/run-semantic.js +71 -0
- package/dist/run-semantic.js.map +1 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +4 -0
- package/dist/run.js.map +1 -1
- package/dist/semantic.d.ts +25 -0
- package/dist/semantic.d.ts.map +1 -0
- package/dist/semantic.js +55 -0
- package/dist/semantic.js.map +1 -0
- package/package.json +4 -3
- package/rules/empty-conclusion.yaml +37 -0
- package/rules/risk-disclosure.yaml +41 -0
- package/rules/unsourced-number.yaml +40 -0
- package/src/checks.ts +74 -0
- package/src/cli.ts +74 -0
- package/src/config/load.ts +16 -1
- package/src/judge.ts +0 -0
- package/src/plugin.ts +2 -0
- package/src/render/semantic.ts +60 -0
- package/src/rule-load.ts +14 -3
- package/src/run-semantic.ts +107 -0
- package/src/run.ts +4 -0
- package/src/semantic.ts +74 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { severityOf } from "./checks.js";
|
|
2
|
+
import { ask } from "./judge.js";
|
|
3
|
+
import { FILTERS } from "./semantic.js";
|
|
4
|
+
import { localized } from "./render/text.js";
|
|
5
|
+
import { lineStarts, placeOf } from "./position.js";
|
|
6
|
+
const filterFor = (name) => FILTERS[name ?? "whole-document"] ?? FILTERS["whole-document"] ?? (() => []);
|
|
7
|
+
const SEVERITY_BY_VALUE = ["info", "info", "warning", "error"];
|
|
8
|
+
const severityFromLevel = (rule, level) => {
|
|
9
|
+
if (level === "off")
|
|
10
|
+
return "info";
|
|
11
|
+
const value = rule.levels[level] ?? rule.levels.normal ?? 2;
|
|
12
|
+
return SEVERITY_BY_VALUE[Math.min(3, Math.max(1, Math.round(value)))] ?? "warning";
|
|
13
|
+
};
|
|
14
|
+
const builtInJobs = (doc, rules, settings, genre) => rules
|
|
15
|
+
.filter((rule) => rule.layer === "L4" && rule.use_for.some((target) => genre.startsWith(target)) && settings[rule.id] !== "off")
|
|
16
|
+
.flatMap((rule) => {
|
|
17
|
+
const rubric = rule.what_to_check === undefined ? undefined : localized(rule.what_to_check, doc.language);
|
|
18
|
+
if (rubric === undefined || rubric.length === 0)
|
|
19
|
+
return [];
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
rule: rule.id,
|
|
23
|
+
name: localized(rule.name, doc.language),
|
|
24
|
+
rubric,
|
|
25
|
+
howToFix: localized(rule.how_to_fix, doc.language),
|
|
26
|
+
severity: severityFromLevel(rule, settings[rule.id] ?? "normal"),
|
|
27
|
+
candidates: filterFor(rule.how_to_find)(doc),
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
});
|
|
31
|
+
const userJobs = (doc, checks, genre) => checks
|
|
32
|
+
.filter((check) => check.level !== "off" && check.use_for.some((target) => genre.startsWith(target)))
|
|
33
|
+
.map((check) => ({
|
|
34
|
+
rule: check.id,
|
|
35
|
+
name: check.name,
|
|
36
|
+
// look_at は自然文なので、まだ絞り込みに変換できない。文書全体を渡す。spec §26-7。
|
|
37
|
+
rubric: check.look_at === undefined ? check.check : `${check.check}\n\n見るところ: ${check.look_at}`,
|
|
38
|
+
howToFix: check.how_to_fix,
|
|
39
|
+
severity: severityOf(check.level),
|
|
40
|
+
candidates: filterFor("whole-document")(doc),
|
|
41
|
+
}));
|
|
42
|
+
const findingOf = (job, candidate, reason, confidence, threshold, starts) => {
|
|
43
|
+
const at = placeOf(starts, candidate.offset);
|
|
44
|
+
// 確からしさが低いものは指摘として出すが、重さを下げる。spec §14。
|
|
45
|
+
const severity = confidence < threshold ? "info" : job.severity;
|
|
46
|
+
return {
|
|
47
|
+
rule: job.rule,
|
|
48
|
+
severity,
|
|
49
|
+
line: at.line,
|
|
50
|
+
column: at.column,
|
|
51
|
+
quote: candidate.text.slice(0, 200),
|
|
52
|
+
values: { reason, confidence: confidence.toFixed(2), offset: candidate.offset },
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
export const runSemantic = async (doc, rules, checks, settings, genre, options) => {
|
|
56
|
+
const jobs = [...builtInJobs(doc, rules, settings, genre), ...userJobs(doc, checks, genre)];
|
|
57
|
+
const starts = lineStarts(doc.source);
|
|
58
|
+
const skipped = jobs.filter((job) => job.candidates.length === 0).map((job) => ({ rule: job.rule, why: "見るところが無かったため" }));
|
|
59
|
+
const live = jobs.filter((job) => job.candidates.length > 0);
|
|
60
|
+
const answers = await Promise.all(live.flatMap((job) => job.candidates.map(async (candidate) => {
|
|
61
|
+
const verdict = await ask({ rule: job.rule, rubric: job.rubric, candidate: candidate.text, language: doc.language }, options);
|
|
62
|
+
return verdict.violated ? findingOf(job, candidate, verdict.reason, verdict.confidence, options.confidenceThreshold, starts) : undefined;
|
|
63
|
+
})));
|
|
64
|
+
return {
|
|
65
|
+
findings: answers.filter((finding) => finding !== undefined).sort((left, right) => left.line - right.line),
|
|
66
|
+
skipped,
|
|
67
|
+
asked: live.reduce((sum, job) => sum + job.candidates.length, 0),
|
|
68
|
+
sentencesSeen: doc.sentences.length,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=run-semantic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-semantic.js","sourceRoot":"","sources":["../src/run-semantic.ts"],"names":[],"mappings":"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"}
|
package/dist/run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGjF,MAAM,MAAM,OAAO,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC;IACrC,+DAA+D;IAC/D,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAgBvD,eAAO,MAAM,QAAQ,GAAI,KAAK,aAAa,EAAE,OAAO,SAAS,cAAc,EAAE,EAAE,UAAU,QAAQ,EAAE,cAAc,OAAO,EAAE,OAAO,MAAM,KAAG,
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGjF,MAAM,MAAM,OAAO,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,CAAC;IACrC,+DAA+D;IAC/D,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;AAgBvD,eAAO,MAAM,QAAQ,GAAI,KAAK,aAAa,EAAE,OAAO,SAAS,cAAc,EAAE,EAAE,UAAU,QAAQ,EAAE,cAAc,OAAO,EAAE,OAAO,MAAM,KAAG,SAkCzI,CAAC"}
|
package/dist/run.js
CHANGED
|
@@ -22,6 +22,10 @@ export const runRules = (doc, rules, settings, experimental, genre) => {
|
|
|
22
22
|
.filter((rule) => rule.status === "experimental" && settings[rule.id] !== undefined && settings[rule.id] !== "off")
|
|
23
23
|
.map((rule) => rule.id);
|
|
24
24
|
const outcome = applicable.reduce((acc, rule) => {
|
|
25
|
+
// L4 は意味を読む検査。chaff test が扱う。ここで「検出器が無い」と言わせない。
|
|
26
|
+
if (rule.layer === "L4") {
|
|
27
|
+
return { findings: acc.findings, skipped: [...acc.skipped, { rule: rule.id, why: "意味を読む検査のため(npx chaff test で動きます)" }] };
|
|
28
|
+
}
|
|
25
29
|
const level = levelFor(rule, settings, experimental);
|
|
26
30
|
if (level === "off") {
|
|
27
31
|
const why = rule.status === "experimental" && settings[rule.id] === undefined ? "まだ試験中のため" : "設定で止めているため";
|
package/dist/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ProseDocument } from "./plugin.ts";
|
|
2
|
+
/**
|
|
3
|
+
* 二段構えの 1 段目。文書全体を LLM に投げない。spec §14。
|
|
4
|
+
*
|
|
5
|
+
* 絞り込みを持たない L4 rule は登録できない。持たせないと、1 記事ごとに全文を
|
|
6
|
+
* 読ませることになり、速度もコストも成り立たない。
|
|
7
|
+
*/
|
|
8
|
+
export type Candidate = {
|
|
9
|
+
readonly text: string;
|
|
10
|
+
readonly offset: number;
|
|
11
|
+
};
|
|
12
|
+
export type Filter = (doc: ProseDocument) => Candidate[];
|
|
13
|
+
/** 提案文書にリスクが書かれているか。見出しにあれば見ない。 */
|
|
14
|
+
export declare const riskDisclosure: Filter;
|
|
15
|
+
/**
|
|
16
|
+
* 結びが本文の要約でしかないか。
|
|
17
|
+
* 最後の節だけを見て、そこに具体物が無いときにだけ問い合わせる。
|
|
18
|
+
*/
|
|
19
|
+
export declare const emptyConclusion: Filter;
|
|
20
|
+
/** 効果を主張する数値に根拠が無い文だけを渡す。 */
|
|
21
|
+
export declare const unsourcedNumber: Filter;
|
|
22
|
+
/** 利用者が checks.yaml に書いた検査の既定。絞り込みを書かなければ文書全体。 */
|
|
23
|
+
export declare const wholeDocument: Filter;
|
|
24
|
+
export declare const FILTERS: Readonly<Record<string, Filter>>;
|
|
25
|
+
//# sourceMappingURL=semantic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic.d.ts","sourceRoot":"","sources":["../src/semantic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAW,MAAM,aAAa,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,aAAa,KAAK,SAAS,EAAE,CAAC;AAWzD,mCAAmC;AACnC,eAAO,MAAM,cAAc,EAAE,MAK5B,CAAC;AAIF;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,MAM7B,CAAC;AAeF,6BAA6B;AAC7B,eAAO,MAAM,eAAe,EAAE,MAGuD,CAAC;AAEtF,kDAAkD;AAClD,eAAO,MAAM,aAAa,EAAE,MAG3B,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAKpD,CAAC"}
|
package/dist/semantic.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const heads = (doc) => doc.sections.map((section) => section.heading).join(" ");
|
|
2
|
+
const sectionText = (doc, section) => doc.source.slice(section.span.start, section.span.end).trim();
|
|
3
|
+
/** 見出しに答えが書いてあるなら、本文を読ませる必要はない。 */
|
|
4
|
+
const skipIfHeadingMatches = (doc, pattern) => pattern.test(heads(doc));
|
|
5
|
+
const RISK_HEADING = /リスク|懸念|デメリット|risk|tradeoff|downside/iu;
|
|
6
|
+
/** 提案文書にリスクが書かれているか。見出しにあれば見ない。 */
|
|
7
|
+
export const riskDisclosure = (doc) => {
|
|
8
|
+
if (skipIfHeadingMatches(doc, RISK_HEADING))
|
|
9
|
+
return [];
|
|
10
|
+
const body = doc.sentences.map((sentence) => sentence.text.trim()).join("");
|
|
11
|
+
if (body.length === 0)
|
|
12
|
+
return [];
|
|
13
|
+
return [{ text: body.slice(0, 4000), offset: doc.sentences[0]?.span.start ?? 0 }];
|
|
14
|
+
};
|
|
15
|
+
const EVIDENCE = /\d|`|https?:\/\//u;
|
|
16
|
+
/**
|
|
17
|
+
* 結びが本文の要約でしかないか。
|
|
18
|
+
* 最後の節だけを見て、そこに具体物が無いときにだけ問い合わせる。
|
|
19
|
+
*/
|
|
20
|
+
export const emptyConclusion = (doc) => {
|
|
21
|
+
const last = doc.sections.at(-1);
|
|
22
|
+
if (last === undefined || last.sentences.length === 0)
|
|
23
|
+
return [];
|
|
24
|
+
const text = sectionText(doc, last);
|
|
25
|
+
if (EVIDENCE.test(text))
|
|
26
|
+
return [];
|
|
27
|
+
return [{ text: text.slice(0, 2000), offset: last.sentences[0]?.span.start ?? last.span.start }];
|
|
28
|
+
};
|
|
29
|
+
// \s* を挟むとバックトラックする。単位の前の空白は 1 つまでで足りる。
|
|
30
|
+
const NUMBER = /\d ?(?:%|%|倍|割|件|人|円|ドル|時間|分|秒)/u;
|
|
31
|
+
const EFFECT = /向上|改善|削減|短縮|増加|減少|成長|伸び/u;
|
|
32
|
+
/**
|
|
33
|
+
* 根拠と認めるもの。ここを狭く取ると、根拠つきの文まで LLM に送ってしまう。
|
|
34
|
+
* 二段構えの 1 段目が防ぐべきなのは、まさにその無駄。
|
|
35
|
+
*/
|
|
36
|
+
const EVIDENCE_WORDS = ["出典", "調査", "自社調べ", "参考", "期間", "比較", "対比", "前年", "昨年", "同期", "前月", "前四半期", "vs"];
|
|
37
|
+
const EVIDENCE_SHAPE = /n ?=|\[\d+\]|\d{4} ?年|Q[1-4]/iu;
|
|
38
|
+
/** 語の照合は正規表現にしない。数が増えるほど式が読めなくなり、複雑度も上がる。 */
|
|
39
|
+
const hasEvidence = (text) => EVIDENCE_WORDS.some((word) => text.includes(word)) || EVIDENCE_SHAPE.test(text);
|
|
40
|
+
/** 効果を主張する数値に根拠が無い文だけを渡す。 */
|
|
41
|
+
export const unsourcedNumber = (doc) => doc.sentences
|
|
42
|
+
.filter((sentence) => NUMBER.test(sentence.text) && EFFECT.test(sentence.text) && !hasEvidence(sentence.text))
|
|
43
|
+
.map((sentence) => ({ text: sentence.text.trim(), offset: sentence.span.start }));
|
|
44
|
+
/** 利用者が checks.yaml に書いた検査の既定。絞り込みを書かなければ文書全体。 */
|
|
45
|
+
export const wholeDocument = (doc) => {
|
|
46
|
+
const body = doc.sentences.map((sentence) => sentence.text.trim()).join("");
|
|
47
|
+
return body.length === 0 ? [] : [{ text: body.slice(0, 4000), offset: doc.sentences[0]?.span.start ?? 0 }];
|
|
48
|
+
};
|
|
49
|
+
export const FILTERS = {
|
|
50
|
+
"risk-disclosure": riskDisclosure,
|
|
51
|
+
"empty-conclusion": emptyConclusion,
|
|
52
|
+
"unsourced-number": unsourcedNumber,
|
|
53
|
+
"whole-document": wholeDocument,
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=semantic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic.js","sourceRoot":"","sources":["../src/semantic.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chaffjs",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Prose validation harness. Finds what is hard to read, and never rewrites it for you.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chaff",
|
|
@@ -51,8 +51,9 @@
|
|
|
51
51
|
"prepack": "yarn build"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@
|
|
55
|
-
"@chaffjs/lang-
|
|
54
|
+
"@anthropic-ai/sdk": "^0.125.0",
|
|
55
|
+
"@chaffjs/lang-en": "0.1.0",
|
|
56
|
+
"@chaffjs/lang-ja": "0.1.0",
|
|
56
57
|
"mdast-util-from-markdown": "^2.0.3",
|
|
57
58
|
"mdast-util-frontmatter": "^2.0.1",
|
|
58
59
|
"mdast-util-gfm-table": "^2.0.0",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
id: empty-conclusion
|
|
2
|
+
layer: L4
|
|
3
|
+
status: stable
|
|
4
|
+
how_to_find: empty-conclusion
|
|
5
|
+
severity: warning
|
|
6
|
+
|
|
7
|
+
name:
|
|
8
|
+
ja: 結びに中身がない
|
|
9
|
+
en: Empty conclusion
|
|
10
|
+
|
|
11
|
+
why:
|
|
12
|
+
ja: 本文を繰り返すだけの結びは、読者に何も残しません。
|
|
13
|
+
en: A closing that only restates the body leaves the reader with nothing.
|
|
14
|
+
|
|
15
|
+
message:
|
|
16
|
+
ja: "{reason}"
|
|
17
|
+
en: "{reason}"
|
|
18
|
+
|
|
19
|
+
how_to_fix:
|
|
20
|
+
ja: 本文で分かったことのうち、いちばん持ち帰ってほしい 1 つを書いてください。
|
|
21
|
+
en: Name the one thing you want the reader to take away.
|
|
22
|
+
|
|
23
|
+
what_to_check:
|
|
24
|
+
ja: |
|
|
25
|
+
文書の結びに、本文の要約以外のものがあること。
|
|
26
|
+
新しい主張、判断、次にすべきこと、読者への問いのいずれかがあればよい。
|
|
27
|
+
本文に書いたことを並べ直しているだけなら、決まりに反する。
|
|
28
|
+
en: |
|
|
29
|
+
The closing must contain something beyond a summary of the body — a new
|
|
30
|
+
claim, a judgement, a next step, or a question to the reader.
|
|
31
|
+
|
|
32
|
+
levels:
|
|
33
|
+
strict: error
|
|
34
|
+
normal: warning
|
|
35
|
+
relaxed: info
|
|
36
|
+
|
|
37
|
+
use_for: [blog]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
id: risk-disclosure
|
|
2
|
+
layer: L4
|
|
3
|
+
status: stable
|
|
4
|
+
how_to_find: risk-disclosure
|
|
5
|
+
severity: warning
|
|
6
|
+
|
|
7
|
+
name:
|
|
8
|
+
ja: リスクが書かれていない
|
|
9
|
+
en: No risk disclosed
|
|
10
|
+
|
|
11
|
+
why:
|
|
12
|
+
ja: 提案を読む人は、うまくいく話だけでは判断できません。
|
|
13
|
+
en: A reader cannot decide from the upside alone.
|
|
14
|
+
|
|
15
|
+
message:
|
|
16
|
+
ja: "{reason}"
|
|
17
|
+
en: "{reason}"
|
|
18
|
+
|
|
19
|
+
how_to_fix:
|
|
20
|
+
ja: 「リスク」または「懸念」の節を足してください。
|
|
21
|
+
en: Add a section naming the risks.
|
|
22
|
+
|
|
23
|
+
what_to_check:
|
|
24
|
+
ja: |
|
|
25
|
+
提案を行っている文書に、次のいずれかが書かれていること。
|
|
26
|
+
・その施策のリスク
|
|
27
|
+
・想定される副作用
|
|
28
|
+
・実施しなかった場合の影響
|
|
29
|
+
いまの課題の記述は、リスクとしては数えない。
|
|
30
|
+
提案をしていない文書(報告・議事録・説明)は、この決まりの対象外とする。
|
|
31
|
+
en: |
|
|
32
|
+
For a document making a proposal, at least one of: risks of the action,
|
|
33
|
+
expected side effects, or the cost of not acting. A description of the
|
|
34
|
+
current problem does not count. Documents that make no proposal are out of scope.
|
|
35
|
+
|
|
36
|
+
levels:
|
|
37
|
+
strict: error
|
|
38
|
+
normal: warning
|
|
39
|
+
relaxed: info
|
|
40
|
+
|
|
41
|
+
use_for: [business]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
id: unsourced-number
|
|
2
|
+
layer: L4
|
|
3
|
+
status: stable
|
|
4
|
+
how_to_find: unsourced-number
|
|
5
|
+
severity: warning
|
|
6
|
+
|
|
7
|
+
name:
|
|
8
|
+
ja: 数字の根拠がない
|
|
9
|
+
en: Number without a source
|
|
10
|
+
|
|
11
|
+
why:
|
|
12
|
+
ja: 根拠のない数字は、読む人が確かめられません。
|
|
13
|
+
en: A number the reader cannot check is not evidence.
|
|
14
|
+
|
|
15
|
+
message:
|
|
16
|
+
ja: "{reason}"
|
|
17
|
+
en: "{reason}"
|
|
18
|
+
|
|
19
|
+
how_to_fix:
|
|
20
|
+
ja: 「(2026年4-6月、対前年同期、n=120)」のように括弧で添えてください。
|
|
21
|
+
en: Add the basis in parentheses — period, comparison, sample size.
|
|
22
|
+
|
|
23
|
+
what_to_check:
|
|
24
|
+
ja: |
|
|
25
|
+
効果を主張する数値について、次のいずれかが同じ文か近くにあること。
|
|
26
|
+
・どう計算したか
|
|
27
|
+
・いつと比べたか
|
|
28
|
+
・母数がいくつか
|
|
29
|
+
どれも無ければ、決まりに反する。
|
|
30
|
+
一般に知られた事実の引用や、仕様として決まっている値は対象外とする。
|
|
31
|
+
en: |
|
|
32
|
+
A number claiming an effect must carry at least one of: how it was
|
|
33
|
+
computed, what it is compared against, or the sample size.
|
|
34
|
+
|
|
35
|
+
levels:
|
|
36
|
+
strict: error
|
|
37
|
+
normal: warning
|
|
38
|
+
relaxed: info
|
|
39
|
+
|
|
40
|
+
use_for: [business]
|
package/src/checks.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { parse } from "yaml";
|
|
3
|
+
import type { Level, Severity } from "./plugin.ts";
|
|
4
|
+
|
|
5
|
+
export const CHECKS_FILE = "checks.yaml";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 利用者が自然文で足す検査。キーは英語、中身は書き手の言葉。
|
|
9
|
+
* プログラムは書かせない。spec §18.3。
|
|
10
|
+
*/
|
|
11
|
+
export type UserCheck = {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly use_for: readonly string[];
|
|
15
|
+
readonly check: string;
|
|
16
|
+
readonly look_at: string | undefined;
|
|
17
|
+
readonly level: Level;
|
|
18
|
+
readonly how_to_fix: string;
|
|
19
|
+
readonly source: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
|
|
24
|
+
const str = (value: unknown): string | undefined => (typeof value === "string" ? value.trim() : undefined);
|
|
25
|
+
|
|
26
|
+
const LEVELS: readonly Level[] = ["strict", "normal", "relaxed", "off"];
|
|
27
|
+
const asLevel = (value: unknown): Level => LEVELS.find((level) => level === value) ?? "normal";
|
|
28
|
+
|
|
29
|
+
/** id は名前から作る。利用者に id を書かせない。 */
|
|
30
|
+
const idOf = (name: string, index: number): string => {
|
|
31
|
+
const slug = name
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9]+/gu, "-")
|
|
34
|
+
.replace(/^-|-$/gu, "");
|
|
35
|
+
return slug.length > 0 ? `check:${slug}` : `check:${index + 1}`;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** use_for は 1 つでも配列でも書ける。書かなければ全ジャンル。 */
|
|
39
|
+
const useForOf = (value: unknown): string[] => {
|
|
40
|
+
if (typeof value === "string") return [value];
|
|
41
|
+
if (Array.isArray(value)) return value.map((entry) => String(entry));
|
|
42
|
+
return ["blog", "business"];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const toCheck = (raw: unknown, index: number, source: string): UserCheck | undefined => {
|
|
46
|
+
if (!isRecord(raw)) return undefined;
|
|
47
|
+
const name = str(raw["name"]);
|
|
48
|
+
const check = str(raw["check"]);
|
|
49
|
+
if (name === undefined || check === undefined) return undefined;
|
|
50
|
+
return {
|
|
51
|
+
id: idOf(name, index),
|
|
52
|
+
name,
|
|
53
|
+
use_for: useForOf(raw["use_for"]),
|
|
54
|
+
check,
|
|
55
|
+
look_at: str(raw["look_at"]),
|
|
56
|
+
level: asLevel(raw["level"]),
|
|
57
|
+
how_to_fix: str(raw["how_to_fix"]) ?? "",
|
|
58
|
+
source,
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const loadChecks = (path: string): UserCheck[] => {
|
|
63
|
+
if (!existsSync(path)) return [];
|
|
64
|
+
const raw: unknown = parse(readFileSync(path, "utf8"));
|
|
65
|
+
if (!isRecord(raw) || !Array.isArray(raw["checks"])) return [];
|
|
66
|
+
return raw["checks"].map((entry, index) => toCheck(entry, index, path)).filter((entry) => entry !== undefined);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/** 4 語を severity にする。L4 は件数ではなく深刻度を段で持つ。 */
|
|
70
|
+
export const severityOf = (level: Level): Severity => {
|
|
71
|
+
if (level === "strict") return "error";
|
|
72
|
+
if (level === "relaxed") return "info";
|
|
73
|
+
return "warning";
|
|
74
|
+
};
|
package/src/cli.ts
CHANGED
|
@@ -11,6 +11,10 @@ import { BASELINE_FILE, fingerprint, readBaseline, splitByBaseline, writeBaselin
|
|
|
11
11
|
import { applySuppressions } from "./stet.ts";
|
|
12
12
|
import { renderSuppressions, type PerFile } from "./render/suppressions.ts";
|
|
13
13
|
import { clock, describeChange, snapshotOf, watchPaths, type Snapshot } from "./watch.ts";
|
|
14
|
+
import { CHECKS_FILE, loadChecks, type UserCheck } from "./checks.ts";
|
|
15
|
+
import { CACHE_DIR, hasCredentials, isAuthFailure } from "./judge.ts";
|
|
16
|
+
import { runSemantic } from "./run-semantic.ts";
|
|
17
|
+
import { MACHINE_BANNER, renderSemantic } from "./render/semantic.ts";
|
|
14
18
|
import { frontMatterGenre, guessGenre } from "./genre.ts";
|
|
15
19
|
import { GENRES, runInit } from "./init.ts";
|
|
16
20
|
import { loadRules } from "./rule-load.ts";
|
|
@@ -26,6 +30,7 @@ const USAGE = `chaff — 文章の読みにくいところを見つけます。
|
|
|
26
30
|
|
|
27
31
|
chaff <file|dir|glob>... 検査する(設定も API key も要りません)
|
|
28
32
|
chaff . この場所の Markdown を全部
|
|
33
|
+
chaff test <file|dir>... 意味を読む検査も動かす(API key が要ります)
|
|
29
34
|
chaff init chaff.yaml を作る
|
|
30
35
|
chaff explain <rule> そのルールの意図と根拠を読む
|
|
31
36
|
chaff genres ジャンルの一覧
|
|
@@ -158,6 +163,74 @@ const runWatch = async (targets: readonly string[], argv: readonly string[]): Pr
|
|
|
158
163
|
return new Promise(() => undefined);
|
|
159
164
|
};
|
|
160
165
|
|
|
166
|
+
type Judged = { path: string; outcome: Awaited<ReturnType<typeof runSemantic>>; rules: RuleDefinition[]; language: string };
|
|
167
|
+
|
|
168
|
+
const judgeAll = async (
|
|
169
|
+
paths: readonly string[],
|
|
170
|
+
config: Config,
|
|
171
|
+
checks: readonly UserCheck[],
|
|
172
|
+
options: Parameters<typeof runSemantic>[5],
|
|
173
|
+
): Promise<Judged[]> =>
|
|
174
|
+
Promise.all(
|
|
175
|
+
paths.map(async (path) => {
|
|
176
|
+
const source = await readFile(path, "utf8");
|
|
177
|
+
const language = config.language ?? guessLanguage(source).language;
|
|
178
|
+
const adapter = await loadAdapter(language);
|
|
179
|
+
const doc = buildDocument(path, source, adapter);
|
|
180
|
+
const { genre } = resolveGenre(path, source, config);
|
|
181
|
+
const rules = loadRules(language);
|
|
182
|
+
return { path, outcome: await runSemantic(doc, rules, checks, config.rules, genre, options), rules, language };
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* 機械と AI を見出しで分けて出す。読む人が「これは揺れる判定か」を知らないと、
|
|
188
|
+
* AI の誤検知に振り回される。workflow spec §7、samples/README の軸 2。
|
|
189
|
+
*/
|
|
190
|
+
const test = async (targets: readonly string[], argv: readonly string[]): Promise<number> => {
|
|
191
|
+
const paths = collectTargets(targets.length > 0 ? targets : ["."]);
|
|
192
|
+
if (paths.length === 0) {
|
|
193
|
+
console.error(`Markdown が 1 つも見つかりませんでした: ${targets.join(", ")}`);
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
const config = readConfig();
|
|
197
|
+
const checks = loadChecks(join(process.cwd(), CHECKS_FILE));
|
|
198
|
+
const results = await Promise.all(paths.map((path) => inspect(path, config, argv)));
|
|
199
|
+
results.forEach((result) => {
|
|
200
|
+
console.log(result.text.replace(/\n/u, `\n${MACHINE_BANNER.join("\n")}`));
|
|
201
|
+
});
|
|
202
|
+
const machineOnly = results.some((result) => result.outcome.findings.some((finding) => finding.severity === "error")) ? 1 : 0;
|
|
203
|
+
const noCredentials = [
|
|
204
|
+
"",
|
|
205
|
+
" 意味を読む検査は動かしていません。Anthropic の認証情報がありません。",
|
|
206
|
+
" ANTHROPIC_API_KEY を設定するか、ant auth login を実行してください。",
|
|
207
|
+
" 機械による判定はすべて動いています。",
|
|
208
|
+
"",
|
|
209
|
+
].join("\n");
|
|
210
|
+
// 呼んでから落ちるのを待たない。認証が無いときの SDK の例外は型で判別できない。
|
|
211
|
+
if (!hasCredentials()) {
|
|
212
|
+
console.log(noCredentials);
|
|
213
|
+
return machineOnly;
|
|
214
|
+
}
|
|
215
|
+
const options = { model: config.aiModel, cacheDir: join(process.cwd(), CACHE_DIR), confidenceThreshold: config.confidenceThreshold };
|
|
216
|
+
const semantic = await judgeAll(paths, config, checks, options).catch((error: unknown) => {
|
|
217
|
+
if (!isAuthFailure(error)) throw error;
|
|
218
|
+
console.log(noCredentials);
|
|
219
|
+
return undefined;
|
|
220
|
+
});
|
|
221
|
+
if (semantic === undefined) return machineOnly;
|
|
222
|
+
semantic.forEach((entry) => {
|
|
223
|
+
if (entry.outcome.findings.length === 0 && entry.outcome.asked === 0) return;
|
|
224
|
+
console.log([`${entry.path}`, ...renderSemantic(entry.outcome, entry.rules, checks, entry.language)].join("\n"));
|
|
225
|
+
});
|
|
226
|
+
const machine = results.flatMap((result) => result.outcome.findings);
|
|
227
|
+
const ai = semantic.flatMap((entry) => entry.outcome.findings);
|
|
228
|
+
console.log(
|
|
229
|
+
["", "─".repeat(60), "", ` 機械 ${machine.length} 件 / AI ${ai.length} 件`, " 文章は書き換えていません。直すのは書いた人です。", ""].join("\n"),
|
|
230
|
+
);
|
|
231
|
+
return [...machine, ...ai].some((finding) => finding.severity === "error") ? 1 : 0;
|
|
232
|
+
};
|
|
233
|
+
|
|
161
234
|
const explain = (ruleId: string | undefined): number => {
|
|
162
235
|
const config = readConfig();
|
|
163
236
|
const language = config.language ?? "ja";
|
|
@@ -239,6 +312,7 @@ const HANDLERS: Readonly<Record<string, Handler>> = {
|
|
|
239
312
|
genres: showGenres,
|
|
240
313
|
rules: showRules,
|
|
241
314
|
explain: (argv) => explain(argv[1]),
|
|
315
|
+
test: (argv) => test(positional(argv), argv),
|
|
242
316
|
baseline: (argv) => runBaseline(positional(argv), argv),
|
|
243
317
|
suppressions: (argv) => runSuppressions(positional(argv), argv),
|
|
244
318
|
relax: (argv) => changeSetting("relaxed", argv[1], flag(argv, "--why")),
|
package/src/config/load.ts
CHANGED
|
@@ -11,9 +11,22 @@ export type Config = {
|
|
|
11
11
|
readonly rules: Readonly<Record<string, Level>>;
|
|
12
12
|
readonly experimental: boolean;
|
|
13
13
|
readonly path: string | undefined;
|
|
14
|
+
readonly aiModel: string;
|
|
15
|
+
readonly confidenceThreshold: number;
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
/** 判定の質が誤検知に直結するので、既定は最上位のモデル。cost は絞り込みで削る。spec §14。 */
|
|
19
|
+
export const DEFAULT_MODEL = "claude-opus-5";
|
|
20
|
+
|
|
21
|
+
export const EMPTY: Config = {
|
|
22
|
+
genre: undefined,
|
|
23
|
+
language: undefined,
|
|
24
|
+
rules: {},
|
|
25
|
+
experimental: false,
|
|
26
|
+
path: undefined,
|
|
27
|
+
aiModel: DEFAULT_MODEL,
|
|
28
|
+
confidenceThreshold: 0.7,
|
|
29
|
+
};
|
|
17
30
|
|
|
18
31
|
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19
32
|
|
|
@@ -34,5 +47,7 @@ export const loadConfig = (path: string): Config => {
|
|
|
34
47
|
rules: rulesOf(raw["rules"]),
|
|
35
48
|
experimental: raw["experimental"] === true,
|
|
36
49
|
path,
|
|
50
|
+
aiModel: str(raw["ai_model"]) ?? DEFAULT_MODEL,
|
|
51
|
+
confidenceThreshold: typeof raw["confidence_threshold"] === "number" ? raw["confidence_threshold"] : 0.7,
|
|
37
52
|
};
|
|
38
53
|
};
|
package/src/judge.ts
ADDED
|
Binary file
|
package/src/plugin.ts
CHANGED
|
@@ -118,6 +118,8 @@ export type RuleDefinition = {
|
|
|
118
118
|
readonly levels: LevelTable;
|
|
119
119
|
readonly how_to_find: string;
|
|
120
120
|
readonly word_list: string | undefined;
|
|
121
|
+
/** L4 のみ。LLM に渡す決まり。言語別。 */
|
|
122
|
+
readonly what_to_check: Localized | undefined;
|
|
121
123
|
readonly where: string | undefined;
|
|
122
124
|
readonly use_for: readonly string[];
|
|
123
125
|
readonly severity: Severity;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Finding, RuleDefinition } from "../plugin.ts";
|
|
2
|
+
import type { UserCheck } from "../checks.ts";
|
|
3
|
+
import type { SemanticResult } from "../run-semantic.ts";
|
|
4
|
+
import { MARK, localized } from "./text.ts";
|
|
5
|
+
|
|
6
|
+
const RULE = 60;
|
|
7
|
+
|
|
8
|
+
const indent = (text: string, pad: string): string[] => text.split("\n").map((line) => `${pad}${line}`);
|
|
9
|
+
|
|
10
|
+
export const MACHINE_BANNER = ["", `═══ 機械による判定 ${"═".repeat(RULE - 18)}`, " 同じ文章なら何度実行しても同じ結果になります", ""];
|
|
11
|
+
|
|
12
|
+
export const aiBanner = (result: SemanticResult): string[] => [
|
|
13
|
+
"",
|
|
14
|
+
`═══ AI による判定 ${"═".repeat(RULE - 17)}`,
|
|
15
|
+
" 文章の意味を読んでいます。実行するたび結果が変わることが",
|
|
16
|
+
" あります。おかしいと思ったら、そのまま無視して構いません。",
|
|
17
|
+
"",
|
|
18
|
+
` ${result.sentencesSeen} 文のうち ${result.asked} 箇所を読みました(残りは機械が対象外と判断)`,
|
|
19
|
+
"",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
type Named = { readonly name: string; readonly howToFix: string };
|
|
23
|
+
|
|
24
|
+
const nameOf = (rule: string, rules: readonly RuleDefinition[], checks: readonly UserCheck[], language: string): Named => {
|
|
25
|
+
const built = rules.find((entry) => entry.id === rule);
|
|
26
|
+
if (built !== undefined) return { name: localized(built.name, language), howToFix: localized(built.how_to_fix, language) };
|
|
27
|
+
const user = checks.find((entry) => entry.id === rule);
|
|
28
|
+
return { name: user?.name ?? rule, howToFix: user?.how_to_fix ?? "" };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* AI の指摘にだけ確からしさを出し、納得できないときの逃げ道を 2 つ添える。
|
|
33
|
+
* 揺れる判定だと読む人が知らないと、誤検知に振り回される。
|
|
34
|
+
*/
|
|
35
|
+
const block = (finding: Finding, named: Named): string[] => {
|
|
36
|
+
const confidence = finding.values["confidence"];
|
|
37
|
+
const reason = String(finding.values["reason"] ?? "");
|
|
38
|
+
const sure = confidence === undefined ? "" : ` 確からしさ ${String(confidence)}`;
|
|
39
|
+
const fix = named.howToFix.length > 0 ? ["", ...indent(`→ ${named.howToFix}`, " ")] : [];
|
|
40
|
+
return [
|
|
41
|
+
"",
|
|
42
|
+
`─── ${finding.line} 行目 ${"─".repeat(Math.max(0, RULE - String(finding.line).length - 8))}`,
|
|
43
|
+
"",
|
|
44
|
+
` ${MARK[finding.severity] ?? "·"} ${named.name}${sure}`,
|
|
45
|
+
"",
|
|
46
|
+
...indent(reason, " "),
|
|
47
|
+
...fix,
|
|
48
|
+
"",
|
|
49
|
+
" この指摘が違うと思ったら:",
|
|
50
|
+
` この箇所だけ黙らせる <!-- stet: ${finding.rule} — 理由 -->`,
|
|
51
|
+
` ルールごとゆるめる npx chaff relax ${finding.rule}`,
|
|
52
|
+
"",
|
|
53
|
+
];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const renderSemantic = (result: SemanticResult, rules: readonly RuleDefinition[], checks: readonly UserCheck[], language: string): string[] => [
|
|
57
|
+
...aiBanner(result),
|
|
58
|
+
...result.findings.flatMap((finding) => block(finding, nameOf(finding.rule, rules, checks, language))),
|
|
59
|
+
...(result.skipped.length > 0 ? [` ${result.skipped.length} 件の検査は、見るところが無いため動いていません。`, ""] : []),
|
|
60
|
+
];
|