chaffjs 0.8.0 → 0.10.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/address-chain.d.ts +9 -0
- package/dist/address-chain.d.ts.map +1 -0
- package/dist/address-chain.js +105 -0
- package/dist/address-chain.js.map +1 -0
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +1 -1
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +4 -0
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +2 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/feedback.d.ts +35 -0
- package/dist/commands/feedback.d.ts.map +1 -0
- package/dist/commands/feedback.js +157 -0
- package/dist/commands/feedback.js.map +1 -0
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +3 -2
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +3 -1
- package/dist/commands/tree.js.map +1 -1
- package/dist/config/by-path.d.ts +5 -1
- package/dist/config/by-path.d.ts.map +1 -1
- package/dist/config/by-path.js +1 -4
- package/dist/config/by-path.js.map +1 -1
- package/dist/config/load.d.ts +2 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +3 -1
- package/dist/config/load.js.map +1 -1
- package/dist/detectors/char-shape.d.ts.map +1 -1
- package/dist/detectors/char-shape.js +3 -2
- package/dist/detectors/char-shape.js.map +1 -1
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +4 -1
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/lexicon.d.ts.map +1 -1
- package/dist/detectors/lexicon.js +8 -1
- package/dist/detectors/lexicon.js.map +1 -1
- package/dist/detectors/structure-tree.d.ts +5 -0
- package/dist/detectors/structure-tree.d.ts.map +1 -1
- package/dist/detectors/structure-tree.js +50 -1
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/team.d.ts.map +1 -1
- package/dist/detectors/team.js +7 -2
- package/dist/detectors/team.js.map +1 -1
- package/dist/detectors/word-list.d.ts +3 -0
- package/dist/detectors/word-list.d.ts.map +1 -0
- package/dist/detectors/word-list.js +3 -0
- package/dist/detectors/word-list.js.map +1 -0
- package/dist/document.d.ts +2 -2
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +4 -2
- package/dist/document.js.map +1 -1
- package/dist/feedback/draft.d.ts +28 -0
- package/dist/feedback/draft.d.ts.map +1 -0
- package/dist/feedback/draft.js +66 -0
- package/dist/feedback/draft.js.map +1 -0
- package/dist/feedback/excerpt.d.ts +10 -0
- package/dist/feedback/excerpt.d.ts.map +1 -0
- package/dist/feedback/excerpt.js +20 -0
- package/dist/feedback/excerpt.js.map +1 -0
- package/dist/legal-address.d.ts +2 -0
- package/dist/legal-address.d.ts.map +1 -0
- package/dist/legal-address.js +16 -0
- package/dist/legal-address.js.map +1 -0
- package/dist/orthography.d.ts.map +1 -1
- package/dist/orthography.js +15 -0
- package/dist/orthography.js.map +1 -1
- package/dist/plugin.d.ts +98 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/profile/for-file.d.ts +7 -0
- package/dist/profile/for-file.d.ts.map +1 -0
- package/dist/profile/for-file.js +6 -0
- package/dist/profile/for-file.js.map +1 -0
- package/dist/profile/load.d.ts +4 -0
- package/dist/profile/load.d.ts.map +1 -0
- package/dist/profile/load.js +25 -0
- package/dist/profile/load.js.map +1 -0
- package/dist/profile/parse.d.ts +19 -0
- package/dist/profile/parse.d.ts.map +1 -0
- package/dist/profile/parse.js +95 -0
- package/dist/profile/parse.js.map +1 -0
- package/dist/profile/select.d.ts +22 -0
- package/dist/profile/select.d.ts.map +1 -0
- package/dist/profile/select.js +21 -0
- package/dist/profile/select.js.map +1 -0
- package/dist/reasons.d.ts +1 -0
- package/dist/reasons.d.ts.map +1 -1
- package/dist/reasons.js +2 -0
- package/dist/reasons.js.map +1 -1
- package/dist/render/compact.d.ts.map +1 -1
- package/dist/render/compact.js +3 -1
- package/dist/render/compact.js.map +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +40 -7
- package/dist/run.js.map +1 -1
- package/dist/structure/build.d.ts +2 -1
- package/dist/structure/build.d.ts.map +1 -1
- package/dist/structure/build.js +105 -11
- package/dist/structure/build.js.map +1 -1
- package/dist/structure/date-order.d.ts +12 -0
- package/dist/structure/date-order.d.ts.map +1 -0
- package/dist/structure/date-order.js +30 -0
- package/dist/structure/date-order.js.map +1 -0
- package/dist/structure/issues.d.ts.map +1 -1
- package/dist/structure/issues.js +9 -2
- package/dist/structure/issues.js.map +1 -1
- package/dist/structure/of.d.ts +2 -1
- package/dist/structure/of.d.ts.map +1 -1
- package/dist/structure/of.js +7 -1
- package/dist/structure/of.js.map +1 -1
- package/dist/structure/relative-find.d.ts +8 -0
- package/dist/structure/relative-find.d.ts.map +1 -0
- package/dist/structure/relative-find.js +190 -0
- package/dist/structure/relative-find.js.map +1 -0
- package/dist/structure/relative-resolve.d.ts +3 -0
- package/dist/structure/relative-resolve.d.ts.map +1 -0
- package/dist/structure/relative-resolve.js +175 -0
- package/dist/structure/relative-resolve.js.map +1 -0
- package/dist/structure/runs.d.ts +20 -0
- package/dist/structure/runs.d.ts.map +1 -0
- package/dist/structure/runs.js +63 -0
- package/dist/structure/runs.js.map +1 -0
- package/dist/structure/total.d.ts +14 -0
- package/dist/structure/total.d.ts.map +1 -0
- package/dist/structure/total.js +119 -0
- package/dist/structure/total.js.map +1 -0
- package/dist/structure/unnumbered.d.ts +15 -0
- package/dist/structure/unnumbered.d.ts.map +1 -0
- package/dist/structure/unnumbered.js +28 -0
- package/dist/structure/unnumbered.js.map +1 -0
- package/dist/structure/unread.d.ts +8 -0
- package/dist/structure/unread.d.ts.map +1 -0
- package/dist/structure/unread.js +34 -0
- package/dist/structure/unread.js.map +1 -0
- package/dist/structure/weekday.d.ts +7 -0
- package/dist/structure/weekday.d.ts.map +1 -0
- package/dist/structure/weekday.js +21 -0
- package/dist/structure/weekday.js.map +1 -0
- package/package.json +4 -3
- package/profiles/statute.yaml +66 -0
- package/rules/date-order.yaml +26 -0
- package/rules/date-weekday-mismatch.yaml +26 -0
- package/rules/internal-jargon.yaml +1 -1
- package/rules/total-mismatch.yaml +26 -0
- package/skills/chaff/SKILL.md +24 -4
- package/src/address-chain.ts +113 -0
- package/src/cli-args.ts +1 -1
- package/src/cli-text.ts +4 -0
- package/src/cli.ts +20 -1
- package/src/commands/eval.ts +2 -1
- package/src/commands/feedback.ts +210 -0
- package/src/commands/test.ts +3 -2
- package/src/commands/tree.ts +3 -1
- package/src/config/by-path.ts +11 -3
- package/src/config/load.ts +5 -1
- package/src/detectors/char-shape.ts +5 -4
- package/src/detectors/index.ts +4 -1
- package/src/detectors/lexicon.ts +11 -1
- package/src/detectors/structure-tree.ts +67 -1
- package/src/detectors/team.ts +7 -2
- package/src/detectors/word-list.ts +2 -0
- package/src/document.ts +11 -3
- package/src/feedback/draft.ts +102 -0
- package/src/feedback/excerpt.ts +23 -0
- package/src/orthography.ts +15 -0
- package/src/plugin.ts +95 -0
- package/src/profile/for-file.ts +11 -0
- package/src/profile/load.ts +26 -0
- package/src/profile/parse.ts +119 -0
- package/src/profile/select.ts +35 -0
- package/src/reasons.ts +5 -0
- package/src/render/compact.ts +4 -1
- package/src/run.ts +42 -7
- package/src/structure/build.ts +135 -15
- package/src/structure/date-order.ts +44 -0
- package/src/structure/issues.ts +10 -2
- package/src/structure/of.ts +15 -3
- package/src/structure/relative-find.ts +237 -0
- package/src/structure/relative-resolve.ts +209 -0
- package/src/structure/runs.ts +71 -0
- package/src/structure/total.ts +157 -0
- package/src/structure/unnumbered.ts +33 -0
- package/src/structure/unread.ts +39 -0
- package/src/structure/weekday.ts +25 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chaffjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"README.md",
|
|
41
41
|
"bin",
|
|
42
42
|
"dist",
|
|
43
|
+
"profiles",
|
|
43
44
|
"rules",
|
|
44
45
|
"skills",
|
|
45
46
|
"src"
|
|
@@ -53,8 +54,8 @@
|
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
55
56
|
"@anthropic-ai/sdk": "^0.125.0",
|
|
56
|
-
"@chaffjs/lang-en": "0.
|
|
57
|
-
"@chaffjs/lang-ja": "0.
|
|
57
|
+
"@chaffjs/lang-en": "0.8.0",
|
|
58
|
+
"@chaffjs/lang-ja": "0.9.0",
|
|
58
59
|
"mdast-util-from-markdown": "^2.0.3",
|
|
59
60
|
"mdast-util-frontmatter": "^2.0.1",
|
|
60
61
|
"mdast-util-gfm-table": "^2.0.0",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
id: statute
|
|
2
|
+
|
|
3
|
+
name:
|
|
4
|
+
ja: 法令
|
|
5
|
+
en: Statute
|
|
6
|
+
|
|
7
|
+
why:
|
|
8
|
+
ja: 法令は条・項・号の番地を漢数字で書き(第二十二条第二項)、各号・及び・本文のような語で番地をつなぎます。番地は割れない一つの単位で、漢字の連なりとして数えません。
|
|
9
|
+
en: A statute writes its addresses in kanji numerals (第二十二条第二項) and joins them with words such as 各号, 及び or 本文. An address is one unit that cannot be broken up, and is not counted as a run of kanji.
|
|
10
|
+
|
|
11
|
+
# 設定(chaff.yaml の profile / by_path)が無いときに、内容から選ぶ。この形の行が min_lines 行以上あれば法令として読む。
|
|
12
|
+
detect:
|
|
13
|
+
ja:
|
|
14
|
+
line: "^第[〇一二三四五六七八九十百千]+条(?:の[〇一二三四五六七八九十百千]+)*[ ]"
|
|
15
|
+
min_lines: 3
|
|
16
|
+
|
|
17
|
+
ja:
|
|
18
|
+
# 番地の書き方。第 + 漢数字 + 単位、枝番号(の二)が続いてもよい。
|
|
19
|
+
# 「前二項」「前三条」は直前の二つの項・三つの条を指す番地で、数量ではない。「前二年」は期間なので書かない。
|
|
20
|
+
addresses:
|
|
21
|
+
- "第[〇一二三四五六七八九十百千]+[条項号章節款目編](?:の[〇一二三四五六七八九十百千]+)*"
|
|
22
|
+
- "前[〇一二三四五六七八九十百千]+[条項号章節款目編]"
|
|
23
|
+
# 番地と番地のあいだ、番地の後ろに付く語。
|
|
24
|
+
connectives: [各号, 各項, 及, 又, 若, 並, 中, 本文, 前段, 後段, 但書, 同条, 乃至]
|
|
25
|
+
# 番地の並びのすぐ後ろに来てよい文字。漢字が続けば、番地ではなく普通の語(第一条件、第五条中央銀行、前二項目標)。
|
|
26
|
+
address_end: "[^\\p{Script=Han}]|$"
|
|
27
|
+
# 条の見出しは、条の前の行に括弧だけで書く(「(解雇の予告)」)。括弧の中が、すぐ次の条の見出しになる。
|
|
28
|
+
caption: "^[ \\t\u3000]*[((]([^()()]{1,40})[))][ \\t\u3000]*$"
|
|
29
|
+
# 古い法令は第 2 項以降に番号を振らず、全角空白で字下げした行を新しい項(深さ 2)にする。
|
|
30
|
+
# 条の行に本文が続く条(「第三十四条 使用者は、…。」)の中だけ。契約書の「第1条(目的)」の次の字下げの行は、第 1 項の本文。
|
|
31
|
+
unnumbered: { indent: "\u3000", inside: article, depth: 2 }
|
|
32
|
+
# 書いた場所から番地が決まる参照(前条・次項・同号・本条・前各項・前二項・前条第二項)。
|
|
33
|
+
relative:
|
|
34
|
+
before: [前]
|
|
35
|
+
after: [次]
|
|
36
|
+
same: [同]
|
|
37
|
+
current: [本]
|
|
38
|
+
every: [各]
|
|
39
|
+
count: "[〇一二三四五六七八九十百千]+"
|
|
40
|
+
# 単位の語と、lang-ja が付ける木の深さ(編 -2・章 -1・節 0・条 1・項 2・号 3)。
|
|
41
|
+
units: { 編: -2, 章: -1, 節: 0, 条: 1, 項: 2, 号: 3 }
|
|
42
|
+
suffix_prefix: 第
|
|
43
|
+
# 法令は第 1 項に番号を振らない。
|
|
44
|
+
implicit_first: 項
|
|
45
|
+
# 漢字の後ろの「前条」は語の一部(事前条件)。後ろの決まりは address_end と connectives を使う。
|
|
46
|
+
not_after: "\\p{Script=Han}"
|
|
47
|
+
# 読み替え(「『前条』とあるのは『…』と読み替える」)の括弧の中の前条・同項は、読み替える先の文の言葉。
|
|
48
|
+
# 中の「第三条」も読み替える先の法令の番地なので、この文書の木では引かず、document の名前の文書を指す。
|
|
49
|
+
substitution: { open: 「, close: 」, after: [とあるのは], before: [とあるのは, とあるのは、], document: (読み替えの中) }
|
|
50
|
+
# 条を書かない「第一項」「第二号」は、書いた条・項の中を指す。
|
|
51
|
+
inside: [項, 号]
|
|
52
|
+
# 並びをつなぐもの(正規表現)。「第三十三条第七項若しくは第九項」の「第九項」は、前の参照(第三十三条)の続き。
|
|
53
|
+
# 号の下の「イ」「ロ」と枝番号の「の二」も、並びの途中に挟まる番地の一部。
|
|
54
|
+
joiners:
|
|
55
|
+
- 及び
|
|
56
|
+
- 並びに
|
|
57
|
+
- 若しくは
|
|
58
|
+
- 又は
|
|
59
|
+
- から
|
|
60
|
+
- まで
|
|
61
|
+
- ただし書
|
|
62
|
+
- 、
|
|
63
|
+
- "[イロハニホヘトチリヌルヲワカヨタレソツネナラム]"
|
|
64
|
+
- "の[〇一二三四五六七八九十百千]+"
|
|
65
|
+
# 並びの途中に挟まる括弧書き。
|
|
66
|
+
aside: { open: (, close: ) }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
id: date-order
|
|
2
|
+
layer: L3
|
|
3
|
+
status: experimental
|
|
4
|
+
how_to_find: date-order
|
|
5
|
+
requires: [dates]
|
|
6
|
+
severity: warning
|
|
7
|
+
|
|
8
|
+
name:
|
|
9
|
+
ja: 日程の順番が前後している
|
|
10
|
+
en: A date out of order in a schedule
|
|
11
|
+
|
|
12
|
+
why:
|
|
13
|
+
ja: 旅程やマイルストーンのように日付を並べた箇条書きや表で、ひとつだけ前の日付より早い日付があると、書き間違いか順番の入れ替わりかを読み手が確かめることになります。並びが古い順か新しい順かは多いほうで決め、それに逆らう日付だけを指します。
|
|
14
|
+
en: In a list or a table of dates, such as an itinerary or milestones, one date earlier than the one before it makes the reader check whether it is a typo or a swapped row. The direction of the list (oldest or newest first) is taken from most of its steps, and only a date against it is pointed out.
|
|
15
|
+
|
|
16
|
+
message:
|
|
17
|
+
ja: 「{date}」が、前後の日付の並びから外れています(前は「{previous}」)
|
|
18
|
+
en: "{date} breaks the order of the dates around it (after {previous})"
|
|
19
|
+
|
|
20
|
+
how_to_fix:
|
|
21
|
+
ja: 日付を確かめて、書き間違いなら直し、順番が違うなら並べ替えてください。
|
|
22
|
+
en: Check the date. Correct it if it is a typo, or move the row if the order is wrong.
|
|
23
|
+
|
|
24
|
+
levels: { strict: 1, normal: 1, relaxed: 1 }
|
|
25
|
+
|
|
26
|
+
use_for: [business, technical, blog]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
id: date-weekday-mismatch
|
|
2
|
+
layer: L3
|
|
3
|
+
status: experimental
|
|
4
|
+
how_to_find: date-weekday-mismatch
|
|
5
|
+
requires: [dates]
|
|
6
|
+
severity: error
|
|
7
|
+
|
|
8
|
+
name:
|
|
9
|
+
ja: 日付と曜日の食い違い
|
|
10
|
+
en: Date and weekday disagree
|
|
11
|
+
|
|
12
|
+
why:
|
|
13
|
+
ja: 「2026年10月1日(金)」のように、日付と曜日が食い違っていると、読み手はどちらが正しいのか確かめに行くことになります。日程を書き写すときや、年をまたいで予定を使い回すときに起きます。年まで書いた日付の曜日だけを、暦と比べます。
|
|
14
|
+
en: When a date and its weekday disagree ("Friday, 1 October 2026"), the reader has to find out which one is right. It happens when a schedule is copied, or reused for another year. Only dates written with their year are checked against the calendar.
|
|
15
|
+
|
|
16
|
+
message:
|
|
17
|
+
ja: 「{date}」は{actual}です({written}と書いてあります)
|
|
18
|
+
en: "{date} is a {actual}, not a {written}"
|
|
19
|
+
|
|
20
|
+
how_to_fix:
|
|
21
|
+
ja: 日付と曜日のどちらが正しいかを確かめて、片方を直してください。
|
|
22
|
+
en: Check which is right, the date or the weekday, and correct the other.
|
|
23
|
+
|
|
24
|
+
levels: { strict: 1, normal: 1, relaxed: 1 }
|
|
25
|
+
|
|
26
|
+
use_for: [business, technical, blog]
|
|
@@ -15,7 +15,7 @@ why:
|
|
|
15
15
|
|
|
16
16
|
message:
|
|
17
17
|
ja: 「{matched}」は社内でしか通じないかもしれません(そういう語が {count} 箇所)
|
|
18
|
-
en: '"{matched}" may only
|
|
18
|
+
en: '"{matched}" may only be understood inside your team ({count} such words)'
|
|
19
19
|
|
|
20
20
|
how_to_fix:
|
|
21
21
|
ja: 初出のところで普通の言葉に言い換えるか、括弧で説明を添えてください。jargon には辞書形(握る)でも語幹(巻き取)でも書けますが、複合動詞は語幹のほうが確実です。
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
id: total-mismatch
|
|
2
|
+
layer: L3
|
|
3
|
+
status: experimental
|
|
4
|
+
how_to_find: total-mismatch
|
|
5
|
+
requires: [quantities]
|
|
6
|
+
severity: error
|
|
7
|
+
|
|
8
|
+
name:
|
|
9
|
+
ja: 合計が内訳の和と合わない
|
|
10
|
+
en: A total that is not the sum of its items
|
|
11
|
+
|
|
12
|
+
why:
|
|
13
|
+
ja: 見積や請求の「合計」が、上に並べた金額の和と合わないと、読み手はどちらが正しいのかを計算し直すことになります。行を足したり消したりしたあとに、合計だけ直し忘れると起きます。箇条書きか表で、合計の語(合計・小計)で始まる行を、同じ単位で同じ列の金額の和と比べます。小計のあとの総計のように、足し方が何通りかあるときは、どの足し方でも合わないときだけ指します。
|
|
14
|
+
en: When the total of a quote or an invoice is not the sum of the amounts listed above it, the reader has to add them up again to find out which is right. It happens when a line is added or removed and the total is not updated. In a list or a table, a line starting with a total word (Total, Subtotal) is compared with the sum of the amounts in the same unit and column above it. When there is more than one way to add them up, such as a grand total after a subtotal and tax, it is pointed out only when none of them matches.
|
|
15
|
+
|
|
16
|
+
message:
|
|
17
|
+
ja: 合計「{written}」が、上の金額の和({sum})と合いません
|
|
18
|
+
en: "The total {written} is not the sum of the amounts above it ({sum})"
|
|
19
|
+
|
|
20
|
+
how_to_fix:
|
|
21
|
+
ja: 内訳の金額と合計のどちらが正しいかを確かめて、片方を直してください。
|
|
22
|
+
en: Check which is right, the items or the total, and correct the other.
|
|
23
|
+
|
|
24
|
+
levels: { strict: 1, normal: 1, relaxed: 1 }
|
|
25
|
+
|
|
26
|
+
use_for: [business, technical, blog]
|
package/skills/chaff/SKILL.md
CHANGED
|
@@ -17,10 +17,14 @@ npx chaffjs . --compact # two lines per finding: line:col severity me
|
|
|
17
17
|
npx chaffjs <file> --experimental # also the experimental rules
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
A run exits 1 when a finding is an `error
|
|
21
|
-
unknown `--genre`, no package for the language
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
A run exits 1 when a finding is an `error`. It also exits 1 when it could not check anything: no file to
|
|
21
|
+
check, an unknown `--genre`, no package for the language. So a clean exit means the files were checked.
|
|
22
|
+
|
|
23
|
+
After the findings comes the list of rules that did not run, and why. Only the default output shows it;
|
|
24
|
+
`--compact` gives just their number. Read it before saying a document is clean.
|
|
25
|
+
|
|
26
|
+
A run over several files prints only the files that have findings. To call one file clean, run chaff on that
|
|
27
|
+
file alone and read its list.
|
|
24
28
|
|
|
25
29
|
## For each finding, choose one of three
|
|
26
30
|
|
|
@@ -53,6 +57,22 @@ relaxing a rule.
|
|
|
53
57
|
- `npx chaffjs cite <source> <quotes.json>` checks that quotations (`[{ "address", "quote" }]`) are really in
|
|
54
58
|
the source; exit 1 if any is not. Use it to back every quotation you put in an answer or a summary.
|
|
55
59
|
|
|
60
|
+
## When chaff is wrong
|
|
61
|
+
|
|
62
|
+
If a finding is wrong for the text (a false positive), or chaff missed something it should catch, draft a report
|
|
63
|
+
for the chaff project:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
npx chaffjs feedback <file> --rule <rule-id> [--line N] # a wrong finding
|
|
67
|
+
npx chaffjs feedback <file> --missed --line N # something missed
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
It writes `.chaff-feedback.md`: chaff's version, Node and OS, the file name, the one finding, the two lines on
|
|
71
|
+
each side of it, and the reported rule's setting in `chaff.yaml` (all of `chaff.yaml` only with `--with-config`).
|
|
72
|
+
When a rule has several findings in the file, add `--line` to pick one. It prints how to send the draft
|
|
73
|
+
(`gh issue create …`, or a link that carries the title only). It sends nothing. Show the draft to the person and let
|
|
74
|
+
them decide; their document may be private.
|
|
75
|
+
|
|
56
76
|
## Do not
|
|
57
77
|
|
|
58
78
|
- Do not change what a document says to satisfy a rule. If a finding cannot be fixed without changing the
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { DocumentProfile, Span } from "./plugin.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 番地は、つなぎの語(各号・及び・本文 …)を挟んで続く。番地とつなぎの並び全体を一つとして探す。
|
|
5
|
+
* 番地の書き方、つなぎの語、並びの後ろに来てよい文字は文書の種類(profiles/*.yaml)が持ち、ここは並べ方だけを知る。
|
|
6
|
+
*
|
|
7
|
+
* 番地は、その位置で正規表現が当たるとおりに一度だけ取り、後ろに合わせて縮めない。縮めると「第五十二条の二中央銀行」の
|
|
8
|
+
* 「第五十二条」が番地になってしまう。並びは、番地かつなぎの語で終わる切れ目のうち、後ろの決まりを満たす最も長いところまで。
|
|
9
|
+
*/
|
|
10
|
+
const escape = (word: string): string => word.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
11
|
+
|
|
12
|
+
type Compiled = { readonly find: RegExp; readonly address: RegExp; readonly connective: RegExp | undefined; readonly end: RegExp | undefined };
|
|
13
|
+
|
|
14
|
+
const compile = (profile: DocumentProfile): Compiled | undefined => {
|
|
15
|
+
if (profile.addresses.length === 0) return undefined;
|
|
16
|
+
const address = `(?:${profile.addresses.join("|")})`;
|
|
17
|
+
return {
|
|
18
|
+
find: new RegExp(address, "gu"),
|
|
19
|
+
address: new RegExp(address, "uy"),
|
|
20
|
+
connective: profile.connectives.length === 0 ? undefined : new RegExp(`(?:${profile.connectives.map(escape).join("|")})`, "uy"),
|
|
21
|
+
end: profile.addressEnd === undefined ? undefined : new RegExp(`(?=${profile.addressEnd})`, "uy"),
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const compiled = new WeakMap<DocumentProfile, Compiled | undefined>();
|
|
26
|
+
|
|
27
|
+
const patternsOf = (profile: DocumentProfile): Compiled | undefined => {
|
|
28
|
+
if (!compiled.has(profile)) compiled.set(profile, compile(profile));
|
|
29
|
+
return compiled.get(profile);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** at から pattern が当たれば、その終わり。空の一致は当たらなかったことにする。 */
|
|
33
|
+
const matchEnd = (pattern: RegExp | undefined, text: string, at: number): number | undefined => {
|
|
34
|
+
if (pattern === undefined) return undefined;
|
|
35
|
+
pattern.lastIndex = at;
|
|
36
|
+
const found = pattern.exec(text);
|
|
37
|
+
return found === null || found[0] === "" ? undefined : at + found[0].length;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const endsHere = (patterns: Compiled, text: string, at: number): boolean => {
|
|
41
|
+
if (patterns.end === undefined) return true;
|
|
42
|
+
patterns.end.lastIndex = at;
|
|
43
|
+
return patterns.end.test(text);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type Cut = { readonly end: number; readonly addresses: number };
|
|
47
|
+
|
|
48
|
+
/** at から続くつなぎの語を読み、その切れ目を cuts に足す。読み終えた位置を返す。 */
|
|
49
|
+
const readConnectives = (patterns: Compiled, text: string, at: number, addresses: number, cuts: Cut[]): number => {
|
|
50
|
+
let position = at;
|
|
51
|
+
for (let next = matchEnd(patterns.connective, text, position); next !== undefined; next = matchEnd(patterns.connective, text, position)) {
|
|
52
|
+
position = next;
|
|
53
|
+
cuts.push({ end: position, addresses });
|
|
54
|
+
}
|
|
55
|
+
return position;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 番地の後ろから並びを読み、切れ目(番地かつなぎの語の終わり)と、そこまでの番地を集める。
|
|
60
|
+
* 並びは設定しだいでいくらでも長くなるので、再帰せずに読む。
|
|
61
|
+
*/
|
|
62
|
+
const readChain = (patterns: Compiled, text: string, first: Span): { spans: Span[]; cuts: Cut[] } => {
|
|
63
|
+
const spans: Span[] = [first];
|
|
64
|
+
const cuts: Cut[] = [{ end: first.end, addresses: 1 }];
|
|
65
|
+
let at = first.end;
|
|
66
|
+
for (;;) {
|
|
67
|
+
const next = readConnectives(patterns, text, at, spans.length, cuts);
|
|
68
|
+
const addressEnd = matchEnd(patterns.address, text, next);
|
|
69
|
+
if (addressEnd === undefined) return { spans, cuts };
|
|
70
|
+
spans.push({ start: next, end: addressEnd });
|
|
71
|
+
cuts.push({ end: addressEnd, addresses: spans.length });
|
|
72
|
+
at = addressEnd;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/** start から始まる並び。後ろの決まりを満たす切れ目が無ければ、並びではない。 */
|
|
77
|
+
const chainAt = (patterns: Compiled, text: string, start: number): { readonly spans: readonly Span[]; readonly end: number } | undefined => {
|
|
78
|
+
const firstEnd = matchEnd(patterns.address, text, start);
|
|
79
|
+
if (firstEnd === undefined) return undefined;
|
|
80
|
+
const { spans, cuts } = readChain(patterns, text, { start, end: firstEnd });
|
|
81
|
+
const cut = [...cuts].reverse().find((candidate) => endsHere(patterns, text, candidate.end));
|
|
82
|
+
return cut === undefined ? undefined : { spans: spans.slice(0, cut.addresses), end: cut.end };
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** 1 文字先。絵文字のような 2 単位の文字の途中に止まると、同じ位置の空の一致を繰り返す。 */
|
|
86
|
+
const nextCharacter = (text: string, at: number): number => at + ((text.codePointAt(at) ?? 0) > 0xffff ? 2 : 1);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 並びとして認めた範囲の中の番地の範囲。「第一条件」「前二項中央銀行」のように後ろが語に続くものは、並びにならないので含まない。
|
|
90
|
+
* 漢字の連なりを数える側も数量を読む側も、同じ範囲を使う。
|
|
91
|
+
*/
|
|
92
|
+
export const addressSpans = (text: string, profile: DocumentProfile | undefined): Span[] => {
|
|
93
|
+
const patterns = profile === undefined ? undefined : patternsOf(profile);
|
|
94
|
+
if (patterns === undefined) return [];
|
|
95
|
+
const spans: Span[] = [];
|
|
96
|
+
patterns.find.lastIndex = 0;
|
|
97
|
+
for (let found = patterns.find.exec(text); found !== null; found = patterns.find.exec(text)) {
|
|
98
|
+
const chain = chainAt(patterns, text, found.index);
|
|
99
|
+
spans.push(...(chain?.spans ?? []));
|
|
100
|
+
patterns.find.lastIndex = chain === undefined ? nextCharacter(text, found.index) : chain.end;
|
|
101
|
+
}
|
|
102
|
+
return spans;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** 番地を空白にする。空白が連なりを切るので、番地は数えられず、前後は別の連なりになる。 */
|
|
106
|
+
export const maskAddresses = (text: string, profile: DocumentProfile | undefined): string => {
|
|
107
|
+
const spans = addressSpans(text, profile);
|
|
108
|
+
if (spans.length === 0) return text;
|
|
109
|
+
const pieces: string[] = [];
|
|
110
|
+
spans.forEach((span, index) => pieces.push(text.slice(spans[index - 1]?.end ?? 0, span.start), " "));
|
|
111
|
+
pieces.push(text.slice(spans.at(-1)?.end ?? 0));
|
|
112
|
+
return pieces.join("");
|
|
113
|
+
};
|
package/src/cli-args.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Options of the commands that take files to check, whose value is the next argument. That value is not a file.
|
|
3
3
|
* `--why` (relax), `--format` and `--language` (tree) belong to commands that read their own arguments.
|
|
4
4
|
*/
|
|
5
|
-
export const VALUE_FLAGS: readonly string[] = ["--sarif", "--genre", "--rule"];
|
|
5
|
+
export const VALUE_FLAGS: readonly string[] = ["--sarif", "--genre", "--rule", "--line"];
|
|
6
6
|
|
|
7
7
|
/** The arguments that name what to check: everything that is neither an option nor an option's value. */
|
|
8
8
|
export const targetsOf = (args: readonly string[]): string[] =>
|
package/src/cli-text.ts
CHANGED
|
@@ -22,6 +22,8 @@ const USAGE_JA = `chaff — 文章の読みにくいところを見つけます
|
|
|
22
22
|
chaff suppressions <dir> stet で黙らせている指摘を数える
|
|
23
23
|
chaff relax|strict|off <rule> [--why "理由"]
|
|
24
24
|
chaff skill [--global] Claude Code の skill を入れる(.claude/skills/chaff/、--global で ~/.claude/)
|
|
25
|
+
chaff feedback <file> --rule <rule> [--line N] | --missed --line N
|
|
26
|
+
誤った指摘・見逃しの報告の下書きを作る(何も送らない。--with-config で chaff.yaml 全体も載せる)
|
|
25
27
|
|
|
26
28
|
--compact エンジニア向けの 1 行形式
|
|
27
29
|
--experimental 試験中の rule も動かす
|
|
@@ -53,6 +55,8 @@ const USAGE_EN = `chaff — finds what makes writing hard to read. It never rewr
|
|
|
53
55
|
chaff suppressions <dir> count the findings silenced with stet
|
|
54
56
|
chaff relax|strict|off <rule> [--why "reason"]
|
|
55
57
|
chaff skill [--global] install the Claude Code skill (.claude/skills/chaff/; --global for ~/.claude/)
|
|
58
|
+
chaff feedback <file> --rule <rule> [--line N] | --missed --line N
|
|
59
|
+
draft a report of a wrong or missed finding (sends nothing; --with-config adds all of chaff.yaml)
|
|
56
60
|
|
|
57
61
|
--compact one line per finding, for engineers
|
|
58
62
|
--experimental run the experimental rules too
|
package/src/cli.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { VERSION } from "./version.ts";
|
|
|
27
27
|
import { runTree, treeTargets, type TreeContext } from "./commands/tree.ts";
|
|
28
28
|
import { citeTargets, runCite } from "./commands/cite.ts";
|
|
29
29
|
import { runSkill } from "./commands/skill.ts";
|
|
30
|
+
import { runFeedback, settingsOf } from "./commands/feedback.ts";
|
|
30
31
|
import { homedir } from "node:os";
|
|
31
32
|
import { ruleProblems } from "./config/rule-problems.ts";
|
|
32
33
|
import { renderSummary, type FileOutcome } from "./render/summary.ts";
|
|
@@ -34,6 +35,7 @@ import { neededBy, runRules } from "./run.ts";
|
|
|
34
35
|
import type { Level, RuleDefinition } from "./plugin.ts";
|
|
35
36
|
import { CLI_TEXT, type CliText, type GenreSource } from "./cli-text.ts";
|
|
36
37
|
import { hostLanguage, uiLanguageOf, type UiLanguage } from "./ui.ts";
|
|
38
|
+
import { profileFor } from "./profile/for-file.ts";
|
|
37
39
|
|
|
38
40
|
/** Text for output that is not about one document. */
|
|
39
41
|
const hostText = (config: Config): CliText => CLI_TEXT[hostLanguage(config.language, process.env)];
|
|
@@ -82,6 +84,7 @@ type Inspected = {
|
|
|
82
84
|
readonly rules: readonly RuleDefinition[];
|
|
83
85
|
/** ファイルごとの言語。by_path で 1 つの repo に 2 言語が混ざる。 */
|
|
84
86
|
readonly language: string;
|
|
87
|
+
readonly genre: string;
|
|
85
88
|
readonly outcome: FileOutcome;
|
|
86
89
|
readonly perFile: PerFile;
|
|
87
90
|
readonly all: readonly string[];
|
|
@@ -101,7 +104,7 @@ const inspect = async (path: string, config: Config, argv: readonly string[]): P
|
|
|
101
104
|
const rules = loadRules(language);
|
|
102
105
|
const experimental = config.experimental || argv.includes("--experimental");
|
|
103
106
|
await adapter.prepare?.(neededBy(rules, config.rules, experimental, genre, language));
|
|
104
|
-
const doc = buildDocument(path, source, adapter, teamRules(config));
|
|
107
|
+
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language));
|
|
105
108
|
const raw = runRules(doc, rules, config.rules, experimental, genre, config.limits);
|
|
106
109
|
// 応答は 3 つ。stet で黙らせたものは、ここで落とす。
|
|
107
110
|
const applied = applySuppressions(
|
|
@@ -118,6 +121,7 @@ const inspect = async (path: string, config: Config, argv: readonly string[]): P
|
|
|
118
121
|
text,
|
|
119
122
|
rules,
|
|
120
123
|
language,
|
|
124
|
+
genre,
|
|
121
125
|
outcome: { path, findings: split.fresh, notRun: raw.skipped.length },
|
|
122
126
|
perFile: { path, suppressed: applied.suppressed, reasonless: applied.unusedReasonless },
|
|
123
127
|
all: applied.kept.map((finding) => fingerprint(path, finding)),
|
|
@@ -297,6 +301,21 @@ const HANDLERS: Readonly<Record<string, Handler>> = {
|
|
|
297
301
|
relax: (argv) => changeSetting("relaxed", argv[1], flag(argv, "--why")),
|
|
298
302
|
strict: (argv) => changeSetting("strict", argv[1], flag(argv, "--why")),
|
|
299
303
|
off: (argv) => changeSetting("off", argv[1], flag(argv, "--why")),
|
|
304
|
+
feedback: (argv) => {
|
|
305
|
+
const config = readConfig();
|
|
306
|
+
return runFeedback(positional(argv), argv, {
|
|
307
|
+
cwd: process.cwd(),
|
|
308
|
+
ui: hostLanguage(config.language, process.env),
|
|
309
|
+
version: VERSION,
|
|
310
|
+
runtime: `Node ${process.version} · ${process.platform} ${process.arch}`,
|
|
311
|
+
flag,
|
|
312
|
+
settingsOf: (ruleIds) => settingsOf(config, ruleIds),
|
|
313
|
+
check: async (path) => {
|
|
314
|
+
const inspected = await inspect(path, config, [...argv, "--show-baseline"]);
|
|
315
|
+
return { findings: inspected.outcome.findings, rules: inspected.rules, language: inspected.language, genre: inspected.genre };
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
},
|
|
300
319
|
skill: (argv) => runSkill(argv, { cwd: process.cwd(), home: homedir(), ui: hostLanguage(readConfig().language, process.env) }),
|
|
301
320
|
};
|
|
302
321
|
|
package/src/commands/eval.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { evaluate } from "../eval.ts";
|
|
|
9
9
|
import { renderEval } from "../render/eval.ts";
|
|
10
10
|
import { neededBy } from "../run.ts";
|
|
11
11
|
import type { Config } from "../config/load.ts";
|
|
12
|
+
import { profileFor } from "../profile/for-file.ts";
|
|
12
13
|
|
|
13
14
|
export type Context = {
|
|
14
15
|
readonly config: Config;
|
|
@@ -35,7 +36,7 @@ export const runEval = async (targets: readonly string[], argv: readonly string[
|
|
|
35
36
|
const adapter = await loadAdapter(language);
|
|
36
37
|
const { genre } = resolveGenre(path, source, config);
|
|
37
38
|
await adapter.prepare?.(neededBy(loadRules(language), config.rules, config.experimental, genre, language));
|
|
38
|
-
return { doc: buildDocument(path, source, adapter, teamRules(config)), language, genre };
|
|
39
|
+
return { doc: buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language)), language, genre };
|
|
39
40
|
}),
|
|
40
41
|
);
|
|
41
42
|
const language = docs[0]?.language ?? "ja";
|