chaffjs 0.14.0 → 0.16.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/README.md +27 -2
- package/dist/address-chain.d.ts +4 -1
- package/dist/address-chain.d.ts.map +1 -1
- package/dist/address-chain.js +6 -3
- package/dist/address-chain.js.map +1 -1
- package/dist/backends/anthropic.d.ts +2 -1
- package/dist/backends/anthropic.d.ts.map +1 -1
- package/dist/backends/anthropic.js +4 -1
- package/dist/backends/anthropic.js.map +1 -1
- package/dist/backends/openai.d.ts +2 -1
- package/dist/backends/openai.d.ts.map +1 -1
- package/dist/backends/openai.js +1 -1
- package/dist/backends/openai.js.map +1 -1
- package/dist/backends/types.d.ts +2 -1
- package/dist/backends/types.d.ts.map +1 -1
- package/dist/backends/types.js +1 -1
- package/dist/backends/types.js.map +1 -1
- package/dist/baseline.js +1 -1
- package/dist/baseline.js.map +1 -1
- package/dist/calendar-number.d.ts +26 -0
- package/dist/calendar-number.d.ts.map +1 -0
- package/dist/calendar-number.js +51 -0
- package/dist/calendar-number.js.map +1 -0
- package/dist/cli-args.d.ts +0 -5
- package/dist/cli-args.d.ts.map +1 -1
- package/dist/cli-args.js +2 -2
- package/dist/cli-args.js.map +1 -1
- package/dist/cli-text.d.ts +10 -3
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +33 -14
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +53 -47
- package/dist/cli.js.map +1 -1
- package/dist/commands/cite.d.ts.map +1 -1
- package/dist/commands/cite.js +4 -4
- package/dist/commands/cite.js.map +1 -1
- package/dist/commands/eval.d.ts +4 -0
- package/dist/commands/eval.d.ts.map +1 -1
- package/dist/commands/eval.js +21 -7
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/feedback.d.ts.map +1 -1
- package/dist/commands/feedback.js +2 -1
- package/dist/commands/feedback.js.map +1 -1
- package/dist/commands/init-ask.d.ts +19 -0
- package/dist/commands/init-ask.d.ts.map +1 -0
- package/dist/commands/init-ask.js +43 -0
- package/dist/commands/init-ask.js.map +1 -0
- package/dist/commands/test.d.ts +8 -0
- package/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +73 -40
- package/dist/commands/test.js.map +1 -1
- package/dist/commands/tree.d.ts +1 -8
- package/dist/commands/tree.d.ts.map +1 -1
- package/dist/commands/tree.js +9 -7
- package/dist/commands/tree.js.map +1 -1
- package/dist/config/by-path.d.ts.map +1 -1
- package/dist/config/by-path.js +10 -9
- package/dist/config/by-path.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 +19 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/name-problems.d.ts +5 -0
- package/dist/config/name-problems.d.ts.map +1 -0
- package/dist/config/name-problems.js +11 -0
- package/dist/config/name-problems.js.map +1 -0
- package/dist/date-stamp.js +1 -1
- package/dist/date-stamp.js.map +1 -1
- package/dist/detectors/acronym-compound.d.ts +8 -0
- package/dist/detectors/acronym-compound.d.ts.map +1 -0
- package/dist/detectors/acronym-compound.js +8 -0
- package/dist/detectors/acronym-compound.js.map +1 -0
- package/dist/detectors/acronym-context.d.ts +4 -0
- package/dist/detectors/acronym-context.d.ts.map +1 -1
- package/dist/detectors/acronym-context.js +17 -1
- package/dist/detectors/acronym-context.js.map +1 -1
- package/dist/detectors/acronym-expansion.d.ts +10 -0
- package/dist/detectors/acronym-expansion.d.ts.map +1 -1
- package/dist/detectors/acronym-expansion.js +63 -5
- package/dist/detectors/acronym-expansion.js.map +1 -1
- package/dist/detectors/char-shape.d.ts.map +1 -1
- package/dist/detectors/char-shape.js +47 -15
- package/dist/detectors/char-shape.js.map +1 -1
- package/dist/detectors/citation-key.d.ts +13 -0
- package/dist/detectors/citation-key.d.ts.map +1 -0
- package/dist/detectors/citation-key.js +9 -0
- package/dist/detectors/citation-key.js.map +1 -0
- package/dist/detectors/cited-name.d.ts +11 -0
- package/dist/detectors/cited-name.d.ts.map +1 -0
- package/dist/detectors/cited-name.js +15 -0
- package/dist/detectors/cited-name.js.map +1 -0
- package/dist/detectors/concrete-evidence.d.ts +7 -0
- package/dist/detectors/concrete-evidence.d.ts.map +1 -0
- package/dist/detectors/concrete-evidence.js +21 -0
- package/dist/detectors/concrete-evidence.js.map +1 -0
- package/dist/detectors/counter-edge.d.ts +8 -0
- package/dist/detectors/counter-edge.d.ts.map +1 -0
- package/dist/detectors/counter-edge.js +27 -0
- package/dist/detectors/counter-edge.js.map +1 -0
- package/dist/detectors/dotted-name.d.ts +13 -0
- package/dist/detectors/dotted-name.d.ts.map +1 -0
- package/dist/detectors/dotted-name.js +10 -0
- package/dist/detectors/dotted-name.js.map +1 -0
- package/dist/detectors/doubled-word.d.ts +6 -1
- package/dist/detectors/doubled-word.d.ts.map +1 -1
- package/dist/detectors/doubled-word.js +22 -2
- package/dist/detectors/doubled-word.js.map +1 -1
- package/dist/detectors/en-shape.d.ts +1 -1
- package/dist/detectors/en-shape.d.ts.map +1 -1
- package/dist/detectors/en-shape.js +112 -29
- package/dist/detectors/en-shape.js.map +1 -1
- package/dist/detectors/enumerated-runs.d.ts.map +1 -1
- package/dist/detectors/enumerated-runs.js +4 -2
- package/dist/detectors/enumerated-runs.js.map +1 -1
- package/dist/detectors/gram-place.d.ts +0 -4
- package/dist/detectors/gram-place.d.ts.map +1 -1
- package/dist/detectors/gram-place.js +7 -2
- package/dist/detectors/gram-place.js.map +1 -1
- package/dist/detectors/gram-predicate.d.ts +7 -0
- package/dist/detectors/gram-predicate.d.ts.map +1 -0
- package/dist/detectors/gram-predicate.js +44 -0
- package/dist/detectors/gram-predicate.js.map +1 -0
- package/dist/detectors/heading-case.d.ts +21 -0
- package/dist/detectors/heading-case.d.ts.map +1 -0
- package/dist/detectors/heading-case.js +40 -0
- package/dist/detectors/heading-case.js.map +1 -0
- package/dist/detectors/heading-echo.d.ts.map +1 -1
- package/dist/detectors/heading-echo.js +4 -1
- package/dist/detectors/heading-echo.js.map +1 -1
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +4 -3
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/lettered-index.d.ts +9 -0
- package/dist/detectors/lettered-index.d.ts.map +1 -0
- package/dist/detectors/lettered-index.js +54 -0
- package/dist/detectors/lettered-index.js.map +1 -0
- package/dist/detectors/lexicon.d.ts +5 -1
- package/dist/detectors/lexicon.d.ts.map +1 -1
- package/dist/detectors/lexicon.js +51 -5
- package/dist/detectors/lexicon.js.map +1 -1
- package/dist/detectors/name-numeral.d.ts +16 -0
- package/dist/detectors/name-numeral.d.ts.map +1 -0
- package/dist/detectors/name-numeral.js +30 -0
- package/dist/detectors/name-numeral.js.map +1 -0
- package/dist/detectors/name-part.d.ts +8 -0
- package/dist/detectors/name-part.d.ts.map +1 -0
- package/dist/detectors/name-part.js +20 -0
- package/dist/detectors/name-part.js.map +1 -0
- package/dist/detectors/name-run.d.ts +8 -0
- package/dist/detectors/name-run.d.ts.map +1 -0
- package/dist/detectors/name-run.js +13 -0
- package/dist/detectors/name-run.js.map +1 -0
- package/dist/detectors/orthography.d.ts.map +1 -1
- package/dist/detectors/orthography.js +35 -7
- package/dist/detectors/orthography.js.map +1 -1
- package/dist/detectors/phrase-match.d.ts +3 -1
- package/dist/detectors/phrase-match.d.ts.map +1 -1
- package/dist/detectors/phrase-match.js +20 -15
- package/dist/detectors/phrase-match.js.map +1 -1
- package/dist/detectors/placeholder.d.ts +14 -0
- package/dist/detectors/placeholder.d.ts.map +1 -0
- package/dist/detectors/placeholder.js +24 -0
- package/dist/detectors/placeholder.js.map +1 -0
- package/dist/detectors/quoted-variant.d.ts +7 -0
- package/dist/detectors/quoted-variant.d.ts.map +1 -0
- package/dist/detectors/quoted-variant.js +18 -0
- package/dist/detectors/quoted-variant.js.map +1 -0
- package/dist/detectors/repeated-head.d.ts.map +1 -1
- package/dist/detectors/repeated-head.js +5 -4
- package/dist/detectors/repeated-head.js.map +1 -1
- package/dist/detectors/series-label.d.ts +13 -0
- package/dist/detectors/series-label.d.ts.map +1 -0
- package/dist/detectors/series-label.js +26 -0
- package/dist/detectors/series-label.js.map +1 -0
- package/dist/detectors/signals.d.ts.map +1 -1
- package/dist/detectors/signals.js +44 -29
- package/dist/detectors/signals.js.map +1 -1
- package/dist/detectors/stacked-hedge.d.ts +23 -0
- package/dist/detectors/stacked-hedge.d.ts.map +1 -0
- package/dist/detectors/stacked-hedge.js +64 -0
- package/dist/detectors/stacked-hedge.js.map +1 -0
- package/dist/detectors/structure-tree.js +1 -1
- package/dist/detectors/structure-tree.js.map +1 -1
- package/dist/detectors/superlative-amount.d.ts +9 -0
- package/dist/detectors/superlative-amount.d.ts.map +1 -0
- package/dist/detectors/superlative-amount.js +20 -0
- package/dist/detectors/superlative-amount.js.map +1 -0
- package/dist/detectors/superlative-clause.d.ts +18 -0
- package/dist/detectors/superlative-clause.d.ts.map +1 -0
- package/dist/detectors/superlative-clause.js +61 -0
- package/dist/detectors/superlative-clause.js.map +1 -0
- package/dist/detectors/superlative-name.d.ts +9 -0
- package/dist/detectors/superlative-name.d.ts.map +1 -0
- package/dist/detectors/superlative-name.js +24 -0
- package/dist/detectors/superlative-name.js.map +1 -0
- package/dist/detectors/superlative-scope.d.ts +6 -0
- package/dist/detectors/superlative-scope.d.ts.map +1 -1
- package/dist/detectors/superlative-scope.js +3 -3
- package/dist/detectors/superlative-scope.js.map +1 -1
- package/dist/detectors/team.d.ts +0 -6
- package/dist/detectors/team.d.ts.map +1 -1
- package/dist/detectors/team.js +17 -1
- package/dist/detectors/team.js.map +1 -1
- package/dist/detectors/token-shape.d.ts.map +1 -1
- package/dist/detectors/token-shape.js +15 -5
- package/dist/detectors/token-shape.js.map +1 -1
- package/dist/document.d.ts +3 -0
- package/dist/document.d.ts.map +1 -1
- package/dist/document.js +28 -7
- package/dist/document.js.map +1 -1
- package/dist/eval.js +1 -1
- package/dist/eval.js.map +1 -1
- package/dist/feedback/draft.js +1 -1
- package/dist/feedback/draft.js.map +1 -1
- package/dist/feedback/excerpt.d.ts +0 -1
- package/dist/feedback/excerpt.d.ts.map +1 -1
- package/dist/feedback/excerpt.js +2 -2
- package/dist/feedback/excerpt.js.map +1 -1
- package/dist/file-header.d.ts +20 -0
- package/dist/file-header.d.ts.map +1 -0
- package/dist/file-header.js +21 -0
- package/dist/file-header.js.map +1 -0
- package/dist/files.js +3 -3
- package/dist/files.js.map +1 -1
- package/dist/genre-check.d.ts +34 -0
- package/dist/genre-check.d.ts.map +1 -0
- package/dist/genre-check.js +21 -0
- package/dist/genre-check.js.map +1 -0
- package/dist/genre-load.d.ts +6 -0
- package/dist/genre-load.d.ts.map +1 -0
- package/dist/genre-load.js +22 -0
- package/dist/genre-load.js.map +1 -0
- package/dist/genre-parse.d.ts +40 -0
- package/dist/genre-parse.d.ts.map +1 -0
- package/dist/genre-parse.js +104 -0
- package/dist/genre-parse.js.map +1 -0
- package/dist/genre-suggest.d.ts +15 -0
- package/dist/genre-suggest.d.ts.map +1 -0
- package/dist/genre-suggest.js +26 -0
- package/dist/genre-suggest.js.map +1 -0
- package/dist/genre.d.ts +8 -1
- package/dist/genre.d.ts.map +1 -1
- package/dist/genre.js +19 -5
- package/dist/genre.js.map +1 -1
- package/dist/init-choice.d.ts +12 -0
- package/dist/init-choice.d.ts.map +1 -0
- package/dist/init-choice.js +41 -0
- package/dist/init-choice.js.map +1 -0
- package/dist/init.d.ts +0 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +16 -17
- package/dist/init.js.map +1 -1
- package/dist/joined-view.d.ts +25 -0
- package/dist/joined-view.d.ts.map +1 -0
- package/dist/joined-view.js +76 -0
- package/dist/joined-view.js.map +1 -0
- package/dist/judge.d.ts +2 -1
- package/dist/judge.d.ts.map +1 -1
- package/dist/judge.js +1 -1
- package/dist/judge.js.map +1 -1
- package/dist/line-continues.d.ts +6 -0
- package/dist/line-continues.d.ts.map +1 -0
- package/dist/line-continues.js +33 -0
- package/dist/line-continues.js.map +1 -0
- package/dist/line-paragraphs.d.ts +4 -0
- package/dist/line-paragraphs.d.ts.map +1 -0
- package/dist/line-paragraphs.js +69 -0
- package/dist/line-paragraphs.js.map +1 -0
- package/dist/mask.d.ts.map +1 -1
- package/dist/mask.js +7 -6
- package/dist/mask.js.map +1 -1
- package/dist/measure.d.ts +1 -0
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +5 -1
- package/dist/measure.js.map +1 -1
- package/dist/number-name.d.ts +6 -3
- package/dist/number-name.d.ts.map +1 -1
- package/dist/number-name.js +31 -14
- package/dist/number-name.js.map +1 -1
- package/dist/orthography.d.ts +4 -0
- package/dist/orthography.d.ts.map +1 -1
- package/dist/orthography.js +11 -9
- package/dist/orthography.js.map +1 -1
- package/dist/plain-source.d.ts +6 -0
- package/dist/plain-source.d.ts.map +1 -0
- package/dist/plain-source.js +6 -0
- package/dist/plain-source.js.map +1 -0
- package/dist/plugin.d.ts +22 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/profile/check.d.ts +28 -0
- package/dist/profile/check.d.ts.map +1 -0
- package/dist/profile/check.js +12 -0
- package/dist/profile/check.js.map +1 -0
- package/dist/profile/for-file.d.ts +2 -2
- package/dist/profile/for-file.d.ts.map +1 -1
- package/dist/profile/for-file.js +9 -2
- package/dist/profile/for-file.js.map +1 -1
- package/dist/profile/load.js +1 -1
- package/dist/profile/load.js.map +1 -1
- package/dist/profile/select.d.ts +4 -2
- package/dist/profile/select.d.ts.map +1 -1
- package/dist/profile/select.js +8 -3
- package/dist/profile/select.js.map +1 -1
- package/dist/quoted-span.d.ts +22 -0
- package/dist/quoted-span.d.ts.map +1 -0
- package/dist/quoted-span.js +25 -0
- package/dist/quoted-span.js.map +1 -0
- package/dist/reasons.d.ts +1 -0
- package/dist/reasons.d.ts.map +1 -1
- package/dist/reasons.js +4 -1
- package/dist/reasons.js.map +1 -1
- package/dist/render/compact.d.ts.map +1 -1
- package/dist/render/compact.js +1 -2
- package/dist/render/compact.js.map +1 -1
- package/dist/render/eval.d.ts +2 -1
- package/dist/render/eval.d.ts.map +1 -1
- package/dist/render/eval.js +85 -43
- package/dist/render/eval.js.map +1 -1
- package/dist/render/friendly.d.ts +2 -1
- package/dist/render/friendly.d.ts.map +1 -1
- package/dist/render/friendly.js +14 -6
- package/dist/render/friendly.js.map +1 -1
- package/dist/render/genres.d.ts +5 -0
- package/dist/render/genres.d.ts.map +1 -0
- package/dist/render/genres.js +28 -0
- package/dist/render/genres.js.map +1 -0
- package/dist/render/plural.d.ts +3 -0
- package/dist/render/plural.d.ts.map +1 -0
- package/dist/render/plural.js +5 -0
- package/dist/render/plural.js.map +1 -0
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +9 -3
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/semantic.d.ts +3 -2
- package/dist/render/semantic.d.ts.map +1 -1
- package/dist/render/semantic.js +94 -34
- package/dist/render/semantic.js.map +1 -1
- package/dist/render/summary.d.ts.map +1 -1
- package/dist/render/summary.js +4 -3
- package/dist/render/summary.js.map +1 -1
- package/dist/render/suppressions.d.ts.map +1 -1
- package/dist/render/suppressions.js +3 -2
- package/dist/render/suppressions.js.map +1 -1
- package/dist/render/text.d.ts +2 -0
- package/dist/render/text.d.ts.map +1 -1
- package/dist/render/text.js +11 -3
- package/dist/render/text.js.map +1 -1
- package/dist/resolve-genre.d.ts +11 -0
- package/dist/resolve-genre.d.ts.map +1 -0
- package/dist/resolve-genre.js +18 -0
- package/dist/resolve-genre.js.map +1 -0
- package/dist/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +4 -1
- package/dist/rule-load.js.map +1 -1
- package/dist/run-semantic.js +1 -1
- package/dist/run-semantic.js.map +1 -1
- package/dist/run.d.ts +2 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +25 -15
- package/dist/run.js.map +1 -1
- package/dist/sentence-shape.d.ts +0 -1
- package/dist/sentence-shape.d.ts.map +1 -1
- package/dist/sentence-shape.js +3 -5
- package/dist/sentence-shape.js.map +1 -1
- package/dist/setting-problems.d.ts +5 -0
- package/dist/setting-problems.d.ts.map +1 -0
- package/dist/setting-problems.js +15 -0
- package/dist/setting-problems.js.map +1 -0
- package/dist/soft-break.d.ts +19 -0
- package/dist/soft-break.d.ts.map +1 -0
- package/dist/soft-break.js +78 -0
- package/dist/soft-break.js.map +1 -0
- package/dist/structure/build.d.ts.map +1 -1
- package/dist/structure/build.js +24 -14
- package/dist/structure/build.js.map +1 -1
- package/dist/structure/cite.js +1 -1
- package/dist/structure/cite.js.map +1 -1
- package/dist/structure/date-order.d.ts +3 -0
- package/dist/structure/date-order.d.ts.map +1 -1
- package/dist/structure/date-order.js +9 -1
- package/dist/structure/date-order.js.map +1 -1
- package/dist/structure/issues.d.ts +1 -1
- package/dist/structure/issues.d.ts.map +1 -1
- package/dist/structure/issues.js +19 -8
- package/dist/structure/issues.js.map +1 -1
- package/dist/structure/listed-tags.d.ts +3 -0
- package/dist/structure/listed-tags.d.ts.map +1 -0
- package/dist/structure/listed-tags.js +6 -0
- package/dist/structure/listed-tags.js.map +1 -0
- package/dist/structure/markdown-path.d.ts.map +1 -1
- package/dist/structure/markdown-path.js +2 -2
- package/dist/structure/markdown-path.js.map +1 -1
- package/dist/structure/number-in-sentence.d.ts +15 -0
- package/dist/structure/number-in-sentence.d.ts.map +1 -0
- package/dist/structure/number-in-sentence.js +13 -0
- package/dist/structure/number-in-sentence.js.map +1 -0
- package/dist/structure/numbering.d.ts.map +1 -1
- package/dist/structure/numbering.js +1 -1
- package/dist/structure/numbering.js.map +1 -1
- package/dist/structure/relative-find.js +1 -1
- package/dist/structure/relative-find.js.map +1 -1
- package/dist/structure/sexp.js +1 -1
- package/dist/structure/sexp.js.map +1 -1
- package/dist/structure/unit-word.d.ts +8 -0
- package/dist/structure/unit-word.d.ts.map +1 -0
- package/dist/structure/unit-word.js +10 -0
- package/dist/structure/unit-word.js.map +1 -0
- package/dist/structure/unnumbered.js +2 -2
- package/dist/structure/unnumbered.js.map +1 -1
- package/dist/structure/wrapped-tail.d.ts +18 -0
- package/dist/structure/wrapped-tail.d.ts.map +1 -0
- package/dist/structure/wrapped-tail.js +11 -0
- package/dist/structure/wrapped-tail.js.map +1 -0
- package/dist/team-names.d.ts +9 -0
- package/dist/team-names.d.ts.map +1 -0
- package/dist/team-names.js +34 -0
- package/dist/team-names.js.map +1 -0
- package/dist/ui.d.ts +2 -0
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +6 -0
- package/dist/ui.js.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/dist/watch.d.ts +2 -2
- package/dist/watch.d.ts.map +1 -1
- package/dist/watch.js +5 -5
- package/dist/watch.js.map +1 -1
- package/genres.yaml +363 -0
- package/package.json +4 -3
- package/rules/adverb-overuse.yaml +2 -2
- package/rules/agentless-passive.yaml +1 -1
- package/rules/ai-generated-composite.yaml +2 -2
- package/rules/ai-tell.yaml +2 -2
- package/rules/bold-density.yaml +2 -2
- package/rules/closing-cliche.yaml +1 -1
- package/rules/concrete-evidence-density.yaml +2 -2
- package/rules/contraction-consistency.yaml +1 -1
- package/rules/cushion-phrase-density.yaml +1 -1
- package/rules/dangling-reference.yaml +3 -3
- package/rules/date-order.yaml +3 -3
- package/rules/date-weekday-mismatch.yaml +1 -1
- package/rules/double-keigo.yaml +1 -1
- package/rules/doubled-word.yaml +1 -1
- package/rules/duplicate-definition.yaml +1 -1
- package/rules/emoji-density.yaml +1 -1
- package/rules/empty-conclusion.yaml +2 -1
- package/rules/empty-intensifier.yaml +1 -1
- package/rules/excessive-hedging.yaml +16 -6
- package/rules/expletive-construction.yaml +2 -2
- package/rules/heading-echo.yaml +1 -1
- package/rules/hiragana-fukushi.yaml +1 -1
- package/rules/internal-jargon.yaml +2 -2
- package/rules/latin-spacing.yaml +6 -3
- package/rules/max-kanji-continuous.yaml +5 -4
- package/rules/max-paragraph-length.yaml +9 -2
- package/rules/max-sentence-length.yaml +14 -2
- package/rules/ngram-repetition.yaml +4 -4
- package/rules/no-doubled-joshi.yaml +4 -4
- package/rules/no-em-dash.yaml +2 -2
- package/rules/no-mixed-desumasu.yaml +1 -1
- package/rules/no-nakaguro-parallel.yaml +2 -2
- package/rules/numbering-gap.yaml +1 -1
- package/rules/oxford-comma-consistency.yaml +2 -1
- package/rules/padded-intro.yaml +2 -1
- package/rules/paragraph-length-variance.yaml +1 -1
- package/rules/preamble-length.yaml +2 -2
- package/rules/preferred-term.yaml +1 -1
- package/rules/proper-noun-density.yaml +3 -2
- package/rules/repeated-conjunction.yaml +2 -2
- package/rules/repeated-sentence-head.yaml +2 -2
- package/rules/required-sections.yaml +1 -1
- package/rules/rule-of-three.yaml +2 -2
- package/rules/sasete-itadaku.yaml +3 -3
- package/rules/section-length-uniformity.yaml +1 -1
- package/rules/sentence-initial-conjunction-run.yaml +2 -2
- package/rules/sentence-rhythm.yaml +1 -1
- package/rules/taigen-dome-in-prose.yaml +2 -2
- package/rules/title-case-consistency.yaml +3 -3
- package/rules/total-mismatch.yaml +1 -1
- package/rules/undefined-acronym.yaml +18 -5
- package/rules/unqualified-superlative.yaml +3 -3
- package/skills/chaff/SKILL.md +28 -9
- package/src/address-chain.ts +6 -3
- package/src/backends/anthropic.ts +5 -1
- package/src/backends/openai.ts +2 -1
- package/src/backends/types.ts +3 -2
- package/src/baseline.ts +1 -1
- package/src/calendar-number.ts +75 -0
- package/src/cli-args.ts +2 -2
- package/src/cli-text.ts +48 -18
- package/src/cli.ts +51 -47
- package/src/commands/cite.ts +4 -4
- package/src/commands/eval.ts +27 -6
- package/src/commands/feedback.ts +2 -1
- package/src/commands/init-ask.ts +53 -0
- package/src/commands/test.ts +105 -47
- package/src/commands/tree.ts +9 -7
- package/src/config/by-path.ts +8 -9
- package/src/config/load.ts +24 -0
- package/src/config/name-problems.ts +15 -0
- package/src/date-stamp.ts +1 -1
- package/src/detectors/acronym-compound.ts +9 -0
- package/src/detectors/acronym-context.ts +22 -1
- package/src/detectors/acronym-expansion.ts +72 -5
- package/src/detectors/char-shape.ts +56 -16
- package/src/detectors/citation-key.ts +13 -0
- package/src/detectors/cited-name.ts +23 -0
- package/src/detectors/concrete-evidence.ts +31 -0
- package/src/detectors/counter-edge.ts +29 -0
- package/src/detectors/dotted-name.ts +14 -0
- package/src/detectors/doubled-word.ts +25 -2
- package/src/detectors/en-shape.ts +123 -38
- package/src/detectors/enumerated-runs.ts +3 -2
- package/src/detectors/gram-place.ts +9 -2
- package/src/detectors/gram-predicate.ts +55 -0
- package/src/detectors/heading-case.ts +44 -0
- package/src/detectors/heading-echo.ts +5 -1
- package/src/detectors/index.ts +4 -3
- package/src/detectors/lettered-index.ts +78 -0
- package/src/detectors/lexicon.ts +68 -8
- package/src/detectors/name-numeral.ts +37 -0
- package/src/detectors/name-part.ts +24 -0
- package/src/detectors/name-run.ts +16 -0
- package/src/detectors/orthography.ts +43 -10
- package/src/detectors/phrase-match.ts +23 -12
- package/src/detectors/placeholder.ts +38 -0
- package/src/detectors/quoted-variant.ts +20 -0
- package/src/detectors/repeated-head.ts +3 -4
- package/src/detectors/series-label.ts +33 -0
- package/src/detectors/signals.ts +49 -35
- package/src/detectors/stacked-hedge.ts +93 -0
- package/src/detectors/structure-tree.ts +2 -2
- package/src/detectors/superlative-amount.ts +24 -0
- package/src/detectors/superlative-clause.ts +82 -0
- package/src/detectors/superlative-name.ts +30 -0
- package/src/detectors/superlative-scope.ts +3 -3
- package/src/detectors/team.ts +18 -2
- package/src/detectors/token-shape.ts +13 -6
- package/src/document.ts +41 -8
- package/src/eval.ts +1 -1
- package/src/feedback/draft.ts +1 -1
- package/src/feedback/excerpt.ts +2 -2
- package/src/file-header.ts +31 -0
- package/src/files.ts +3 -3
- package/src/genre-check.ts +46 -0
- package/src/genre-load.ts +24 -0
- package/src/genre-parse.ts +126 -0
- package/src/genre-suggest.ts +37 -0
- package/src/genre.ts +25 -5
- package/src/init-choice.ts +47 -0
- package/src/init.ts +20 -19
- package/src/joined-view.ts +96 -0
- package/src/judge.ts +3 -1
- package/src/line-continues.ts +43 -0
- package/src/line-paragraphs.ts +83 -0
- package/src/mask.ts +5 -6
- package/src/measure.ts +9 -1
- package/src/number-name.ts +35 -13
- package/src/orthography.ts +15 -9
- package/src/plain-source.ts +5 -0
- package/src/plugin.ts +27 -2
- package/src/profile/check.ts +34 -0
- package/src/profile/for-file.ts +10 -3
- package/src/profile/load.ts +1 -1
- package/src/profile/select.ts +11 -4
- package/src/quoted-span.ts +39 -0
- package/src/reasons.ts +5 -1
- package/src/render/compact.ts +1 -3
- package/src/render/eval.ts +102 -38
- package/src/render/friendly.ts +21 -6
- package/src/render/genres.ts +31 -0
- package/src/render/plural.ts +6 -0
- package/src/render/rules-json.ts +10 -3
- package/src/render/semantic.ts +117 -34
- package/src/render/summary.ts +4 -3
- package/src/render/suppressions.ts +3 -2
- package/src/render/text.ts +18 -3
- package/src/resolve-genre.ts +20 -0
- package/src/rule-load.ts +6 -1
- package/src/run-semantic.ts +1 -1
- package/src/run.ts +28 -15
- package/src/sentence-shape.ts +3 -7
- package/src/setting-problems.ts +24 -0
- package/src/soft-break.ts +94 -0
- package/src/structure/build.ts +24 -15
- package/src/structure/cite.ts +1 -1
- package/src/structure/date-order.ts +16 -1
- package/src/structure/issues.ts +24 -8
- package/src/structure/listed-tags.ts +7 -0
- package/src/structure/markdown-path.ts +2 -2
- package/src/structure/number-in-sentence.ts +20 -0
- package/src/structure/numbering.ts +1 -1
- package/src/structure/relative-find.ts +1 -1
- package/src/structure/sexp.ts +1 -1
- package/src/structure/unit-word.ts +11 -0
- package/src/structure/unnumbered.ts +2 -2
- package/src/structure/wrapped-tail.ts +22 -0
- package/src/team-names.ts +39 -0
- package/src/ui.ts +7 -0
- package/src/version.ts +1 -1
- package/src/watch.ts +5 -5
package/src/commands/cite.ts
CHANGED
|
@@ -2,12 +2,12 @@ import { checkCitations, type Citation, type CitationResult } from "../structure
|
|
|
2
2
|
import { readSource, readTree, type TreeContext } from "./tree.ts";
|
|
3
3
|
import type { Texts, UiLanguage } from "../ui.ts";
|
|
4
4
|
|
|
5
|
-
const FORMATS = ["text", "json"];
|
|
5
|
+
const FORMATS: ReadonlySet<string> = new Set(["text", "json"]);
|
|
6
6
|
const QUOTE_WIDTH = 40;
|
|
7
|
-
const VALUED = ["--format", "--language"];
|
|
7
|
+
const VALUED: ReadonlySet<string> = new Set(["--format", "--language", "--genre"]);
|
|
8
8
|
|
|
9
9
|
export const citeTargets = (argv: readonly string[]): string[] =>
|
|
10
|
-
argv.slice(1).filter((arg, index, all) => !arg.startsWith("--") && !VALUED.
|
|
10
|
+
argv.slice(1).filter((arg, index, all) => !arg.startsWith("--") && !VALUED.has(all[index - 1] ?? ""));
|
|
11
11
|
|
|
12
12
|
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
13
|
|
|
@@ -88,7 +88,7 @@ const describe = (result: CitationResult, text: CiteText): string => {
|
|
|
88
88
|
export const runCite = async (targets: readonly string[], argv: readonly string[], context: TreeContext): Promise<number> => {
|
|
89
89
|
const [sourcePath, citationsPath] = targets;
|
|
90
90
|
const format = context.flag(argv, "--format") ?? "text";
|
|
91
|
-
if (sourcePath === undefined || citationsPath === undefined || targets.length !== 2 || !FORMATS.
|
|
91
|
+
if (sourcePath === undefined || citationsPath === undefined || targets.length !== 2 || !FORMATS.has(format)) {
|
|
92
92
|
console.error(TEXT[context.ui ?? "ja"].usage);
|
|
93
93
|
return 1;
|
|
94
94
|
}
|
package/src/commands/eval.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { plainSource } from "../plain-source.ts";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { loadAdapter } from "../adapter-load.ts";
|
|
3
4
|
import { applyByPath } from "../config/by-path.ts";
|
|
@@ -10,33 +11,52 @@ import { renderEval } from "../render/eval.ts";
|
|
|
10
11
|
import { neededBy } from "../run.ts";
|
|
11
12
|
import type { Config } from "../config/load.ts";
|
|
12
13
|
import { profileFor } from "../profile/for-file.ts";
|
|
14
|
+
import { CLI_TEXT } from "../cli-text.ts";
|
|
15
|
+
import { uiLanguageOf, type Texts, type UiLanguage } from "../ui.ts";
|
|
16
|
+
|
|
17
|
+
const TEXT: Texts<{
|
|
18
|
+
readonly mixed: (mixes: string) => string;
|
|
19
|
+
readonly unknownRule: (id: string) => string;
|
|
20
|
+
}> = {
|
|
21
|
+
ja: {
|
|
22
|
+
mixed: (mixes) => `言語かジャンルが混ざっています: ${mixes}\n1 つに絞って測ってください(例: npx chaff eval examples/blog-ja/)。`,
|
|
23
|
+
unknownRule: (id) => `${id} という rule はありません。`,
|
|
24
|
+
},
|
|
25
|
+
en: {
|
|
26
|
+
mixed: (mixes) => `Languages or genres are mixed: ${mixes}\nMeasure one at a time (for example npx chaff eval examples/blog-en/).`,
|
|
27
|
+
unknownRule: (id) => `There is no rule named ${id}.`,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
13
30
|
|
|
14
31
|
export type Context = {
|
|
15
32
|
readonly config: Config;
|
|
16
33
|
readonly resolveGenre: (path: string, source: string, config: Config) => { genre: string; from: string };
|
|
17
34
|
readonly flag: (argv: readonly string[], name: string) => string | undefined;
|
|
35
|
+
/** The language of what is not about the documents measured: chaff.yaml's language, else the locale. */
|
|
36
|
+
readonly ui: UiLanguage;
|
|
18
37
|
};
|
|
19
38
|
|
|
20
39
|
/**
|
|
21
40
|
* 閾値の較正。手元の文書を「人間の良い文書」として扱い、どの閾値なら
|
|
22
41
|
* 誤検知が目標を下回るかを測る。書き換えはしない。
|
|
42
|
+
* 測った結果は文書の言語で出す(混ざっていれば測らないので、言語は 1 つ)。断りは ui の言語。
|
|
23
43
|
*/
|
|
24
44
|
export const runEval = async (targets: readonly string[], argv: readonly string[], context: Context): Promise<number> => {
|
|
25
45
|
const paths = collectTargets(targets.length > 0 ? targets : ["."]);
|
|
26
46
|
if (paths.length === 0) {
|
|
27
|
-
console.error(
|
|
47
|
+
console.error(CLI_TEXT[context.ui].noMarkdown(targets.join(", ")));
|
|
28
48
|
return 1;
|
|
29
49
|
}
|
|
30
|
-
const { config, resolveGenre, flag } = context;
|
|
50
|
+
const { config, resolveGenre, flag, ui } = context;
|
|
31
51
|
const only = flag(argv, "--rule");
|
|
32
52
|
const docs = await Promise.all(
|
|
33
53
|
paths.map(async (path) => {
|
|
34
|
-
const source = await readFile(path, "utf8");
|
|
54
|
+
const source = plainSource(await readFile(path, "utf8"));
|
|
35
55
|
const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
|
|
36
56
|
const adapter = await loadAdapter(language);
|
|
37
57
|
const { genre } = resolveGenre(path, source, config);
|
|
38
58
|
await adapter.prepare?.(neededBy(loadRules(language), config.rules, config.experimental, genre, language));
|
|
39
|
-
return { doc: buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language)), language, genre };
|
|
59
|
+
return { doc: buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language, genre)), language, genre };
|
|
40
60
|
}),
|
|
41
61
|
);
|
|
42
62
|
const language = docs[0]?.language ?? "ja";
|
|
@@ -44,12 +64,12 @@ export const runEval = async (targets: readonly string[], argv: readonly string[
|
|
|
44
64
|
// 言語とジャンルが混ざった corpus は測れない。閾値の単位が違うため。
|
|
45
65
|
const mixed = new Set(docs.map((entry) => `${entry.language}/${entry.genre}`));
|
|
46
66
|
if (mixed.size > 1) {
|
|
47
|
-
console.error(
|
|
67
|
+
console.error(TEXT[ui].mixed([...mixed].join(", ")));
|
|
48
68
|
return 1;
|
|
49
69
|
}
|
|
50
70
|
const rules = loadRules(language).filter((rule) => only === undefined || rule.id === only);
|
|
51
71
|
if (rules.length === 0) {
|
|
52
|
-
console.error(
|
|
72
|
+
console.error(TEXT[ui].unknownRule(only ?? CLI_TEXT[ui].unnamed));
|
|
53
73
|
return 1;
|
|
54
74
|
}
|
|
55
75
|
console.log(
|
|
@@ -63,6 +83,7 @@ export const runEval = async (targets: readonly string[], argv: readonly string[
|
|
|
63
83
|
),
|
|
64
84
|
docs.length,
|
|
65
85
|
paths.length,
|
|
86
|
+
uiLanguageOf(language),
|
|
66
87
|
),
|
|
67
88
|
);
|
|
68
89
|
return 0;
|
package/src/commands/feedback.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { plainSource } from "../plain-source.ts";
|
|
1
2
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { basename, join } from "node:path";
|
|
3
4
|
import type { Finding, RuleDefinition } from "../plugin.ts";
|
|
@@ -178,7 +179,7 @@ export const runFeedback = async (targets: readonly string[], argv: readonly str
|
|
|
178
179
|
console.error(text.notFound(request.path));
|
|
179
180
|
return 1;
|
|
180
181
|
}
|
|
181
|
-
const source = readFileSync(request.path, "utf8");
|
|
182
|
+
const source = plainSource(readFileSync(request.path, "utf8"));
|
|
182
183
|
const checked = await context.check(request.path);
|
|
183
184
|
const picked = pick(request, checked, source.split("\n").length, context.ui);
|
|
184
185
|
if ("error" in picked) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createInterface, type Interface } from "node:readline";
|
|
4
|
+
import { CONFIG_FILE } from "../config/load.ts";
|
|
5
|
+
import { loadGenres } from "../genre-load.ts";
|
|
6
|
+
import { choicePrompt, DEFAULT_GENRE, genreChoices, genreFromAnswer, noAnswer, notAGenre } from "../init-choice.ts";
|
|
7
|
+
import type { UiLanguage } from "../ui.ts";
|
|
8
|
+
|
|
9
|
+
type Terminal = { readonly isTTY?: boolean | undefined };
|
|
10
|
+
|
|
11
|
+
/** Where init asks: the terminal, or streams a test gives it. */
|
|
12
|
+
export type Prompter = { readonly input: NodeJS.ReadableStream & Terminal; readonly output: NodeJS.WritableStream & Terminal };
|
|
13
|
+
|
|
14
|
+
/** Ask only at a terminal, and only when init will write chaff.yaml: a script or CI gets the default without waiting. */
|
|
15
|
+
export const shouldAsk = (stdin: Terminal, stdout: Terminal, configExists: boolean): boolean => stdin.isTTY === true && stdout.isTTY === true && !configExists;
|
|
16
|
+
|
|
17
|
+
/** The first line typed, or undefined when the input ends with none (Ctrl-D): waiting for a question's answer would never end then. */
|
|
18
|
+
const answerOf = (reader: Interface, prompt: string): Promise<string | undefined> => {
|
|
19
|
+
const answer = new Promise<string | undefined>((resolve) => {
|
|
20
|
+
reader.once("line", resolve);
|
|
21
|
+
reader.once("close", () => resolve(undefined));
|
|
22
|
+
});
|
|
23
|
+
reader.setPrompt(prompt);
|
|
24
|
+
reader.prompt();
|
|
25
|
+
return answer;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const ask = async (ui: UiLanguage, io: Prompter): Promise<{ readonly genre: string | undefined; readonly answer: string | undefined }> => {
|
|
29
|
+
const data = loadGenres();
|
|
30
|
+
io.output.write(`${genreChoices(data, ui).join("\n")}\n`);
|
|
31
|
+
const reader = createInterface({ input: io.input, output: io.output });
|
|
32
|
+
try {
|
|
33
|
+
const answer = await answerOf(reader, choicePrompt(ui));
|
|
34
|
+
const ids = data.genres.map((genre) => genre.id);
|
|
35
|
+
return { genre: answer === undefined ? undefined : genreFromAnswer(answer, ids), answer };
|
|
36
|
+
} finally {
|
|
37
|
+
reader.close();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** The genre init writes: --genre, else the one picked at the prompt, else the default. An answer that is no genre is an error. */
|
|
42
|
+
export const initGenre = async (
|
|
43
|
+
flagged: string | undefined,
|
|
44
|
+
ui: UiLanguage,
|
|
45
|
+
dir: string,
|
|
46
|
+
io: Prompter = { input: process.stdin, output: process.stdout },
|
|
47
|
+
): Promise<{ readonly genre: string } | { readonly error: string }> => {
|
|
48
|
+
if (flagged !== undefined) return { genre: flagged };
|
|
49
|
+
if (!shouldAsk(io.input, io.output, existsSync(join(dir, CONFIG_FILE)))) return { genre: DEFAULT_GENRE };
|
|
50
|
+
const picked = await ask(ui, io);
|
|
51
|
+
if (picked.answer === undefined) return { error: noAnswer(ui) };
|
|
52
|
+
return picked.genre === undefined ? { error: notAGenre(picked.answer.trim(), ui) } : { genre: picked.genre };
|
|
53
|
+
};
|
package/src/commands/test.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { plainSource } from "../plain-source.ts";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { loadAdapter } from "../adapter-load.ts";
|
|
@@ -6,20 +7,80 @@ import { buildDocument, teamRules } from "../document.ts";
|
|
|
6
7
|
import { guessLanguage } from "../detect.ts";
|
|
7
8
|
import { collectTargets } from "../files.ts";
|
|
8
9
|
import { loadRules } from "../rule-load.ts";
|
|
10
|
+
import { counted } from "../render/plural.ts";
|
|
9
11
|
import { CHECKS_FILE, loadChecks, type UserCheck } from "../checks.ts";
|
|
10
|
-
import { CACHE_DIR, credentialHint, describeFailure, hasCredentials } from "../judge.ts";
|
|
12
|
+
import { CACHE_DIR, credentialHint, describeFailure, hasCredentials, type JudgeOptions } from "../judge.ts";
|
|
11
13
|
import { ENV_FILE, loadEnvFile } from "../env.ts";
|
|
12
14
|
import { planSemantic, runSemantic } from "../run-semantic.ts";
|
|
13
|
-
import {
|
|
15
|
+
import { machineBanner, renderPlan, renderSemantic } from "../render/semantic.ts";
|
|
14
16
|
import type { Config } from "../config/load.ts";
|
|
15
17
|
import type { Finding, RuleDefinition } from "../plugin.ts";
|
|
16
|
-
import type { Failure } from "../backends/types.ts";
|
|
18
|
+
import type { BackendName, Failure } from "../backends/types.ts";
|
|
17
19
|
import { profileFor } from "../profile/for-file.ts";
|
|
20
|
+
import { CLI_TEXT } from "../cli-text.ts";
|
|
21
|
+
import { sharedLanguage, type Texts, type UiLanguage } from "../ui.ts";
|
|
22
|
+
|
|
23
|
+
const TEXT: Texts<{
|
|
24
|
+
readonly total: (total: number) => string;
|
|
25
|
+
readonly sentTo: (backend: string, model: string, found: boolean) => string;
|
|
26
|
+
readonly envRead: (path: string) => string;
|
|
27
|
+
readonly envWhere: (found: string | undefined) => string;
|
|
28
|
+
readonly notRun: (why: string, what: string, where: string) => readonly string[];
|
|
29
|
+
readonly noCredentials: (backend: BackendName) => string;
|
|
30
|
+
readonly refused: (backend: BackendName, status: string) => string;
|
|
31
|
+
readonly checkKey: string;
|
|
32
|
+
readonly quota: (backend: BackendName, status: string) => string;
|
|
33
|
+
readonly returned: (backend: BackendName, status: string) => string;
|
|
34
|
+
readonly noCause: string;
|
|
35
|
+
readonly tally: (machine: number, ai: number) => string;
|
|
36
|
+
readonly untouched: string;
|
|
37
|
+
}> = {
|
|
38
|
+
ja: {
|
|
39
|
+
total: (total) => ` 合計 ${total} 箇所を送ります。--dry-run なので API は呼んでいません。`,
|
|
40
|
+
sentTo: (backend, model, found) => ` 送り先: ${backend} / ${model}(認証${found ? "あり" : "なし"})`,
|
|
41
|
+
envRead: (path) => ` ${path} を読みました。`,
|
|
42
|
+
envWhere: (found) => (found === undefined ? `${ENV_FILE} に書いても読みます(いまは ${ENV_FILE} がありません)` : `${found} は読みました`),
|
|
43
|
+
notRun: (why, what, where) => [` 意味を読む検査は動かしていません。${why}`, ` ${what}`, ` ${where}。`, " 機械による判定はすべて動いています。"],
|
|
44
|
+
noCredentials: (backend) => `${backend} の認証情報がありません。`,
|
|
45
|
+
refused: (backend, status) => `${backend} が鍵を受け付けませんでした(${status})。`,
|
|
46
|
+
checkKey: "鍵が正しいか、その provider のものかを確かめてください。",
|
|
47
|
+
quota: (backend, status) => `${backend} の残高か上限に達しました(${status})。`,
|
|
48
|
+
returned: (backend, status) => `${backend} が ${status} を返しました。`,
|
|
49
|
+
noCause: "原因は返ってきませんでした",
|
|
50
|
+
tally: (machine, ai) => ` 機械 ${machine} 件 / AI ${ai} 件`,
|
|
51
|
+
untouched: " 文章は書き換えていません。直すのは書いた人です。",
|
|
52
|
+
},
|
|
53
|
+
en: {
|
|
54
|
+
total: (total) => ` ${counted(total, "passage")} in all would be sent. With --dry-run the API was not called.`,
|
|
55
|
+
sentTo: (backend, model, found) => ` Sent to: ${backend} / ${model} (${found ? "credentials found" : "no credentials"})`,
|
|
56
|
+
envRead: (path) => ` Read ${path}.`,
|
|
57
|
+
envWhere: (found) => (found === undefined ? `A key written in ${ENV_FILE} is read too (there is no ${ENV_FILE} now)` : `${found} was read`),
|
|
58
|
+
notRun: (why, what, where) => [` The checks that read meaning did not run. ${why}`, ` ${what}`, ` ${where}.`, " Every machine check ran."],
|
|
59
|
+
noCredentials: (backend) => `There are no credentials for ${backend}.`,
|
|
60
|
+
refused: (backend, status) => `${backend} did not accept the key (${status}).`,
|
|
61
|
+
checkKey: "Check that the key is right and that it is for that provider.",
|
|
62
|
+
quota: (backend, status) => `${backend} has run out of balance or hit a limit (${status}).`,
|
|
63
|
+
returned: (backend, status) => `${backend} returned ${status}.`,
|
|
64
|
+
noCause: "No reason was given.",
|
|
65
|
+
tally: (machine, ai) => ` ${machine} by machine / ${ai} by AI`,
|
|
66
|
+
untouched: " The text was not changed. Fixing it is the writer's job.",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const DIVIDER = "─".repeat(60);
|
|
18
71
|
|
|
19
72
|
export type TestContext = {
|
|
20
73
|
readonly config: Config;
|
|
21
74
|
readonly resolveGenre: (path: string, source: string, config: Config) => { genre: string; from: string };
|
|
22
|
-
readonly inspect: (
|
|
75
|
+
readonly inspect: (
|
|
76
|
+
path: string,
|
|
77
|
+
config: Config,
|
|
78
|
+
argv: readonly string[],
|
|
79
|
+
) => Promise<{ text: string; language: string; outcome: { findings: readonly Finding[] } }>;
|
|
80
|
+
/** The language of what is not about the documents: chaff.yaml's language, else the locale. */
|
|
81
|
+
readonly ui: UiLanguage;
|
|
82
|
+
/** The judges to call instead of the providers' own clients. Tests pass stand-ins; the command line never does. */
|
|
83
|
+
readonly clients?: Pick<JudgeOptions, "anthropicClient" | "openaiClient">;
|
|
23
84
|
};
|
|
24
85
|
|
|
25
86
|
type Judged = { path: string; outcome: Awaited<ReturnType<typeof runSemantic>>; rules: RuleDefinition[]; language: string };
|
|
@@ -33,11 +94,11 @@ const judgeAll = async (
|
|
|
33
94
|
): Promise<Judged[]> =>
|
|
34
95
|
Promise.all(
|
|
35
96
|
paths.map(async (path) => {
|
|
36
|
-
const source = await readFile(path, "utf8");
|
|
97
|
+
const source = plainSource(await readFile(path, "utf8"));
|
|
37
98
|
const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
|
|
38
99
|
const adapter = await loadAdapter(language);
|
|
39
|
-
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language));
|
|
40
100
|
const { genre } = resolveGenre(path, source, config);
|
|
101
|
+
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language, genre));
|
|
41
102
|
const rules = loadRules(language);
|
|
42
103
|
return { path, outcome: await runSemantic(doc, rules, checks, config.rules, genre, options), rules, language };
|
|
43
104
|
}),
|
|
@@ -49,43 +110,51 @@ const dryRun = async (
|
|
|
49
110
|
config: Config,
|
|
50
111
|
checks: readonly UserCheck[],
|
|
51
112
|
resolveGenre: TestContext["resolveGenre"],
|
|
52
|
-
envFile: string | undefined,
|
|
113
|
+
closing: { readonly envFile: string | undefined; readonly language: UiLanguage },
|
|
53
114
|
): Promise<void> => {
|
|
54
115
|
const plans = await Promise.all(
|
|
55
116
|
paths.map(async (path) => {
|
|
56
|
-
const source = await readFile(path, "utf8");
|
|
117
|
+
const source = plainSource(await readFile(path, "utf8"));
|
|
57
118
|
const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
|
|
58
119
|
const adapter = await loadAdapter(language);
|
|
59
|
-
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language));
|
|
60
120
|
const { genre } = resolveGenre(path, source, config);
|
|
61
|
-
|
|
121
|
+
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language, genre));
|
|
122
|
+
return { path, jobs: planSemantic(doc, loadRules(language), checks, config.rules, genre), sentences: doc.sentences.length, language };
|
|
62
123
|
}),
|
|
63
124
|
);
|
|
64
|
-
plans.forEach(({ path, jobs, sentences }) => console.log(renderPlan(path, jobs, sentences).join("\n")));
|
|
125
|
+
plans.forEach(({ path, jobs, sentences, language }) => console.log(renderPlan(path, jobs, sentences, language).join("\n")));
|
|
65
126
|
const total = plans.reduce((sum, plan) => sum + plan.jobs.reduce((count, job) => count + job.candidates.length, 0), 0);
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
console.log(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
);
|
|
127
|
+
const text = TEXT[closing.language];
|
|
128
|
+
const from = closing.envFile === undefined ? [] : [text.envRead(closing.envFile)];
|
|
129
|
+
console.log(["", DIVIDER, "", text.total(total), text.sentTo(config.aiBackend, config.aiModel, hasCredentials(config.aiBackend)), ...from, ""].join("\n"));
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* API が返した失敗を人の言葉にする。生のスタックトレースを人に見せない。
|
|
134
|
+
* 鍵はあるのに弾かれたときに「ありません」と言うと、設定済みの鍵をもう一度設定しに行かせる。
|
|
135
|
+
* 残高切れも同じで、「鍵を確かめてください」と言われても鍵は正しい。
|
|
136
|
+
*/
|
|
137
|
+
const whyFailed = (failure: Failure, backend: BackendName, language: UiLanguage): { why: string; what: string } => {
|
|
138
|
+
const text = TEXT[language];
|
|
139
|
+
const status = String(failure.status ?? "?");
|
|
140
|
+
if (failure.kind === "auth") return { why: text.refused(backend, status), what: text.checkKey };
|
|
141
|
+
const what = failure.message ?? text.noCause;
|
|
142
|
+
return failure.kind === "quota" ? { why: text.quota(backend, status), what } : { why: text.returned(backend, status), what };
|
|
79
143
|
};
|
|
80
144
|
|
|
145
|
+
/** Why the checks that read meaning did not run, and where a key would be read from. */
|
|
146
|
+
const notRunNotice = (why: string, what: string, envFile: string | undefined, language: UiLanguage): string =>
|
|
147
|
+
["", ...TEXT[language].notRun(why, what, TEXT[language].envWhere(envFile)), ""].join("\n");
|
|
148
|
+
|
|
81
149
|
/**
|
|
82
150
|
* 機械と AI を見出しで分けて出す。読む人が「これは揺れる判定か」を知らないと、
|
|
83
151
|
* AI の誤検知に振り回される。workflow spec §7、samples/README の軸 2。
|
|
152
|
+
* 見出しと指摘は文書の言語、締めは文書がそろっていればその言語、混ざっていれば ui の言語。
|
|
84
153
|
*/
|
|
85
154
|
export const runTest = async (targets: readonly string[], argv: readonly string[], context: TestContext): Promise<number> => {
|
|
86
155
|
const paths = collectTargets(targets.length > 0 ? targets : ["."]);
|
|
87
156
|
if (paths.length === 0) {
|
|
88
|
-
console.error(
|
|
157
|
+
console.error(CLI_TEXT[context.ui].noMarkdown(targets.join(", ")));
|
|
89
158
|
return 1;
|
|
90
159
|
}
|
|
91
160
|
const { config, resolveGenre, inspect } = context;
|
|
@@ -94,31 +163,20 @@ export const runTest = async (targets: readonly string[], argv: readonly string[
|
|
|
94
163
|
const checks = loadChecks(join(process.cwd(), CHECKS_FILE));
|
|
95
164
|
const results = await Promise.all(paths.map((path) => inspect(path, config, argv)));
|
|
96
165
|
results.forEach((result) => {
|
|
97
|
-
console.log(result.text.replace(/\n/u, `\n${
|
|
166
|
+
console.log(result.text.replace(/\n/u, `\n${machineBanner(result.language).join("\n")}`));
|
|
98
167
|
});
|
|
168
|
+
const closing = sharedLanguage(
|
|
169
|
+
results.map((result) => result.language),
|
|
170
|
+
context.ui,
|
|
171
|
+
);
|
|
99
172
|
const machineOnly = results.some((result) => result.outcome.findings.some((finding) => finding.severity === "error")) ? 1 : 0;
|
|
100
|
-
const where = envFile === undefined ? `${ENV_FILE} に書いても読みます(いまは ${ENV_FILE} がありません)` : `${envFile} は読みました`;
|
|
101
|
-
const notRun = (why: string, what: string): string =>
|
|
102
|
-
["", ` 意味を読む検査は動かしていません。${why}`, ` ${what}`, ` ${where}。`, " 機械による判定はすべて動いています。", ""].join("\n");
|
|
103
|
-
const noCredentials = notRun(`${config.aiBackend} の認証情報がありません。`, credentialHint(config.aiBackend));
|
|
104
|
-
/**
|
|
105
|
-
* API が返した失敗を人の言葉にする。生のスタックトレースを人に見せない。
|
|
106
|
-
* 鍵はあるのに弾かれたときに「ありません」と言うと、設定済みの鍵をもう一度設定しに行かせる。
|
|
107
|
-
* 残高切れも同じで、「鍵を確かめてください」と言われても鍵は正しい。
|
|
108
|
-
*/
|
|
109
|
-
const failed = (failure: Failure): string => {
|
|
110
|
-
if (failure.kind === "auth")
|
|
111
|
-
return notRun(`${config.aiBackend} が鍵を受け付けませんでした(${failure.status ?? "?"})。`, "鍵が正しいか、その provider のものかを確かめてください。");
|
|
112
|
-
if (failure.kind === "quota") return notRun(`${config.aiBackend} の残高か上限に達しました(${failure.status ?? "?"})。`, failure.message);
|
|
113
|
-
return notRun(`${config.aiBackend} が ${failure.status ?? "?"} を返しました。`, failure.message);
|
|
114
|
-
};
|
|
115
173
|
if (argv.includes("--dry-run")) {
|
|
116
|
-
await dryRun(paths, config, checks, resolveGenre, envFile);
|
|
174
|
+
await dryRun(paths, config, checks, resolveGenre, { envFile, language: closing });
|
|
117
175
|
return machineOnly;
|
|
118
176
|
}
|
|
119
177
|
// 呼んでから落ちるのを待たない。認証が無いときの SDK の例外は型で判別できない。
|
|
120
178
|
if (!hasCredentials(config.aiBackend)) {
|
|
121
|
-
console.log(noCredentials);
|
|
179
|
+
console.log(notRunNotice(TEXT[closing].noCredentials(config.aiBackend), credentialHint(config.aiBackend, closing), envFile, closing));
|
|
122
180
|
return machineOnly;
|
|
123
181
|
}
|
|
124
182
|
const options = {
|
|
@@ -126,12 +184,14 @@ export const runTest = async (targets: readonly string[], argv: readonly string[
|
|
|
126
184
|
backend: config.aiBackend,
|
|
127
185
|
cacheDir: join(process.cwd(), CACHE_DIR),
|
|
128
186
|
confidenceThreshold: config.confidenceThreshold,
|
|
187
|
+
...context.clients,
|
|
129
188
|
};
|
|
130
189
|
const semantic = await judgeAll(paths, config, checks, options, resolveGenre).catch((error: unknown) => {
|
|
131
190
|
// API が返した失敗だけを受け止める。ネットワーク断やコードの誤りは握りつぶさない。
|
|
132
191
|
const failure = describeFailure(config.aiBackend, error);
|
|
133
192
|
if (failure === undefined) throw error;
|
|
134
|
-
|
|
193
|
+
const { why, what } = whyFailed(failure, config.aiBackend, closing);
|
|
194
|
+
console.log(notRunNotice(why, what, envFile, closing));
|
|
135
195
|
return undefined;
|
|
136
196
|
});
|
|
137
197
|
if (semantic === undefined) return machineOnly;
|
|
@@ -141,8 +201,6 @@ export const runTest = async (targets: readonly string[], argv: readonly string[
|
|
|
141
201
|
});
|
|
142
202
|
const machine = results.flatMap((result) => result.outcome.findings);
|
|
143
203
|
const ai = semantic.flatMap((entry) => entry.outcome.findings);
|
|
144
|
-
console.log(
|
|
145
|
-
["", "─".repeat(60), "", ` 機械 ${machine.length} 件 / AI ${ai.length} 件`, " 文章は書き換えていません。直すのは書いた人です。", ""].join("\n"),
|
|
146
|
-
);
|
|
204
|
+
console.log(["", DIVIDER, "", TEXT[closing].tally(machine.length, ai.length), TEXT[closing].untouched, ""].join("\n"));
|
|
147
205
|
return [...machine, ...ai].some((finding) => finding.severity === "error") ? 1 : 0;
|
|
148
206
|
};
|
package/src/commands/tree.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { plainSource } from "../plain-source.ts";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { loadAdapter } from "../adapter-load.ts";
|
|
3
4
|
import { applyByPath } from "../config/by-path.ts";
|
|
4
5
|
import { profileFor } from "../profile/for-file.ts";
|
|
6
|
+
import { resolveGenre } from "../resolve-genre.ts";
|
|
5
7
|
import { guessLanguage } from "../detect.ts";
|
|
6
8
|
import { isMarkdownPath } from "../structure/markdown-path.ts";
|
|
7
9
|
import { buildStructure } from "../structure/of.ts";
|
|
@@ -34,19 +36,19 @@ const TEXT: Texts<{
|
|
|
34
36
|
},
|
|
35
37
|
};
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
const treeText = (context: TreeContext): (typeof TEXT)["ja"] => TEXT[context.ui ?? "ja"];
|
|
38
40
|
|
|
39
|
-
const FORMATS = ["sexp", "json"];
|
|
41
|
+
const FORMATS: ReadonlySet<string> = new Set(["sexp", "json"]);
|
|
40
42
|
|
|
41
43
|
/** 値を取るフラグ。その次の引数は値で、対象のファイルではない。 */
|
|
42
|
-
const VALUED = ["--format", "--language"];
|
|
44
|
+
const VALUED: ReadonlySet<string> = new Set(["--format", "--language", "--genre"]);
|
|
43
45
|
|
|
44
46
|
export const treeTargets = (argv: readonly string[]): string[] =>
|
|
45
|
-
argv.slice(1).filter((arg, index, all) => !arg.startsWith("--") && !VALUED.
|
|
47
|
+
argv.slice(1).filter((arg, index, all) => !arg.startsWith("--") && !VALUED.has(all[index - 1] ?? ""));
|
|
46
48
|
|
|
47
49
|
export const readSource = async (path: string, context: TreeContext): Promise<string | undefined> => {
|
|
48
50
|
try {
|
|
49
|
-
return await readFile(path, "utf8");
|
|
51
|
+
return plainSource(await readFile(path, "utf8"));
|
|
50
52
|
} catch (err) {
|
|
51
53
|
console.error(treeText(context).unreadable(path, err instanceof Error ? err.message : String(err)));
|
|
52
54
|
return undefined;
|
|
@@ -80,7 +82,7 @@ export const readTree = async (path: string, argv: readonly string[], context: T
|
|
|
80
82
|
}
|
|
81
83
|
// 日本語は形態素で数量と日付を読む。解析器が無ければ単位の表で読むので、木は作れる。
|
|
82
84
|
await adapter.prepare?.({ pos: true });
|
|
83
|
-
const profile = profileFor(context.config, path, source, language);
|
|
85
|
+
const profile = profileFor(context.config, path, source, language, resolveGenre(path, source, context.config, context.flag(argv, "--genre")).genre);
|
|
84
86
|
return { source, tree: buildStructure({ path, source, language, markdown: isMarkdownPath(path), profile }, adapter.structure) };
|
|
85
87
|
};
|
|
86
88
|
|
|
@@ -107,7 +109,7 @@ export const inOrder = async (paths: readonly string[], each: (path: string) =>
|
|
|
107
109
|
*/
|
|
108
110
|
export const runTree = async (paths: readonly string[], argv: readonly string[], context: TreeContext): Promise<number> => {
|
|
109
111
|
const format = context.flag(argv, "--format") ?? "sexp";
|
|
110
|
-
if (paths.length === 0 || !FORMATS.
|
|
112
|
+
if (paths.length === 0 || !FORMATS.has(format)) {
|
|
111
113
|
console.error(treeText(context).usage);
|
|
112
114
|
return 1;
|
|
113
115
|
}
|
package/src/config/by-path.ts
CHANGED
|
@@ -44,12 +44,11 @@ export const matches = (glob: string, base: string, path: string): boolean => to
|
|
|
44
44
|
type PathSettings = { genre?: string; language?: string; profile?: string };
|
|
45
45
|
|
|
46
46
|
export const applyByPath = (rules: readonly PathRule[], base: string, path: string): PathSettings =>
|
|
47
|
-
rules
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
};
|
|
55
|
-
}, {});
|
|
47
|
+
rules
|
|
48
|
+
.filter((rule) => rule.files.some((glob) => matches(glob, base, path)))
|
|
49
|
+
.reduce<PathSettings>((settings, rule) => {
|
|
50
|
+
if (rule.genre !== undefined) settings.genre = rule.genre;
|
|
51
|
+
if (rule.language !== undefined) settings.language = rule.language;
|
|
52
|
+
if (rule.profile !== undefined) settings.profile = rule.profile;
|
|
53
|
+
return settings;
|
|
54
|
+
}, {});
|
package/src/config/load.ts
CHANGED
|
@@ -30,6 +30,10 @@ export type Config = {
|
|
|
30
30
|
readonly prefer: Readonly<Record<string, string>>;
|
|
31
31
|
/** この種類の文書に無いと困る見出し。チームが自分で決める。 */
|
|
32
32
|
readonly requiredSections: readonly string[];
|
|
33
|
+
/** チームの固有名詞(組織名・製品名)。1 つの名前として読み、漢字の連なりや繰り返しに数えない。 */
|
|
34
|
+
readonly names: readonly string[];
|
|
35
|
+
/** names に書かれていたが名前として読めなかった値。黙って捨てると、並べたつもりの名前が効いていないことに気づけない。 */
|
|
36
|
+
readonly unreadableNames: readonly string[];
|
|
33
37
|
/** パスごとの上書き。設定ファイルのある場所からの相対で照合する。 */
|
|
34
38
|
readonly byPath: readonly PathRule[];
|
|
35
39
|
readonly baseDir: string;
|
|
@@ -54,6 +58,8 @@ export const EMPTY: Config = {
|
|
|
54
58
|
jargon: [],
|
|
55
59
|
prefer: {},
|
|
56
60
|
requiredSections: [],
|
|
61
|
+
names: [],
|
|
62
|
+
unreadableNames: [],
|
|
57
63
|
byPath: [],
|
|
58
64
|
baseDir: process.cwd(),
|
|
59
65
|
};
|
|
@@ -114,6 +120,21 @@ const preferOf = (raw: unknown): Record<string, string> =>
|
|
|
114
120
|
)
|
|
115
121
|
: {};
|
|
116
122
|
|
|
123
|
+
const isNameEntry = (value: unknown): value is string | number => typeof value === "string" || typeof value === "number";
|
|
124
|
+
|
|
125
|
+
const printed = (value: unknown): string => JSON.stringify(value) ?? String(value);
|
|
126
|
+
|
|
127
|
+
/** 名前は文字列か数(2025 のような名前)の並び。並びでない値と、並びの中の名前でない項目は読めないものとして返す。 */
|
|
128
|
+
const namesOf = (raw: unknown): { readonly names: string[]; readonly unreadable: string[] } => {
|
|
129
|
+
if (raw === undefined || raw === null) return { names: [], unreadable: [] };
|
|
130
|
+
if (!Array.isArray(raw)) return { names: [], unreadable: [printed(raw)] };
|
|
131
|
+
const entries: unknown[] = raw;
|
|
132
|
+
return {
|
|
133
|
+
names: wordsOf(entries.filter(isNameEntry)),
|
|
134
|
+
unreadable: entries.filter((entry) => !isNameEntry(entry)).map(printed),
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
|
|
117
138
|
const byPathOf = (raw: unknown): PathRule[] => (Array.isArray(raw) ? raw.map(toPathRule).filter((rule) => rule !== undefined) : []);
|
|
118
139
|
|
|
119
140
|
/** 設定ファイルが無くても動く。あっても、既定から変えたものだけが書かれている。spec §18。 */
|
|
@@ -122,6 +143,7 @@ export const loadConfig = (path: string): Config => {
|
|
|
122
143
|
if (!isRecord(raw)) return { ...EMPTY, path };
|
|
123
144
|
const declared: unknown = raw["ai_backend"];
|
|
124
145
|
const backend: BackendName = isBackend(declared) ? declared : DEFAULT_BACKEND;
|
|
146
|
+
const names = namesOf(raw["names"]);
|
|
125
147
|
return {
|
|
126
148
|
genre: str(raw["genre"]),
|
|
127
149
|
profile: str(raw["profile"]),
|
|
@@ -137,6 +159,8 @@ export const loadConfig = (path: string): Config => {
|
|
|
137
159
|
jargon: wordsOf(raw["jargon"]),
|
|
138
160
|
prefer: preferOf(raw["prefer"]),
|
|
139
161
|
requiredSections: wordsOf(raw["required_sections"]),
|
|
162
|
+
names: names.names,
|
|
163
|
+
unreadableNames: names.unreadable,
|
|
140
164
|
byPath: byPathOf(raw["by_path"]),
|
|
141
165
|
baseDir: dirname(path),
|
|
142
166
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Config } from "./load.ts";
|
|
2
|
+
import type { Texts, UiLanguage } from "../ui.ts";
|
|
3
|
+
|
|
4
|
+
const TEXT: Texts<{ readonly unreadable: (where: string, value: string) => string }> = {
|
|
5
|
+
ja: {
|
|
6
|
+
unreadable: (where, value) => `${where}: names の ${value} は名前として読めません。names には名前を 1 行に 1 つずつ並べてください(- 個人情報保護委員会)`,
|
|
7
|
+
},
|
|
8
|
+
en: {
|
|
9
|
+
unreadable: (where, value) => `${where}: cannot read ${value} under names as a name. Write names as a list, one name per line (- Bank of England)`,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** chaff.yaml の names のうち、名前として読めなかったもの。読めないまま黙ると、書いた人は名前を並べたつもりで何も変わっていない。 */
|
|
14
|
+
export const nameProblems = (config: Pick<Config, "unreadableNames" | "path">, ui: UiLanguage = "ja"): string[] =>
|
|
15
|
+
config.unreadableNames.map((value) => TEXT[ui].unreadable(config.path ?? "chaff.yaml", value));
|
package/src/date-stamp.ts
CHANGED
|
@@ -21,7 +21,7 @@ const LABEL_EDGE = /[\s::]/u;
|
|
|
21
21
|
const datesOf = (candidate: StampCandidate): Span[] => candidate.dates.filter((date) => date.end > date.start);
|
|
22
22
|
|
|
23
23
|
const withoutDates = (candidate: StampCandidate): string => {
|
|
24
|
-
const ordered = datesOf(candidate).
|
|
24
|
+
const ordered = datesOf(candidate).toSorted((left, right) => left.start - right.start);
|
|
25
25
|
const gaps = ordered.reduce<{ readonly parts: readonly string[]; readonly from: number }>(
|
|
26
26
|
(acc, date) => ({ parts: [...acc.parts, candidate.text.slice(acc.from, date.start)], from: Math.max(acc.from, date.end) }),
|
|
27
27
|
{ parts: [], from: 0 },
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** - で繋いだ略語(RT-PCR)の部品。繋いでいなければ、その語だけ。 */
|
|
2
|
+
export const partsOf = (acronym: string): string[] => acronym.split("-");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 略語が説明済みか。- で繋いだ語は、繋いだ形のまま説明されているか、部品がどれも説明済みなら説明済み。
|
|
6
|
+
* US-EU は US も EU も通じるので指摘しない。RT-PCR は RT がどこにも説明されていないので、繋いだ形で指摘する。
|
|
7
|
+
*/
|
|
8
|
+
export const isExplained = (acronym: string, explainedAlone: (word: string) => boolean): boolean =>
|
|
9
|
+
explainedAlone(acronym) || partsOf(acronym).every(explainedAlone);
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { escapeRegExp } from "../orthography.ts";
|
|
2
2
|
import { ROMAN_NUMERAL } from "./roman-numeral.ts";
|
|
3
|
+
import { citationKeySpans } from "./citation-key.ts";
|
|
4
|
+
import { dottedNameSpans } from "./dotted-name.ts";
|
|
5
|
+
import { loneNumeralSpans, nameNumeralSpans } from "./name-numeral.ts";
|
|
6
|
+
import { placeholderSpans } from "./placeholder.ts";
|
|
7
|
+
import { seriesLabelSpans } from "./series-label.ts";
|
|
3
8
|
|
|
4
9
|
/**
|
|
5
10
|
* 大文字の語が略語ではなく、決まった書き方の一部として読める所(3:30 PM、1pm ET、USD 1,000、Kansas City, MO 64108)。
|
|
@@ -18,6 +23,10 @@ export type NotationWords = {
|
|
|
18
23
|
readonly emphasis: readonly string[];
|
|
19
24
|
/** 番号を後ろに書く、文書の区切りの名前(Part、Section、Title)。 */
|
|
20
25
|
readonly divisions: readonly string[];
|
|
26
|
+
/** 速記録が大文字で書く発言者の姓の前に置く敬称(Mr.、Mrs.、Madam)。 */
|
|
27
|
+
readonly honorifics: readonly string[];
|
|
28
|
+
/** 日付・時刻の数字の後ろに書く単位(年、月、日、時、分)。その前の同じ大文字の繰り返し(YY年)は書式の置き場所。 */
|
|
29
|
+
readonly dateTimeUnits: readonly string[];
|
|
21
30
|
};
|
|
22
31
|
|
|
23
32
|
// 空の語彙表は「その書き方が無い」。空の選択肢 (?:) は至る所で空文字に当たるので、何にも当たらない形にする。
|
|
@@ -37,6 +46,9 @@ const AMOUNT = String.raw`(?:\d{1,3}(?:,\d{3})+|\d+)(?:\.\d+)?`;
|
|
|
37
46
|
*/
|
|
38
47
|
const divisionOf = (words: readonly string[]): string => oneOf(words.flatMap((word) => [word, word.toUpperCase()]));
|
|
39
48
|
|
|
49
|
+
/** 敬称のすぐ後ろの大文字の姓(Mr. HAWLEY、Dr. O'NEIL)。速記録は発言者をこう書き、略語ではない。 */
|
|
50
|
+
const SURNAME_IN_CAPITALS = String.raw`[A-Z]+(?:['’-][A-Z]+)*(?![\p{L}\p{N}_])`;
|
|
51
|
+
|
|
40
52
|
// 強調の記号は空白に置き換えてある(**3:30** PM)ので、部品の間の空白は数を問わない。
|
|
41
53
|
const patternsOf = (words: NotationWords): readonly RegExp[] => [
|
|
42
54
|
new RegExp(String.raw`${CLOCK_12}\s*${oneOf(words.meridiem)}`, "gu"),
|
|
@@ -46,6 +58,7 @@ const patternsOf = (words: NotationWords): readonly RegExp[] => [
|
|
|
46
58
|
new RegExp(String.raw`,\s+${oneOf(words.usStates)}\s+\d{5}(?:-\d{4})?(?!\d)`, "gu"),
|
|
47
59
|
new RegExp(oneOf(words.emphasis), "gu"),
|
|
48
60
|
new RegExp(String.raw`(?<![\p{L}\p{N}_])${divisionOf(words.divisions)}\s+${ROMAN_NUMERAL}(?![\p{L}\p{N}_&])`, "gu"),
|
|
61
|
+
new RegExp(String.raw`(?<![\p{L}\p{N}_])${oneOf(words.honorifics)}\s+${SURNAME_IN_CAPITALS}`, "gu"),
|
|
49
62
|
];
|
|
50
63
|
|
|
51
64
|
export type NotAcronymSpans = (text: string) => Span[];
|
|
@@ -56,5 +69,13 @@ export type NotAcronymSpans = (text: string) => Span[];
|
|
|
56
69
|
*/
|
|
57
70
|
export const notAcronymSpansOf = (words: NotationWords): NotAcronymSpans => {
|
|
58
71
|
const patterns = patternsOf(words);
|
|
59
|
-
return (text) =>
|
|
72
|
+
return (text) => [
|
|
73
|
+
...patterns.flatMap((pattern) => [...text.matchAll(pattern)].map((match) => ({ start: match.index, end: match.index + match[0].length }))),
|
|
74
|
+
...citationKeySpans(text),
|
|
75
|
+
...dottedNameSpans(text),
|
|
76
|
+
...nameNumeralSpans(text),
|
|
77
|
+
...loneNumeralSpans(text),
|
|
78
|
+
...seriesLabelSpans(text),
|
|
79
|
+
...placeholderSpans(text, words.dateTimeUnits),
|
|
80
|
+
];
|
|
60
81
|
};
|