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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Lexicon, Token } from "../plugin.ts";
|
|
2
|
+
import type { TokenRange } from "./lexicon-match.ts";
|
|
3
|
+
|
|
4
|
+
/** 名詞の後ろにこれが続けば、その名詞は句の頭ではない。解析器は形容詞を名詞と読むことがある(the most frigid weather・the most cost-effective)。 */
|
|
5
|
+
const JOINED = new Set(["NOUN", "PROPN", "ADJ", "X"]);
|
|
6
|
+
|
|
7
|
+
const listed = (tokens: readonly Token[], range: TokenRange, amounts: Lexicon): boolean => {
|
|
8
|
+
const words = tokens
|
|
9
|
+
.slice(range.start, range.end)
|
|
10
|
+
.map((token) => token.surface.toLowerCase())
|
|
11
|
+
.join(" ");
|
|
12
|
+
return amounts.some((entry) => entry.pattern.toLowerCase() === words);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 量も言う最上級に、句の頭になる名詞がそのまま続くか(the most work・the most students・for the most part)。
|
|
17
|
+
* それは一番多いものを言っていて、何かが一番良いという主張ではない。形容詞を挟めば(the most powerful tool)主張のまま。
|
|
18
|
+
* どの最上級が量を言うかは言語パッケージの語彙表 superlative-amount が持つ。
|
|
19
|
+
*/
|
|
20
|
+
export const namesAmount = (tokens: readonly Token[], range: TokenRange, amounts: Lexicon): boolean => {
|
|
21
|
+
const noun = tokens[range.end];
|
|
22
|
+
const after = tokens[range.end + 1];
|
|
23
|
+
return listed(tokens, range, amounts) && noun?.pos === "NOUN" && (after === undefined || !JOINED.has(after.pos));
|
|
24
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Lexicon, Token } from "../plugin.ts";
|
|
2
|
+
import type { TokenRange } from "./lexicon-match.ts";
|
|
3
|
+
import { PHRASE_END, PHRASE_START, phraseEnd } from "./superlative-scope.ts";
|
|
4
|
+
|
|
5
|
+
/** 最上級の名詞句を後ろから限る語。どちらも言語パッケージの語彙表が持つ。 */
|
|
6
|
+
export type Restrictors = {
|
|
7
|
+
/** 名詞の後ろで関係節を始める語(that・which)。空の言語は、名詞の後ろに節を置かない。 */
|
|
8
|
+
readonly relatives: Lexicon;
|
|
9
|
+
/** 最上級を手に入る範囲に限る形容詞(possible・available)。 */
|
|
10
|
+
readonly bounds: Lexicon;
|
|
11
|
+
/** 節の主語になる代名詞(we・they)。themselves のような再帰の代名詞は主語にならない。 */
|
|
12
|
+
readonly subjects: Lexicon;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const VERBAL = new Set(["VERB", "AUX"]);
|
|
16
|
+
|
|
17
|
+
const isWord = (token: Token | undefined, words: Lexicon): boolean =>
|
|
18
|
+
token !== undefined && words.some((entry) => entry.pattern.toLowerCase() === token.surface.toLowerCase());
|
|
19
|
+
|
|
20
|
+
const isVerbal = (token: Token | undefined): boolean => token !== undefined && VERBAL.has(token.pos);
|
|
21
|
+
|
|
22
|
+
/** 節の主語(we・managers・the team・this)を読み飛ばした先に動詞があるか。主語の無い節(that has defined)は動詞で始まる。 */
|
|
23
|
+
const verbAfterSubject = (tokens: readonly Token[], at: number, subjects: Lexicon): boolean => {
|
|
24
|
+
if (isVerbal(tokens[at])) return true;
|
|
25
|
+
if (isWord(tokens[at], subjects)) return isVerbal(tokens[at + 1]);
|
|
26
|
+
if (tokens[at]?.pos === "DET") return isVerbal(tokens[at + 1]) || isVerbal(tokens[phraseEnd(tokens, at + 1)]);
|
|
27
|
+
return isVerbal(tokens[phraseEnd(tokens, at)]);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const isArticle = (token: Token | undefined): boolean => token?.features?.["PronType"] === "Art";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 関係詞を省いた節の主語は、代名詞(the best we have measured)か冠詞で始まる名詞句(the best option the team selected)。
|
|
34
|
+
* each・all のような限定詞(The best teams all adapt)や、冠詞の無い名詞(The best teams adapt)は節の始まりと読まない。
|
|
35
|
+
*/
|
|
36
|
+
const contactClause = (tokens: readonly Token[], at: number, subjects: Lexicon): boolean => {
|
|
37
|
+
if (isWord(tokens[at], subjects)) return isVerbal(tokens[at + 1]);
|
|
38
|
+
return isArticle(tokens[at]) && isVerbal(tokens[phraseEnd(tokens, at + 1)]);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** 関係節(the threats that we have seen・the option which we chose)と、関係詞を省いた節。 */
|
|
42
|
+
const clause = (tokens: readonly Token[], at: number, restrictors: Restrictors): boolean =>
|
|
43
|
+
isWord(tokens[at], restrictors.relatives) ? verbAfterSubject(tokens, at + 1, restrictors.subjects) : contactClause(tokens, at, restrictors.subjects);
|
|
44
|
+
|
|
45
|
+
/** 分詞の後ろに文の動詞があるか。and を挟んだ動詞(The best option changed quickly and won)は分詞と並ぶ述語。 */
|
|
46
|
+
const mainVerbFollows = (tokens: readonly Token[], at: number): boolean => {
|
|
47
|
+
const rest = tokens.slice(at + 1);
|
|
48
|
+
const verb = rest.findIndex(isVerbal);
|
|
49
|
+
return verb !== -1 && !rest.slice(0, verb).some((token) => token.pos === "CCONJ");
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 名詞の後ろの過去分詞(the most scalable option discussed)。名詞句が動詞の目的語か補語で(We chose the best option discussed)、
|
|
54
|
+
* または分詞の後ろに文の動詞がある(The best results obtained were poor)ときだけ。目的語を取る分詞(The best solution
|
|
55
|
+
* proposed a fix)と、ほかに述語の無い分詞(The best option changed.)は文の動詞。
|
|
56
|
+
*/
|
|
57
|
+
const participle = (tokens: readonly Token[], range: TokenRange, at: number): boolean => {
|
|
58
|
+
const next = tokens[at + 1];
|
|
59
|
+
if (tokens[at]?.features?.["VerbForm"] !== "Part" || (next !== undefined && PHRASE_START.has(next.pos))) return false;
|
|
60
|
+
return isVerbal(tokens[range.start - 1]) || mainVerbFollows(tokens, at);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** 最上級のすぐ後ろ(the best possible outcome)か、名詞句の後ろ(the best education possible)。 */
|
|
64
|
+
const bounded = (tokens: readonly Token[], range: TokenRange, at: number, bounds: Lexicon): boolean =>
|
|
65
|
+
isWord(tokens[range.end], bounds) || isWord(tokens[at], bounds);
|
|
66
|
+
|
|
67
|
+
/** 修飾の語は名詞の前に来るので、句の後ろで止まった分詞は名詞の後ろにある。 */
|
|
68
|
+
const clauseOrParticiple = (tokens: readonly Token[], range: TokenRange, at: number, restrictors: Restrictors): boolean => {
|
|
69
|
+
const last = tokens[at - 1];
|
|
70
|
+
const endsPhrase = at === range.end || (last !== undefined && PHRASE_END.has(last.pos));
|
|
71
|
+
return (endsPhrase && clause(tokens, at, restrictors)) || participle(tokens, range, at);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 最上級の名詞句を後ろの節か分詞か形容詞が限っているか(the best we have measured・the most scalable option discussed・
|
|
76
|
+
* the best education possible)。何の中で最もなのかを言っているので、限定の無い最上級ではない。
|
|
77
|
+
* 名詞の後ろに節を置かない言語は関係詞の語彙表が空で、節も分詞も読まない。
|
|
78
|
+
*/
|
|
79
|
+
export const restricted = (tokens: readonly Token[], range: TokenRange, restrictors: Restrictors): boolean => {
|
|
80
|
+
const at = phraseEnd(tokens, range.end);
|
|
81
|
+
return bounded(tokens, range, at, restrictors.bounds) || (restrictors.relatives.length > 0 && clauseOrParticiple(tokens, range, at, restrictors));
|
|
82
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Lexicon, Token } from "../plugin.ts";
|
|
2
|
+
import type { TokenRange } from "./lexicon-match.ts";
|
|
3
|
+
|
|
4
|
+
/** 記号(最高!)は名詞と付いても語を作らない。 */
|
|
5
|
+
const LETTER = /\p{L}/u;
|
|
6
|
+
|
|
7
|
+
const joinsAsNoun = (previous: Token, token: Token | undefined): token is Token =>
|
|
8
|
+
token !== undefined && token.pos === "NOUN" && LETTER.test(token.surface) && previous.span.end === token.span.start;
|
|
9
|
+
|
|
10
|
+
/** 空白も助詞も挟まずに続く名詞の連なり(最大|瞬間|風速)の最後の語。連なりが無ければ undefined。 */
|
|
11
|
+
const lastJoined = (tokens: readonly Token[], at: number): Token | undefined => {
|
|
12
|
+
const previous = tokens[at - 1];
|
|
13
|
+
const token = tokens[at];
|
|
14
|
+
if (previous === undefined || !joinsAsNoun(previous, token)) return undefined;
|
|
15
|
+
return lastJoined(tokens, at + 1) ?? token;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const measures = (noun: Token, quantities: Lexicon): boolean => quantities.some((entry) => entry.pattern === noun.surface);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 最上級の名詞がそのまま名詞と 1 語になり、その連なりが測る量の名詞で終わるか(最大風速・最高気温・最大値・最大駐車台数)。
|
|
22
|
+
* これは量の名前で、何かが一番だという主張ではない。「最大の効果」「最も速い」は助詞や用言を挟むので当たらない。
|
|
23
|
+
* ほかの名詞で終わる語(最高品質・最高精度・最大効果)は一番だという主張のまま。量の名詞は言語パッケージの語彙表が持つ。
|
|
24
|
+
*/
|
|
25
|
+
export const namesQuantity = (tokens: readonly Token[], range: TokenRange, quantities: Lexicon): boolean => {
|
|
26
|
+
const superlative = tokens[range.start];
|
|
27
|
+
const single = range.end - range.start === 1 && superlative?.pos === "NOUN";
|
|
28
|
+
const last = single ? lastJoined(tokens, range.end) : undefined;
|
|
29
|
+
return last !== undefined && measures(last, quantities);
|
|
30
|
+
};
|
|
@@ -21,14 +21,14 @@ export const scopeMarkersOf = (lexicon: Lexicon): ScopeMarkers => ({
|
|
|
21
21
|
const NOMINAL = new Set(["NOUN", "PROPN"]);
|
|
22
22
|
|
|
23
23
|
/** 名詞句の始まり。the world・Chicago・our products・the three・all。 */
|
|
24
|
-
const PHRASE_START = new Set(["DET", "NOUN", "PROPN", "PRON", "NUM", "ADJ"]);
|
|
24
|
+
export const PHRASE_START = new Set(["DET", "NOUN", "PROPN", "PRON", "NUM", "ADJ"]);
|
|
25
25
|
|
|
26
26
|
/** 名詞の前に来る修飾(most skilled・widely used・stuffed)。名詞が出た後は名詞の連なりだけが続く。 */
|
|
27
27
|
const PREMODIFIER = new Set(["ADJ", "ADV", "VERB", "NOUN", "PROPN", "NUM"]);
|
|
28
28
|
const NOUN_RUN = new Set(["NOUN", "PROPN", "NUM"]);
|
|
29
29
|
|
|
30
30
|
/** 範囲の前に置ける語。修飾だけで終わった句(the best is in …)は範囲ではなく述語。 */
|
|
31
|
-
const PHRASE_END = new Set(["NOUN", "PROPN", "NUM", "ADJ"]);
|
|
31
|
+
export const PHRASE_END = new Set(["NOUN", "PROPN", "NUM", "ADJ"]);
|
|
32
32
|
|
|
33
33
|
const isMarker = (token: Token | undefined, markers: Lexicon): boolean =>
|
|
34
34
|
token !== undefined && markers.some((entry) => entry.pattern.toLowerCase() === token.surface.toLowerCase());
|
|
@@ -47,7 +47,7 @@ const namedBefore = (tokens: readonly Token[], range: TokenRange, markers: Lexic
|
|
|
47
47
|
isMarker(tokens[range.start - 1], markers) && isNamed(tokens[range.start - 2]);
|
|
48
48
|
|
|
49
49
|
/** 最上級に続く名詞句の終わり。名詞が出るまでは修飾を、出た後は名詞の連なりを読む。 */
|
|
50
|
-
const phraseEnd = (tokens: readonly Token[], at: number, nounSeen = false): number => {
|
|
50
|
+
export const phraseEnd = (tokens: readonly Token[], at: number, nounSeen = false): number => {
|
|
51
51
|
const token = tokens[at];
|
|
52
52
|
if (token === undefined || !(nounSeen ? NOUN_RUN : PREMODIFIER).has(token.pos)) return at;
|
|
53
53
|
return phraseEnd(tokens, at + 1, nounSeen || NOMINAL.has(token.pos));
|
package/src/detectors/team.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { proseText } from "../measure.ts";
|
|
2
2
|
import { joinWords } from "./word-list.ts";
|
|
3
3
|
import { wordsOf } from "./structure.ts";
|
|
4
|
-
import
|
|
4
|
+
import { nameSpans } from "../team-names.ts";
|
|
5
|
+
import type { Detector, Finding, Sentence, Span, Token } from "../plugin.ts";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* チームの言葉。社内でしか通じない語を、チームが chaff.yaml に自分で並べる。
|
|
@@ -76,8 +77,23 @@ const isProperNoun = (token: Token): boolean => token.pos === "PROPN";
|
|
|
76
77
|
* spec は未知語率としていたが、**品詞解析があれば PROPN を数えるだけで足りる**。
|
|
77
78
|
* 辞書を別に持つ必要はない。
|
|
78
79
|
*/
|
|
80
|
+
const within = (spans: readonly Span[], token: Token): Span | undefined => spans.find((span) => token.span.start >= span.start && token.span.end <= span.end);
|
|
81
|
+
|
|
82
|
+
/** 並べた名前は、何語に割れても 1 つの固有名詞(Bank of England は 2 つでなく 1 つ)。名前の中の 2 語目からを落とす。 */
|
|
83
|
+
const properNounsOf = (sentence: Sentence, names: readonly string[]): Token[] => {
|
|
84
|
+
const named = nameSpans(sentence.text, names).map((span) => ({ start: sentence.span.start + span.start, end: sentence.span.start + span.end }));
|
|
85
|
+
const counted = new Set<Span>();
|
|
86
|
+
return (sentence.tokens ?? []).filter(isProperNoun).filter((token) => {
|
|
87
|
+
const name = within(named, token);
|
|
88
|
+
if (name === undefined) return true;
|
|
89
|
+
if (counted.has(name)) return false;
|
|
90
|
+
counted.add(name);
|
|
91
|
+
return true;
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
79
95
|
export const properNounDensity: Detector = (doc, options): Finding[] => {
|
|
80
|
-
const hits = doc.sentences.flatMap((sentence) => (sentence.
|
|
96
|
+
const hits = doc.sentences.flatMap((sentence) => properNounsOf(sentence, doc.names ?? []).map((token) => ({ sentence, token })));
|
|
81
97
|
const length = wordsOf(doc);
|
|
82
98
|
const rate = length === 0 ? 0 : Math.round((hits.length / length) * PER);
|
|
83
99
|
const first = hits[0];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasParticle, isClosed, lastContent } from "../sentence-shape.ts";
|
|
2
|
+
import { isWithinAny, quotedIn } from "../quoted-span.ts";
|
|
2
3
|
import type { Detector, Finding, Span, Token } from "../plugin.ts";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -44,10 +45,11 @@ export const nounEnding: Detector = (doc, options): Finding[] => {
|
|
|
44
45
|
*/
|
|
45
46
|
type Run = { readonly surface: string; readonly tokens: readonly Token[] };
|
|
46
47
|
|
|
47
|
-
const extend = (runs:
|
|
48
|
+
const extend = (runs: Run[], token: Token): Run[] => {
|
|
48
49
|
const last = runs.at(-1);
|
|
49
|
-
if (last !== undefined && last.surface === token.surface)
|
|
50
|
-
|
|
50
|
+
if (last !== undefined && last.surface === token.surface) runs[runs.length - 1] = { surface: last.surface, tokens: [...last.tokens, token] };
|
|
51
|
+
else runs.push({ surface: token.surface, tokens: [token] });
|
|
52
|
+
return runs;
|
|
51
53
|
};
|
|
52
54
|
|
|
53
55
|
const PARTICLE = new Set(["ADP", "SCONJ", "PART", "CCONJ"]);
|
|
@@ -57,17 +59,22 @@ const BREAK: Run = { surface: "", tokens: [] };
|
|
|
57
59
|
/**
|
|
58
60
|
* 名詞は連なりの中身なので飛ばす。読点と、語彙表に無い助詞は連なりを切る。
|
|
59
61
|
* 「弊社のAはBの」を 2 回続いたと数えないため。「は」でいちど句が閉じている。
|
|
62
|
+
*
|
|
63
|
+
* 鉤括弧で引いたもの(括弧ごと)は、名詞と同じく連なりの中身として飛ばす。引いた題名(「食品中のウイルスの制御のための…ガイドライン」)や
|
|
64
|
+
* 発言は書き手が言い換えられないので、中の助詞は数えない。題名も名前なので、外の連なり(弊社の「新製品」の販売の計画)は続けて数える。
|
|
60
65
|
*/
|
|
61
|
-
const runsOf = (tokens: readonly Token[], nesting: readonly string[]): Run[] =>
|
|
66
|
+
const runsOf = (tokens: readonly Token[], nesting: readonly string[], quoted: readonly Span[]): Run[] =>
|
|
62
67
|
tokens.reduce<Run[]>((acc, token) => {
|
|
68
|
+
if (isWithinAny(quoted, token.span)) return acc;
|
|
63
69
|
if (nesting.includes(token.surface)) return extend(acc, token);
|
|
64
|
-
|
|
70
|
+
if (token.pos === "PUNCT" || PARTICLE.has(token.pos)) acc.push(BREAK);
|
|
71
|
+
return acc;
|
|
65
72
|
}, []);
|
|
66
73
|
|
|
67
74
|
export const doubledParticle: Detector = (doc, options): Finding[] => {
|
|
68
75
|
const nesting = (options.lexicon ?? []).map((entry) => entry.pattern);
|
|
69
76
|
return doc.sentences.flatMap((sentence) =>
|
|
70
|
-
runsOf(sentence.tokens ?? [], nesting)
|
|
77
|
+
runsOf(sentence.tokens ?? [], nesting, quotedIn(sentence))
|
|
71
78
|
.filter((run) => run.tokens.length > options.limit)
|
|
72
79
|
.map((run) => ({
|
|
73
80
|
rule: "no-doubled-joshi",
|
package/src/document.ts
CHANGED
|
@@ -6,6 +6,9 @@ import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
|
|
|
6
6
|
import { atxHeadingText, headingText } from "./heading-text.ts";
|
|
7
7
|
import { hasTitle } from "./heading-title.ts";
|
|
8
8
|
import { maskSpans } from "./mask.ts";
|
|
9
|
+
import { spansWithin, unmaskedSoftBreaks } from "./soft-break.ts";
|
|
10
|
+
import { segmentJoined } from "./joined-view.ts";
|
|
11
|
+
import { lineParagraphs } from "./line-paragraphs.ts";
|
|
9
12
|
import { buildTree, type Outline } from "./structure/build.ts";
|
|
10
13
|
import { isMarkdownPath } from "./structure/markdown-path.ts";
|
|
11
14
|
import { pageFurniture, textOutline } from "./page-furniture.ts";
|
|
@@ -187,15 +190,17 @@ const spansOfType = (root: Node, type: string, keep: (node: Node) => boolean = (
|
|
|
187
190
|
*/
|
|
188
191
|
const shift = (span: Span, by: number): Span => ({ start: by + span.start, end: by + span.end });
|
|
189
192
|
|
|
190
|
-
const
|
|
193
|
+
const breaksWithin = (breaks: readonly Span[], paragraph: Span): Span[] => spansWithin(breaks, paragraph).map((span) => shift(span, -paragraph.start));
|
|
194
|
+
|
|
195
|
+
const sentencesOf = (prose: string, paragraphs: readonly Span[], adapter: LanguageAdapter, softBreaks: readonly Span[]): Sentence[] =>
|
|
191
196
|
paragraphs.flatMap((paragraph) =>
|
|
192
|
-
adapter
|
|
193
|
-
.
|
|
194
|
-
.sentences.filter((sentence) => sentence.text.trim().length > 0)
|
|
197
|
+
segmentJoined(prose.slice(paragraph.start, paragraph.end), breaksWithin(softBreaks, paragraph), (text) => adapter.segment(text))
|
|
198
|
+
.filter((sentence) => sentence.text.trim().length > 0)
|
|
195
199
|
.map((sentence) => ({
|
|
196
200
|
span: shift(sentence.span, paragraph.start),
|
|
197
201
|
text: sentence.text,
|
|
198
202
|
...(sentence.tokens === undefined ? {} : { tokens: sentence.tokens.map((token) => ({ ...token, span: shift(token.span, paragraph.start) })) }),
|
|
203
|
+
...(sentence.wrapBreaks === undefined ? {} : { wrapBreaks: sentence.wrapBreaks.map((span) => shift(span, paragraph.start)) }),
|
|
199
204
|
})),
|
|
200
205
|
);
|
|
201
206
|
|
|
@@ -217,12 +222,32 @@ const sectionsOf = (headings: readonly Heading[], sentences: readonly Sentence[]
|
|
|
217
222
|
});
|
|
218
223
|
};
|
|
219
224
|
|
|
220
|
-
|
|
225
|
+
/** sentences(並び順)の中で、start が offset 以上の最初の添字。1 行 1 段落の何万行でも、段落ごとに全部をなめない。 */
|
|
226
|
+
const firstStartingAt = (sentences: readonly Sentence[], offset: number): number => {
|
|
227
|
+
const search = (low: number, high: number): number => {
|
|
228
|
+
if (low >= high) return low;
|
|
229
|
+
const middle = (low + high) >> 1;
|
|
230
|
+
return (sentences[middle]?.span.start ?? Number.POSITIVE_INFINITY) >= offset ? search(low, middle) : search(middle + 1, high);
|
|
231
|
+
};
|
|
232
|
+
return search(0, sentences.length);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const sentencesWithin = (sentences: readonly Sentence[], span: Span): Sentence[] =>
|
|
236
|
+
sentences.slice(firstStartingAt(sentences, span.start), firstStartingAt(sentences, span.end));
|
|
237
|
+
|
|
238
|
+
const paragraphsOf = (prose: string, spans: readonly Span[], sentences: readonly Sentence[], listSpans: readonly Span[]): Paragraph[] =>
|
|
221
239
|
spans
|
|
222
240
|
// 箇条書きの中の段落は「段落」として数えない。項目 1 つを 1 段落と読むと、
|
|
223
241
|
// 段落あたりの文数も長さのばらつきも、箇条書きの多い文書で壊れる。
|
|
224
242
|
.filter((span) => !listSpans.some((list) => span.start >= list.start && span.start < list.end))
|
|
225
|
-
.
|
|
243
|
+
.flatMap((span) =>
|
|
244
|
+
lineParagraphs(
|
|
245
|
+
prose,
|
|
246
|
+
span,
|
|
247
|
+
sentencesWithin(sentences, span).map((sentence) => sentence.span),
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
.map((span) => ({ span, sentences: sentencesWithin(sentences, span) }));
|
|
226
251
|
|
|
227
252
|
/** 箇条書きは list ノードの直下の項目を数える。入れ子の項目は内側の list のものとして数える。目次のような案内だけの箇条書きは数えない。 */
|
|
228
253
|
const listsOf = (root: Node, source: string, anchors: InPageAnchors): BulletList[] => {
|
|
@@ -246,6 +271,8 @@ export type TeamRules = {
|
|
|
246
271
|
readonly requiredSections: readonly string[];
|
|
247
272
|
/** { 使わない書き方: 使う書き方 }。preferred-term が語彙表として読む。 */
|
|
248
273
|
readonly prefer?: Readonly<Record<string, string>>;
|
|
274
|
+
/** チームの固有名詞。1 つの名前として読む rule が見る。 */
|
|
275
|
+
readonly names?: readonly string[];
|
|
249
276
|
};
|
|
250
277
|
|
|
251
278
|
const EMPTY_TEAM: TeamRules = { jargon: [], requiredSections: [] };
|
|
@@ -255,10 +282,12 @@ export const teamRules = (config: {
|
|
|
255
282
|
readonly jargon: readonly string[];
|
|
256
283
|
readonly requiredSections: readonly string[];
|
|
257
284
|
readonly prefer?: Readonly<Record<string, string>>;
|
|
285
|
+
readonly names?: readonly string[];
|
|
258
286
|
}): TeamRules => ({
|
|
259
287
|
jargon: config.jargon,
|
|
260
288
|
prefer: config.prefer ?? {},
|
|
261
289
|
requiredSections: config.requiredSections,
|
|
290
|
+
names: config.names ?? [],
|
|
262
291
|
});
|
|
263
292
|
|
|
264
293
|
export const buildDocument = (
|
|
@@ -279,7 +308,9 @@ export const buildDocument = (
|
|
|
279
308
|
// ページの案内は段落としても数えない。数えると、目次の行が「本題までの段落」に入る。
|
|
280
309
|
const paragraphSpans = spansOfType(root, "paragraph", (node) => !isInPageNavigation(node, anchors));
|
|
281
310
|
const listItems = spansOfType(root, "listItem");
|
|
282
|
-
|
|
311
|
+
// Markdown は段落を流し込んで表示するので、段落の中の改行は読み手に見えない。テキストの文書は行をそのまま見せる(法令は 1 行 1 号)。
|
|
312
|
+
const softBreaks = isMarkdownPath(path) ? unmaskedSoftBreaks(source, prose) : [];
|
|
313
|
+
const sentences = sentencesOf(prose, paragraphSpans, adapter, softBreaks);
|
|
283
314
|
const lexicons = {
|
|
284
315
|
...adapter.lexicons,
|
|
285
316
|
"internal-jargon": team.jargon.map((pattern) => ({ pattern })),
|
|
@@ -299,10 +330,12 @@ export const buildDocument = (
|
|
|
299
330
|
sections: sectionsOf(headingsOf(root, source), sentences, strongSpans(root, blocks), source.length),
|
|
300
331
|
sentences,
|
|
301
332
|
listSpans: listItems,
|
|
302
|
-
paragraphs: paragraphsOf(paragraphSpans, sentences, listItems),
|
|
333
|
+
paragraphs: paragraphsOf(prose, paragraphSpans, sentences, listItems),
|
|
303
334
|
lists: listsOf(root, source, anchors),
|
|
335
|
+
links: [...spansOfType(root, "link"), ...spansOfType(root, "linkReference")],
|
|
304
336
|
lexicons: tagged ? tokenizedLexicons(lexicons, adapter) : lexicons,
|
|
305
337
|
requiredSections: team.requiredSections,
|
|
338
|
+
names: team.names ?? [],
|
|
306
339
|
// 構造の rule(参照先が無い・番号の抜け)が読む木。どの rule も読まなければ作らない。何万行の契約書で、他の rule の lint に代金を払わせない。
|
|
307
340
|
get structure(): StructureNode | undefined {
|
|
308
341
|
tree.value ??=
|
package/src/eval.ts
CHANGED
|
@@ -41,7 +41,7 @@ const candidates = (rule: RuleDefinition, current: number): number[] => {
|
|
|
41
41
|
const fromLevels = [rule.levels.strict, rule.levels.normal, rule.levels.relaxed].filter((value) => value !== undefined);
|
|
42
42
|
const scaled = MULTIPLIERS.map((factor) => Math.max(1, Math.round(base * factor)));
|
|
43
43
|
// 数値で決めた上限は、段の表に無くても掃引に入れる。入れないと「現在」の行が出ない。
|
|
44
|
-
return [...new Set([...fromLevels, ...scaled, current])].
|
|
44
|
+
return [...new Set([...fromLevels, ...scaled, current])].toSorted((left, right) => left - right);
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const countAt = (docs: readonly ProseDocument[], rule: RuleDefinition, limit: number): Point => {
|
package/src/feedback/draft.ts
CHANGED
|
@@ -61,7 +61,7 @@ const TEXT: Texts<{
|
|
|
61
61
|
"false-positive": "<!-- Why it is wrong, and how the text should be read -->",
|
|
62
62
|
missed: "<!-- What is wrong in the text, and which rule should have said so -->",
|
|
63
63
|
},
|
|
64
|
-
lines: (from, to) => `lines ${String(from)}-${String(to)}
|
|
64
|
+
lines: (from, to) => (from === to ? `line ${String(from)}` : `lines ${String(from)}-${String(to)}`),
|
|
65
65
|
},
|
|
66
66
|
};
|
|
67
67
|
|
package/src/feedback/excerpt.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/** Lines around a line of a document, 1-based and inclusive. Only these lines go into a report. */
|
|
2
2
|
export type Excerpt = { readonly from: number; readonly to: number; readonly lines: readonly string[] };
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
const EXCERPT_RADIUS = 2;
|
|
5
5
|
|
|
6
6
|
/** Pure: the excerpts around each line, overlapping ones merged, in document order. Lines outside the document are dropped. */
|
|
7
7
|
export const excerptsAround = (source: string, lines: readonly number[], radius: number = EXCERPT_RADIUS): Excerpt[] => {
|
|
8
8
|
const all = source.split("\n");
|
|
9
9
|
const ranges = [...new Set(lines)]
|
|
10
10
|
.filter((line) => line >= 1 && line <= all.length)
|
|
11
|
-
.
|
|
11
|
+
.toSorted((left, right) => left - right)
|
|
12
12
|
.map((line) => ({ from: Math.max(1, line - radius), to: Math.min(all.length, line + radius) }));
|
|
13
13
|
const merged = ranges.reduce<{ from: number; to: number }[]>((acc, range) => {
|
|
14
14
|
const last = acc.at(-1);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CLI_TEXT, type GenreSource } from "./cli-text.ts";
|
|
2
|
+
import { loadGenres } from "./genre-load.ts";
|
|
3
|
+
import type { GenreDefinition } from "./genre-parse.ts";
|
|
4
|
+
import { suggestGenre } from "./genre-suggest.ts";
|
|
5
|
+
import { loadProfiles } from "./profile/load.ts";
|
|
6
|
+
import { uiLanguageOf } from "./ui.ts";
|
|
7
|
+
|
|
8
|
+
/** The file's first lines, and the notes after its not-run list: the genre it looks like, when none was set. */
|
|
9
|
+
type FileHeader = { readonly header: string; readonly notes: readonly string[] };
|
|
10
|
+
|
|
11
|
+
/** unread: a genre the front matter wrote that chaff does not know (already reported on stderr). */
|
|
12
|
+
type Placed = { readonly genre: string; readonly from: GenreSource; readonly unread?: string | undefined };
|
|
13
|
+
|
|
14
|
+
type Counts = { readonly shelved: number; readonly hushed: number };
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The genre the file looks like, only when nothing set or guessed one: suggested on screen, never used for the run.
|
|
18
|
+
* A front matter genre chaff could not read is a genre the writer did set, so the note's "no genre was set" would be untrue.
|
|
19
|
+
*/
|
|
20
|
+
const suggestionFor = (path: string, source: string, language: string, placed: Placed): GenreDefinition | undefined =>
|
|
21
|
+
placed.from === "default" && placed.unread === undefined ? suggestGenre({ path, source, language }, loadGenres(), loadProfiles()) : undefined;
|
|
22
|
+
|
|
23
|
+
/** In the file's language. The note names the genre the file was checked as. */
|
|
24
|
+
export const fileHeader = (path: string, source: string, language: string, placed: Placed, counts: Counts): FileHeader => {
|
|
25
|
+
const text = CLI_TEXT[uiLanguageOf(language)];
|
|
26
|
+
const first = text.header(path, placed.genre, text.languageName(language), text.genreSource[placed.from], counts.shelved, counts.hushed);
|
|
27
|
+
const suggestion = suggestionFor(path, source, language, placed);
|
|
28
|
+
if (suggestion === undefined) return { header: first, notes: [] };
|
|
29
|
+
const name = suggestion.name[uiLanguageOf(language)] ?? suggestion.id;
|
|
30
|
+
return { header: [first, text.suggested(name, suggestion.id)].join("\n"), notes: [text.suggestedNote(name, suggestion.id, placed.genre)] };
|
|
31
|
+
};
|
package/src/files.ts
CHANGED
|
@@ -4,11 +4,11 @@ import { join, relative, sep } from "node:path";
|
|
|
4
4
|
const MARKDOWN = [".md", ".markdown", ".mdx"];
|
|
5
5
|
|
|
6
6
|
/** 走査から外す。ここを通すと node_modules の README を延々と検査することになる。 */
|
|
7
|
-
const SKIP = ["node_modules", "dist", "build", "coverage", ".git", ".chaff-cache"];
|
|
7
|
+
const SKIP: ReadonlySet<string> = new Set(["node_modules", "dist", "build", "coverage", ".git", ".chaff-cache"]);
|
|
8
8
|
|
|
9
9
|
const isMarkdown = (path: string): boolean => MARKDOWN.some((ext) => path.toLowerCase().endsWith(ext));
|
|
10
10
|
|
|
11
|
-
const isSkipped = (path: string): boolean => path.split(sep).some((part) => SKIP.
|
|
11
|
+
const isSkipped = (path: string): boolean => path.split(sep).some((part) => SKIP.has(part));
|
|
12
12
|
|
|
13
13
|
const expand = (target: string): string[] => {
|
|
14
14
|
const stat = statSync(target, { throwIfNoEntry: false });
|
|
@@ -30,5 +30,5 @@ const byPath = (left: string, right: string): number => left.localeCompare(right
|
|
|
30
30
|
|
|
31
31
|
export const collectTargets = (targets: readonly string[]): string[] => {
|
|
32
32
|
const found = targets.flatMap(expand).filter((path) => !isSkipped(path));
|
|
33
|
-
return [...new Set(found)].
|
|
33
|
+
return [...new Set(found)].toSorted((left, right) => byPath(relative(".", left), relative(".", right)));
|
|
34
34
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { frontMatterValues } from "./genre.ts";
|
|
2
|
+
|
|
3
|
+
/** Where a genre was set: the command line, chaff.yaml's genre, or one of its by_path entries. */
|
|
4
|
+
export type GenreSetting = "--genre" | "config" | "by_path";
|
|
5
|
+
|
|
6
|
+
type UnknownGenre = {
|
|
7
|
+
readonly where: GenreSetting;
|
|
8
|
+
readonly genre: string;
|
|
9
|
+
/** The by_path entry's files, so the message can say which entry; empty elsewhere. */
|
|
10
|
+
readonly files: readonly string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type PathGenre = { readonly files: readonly string[]; readonly genre: string | undefined };
|
|
14
|
+
|
|
15
|
+
type WrittenGenres = { readonly flag: string | undefined; readonly config: string | undefined; readonly byPath: readonly PathGenre[] };
|
|
16
|
+
|
|
17
|
+
/** Commands that never read a genre from chaff.yaml. A wrong one there must not stop them: genres is how to find the right name. */
|
|
18
|
+
const GENRE_FREE: ReadonlySet<string> = new Set(["init", "genres", "skill", "relax", "strict", "off"]);
|
|
19
|
+
|
|
20
|
+
/** The genres a command would read: --genre always (every command refuses a wrong one), chaff.yaml's only where the command reads a genre. */
|
|
21
|
+
export const writtenGenres = (
|
|
22
|
+
command: string,
|
|
23
|
+
flag: string | undefined,
|
|
24
|
+
config: { readonly genre: string | undefined; readonly byPath: readonly PathGenre[] },
|
|
25
|
+
): WrittenGenres => (GENRE_FREE.has(command) ? { flag, config: undefined, byPath: [] } : { flag, config: config.genre, byPath: config.byPath });
|
|
26
|
+
|
|
27
|
+
const setting = (where: GenreSetting, genre: string | undefined, files: readonly string[] = []): UnknownGenre[] =>
|
|
28
|
+
genre === undefined ? [] : [{ where, genre, files }];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Every genre a setting names that chaff does not know: no rule's use_for matches one, so nothing would run and nothing be listed as not run.
|
|
32
|
+
* All of them, not only the one a file would use, because a wrong setting is wrong for every file.
|
|
33
|
+
*/
|
|
34
|
+
export const unknownGenres = (written: WrittenGenres, known: readonly string[]): UnknownGenre[] =>
|
|
35
|
+
[
|
|
36
|
+
...setting("--genre", written.flag),
|
|
37
|
+
...setting("config", written.config),
|
|
38
|
+
...written.byPath.flatMap((entry) => setting("by_path", entry.genre, entry.files)),
|
|
39
|
+
].filter((entry) => !known.includes(entry.genre));
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The front matter's genre that was not read. Front matter is the document's and often another tool's, so this warns and does not stop:
|
|
43
|
+
* the file falls back to the path, the content or the default. type is not looked at (Zenn's tech / idea mean something else).
|
|
44
|
+
*/
|
|
45
|
+
export const unknownFrontMatterGenre = (source: string, known: readonly string[]): string | undefined =>
|
|
46
|
+
frontMatterValues(source, "genre").find((value) => value !== "" && !known.includes(value));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { parse } from "yaml";
|
|
5
|
+
import { parseGenres, presetLevelsOf, presetProfileOf, type GenreData, type PresetLevels } from "./genre-parse.ts";
|
|
6
|
+
|
|
7
|
+
const GENRES_FILE = join(dirname(fileURLToPath(import.meta.url)), "..", "genres.yaml");
|
|
8
|
+
|
|
9
|
+
const loaded: { value: GenreData | undefined } = { value: undefined };
|
|
10
|
+
|
|
11
|
+
/** The bundled genres.yaml. Read once. */
|
|
12
|
+
export const loadGenres = (): GenreData => {
|
|
13
|
+
if (loaded.value !== undefined) return loaded.value;
|
|
14
|
+
try {
|
|
15
|
+
loaded.value = parseGenres(parse(readFileSync(GENRES_FILE, "utf8")));
|
|
16
|
+
} catch (error) {
|
|
17
|
+
throw new Error(`cannot read ${GENRES_FILE}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
18
|
+
}
|
|
19
|
+
return loaded.value;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const presetLevels = (genre: string): PresetLevels => presetLevelsOf(loadGenres(), genre);
|
|
23
|
+
|
|
24
|
+
export const presetProfile = (genre: string): string | undefined => presetProfileOf(loadGenres(), genre);
|