chaffjs 0.7.0 → 0.9.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 (215) hide show
  1. package/README.md +11 -4
  2. package/dist/adapter-load.d.ts +12 -1
  3. package/dist/adapter-load.d.ts.map +1 -1
  4. package/dist/adapter-load.js +37 -13
  5. package/dist/adapter-load.js.map +1 -1
  6. package/dist/address-chain.d.ts +9 -0
  7. package/dist/address-chain.d.ts.map +1 -0
  8. package/dist/address-chain.js +105 -0
  9. package/dist/address-chain.js.map +1 -0
  10. package/dist/cli-args.d.ts +8 -0
  11. package/dist/cli-args.d.ts.map +1 -0
  12. package/dist/cli-args.js +8 -0
  13. package/dist/cli-args.js.map +1 -0
  14. package/dist/cli-text.d.ts +26 -0
  15. package/dist/cli-text.d.ts.map +1 -0
  16. package/dist/cli-text.js +155 -0
  17. package/dist/cli-text.js.map +1 -0
  18. package/dist/cli.d.ts +2 -0
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +94 -87
  21. package/dist/cli.js.map +1 -1
  22. package/dist/commands/cite.d.ts +2 -1
  23. package/dist/commands/cite.d.ts.map +1 -1
  24. package/dist/commands/cite.js +39 -18
  25. package/dist/commands/cite.js.map +1 -1
  26. package/dist/commands/eval.d.ts.map +1 -1
  27. package/dist/commands/eval.js +2 -1
  28. package/dist/commands/eval.js.map +1 -1
  29. package/dist/commands/feedback.d.ts +35 -0
  30. package/dist/commands/feedback.d.ts.map +1 -0
  31. package/dist/commands/feedback.js +157 -0
  32. package/dist/commands/feedback.js.map +1 -0
  33. package/dist/commands/skill.d.ts +22 -0
  34. package/dist/commands/skill.d.ts.map +1 -0
  35. package/dist/commands/skill.js +68 -0
  36. package/dist/commands/skill.js.map +1 -0
  37. package/dist/commands/test.d.ts.map +1 -1
  38. package/dist/commands/test.js +3 -2
  39. package/dist/commands/test.js.map +1 -1
  40. package/dist/commands/tree.d.ts +11 -1
  41. package/dist/commands/tree.d.ts.map +1 -1
  42. package/dist/commands/tree.js +21 -7
  43. package/dist/commands/tree.js.map +1 -1
  44. package/dist/config/by-path.d.ts +5 -1
  45. package/dist/config/by-path.d.ts.map +1 -1
  46. package/dist/config/by-path.js +1 -4
  47. package/dist/config/by-path.js.map +1 -1
  48. package/dist/config/load.d.ts +2 -0
  49. package/dist/config/load.d.ts.map +1 -1
  50. package/dist/config/load.js +3 -1
  51. package/dist/config/load.js.map +1 -1
  52. package/dist/config/rule-problems.d.ts +2 -1
  53. package/dist/config/rule-problems.d.ts.map +1 -1
  54. package/dist/config/rule-problems.js +17 -6
  55. package/dist/config/rule-problems.js.map +1 -1
  56. package/dist/config/write.d.ts.map +1 -1
  57. package/dist/config/write.js +29 -9
  58. package/dist/config/write.js.map +1 -1
  59. package/dist/detectors/char-shape.d.ts.map +1 -1
  60. package/dist/detectors/char-shape.js +3 -2
  61. package/dist/detectors/char-shape.js.map +1 -1
  62. package/dist/document.d.ts +2 -2
  63. package/dist/document.d.ts.map +1 -1
  64. package/dist/document.js +4 -2
  65. package/dist/document.js.map +1 -1
  66. package/dist/feedback/draft.d.ts +28 -0
  67. package/dist/feedback/draft.d.ts.map +1 -0
  68. package/dist/feedback/draft.js +66 -0
  69. package/dist/feedback/draft.js.map +1 -0
  70. package/dist/feedback/excerpt.d.ts +10 -0
  71. package/dist/feedback/excerpt.d.ts.map +1 -0
  72. package/dist/feedback/excerpt.js +20 -0
  73. package/dist/feedback/excerpt.js.map +1 -0
  74. package/dist/genre.d.ts +1 -1
  75. package/dist/genre.d.ts.map +1 -1
  76. package/dist/genre.js +3 -3
  77. package/dist/genre.js.map +1 -1
  78. package/dist/init.d.ts +2 -1
  79. package/dist/init.d.ts.map +1 -1
  80. package/dist/init.js +62 -13
  81. package/dist/init.js.map +1 -1
  82. package/dist/legal-address.d.ts +2 -0
  83. package/dist/legal-address.d.ts.map +1 -0
  84. package/dist/legal-address.js +16 -0
  85. package/dist/legal-address.js.map +1 -0
  86. package/dist/orthography.d.ts.map +1 -1
  87. package/dist/orthography.js +15 -0
  88. package/dist/orthography.js.map +1 -1
  89. package/dist/plugin.d.ts +94 -0
  90. package/dist/plugin.d.ts.map +1 -1
  91. package/dist/profile/for-file.d.ts +7 -0
  92. package/dist/profile/for-file.d.ts.map +1 -0
  93. package/dist/profile/for-file.js +6 -0
  94. package/dist/profile/for-file.js.map +1 -0
  95. package/dist/profile/load.d.ts +4 -0
  96. package/dist/profile/load.d.ts.map +1 -0
  97. package/dist/profile/load.js +25 -0
  98. package/dist/profile/load.js.map +1 -0
  99. package/dist/profile/parse.d.ts +19 -0
  100. package/dist/profile/parse.d.ts.map +1 -0
  101. package/dist/profile/parse.js +82 -0
  102. package/dist/profile/parse.js.map +1 -0
  103. package/dist/profile/select.d.ts +22 -0
  104. package/dist/profile/select.d.ts.map +1 -0
  105. package/dist/profile/select.js +21 -0
  106. package/dist/profile/select.js.map +1 -0
  107. package/dist/reasons.d.ts +16 -0
  108. package/dist/reasons.d.ts.map +1 -0
  109. package/dist/reasons.js +31 -0
  110. package/dist/reasons.js.map +1 -0
  111. package/dist/render/explain.d.ts.map +1 -1
  112. package/dist/render/explain.js +36 -12
  113. package/dist/render/explain.js.map +1 -1
  114. package/dist/render/friendly.d.ts.map +1 -1
  115. package/dist/render/friendly.js +32 -9
  116. package/dist/render/friendly.js.map +1 -1
  117. package/dist/render/rules-json.d.ts.map +1 -1
  118. package/dist/render/rules-json.js +47 -27
  119. package/dist/render/rules-json.js.map +1 -1
  120. package/dist/render/summary.d.ts +3 -2
  121. package/dist/render/summary.d.ts.map +1 -1
  122. package/dist/render/summary.js +24 -9
  123. package/dist/render/summary.js.map +1 -1
  124. package/dist/render/suppressions.d.ts +2 -1
  125. package/dist/render/suppressions.d.ts.map +1 -1
  126. package/dist/render/suppressions.js +34 -10
  127. package/dist/render/suppressions.js.map +1 -1
  128. package/dist/run.d.ts.map +1 -1
  129. package/dist/run.js +41 -12
  130. package/dist/run.js.map +1 -1
  131. package/dist/structure/build.d.ts +2 -1
  132. package/dist/structure/build.d.ts.map +1 -1
  133. package/dist/structure/build.js +102 -9
  134. package/dist/structure/build.js.map +1 -1
  135. package/dist/structure/cite.d.ts.map +1 -1
  136. package/dist/structure/cite.js +3 -1
  137. package/dist/structure/cite.js.map +1 -1
  138. package/dist/structure/issues.d.ts +5 -2
  139. package/dist/structure/issues.d.ts.map +1 -1
  140. package/dist/structure/issues.js +35 -10
  141. package/dist/structure/issues.js.map +1 -1
  142. package/dist/structure/numbering.d.ts.map +1 -1
  143. package/dist/structure/numbering.js +11 -6
  144. package/dist/structure/numbering.js.map +1 -1
  145. package/dist/structure/of.d.ts +2 -1
  146. package/dist/structure/of.d.ts.map +1 -1
  147. package/dist/structure/of.js +7 -1
  148. package/dist/structure/of.js.map +1 -1
  149. package/dist/structure/relative-find.d.ts +6 -0
  150. package/dist/structure/relative-find.d.ts.map +1 -0
  151. package/dist/structure/relative-find.js +179 -0
  152. package/dist/structure/relative-find.js.map +1 -0
  153. package/dist/structure/relative-resolve.d.ts +3 -0
  154. package/dist/structure/relative-resolve.d.ts.map +1 -0
  155. package/dist/structure/relative-resolve.js +175 -0
  156. package/dist/structure/relative-resolve.js.map +1 -0
  157. package/dist/structure/unread.d.ts +8 -0
  158. package/dist/structure/unread.d.ts.map +1 -0
  159. package/dist/structure/unread.js +34 -0
  160. package/dist/structure/unread.js.map +1 -0
  161. package/dist/ui.d.ts +14 -0
  162. package/dist/ui.d.ts.map +1 -0
  163. package/dist/ui.js +14 -0
  164. package/dist/ui.js.map +1 -0
  165. package/dist/watch.d.ts +1 -1
  166. package/dist/watch.d.ts.map +1 -1
  167. package/dist/watch.js +2 -2
  168. package/dist/watch.js.map +1 -1
  169. package/package.json +5 -3
  170. package/profiles/statute.yaml +62 -0
  171. package/skills/chaff/SKILL.md +81 -0
  172. package/src/adapter-load.ts +41 -10
  173. package/src/address-chain.ts +113 -0
  174. package/src/cli-args.ts +9 -0
  175. package/src/cli-text.ts +186 -0
  176. package/src/cli.ts +111 -90
  177. package/src/commands/cite.ts +51 -18
  178. package/src/commands/eval.ts +2 -1
  179. package/src/commands/feedback.ts +210 -0
  180. package/src/commands/skill.ts +74 -0
  181. package/src/commands/test.ts +3 -2
  182. package/src/commands/tree.ts +30 -8
  183. package/src/config/by-path.ts +11 -3
  184. package/src/config/load.ts +5 -1
  185. package/src/config/rule-problems.ts +24 -5
  186. package/src/config/write.ts +33 -9
  187. package/src/detectors/char-shape.ts +5 -4
  188. package/src/document.ts +11 -3
  189. package/src/feedback/draft.ts +102 -0
  190. package/src/feedback/excerpt.ts +23 -0
  191. package/src/genre.ts +4 -4
  192. package/src/init.ts +74 -15
  193. package/src/orthography.ts +15 -0
  194. package/src/plugin.ts +88 -0
  195. package/src/profile/for-file.ts +11 -0
  196. package/src/profile/load.ts +26 -0
  197. package/src/profile/parse.ts +106 -0
  198. package/src/profile/select.ts +35 -0
  199. package/src/reasons.ts +49 -0
  200. package/src/render/explain.ts +46 -12
  201. package/src/render/friendly.ts +42 -10
  202. package/src/render/rules-json.ts +35 -9
  203. package/src/render/summary.ts +34 -9
  204. package/src/render/suppressions.ts +45 -10
  205. package/src/run.ts +44 -11
  206. package/src/structure/build.ts +128 -12
  207. package/src/structure/cite.ts +3 -1
  208. package/src/structure/issues.ts +42 -15
  209. package/src/structure/numbering.ts +12 -7
  210. package/src/structure/of.ts +15 -3
  211. package/src/structure/relative-find.ts +224 -0
  212. package/src/structure/relative-resolve.ts +209 -0
  213. package/src/structure/unread.ts +39 -0
  214. package/src/ui.ts +23 -0
  215. package/src/watch.ts +2 -2
package/dist/watch.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"watch.js","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaffjs",
3
- "version": "0.7.0",
3
+ "version": "0.9.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,7 +40,9 @@
40
40
  "README.md",
41
41
  "bin",
42
42
  "dist",
43
+ "profiles",
43
44
  "rules",
45
+ "skills",
44
46
  "src"
45
47
  ],
46
48
  "engines": {
@@ -52,8 +54,8 @@
52
54
  },
53
55
  "dependencies": {
54
56
  "@anthropic-ai/sdk": "^0.125.0",
55
- "@chaffjs/lang-en": "0.6.0",
56
- "@chaffjs/lang-ja": "0.6.0",
57
+ "@chaffjs/lang-en": "0.7.0",
58
+ "@chaffjs/lang-ja": "0.8.0",
57
59
  "mdast-util-from-markdown": "^2.0.3",
58
60
  "mdast-util-frontmatter": "^2.0.1",
59
61
  "mdast-util-gfm-table": "^2.0.0",
@@ -0,0 +1,62 @@
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
+ # 書いた場所から番地が決まる参照(前条・次項・同号・本条・前各項・前二項・前条第二項)。
30
+ relative:
31
+ before: [前]
32
+ after: [次]
33
+ same: [同]
34
+ current: [本]
35
+ every: [各]
36
+ count: "[〇一二三四五六七八九十百千]+"
37
+ # 単位の語と、lang-ja が付ける木の深さ(編 -2・章 -1・節 0・条 1・項 2・号 3)。
38
+ units: { 編: -2, 章: -1, 節: 0, 条: 1, 項: 2, 号: 3 }
39
+ suffix_prefix: 第
40
+ # 法令は第 1 項に番号を振らない。
41
+ implicit_first: 項
42
+ # 漢字の後ろの「前条」は語の一部(事前条件)。後ろの決まりは address_end と connectives を使う。
43
+ not_after: "\\p{Script=Han}"
44
+ # 読み替え(「『前条』とあるのは『…』と読み替える」)の括弧の中の前条・同項は、読み替える先の文の言葉。
45
+ substitution: { open: 「, close: 」, after: [とあるのは], before: [とあるのは, とあるのは、] }
46
+ # 条を書かない「第一項」「第二号」は、書いた条・項の中を指す。
47
+ inside: [項, 号]
48
+ # 並びをつなぐもの(正規表現)。「第三十三条第七項若しくは第九項」の「第九項」は、前の参照(第三十三条)の続き。
49
+ # 号の下の「イ」「ロ」と枝番号の「の二」も、並びの途中に挟まる番地の一部。
50
+ joiners:
51
+ - 及び
52
+ - 並びに
53
+ - 若しくは
54
+ - 又は
55
+ - から
56
+ - まで
57
+ - ただし書
58
+ - 、
59
+ - "[イロハニホヘトチリヌルヲワカヨタレソツネナラム]"
60
+ - "の[〇一二三四五六七八九十百千]+"
61
+ # 並びの途中に挟まる括弧書き。
62
+ aside: { open: (, close: ) }
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: chaff
3
+ description: "Check prose with chaff (npx chaffjs): run it on Markdown or text, read each finding, fix the text or record why not, and tune chaff.yaml with rules --json and relax --why. Use when asked to lint, proofread or tighten documents, or to set up a team's writing rules."
4
+ ---
5
+
6
+ # Checking writing with chaff
7
+
8
+ chaff finds what makes writing hard to read — long sentences, padding, mixed styles, broken references in a
9
+ contract — and never rewrites anything. Reading the findings and changing the text is your part. Japanese and
10
+ English documents both work; the language is detected per file.
11
+
12
+ ## Run it
13
+
14
+ ```sh
15
+ npx chaffjs <file|dir|glob>... # human-readable, one block per finding (a folder or glob picks up .md / .markdown / .mdx; name a .txt file directly)
16
+ npx chaffjs . --compact # two lines per finding: line:col severity message, then the rule id
17
+ npx chaffjs <file> --experimental # also the experimental rules
18
+ ```
19
+
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.
28
+
29
+ ## For each finding, choose one of three
30
+
31
+ | | When | How |
32
+ | --- | --- | --- |
33
+ | Fix the text | The finding is right | Edit the sentence. Keep the meaning, the facts and the numbers; change only how it is said. |
34
+ | Silence that spot | Right in general, wrong here (a quotation, a term that must repeat) | `<!-- stet: rule-id — reason -->` before it; `stet-section` / `stet-file` for wider scope. Always give the reason. |
35
+ | Change the rule | Wrong for this team or genre | `npx chaffjs relax <rule-id> --why "reason"` (or `strict` / `off`). The reason and date are written into `chaff.yaml`. |
36
+
37
+ `npx chaffjs explain <rule-id>` shows why a rule exists, its levels and the current one. Read it before
38
+ relaxing a rule.
39
+
40
+ ## Tune chaff.yaml
41
+
42
+ - `npx chaffjs init` writes a commented `chaff.yaml` when there is none (an existing one is left as it is), and
43
+ adds `.chaff-cache/` and `.env*` to `.gitignore`.
44
+ - `npx chaffjs rules --json` prints every rule with its levels, the setting in effect (`now`) and why a rule is
45
+ off. Read it instead of guessing rule names or limits.
46
+ - A level is `strict` / `normal` / `relaxed` / `off`; when even `relaxed` is too tight, a positive number is the
47
+ limit itself (`max-sentence-length: 260`).
48
+ - `prefer:` maps spellings to the team's (`e-mail: email`); `preferred-term: normal` turns the rule on.
49
+ - Unknown rule names and unreadable values are reported on stderr — read stderr after editing `chaff.yaml`.
50
+ - `genre:` sets the kind of document (`npx chaffjs genres` lists them); `--genre` overrides it for one run.
51
+
52
+ ## Structured documents
53
+
54
+ - `npx chaffjs tree <file>` shows a contract or specification as a tree of addresses (`3.2` for 第3条第2項 or
55
+ Section 3.2). `--experimental` adds the structure rules: references to missing provisions, numbering gaps,
56
+ terms defined twice.
57
+ - `npx chaffjs cite <source> <quotes.json>` checks that quotations (`[{ "address", "quote" }]`) are really in
58
+ the source; exit 1 if any is not. Use it to back every quotation you put in an answer or a summary.
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
+
76
+ ## Do not
77
+
78
+ - Do not change what a document says to satisfy a rule. If a finding cannot be fixed without changing the
79
+ meaning, leave it and say so.
80
+ - Do not turn a rule off to make a run pass; relax it with a reason the team would accept.
81
+ - Do not report "no findings" without reading the not-run list.
@@ -59,13 +59,41 @@ const LANGUAGE_ID = /^[a-z]{2,3}$/u;
59
59
  export const packageFor = (language: string): string | undefined =>
60
60
  ADAPTER_PACKAGE[language] ?? (LANGUAGE_ID.test(language) ? `@chaffjs/lang-${language}` : undefined);
61
61
 
62
- const importAdapter = async (specifier: string, language: string): Promise<unknown> => {
62
+ /**
63
+ * 探す順。公式(`@chaffjs/lang-<言語>`)が先で、無ければ第三者の `chaff-lang-<言語>`。
64
+ * `@typescript-eslint/*` と `eslint-plugin-*` の分け方と同じで、README にもそう書いてある。同梱の言語は表のものだけ。
65
+ */
66
+ export const packagesFor = (language: string): readonly string[] => {
67
+ if (ADAPTER_PACKAGE[language] !== undefined) return [ADAPTER_PACKAGE[language]];
68
+ return LANGUAGE_ID.test(language) ? [`@chaffjs/lang-${language}`, `chaff-lang-${language}`] : [];
69
+ };
70
+
71
+ export type Importer = (specifier: string) => Promise<unknown>;
72
+
73
+ /**
74
+ * そのパッケージ自体が入っていないときだけ真。入っているパッケージが自分の依存を見つけられないときも同じ
75
+ * ERR_MODULE_NOT_FOUND になるので、Node の文言が探した名前を挙げているかで分ける。
76
+ */
77
+ export const isAbsent = (err: unknown, specifier: string): boolean =>
78
+ err instanceof Error && "code" in err && err.code === "ERR_MODULE_NOT_FOUND" && err.message.includes(`'${specifier}'`);
79
+
80
+ /** 入っている最初のものを読む。入っていないのは次へ。入っていて壊れているものは、次へ行かずにそのまま投げる。 */
81
+ const importFirst = async (specifiers: readonly string[], language: string, importer: Importer): Promise<{ specifier: string; module: unknown }> => {
82
+ const [first, ...rest] = specifiers;
83
+ if (first === undefined) throw new Error(`no adapter for language "${language}"`);
63
84
  try {
64
- return await import(specifier);
85
+ return { specifier: first, module: await importer(first) };
65
86
  } catch (err) {
66
- const missing = err instanceof Error && "code" in err && err.code === "ERR_MODULE_NOT_FOUND";
67
- if (missing) throw new Error(`言語 ${language} のパッケージ ${specifier} が入っていません(npm i -D ${specifier})`, { cause: err });
68
- throw err;
87
+ if (!isAbsent(err, first)) throw err;
88
+ if (rest.length > 0) return importFirst(rest, language, importer);
89
+ const all = packagesFor(language);
90
+ const install = `npm i -D ${all[0] ?? first}`;
91
+ // Only the requested language is known here; a Japanese message for Japanese, English for any other.
92
+ const message =
93
+ language === "ja"
94
+ ? `言語 ${language} のパッケージが入っていません(${all.join(" か ")}。${install})`
95
+ : `No package for language ${language} is installed (${all.join(" or ")}; ${install})`;
96
+ throw new Error(message, { cause: err });
69
97
  }
70
98
  };
71
99
 
@@ -74,13 +102,16 @@ const importAdapter = async (specifier: string, language: string): Promise<unkno
74
102
  * core だけで起動し、文字種で言語を当ててから、その言語のアダプタを読む。
75
103
  * 全言語を静的に import すると、使わない言語のぶんまで npx の初回取得が膨らむ。
76
104
  */
77
- export const loadAdapter = async (language: string): Promise<LanguageAdapter> => {
78
- const specifier = packageFor(language);
79
- if (specifier === undefined) throw new Error(`no adapter for language "${language}"`);
80
- const module = await importAdapter(specifier, language);
105
+ export const loadAdapter = async (language: string, importer: Importer = (specifier) => import(specifier)): Promise<LanguageAdapter> => {
106
+ const { specifier, module } = await importFirst(packagesFor(language), language, importer);
81
107
  if (typeof module !== "object" || module === null) throw new Error(`${specifier} did not export a module`);
82
108
  const candidate = pickExport(module);
83
109
  const broken = checkAdapter(candidate);
84
- if (broken !== undefined || !isAdapter(candidate)) throw new Error(`${specifier} が LanguageAdapter を export していません: ${broken ?? ""}`);
110
+ if (broken !== undefined || !isAdapter(candidate)) {
111
+ const why = broken ?? "";
112
+ throw new Error(
113
+ language === "ja" ? `${specifier} が LanguageAdapter を export していません: ${why}` : `${specifier} does not export a LanguageAdapter: ${why}`,
114
+ );
115
+ }
85
116
  return candidate;
86
117
  };
@@ -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
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Options of the commands that take files to check, whose value is the next argument. That value is not a file.
3
+ * `--why` (relax), `--format` and `--language` (tree) belong to commands that read their own arguments.
4
+ */
5
+ export const VALUE_FLAGS: readonly string[] = ["--sarif", "--genre", "--rule", "--line"];
6
+
7
+ /** The arguments that name what to check: everything that is neither an option nor an option's value. */
8
+ export const targetsOf = (args: readonly string[]): string[] =>
9
+ args.filter((arg, index) => !arg.startsWith("--") && !(index > 0 && VALUE_FLAGS.includes(args[index - 1] ?? "")));
@@ -0,0 +1,186 @@
1
+ // What the command line says, in Japanese and English. Per-file text follows the file's language; the rest
2
+ // follows hostLanguage (chaff.yaml's language, else the terminal's locale).
3
+ import type { Texts } from "./ui.ts";
4
+
5
+ /** Where a file's genre came from, as the header names it. */
6
+ export type GenreSource = "--genre" | "by_path" | "config" | "default" | "front-matter" | "path" | "content";
7
+
8
+ const USAGE_JA = `chaff — 文章の読みにくいところを見つけます。文章は書き換えません。
9
+
10
+ chaff <file|dir|glob>... 検査する(設定も API key も要りません)
11
+ chaff . この場所の Markdown を全部
12
+ chaff test <file|dir>... 意味を読む検査も動かす(API key が要ります)
13
+ 判定役は chaff.yaml の ai_backend で選びます
14
+ chaff init chaff.yaml を作る
15
+ chaff eval <dir> 手元の文書で閾値を測り直す
16
+ chaff explain <rule> そのルールの意図と根拠を読む
17
+ chaff genres ジャンルの一覧
18
+ chaff tree <file> [--format sexp|json] 文書を番地の付いた木にする(条・項・定義・参照)
19
+ chaff cite <原文> <引用.json> 回答の引用(番地と引用文)が原文にあるかを確かめる
20
+ chaff rules --json いまの設定を JSON で出す(AI に渡す用)
21
+ chaff baseline <dir> いまある指摘を棚上げする(既存の repo に入れるとき)
22
+ chaff suppressions <dir> stet で黙らせている指摘を数える
23
+ chaff relax|strict|off <rule> [--why "理由"]
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 全体も載せる)
27
+
28
+ --compact エンジニア向けの 1 行形式
29
+ --experimental 試験中の rule も動かす
30
+ --genre <ジャンル> この実行だけジャンルを決める(chaff.yaml より優先)
31
+ --show-baseline 棚上げした分も含めて全部見る
32
+ --watch 保存のたびに見直し、変わったところだけ出す
33
+ --dry-run test で、何を AI に送るかだけを見る(API を呼びません)
34
+ --sarif <path> 指摘を SARIF で書き出す(GitHub の PR の行に出すため)
35
+
36
+ この箇所だけ黙らせる: <!-- stet: rule-id — 理由 -->
37
+
38
+ 値は strict / normal / relaxed / off の 4 つから選びます。
39
+ `;
40
+
41
+ const USAGE_EN = `chaff — finds what makes writing hard to read. It never rewrites the text.
42
+
43
+ chaff <file|dir|glob>... check (no config or API key needed)
44
+ chaff . every Markdown file here
45
+ chaff test <file|dir>... also run the checks that read meaning (needs an API key)
46
+ the judge is chosen by ai_backend in chaff.yaml
47
+ chaff init create chaff.yaml
48
+ chaff eval <dir> re-measure the limits on your own documents
49
+ chaff explain <rule> read what a rule is for and why
50
+ chaff genres list the genres
51
+ chaff tree <file> [--format sexp|json] the document as a tree of addresses (sections, clauses, definitions, references)
52
+ chaff cite <source> <quotes.json> check that quoted passages (address and text) are in the source
53
+ chaff rules --json the current settings as JSON (to give to an AI)
54
+ chaff baseline <dir> shelve today's findings (when adding chaff to an existing repository)
55
+ chaff suppressions <dir> count the findings silenced with stet
56
+ chaff relax|strict|off <rule> [--why "reason"]
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)
60
+
61
+ --compact one line per finding, for engineers
62
+ --experimental run the experimental rules too
63
+ --genre <genre> the genre for this run only (wins over chaff.yaml)
64
+ --show-baseline show the shelved findings too
65
+ --watch re-check on every save and print only what changed
66
+ --dry-run with test, show what would be sent to the AI (no API call)
67
+ --sarif <path> write the findings as SARIF (for comments on a GitHub PR)
68
+
69
+ Silence one spot: <!-- stet: rule-id — reason -->
70
+
71
+ A rule's level is one of strict / normal / relaxed / off.
72
+ `;
73
+
74
+ export type CliText = {
75
+ readonly usage: string;
76
+ readonly languageName: (language: string) => string;
77
+ readonly genreSource: Readonly<Record<GenreSource, string>>;
78
+ readonly header: (path: string, genre: string, language: string, from: string, shelved: number, hushed: number) => string;
79
+ readonly noMarkdown: (targets: string) => string;
80
+ readonly noMarkdownHere: string;
81
+ readonly noAdapter: (language: string) => string;
82
+ readonly unknownGenre: (genre: string) => string;
83
+ readonly unknownRule: (id: string) => string;
84
+ readonly unknownRuleWithList: (id: string, list: string) => string;
85
+ readonly unnamed: string;
86
+ readonly sarifWritten: (path: string, count: number) => string;
87
+ readonly unchanged: string;
88
+ readonly findingsUnit: string;
89
+ readonly watching: (files: number, findings: number) => string;
90
+ readonly watchHint: string;
91
+ readonly baselineDone: (files: number, entries: number, file: string) => readonly string[];
92
+ readonly genres: (list: readonly string[]) => string;
93
+ readonly unit: (ruleId: string, language: string) => string;
94
+ };
95
+
96
+ export const CLI_TEXT: Texts<CliText> = {
97
+ ja: {
98
+ usage: USAGE_JA,
99
+ languageName: (language) => (language === "ja" ? "日本語" : "英語"),
100
+ genreSource: {
101
+ "--genre": "--genre",
102
+ by_path: "chaff.yaml の by_path",
103
+ config: "chaff.yaml",
104
+ default: "既定",
105
+ "front-matter": "front matter",
106
+ path: "パス",
107
+ content: "内容",
108
+ },
109
+ header: (path, genre, language, from, shelved, hushed) =>
110
+ [`${path} ${genre} · ${language} ジャンルは${from}から`, shelved > 0 ? ` 棚上げ ${shelved} 件` : "", hushed > 0 ? ` stet ${hushed} 件` : ""].join(
111
+ "",
112
+ ),
113
+ noMarkdown: (targets) => `Markdown が 1 つも見つかりませんでした: ${targets}`,
114
+ noMarkdownHere: "Markdown が 1 つも見つかりませんでした。",
115
+ noAdapter: (language) => `言語 "${language}" のアダプタがありません。`,
116
+ unknownGenre: (genre) => `ジャンル "${genre}" はありません。npx chaff genres で一覧が出ます。`,
117
+ unknownRule: (id) => `${id} というルールはありません。npx chaff rules --json で一覧が出ます。`,
118
+ unknownRuleWithList: (id, list) => `${id} というルールはありません。\n一覧:\n${list}`,
119
+ unnamed: "(名前なし)",
120
+ sarifWritten: (path, count) => `\n SARIF を書きました: ${path}(${count} 件)`,
121
+ unchanged: "変わりませんでした",
122
+ findingsUnit: " 件",
123
+ watching: (files, findings) => `\n ${files} ファイルを見ています。いまの指摘は ${findings} 件です。`,
124
+ watchHint: " 保存するたびに、変わったところだけ出します。止めるには Ctrl-C。\n",
125
+ baselineDone: (files, entries, file) => [
126
+ "",
127
+ ` ${files} ファイルを走査しました。`,
128
+ "",
129
+ ` ${entries} 件の指摘を ${file} に記録しました。`,
130
+ " 以後、これらは報告されません。新しく増えたものだけが出ます。",
131
+ "",
132
+ ` ${file} を commit してください。`,
133
+ "",
134
+ ],
135
+ genres: (list) =>
136
+ ["", " 使えるジャンル:", ...list.map((genre) => ` ${genre}`), "", " chaff.yaml の genre に書くか、--genre で指定します。", ""].join("\n"),
137
+ unit: (ruleId, language) => {
138
+ if (ruleId === "bold-density") return "1000 字あたりの箇所数";
139
+ if (ruleId !== "max-sentence-length") return "回";
140
+ return language === "ja" ? "文字" : "語";
141
+ },
142
+ },
143
+ en: {
144
+ usage: USAGE_EN,
145
+ languageName: (language) => (language === "ja" ? "Japanese" : "English"),
146
+ genreSource: {
147
+ "--genre": "--genre",
148
+ by_path: "by_path in chaff.yaml",
149
+ config: "chaff.yaml",
150
+ default: "the default",
151
+ "front-matter": "front matter",
152
+ path: "the path",
153
+ content: "the content",
154
+ },
155
+ header: (path, genre, language, from, shelved, hushed) =>
156
+ [`${path} ${genre} · ${language} genre from ${from}`, shelved > 0 ? ` ${shelved} shelved` : "", hushed > 0 ? ` ${hushed} stet` : ""].join(""),
157
+ noMarkdown: (targets) => `No Markdown files found: ${targets}`,
158
+ noMarkdownHere: "No Markdown files found.",
159
+ noAdapter: (language) => `No language package for "${language}".`,
160
+ unknownGenre: (genre) => `There is no genre "${genre}". npx chaff genres lists them.`,
161
+ unknownRule: (id) => `There is no rule named ${id}. npx chaff rules --json lists them.`,
162
+ unknownRuleWithList: (id, list) => `There is no rule named ${id}.\nRules:\n${list}`,
163
+ unnamed: "(no name)",
164
+ sarifWritten: (path, count) => `\n Wrote SARIF: ${path} (${count} finding${count === 1 ? "" : "s"})`,
165
+ unchanged: "no change",
166
+ findingsUnit: " findings",
167
+ watching: (files, findings) => `\n Watching ${files} file${files === 1 ? "" : "s"}. ${findings} finding${findings === 1 ? "" : "s"} now.`,
168
+ watchHint: " Each save prints only what changed. Ctrl-C to stop.\n",
169
+ baselineDone: (files, entries, file) => [
170
+ "",
171
+ ` Checked ${files} file${files === 1 ? "" : "s"}.`,
172
+ "",
173
+ ` Recorded ${entries} finding${entries === 1 ? "" : "s"} in ${file}.`,
174
+ " They will not be reported again; only new ones will.",
175
+ "",
176
+ ` Commit ${file}.`,
177
+ "",
178
+ ],
179
+ genres: (list) => ["", " Genres:", ...list.map((genre) => ` ${genre}`), "", " Set one with genre in chaff.yaml, or with --genre.", ""].join("\n"),
180
+ unit: (ruleId, language) => {
181
+ if (ruleId === "bold-density") return "places per 1000 characters";
182
+ if (ruleId !== "max-sentence-length") return "times";
183
+ return language === "ja" ? "characters" : "words";
184
+ },
185
+ },
186
+ };