chaffjs 0.20.0 → 0.21.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 +5 -4
- package/dist/api.d.ts +16 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js.map +1 -1
- package/dist/cli-text.d.ts +2 -0
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +14 -5
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/command-help.d.ts +7 -0
- package/dist/command-help.d.ts.map +1 -0
- package/dist/command-help.js +18 -0
- package/dist/command-help.js.map +1 -0
- package/dist/commands/eval.js +2 -2
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/fix-plan.d.ts.map +1 -1
- package/dist/commands/fix-plan.js +11 -1
- package/dist/commands/fix-plan.js.map +1 -1
- package/dist/commands/help.d.ts +4 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +7 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/config/load.d.ts +2 -0
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +1 -0
- package/dist/config/load.js.map +1 -1
- package/dist/config/option-problems.js +2 -2
- package/dist/config/option-problems.js.map +1 -1
- package/dist/config/rule-problems.js +2 -2
- package/dist/config/rule-problems.js.map +1 -1
- package/dist/custom/checked.d.ts +9 -0
- package/dist/custom/checked.d.ts.map +1 -0
- package/dist/custom/checked.js +3 -0
- package/dist/custom/checked.js.map +1 -0
- package/dist/custom/dsl-fields.d.ts +27 -0
- package/dist/custom/dsl-fields.d.ts.map +1 -0
- package/dist/custom/dsl-fields.js +86 -0
- package/dist/custom/dsl-fields.js.map +1 -0
- package/dist/custom/load.d.ts.map +1 -1
- package/dist/custom/load.js +2 -0
- package/dist/custom/load.js.map +1 -1
- package/dist/custom/parse.d.ts +14 -3
- package/dist/custom/parse.d.ts.map +1 -1
- package/dist/custom/parse.js +44 -40
- package/dist/custom/parse.js.map +1 -1
- package/dist/custom/problems.d.ts.map +1 -1
- package/dist/custom/problems.js +11 -1
- package/dist/custom/problems.js.map +1 -1
- package/dist/detectors/blank-structure.d.ts.map +1 -1
- package/dist/detectors/blank-structure.js +6 -4
- package/dist/detectors/blank-structure.js.map +1 -1
- package/dist/detectors/clause-shape.d.ts +22 -0
- package/dist/detectors/clause-shape.d.ts.map +1 -0
- package/dist/detectors/clause-shape.js +149 -0
- package/dist/detectors/clause-shape.js.map +1 -0
- package/dist/detectors/code-fences.d.ts +34 -0
- package/dist/detectors/code-fences.d.ts.map +1 -0
- package/dist/detectors/code-fences.js +100 -0
- package/dist/detectors/code-fences.js.map +1 -0
- package/dist/detectors/cross-definitions.d.ts +4 -0
- package/dist/detectors/cross-definitions.d.ts.map +1 -0
- package/dist/detectors/cross-definitions.js +43 -0
- package/dist/detectors/cross-definitions.js.map +1 -0
- package/dist/detectors/cross-facts.d.ts +4 -0
- package/dist/detectors/cross-facts.d.ts.map +1 -0
- package/dist/detectors/cross-facts.js +53 -0
- package/dist/detectors/cross-facts.js.map +1 -0
- package/dist/detectors/cross-registry/cross-doc-duplicate-definition.d.ts +3 -0
- package/dist/detectors/cross-registry/cross-doc-duplicate-definition.d.ts.map +1 -0
- package/dist/detectors/cross-registry/cross-doc-duplicate-definition.js +3 -0
- package/dist/detectors/cross-registry/cross-doc-duplicate-definition.js.map +1 -0
- package/dist/detectors/cross-registry/cross-doc-fact-conflict.d.ts +3 -0
- package/dist/detectors/cross-registry/cross-doc-fact-conflict.d.ts.map +1 -0
- package/dist/detectors/cross-registry/cross-doc-fact-conflict.js +3 -0
- package/dist/detectors/cross-registry/cross-doc-fact-conflict.js.map +1 -0
- package/dist/detectors/custom.d.ts.map +1 -1
- package/dist/detectors/custom.js +4 -1
- package/dist/detectors/custom.js.map +1 -1
- package/dist/detectors/defined-term-form.d.ts +25 -0
- package/dist/detectors/defined-term-form.d.ts.map +1 -0
- package/dist/detectors/defined-term-form.js +170 -0
- package/dist/detectors/defined-term-form.js.map +1 -0
- package/dist/detectors/email-letter.d.ts +46 -0
- package/dist/detectors/email-letter.d.ts.map +1 -0
- package/dist/detectors/email-letter.js +234 -0
- package/dist/detectors/email-letter.js.map +1 -0
- package/dist/detectors/impossible-date.d.ts +20 -0
- package/dist/detectors/impossible-date.d.ts.map +1 -0
- package/dist/detectors/impossible-date.js +105 -0
- package/dist/detectors/impossible-date.js.map +1 -0
- package/dist/detectors/impossible-time.d.ts +18 -0
- package/dist/detectors/impossible-time.d.ts.map +1 -0
- package/dist/detectors/impossible-time.js +116 -0
- package/dist/detectors/impossible-time.js.map +1 -0
- package/dist/detectors/link-shape.d.ts +11 -0
- package/dist/detectors/link-shape.d.ts.map +1 -0
- package/dist/detectors/link-shape.js +56 -0
- package/dist/detectors/link-shape.js.map +1 -0
- package/dist/detectors/mark-shape.d.ts +53 -0
- package/dist/detectors/mark-shape.d.ts.map +1 -0
- package/dist/detectors/mark-shape.js +160 -0
- package/dist/detectors/mark-shape.js.map +1 -0
- package/dist/detectors/registry/adverb-polarity.d.ts +3 -0
- package/dist/detectors/registry/adverb-polarity.d.ts.map +1 -0
- package/dist/detectors/registry/adverb-polarity.js +3 -0
- package/dist/detectors/registry/adverb-polarity.js.map +1 -0
- package/dist/detectors/registry/attachment-missing.d.ts +3 -0
- package/dist/detectors/registry/attachment-missing.d.ts.map +1 -0
- package/dist/detectors/registry/attachment-missing.js +3 -0
- package/dist/detectors/registry/attachment-missing.js.map +1 -0
- package/dist/detectors/registry/bare-url-mix.d.ts +3 -0
- package/dist/detectors/registry/bare-url-mix.d.ts.map +1 -0
- package/dist/detectors/registry/bare-url-mix.js +3 -0
- package/dist/detectors/registry/bare-url-mix.js.map +1 -0
- package/dist/detectors/registry/caps-shouting.d.ts +3 -0
- package/dist/detectors/registry/caps-shouting.d.ts.map +1 -0
- package/dist/detectors/registry/caps-shouting.js +3 -0
- package/dist/detectors/registry/caps-shouting.js.map +1 -0
- package/dist/detectors/registry/code-fence-language.d.ts +3 -0
- package/dist/detectors/registry/code-fence-language.d.ts.map +1 -0
- package/dist/detectors/registry/code-fence-language.js +3 -0
- package/dist/detectors/registry/code-fence-language.js.map +1 -0
- package/dist/detectors/registry/defined-name-repeated.d.ts +3 -0
- package/dist/detectors/registry/defined-name-repeated.d.ts.map +1 -0
- package/dist/detectors/registry/defined-name-repeated.js +3 -0
- package/dist/detectors/registry/defined-name-repeated.js.map +1 -0
- package/dist/detectors/registry/defined-term-form.d.ts +3 -0
- package/dist/detectors/registry/defined-term-form.d.ts.map +1 -0
- package/dist/detectors/registry/defined-term-form.js +3 -0
- package/dist/detectors/registry/defined-term-form.js.map +1 -0
- package/dist/detectors/registry/email-frame.d.ts +3 -0
- package/dist/detectors/registry/email-frame.d.ts.map +1 -0
- package/dist/detectors/registry/email-frame.js +3 -0
- package/dist/detectors/registry/email-frame.js.map +1 -0
- package/dist/detectors/registry/email-subject.d.ts +3 -0
- package/dist/detectors/registry/email-subject.d.ts.map +1 -0
- package/dist/detectors/registry/email-subject.js +3 -0
- package/dist/detectors/registry/email-subject.js.map +1 -0
- package/dist/detectors/registry/ending-run.d.ts +3 -0
- package/dist/detectors/registry/ending-run.d.ts.map +1 -0
- package/dist/detectors/registry/ending-run.js +3 -0
- package/dist/detectors/registry/ending-run.js.map +1 -0
- package/dist/detectors/registry/heading-command.d.ts +3 -0
- package/dist/detectors/registry/heading-command.d.ts.map +1 -0
- package/dist/detectors/registry/heading-command.js +3 -0
- package/dist/detectors/registry/heading-command.js.map +1 -0
- package/dist/detectors/registry/hyphen-dash.d.ts +3 -0
- package/dist/detectors/registry/hyphen-dash.d.ts.map +1 -0
- package/dist/detectors/registry/hyphen-dash.js +3 -0
- package/dist/detectors/registry/hyphen-dash.js.map +1 -0
- package/dist/detectors/registry/image-file-name-alt.d.ts +3 -0
- package/dist/detectors/registry/image-file-name-alt.d.ts.map +1 -0
- package/dist/detectors/registry/image-file-name-alt.js +3 -0
- package/dist/detectors/registry/image-file-name-alt.js.map +1 -0
- package/dist/detectors/registry/impossible-date.d.ts +3 -0
- package/dist/detectors/registry/impossible-date.d.ts.map +1 -0
- package/dist/detectors/registry/impossible-date.js +3 -0
- package/dist/detectors/registry/impossible-date.js.map +1 -0
- package/dist/detectors/registry/impossible-time.d.ts +3 -0
- package/dist/detectors/registry/impossible-time.d.ts.map +1 -0
- package/dist/detectors/registry/impossible-time.js +3 -0
- package/dist/detectors/registry/impossible-time.js.map +1 -0
- package/dist/detectors/registry/latin-abbreviation.d.ts +3 -0
- package/dist/detectors/registry/latin-abbreviation.d.ts.map +1 -0
- package/dist/detectors/registry/latin-abbreviation.js +3 -0
- package/dist/detectors/registry/latin-abbreviation.js.map +1 -0
- package/dist/detectors/registry/link-text-url-mismatch.d.ts +3 -0
- package/dist/detectors/registry/link-text-url-mismatch.d.ts.map +1 -0
- package/dist/detectors/registry/link-text-url-mismatch.js +3 -0
- package/dist/detectors/registry/link-text-url-mismatch.js.map +1 -0
- package/dist/detectors/registry/mark-consistency.d.ts +3 -0
- package/dist/detectors/registry/mark-consistency.d.ts.map +1 -0
- package/dist/detectors/registry/mark-consistency.js +3 -0
- package/dist/detectors/registry/mark-consistency.js.map +1 -0
- package/dist/detectors/registry/origin-particle.d.ts +3 -0
- package/dist/detectors/registry/origin-particle.d.ts.map +1 -0
- package/dist/detectors/registry/origin-particle.js +3 -0
- package/dist/detectors/registry/origin-particle.js.map +1 -0
- package/dist/detectors/registry/request-without-deadline.d.ts +3 -0
- package/dist/detectors/registry/request-without-deadline.d.ts.map +1 -0
- package/dist/detectors/registry/request-without-deadline.js +3 -0
- package/dist/detectors/registry/request-without-deadline.js.map +1 -0
- package/dist/detectors/registry/sentence-spacing.d.ts +3 -0
- package/dist/detectors/registry/sentence-spacing.d.ts.map +1 -0
- package/dist/detectors/registry/sentence-spacing.js +3 -0
- package/dist/detectors/registry/sentence-spacing.js.map +1 -0
- package/dist/detectors/registry/shell-prompt.d.ts +3 -0
- package/dist/detectors/registry/shell-prompt.d.ts.map +1 -0
- package/dist/detectors/registry/shell-prompt.js +3 -0
- package/dist/detectors/registry/shell-prompt.js.map +1 -0
- package/dist/detectors/registry/step-statement.d.ts +3 -0
- package/dist/detectors/registry/step-statement.d.ts.map +1 -0
- package/dist/detectors/registry/step-statement.js +3 -0
- package/dist/detectors/registry/step-statement.js.map +1 -0
- package/dist/detectors/registry/unpaired-parallel.d.ts +3 -0
- package/dist/detectors/registry/unpaired-parallel.d.ts.map +1 -0
- package/dist/detectors/registry/unpaired-parallel.js +3 -0
- package/dist/detectors/registry/unpaired-parallel.js.map +1 -0
- package/dist/detectors/request-owner.d.ts +23 -0
- package/dist/detectors/request-owner.d.ts.map +1 -0
- package/dist/detectors/request-owner.js +110 -0
- package/dist/detectors/request-owner.js.map +1 -0
- package/dist/detectors/technical-docs.d.ts +32 -0
- package/dist/detectors/technical-docs.d.ts.map +1 -0
- package/dist/detectors/technical-docs.js +199 -0
- package/dist/detectors/technical-docs.js.map +1 -0
- package/dist/extension/pack-read.d.ts +21 -0
- package/dist/extension/pack-read.d.ts.map +1 -0
- package/dist/extension/pack-read.js +117 -0
- package/dist/extension/pack-read.js.map +1 -0
- package/dist/extension/plugin-load.d.ts.map +1 -1
- package/dist/extension/plugin-load.js +44 -10
- package/dist/extension/plugin-load.js.map +1 -1
- package/dist/extension/plugin-parse.d.ts.map +1 -1
- package/dist/extension/plugin-parse.js +10 -3
- package/dist/extension/plugin-parse.js.map +1 -1
- package/dist/facts/address-values.d.ts +5 -0
- package/dist/facts/address-values.d.ts.map +1 -0
- package/dist/facts/address-values.js +64 -0
- package/dist/facts/address-values.js.map +1 -0
- package/dist/facts/cross-facts.d.ts +20 -0
- package/dist/facts/cross-facts.d.ts.map +1 -0
- package/dist/facts/cross-facts.js +41 -0
- package/dist/facts/cross-facts.js.map +1 -0
- package/dist/fix-plan/chosen-depth.d.ts +10 -0
- package/dist/fix-plan/chosen-depth.d.ts.map +1 -0
- package/dist/fix-plan/chosen-depth.js +24 -0
- package/dist/fix-plan/chosen-depth.js.map +1 -0
- package/dist/fix-plan/mode.d.ts +13 -4
- package/dist/fix-plan/mode.d.ts.map +1 -1
- package/dist/fix-plan/mode.js +38 -7
- package/dist/fix-plan/mode.js.map +1 -1
- package/dist/fix-plan/plan.d.ts +20 -0
- package/dist/fix-plan/plan.d.ts.map +1 -1
- package/dist/fix-plan/plan.js +23 -5
- package/dist/fix-plan/plan.js.map +1 -1
- package/dist/fix-plan/render.d.ts.map +1 -1
- package/dist/fix-plan/render.js +24 -0
- package/dist/fix-plan/render.js.map +1 -1
- package/dist/fix-plan/text.d.ts +8 -0
- package/dist/fix-plan/text.d.ts.map +1 -1
- package/dist/fix-plan/text.js +36 -10
- package/dist/fix-plan/text.js.map +1 -1
- package/dist/genre-load.d.ts +1 -1
- package/dist/genre-load.d.ts.map +1 -1
- package/dist/genre-load.js +7 -4
- package/dist/genre-load.js.map +1 -1
- package/dist/genre-parse.d.ts +6 -0
- package/dist/genre-parse.d.ts.map +1 -1
- package/dist/genre-parse.js +18 -0
- package/dist/genre-parse.js.map +1 -1
- package/dist/init.js +10 -10
- package/dist/init.js.map +1 -1
- package/dist/known-genres.d.ts +8 -0
- package/dist/known-genres.d.ts.map +1 -0
- package/dist/known-genres.js +17 -0
- package/dist/known-genres.js.map +1 -0
- package/dist/link-shape.d.ts +20 -0
- package/dist/link-shape.d.ts.map +1 -0
- package/dist/link-shape.js +58 -0
- package/dist/link-shape.js.map +1 -0
- package/dist/reasons.d.ts.map +1 -1
- package/dist/reasons.js +15 -4
- package/dist/reasons.js.map +1 -1
- package/dist/render/explain.d.ts.map +1 -1
- package/dist/render/explain.js +11 -2
- package/dist/render/explain.js.map +1 -1
- package/dist/render/friendly.js +1 -1
- package/dist/render/friendly.js.map +1 -1
- package/dist/render/rules-json.d.ts.map +1 -1
- package/dist/render/rules-json.js +12 -3
- package/dist/render/rules-json.js.map +1 -1
- package/dist/render/rules-table.js +6 -6
- package/dist/render/rules-table.js.map +1 -1
- package/dist/render/semantic.js +2 -2
- package/dist/render/semantic.js.map +1 -1
- package/dist/render/suppressions.js +2 -2
- package/dist/render/suppressions.js.map +1 -1
- package/dist/rewrite-depth.d.ts +22 -0
- package/dist/rewrite-depth.d.ts.map +1 -0
- package/dist/rewrite-depth.js +42 -0
- package/dist/rewrite-depth.js.map +1 -0
- package/dist/rule-fields.d.ts +23 -0
- package/dist/rule-fields.d.ts.map +1 -0
- package/dist/rule-fields.js +64 -0
- package/dist/rule-fields.js.map +1 -0
- package/dist/rule-guide.d.ts +3 -0
- package/dist/rule-guide.d.ts.map +1 -1
- package/dist/rule-guide.js +6 -0
- package/dist/rule-guide.js.map +1 -1
- package/dist/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +13 -5
- package/dist/rule-load.js.map +1 -1
- package/dist/rule-offs.d.ts +14 -0
- package/dist/rule-offs.d.ts.map +1 -0
- package/dist/rule-offs.js +45 -0
- package/dist/rule-offs.js.map +1 -0
- package/dist/structure/build.d.ts.map +1 -1
- package/dist/structure/build.js +14 -1
- package/dist/structure/build.js.map +1 -1
- package/dist/structure/cross-definitions.d.ts +23 -0
- package/dist/structure/cross-definitions.d.ts.map +1 -0
- package/dist/structure/cross-definitions.js +34 -0
- package/dist/structure/cross-definitions.js.map +1 -0
- package/genres.yaml +14 -313
- package/package.json +3 -3
- package/rules/absolute-exception.yaml +3 -1
- package/rules/abstract-length.yaml +7 -1
- package/rules/acronym-expansion-conflict.yaml +2 -1
- package/rules/adverb-overuse.yaml +7 -1
- package/rules/adverb-without-negation.yaml +60 -0
- package/rules/adversative-ga-repeat.yaml +2 -1
- package/rules/agentless-passive.yaml +10 -1
- package/rules/agreement-slip.yaml +4 -1
- package/rules/ai-generated-composite.yaml +6 -1
- package/rules/ai-structure.yaml +8 -2
- package/rules/ai-tell.yaml +7 -1
- package/rules/all-caps-shouting.yaml +61 -0
- package/rules/announced-count-mismatch.yaml +3 -1
- package/rules/announcing-opener.yaml +7 -1
- package/rules/article-sound.yaml +6 -2
- package/rules/assistant-residue.yaml +4 -1
- package/rules/attachment-not-attached.yaml +112 -0
- package/rules/baito-keigo.yaml +64 -0
- package/rules/bare-url-mix.yaml +86 -0
- package/rules/bold-density.yaml +1 -0
- package/rules/bold-label-list.yaml +9 -1
- package/rules/bracket-nesting.yaml +2 -1
- package/rules/bracket-width-consistency.yaml +64 -0
- package/rules/broken-link.yaml +1 -1
- package/rules/chat-citation-residue.yaml +2 -1
- package/rules/citation-reference-mismatch.yaml +7 -1
- package/rules/citation-style-mix.yaml +7 -1
- package/rules/clause-chain.yaml +6 -1
- package/rules/closing-cliche.yaml +7 -0
- package/rules/code-fence-language-mix.yaml +176 -0
- package/rules/colloquial-opener.yaml +62 -0
- package/rules/colon-lead-in.yaml +7 -1
- package/rules/concrete-evidence-density.yaml +6 -0
- package/rules/contraction-consistency.yaml +4 -1
- package/rules/contrast-framing.yaml +7 -1
- package/rules/cross-doc-duplicate-definition.yaml +70 -0
- package/rules/cross-doc-fact-conflict.yaml +64 -0
- package/rules/cushion-phrase-density.yaml +8 -1
- package/rules/dangling-figure-reference.yaml +3 -1
- package/rules/dangling-reference.yaml +3 -1
- package/rules/date-format-consistency.yaml +3 -2
- package/rules/date-order.yaml +3 -1
- package/rules/date-range-reversed.yaml +3 -1
- package/rules/date-weekday-mismatch.yaml +3 -1
- package/rules/date-without-year.yaml +4 -3
- package/rules/defined-name-repeated.yaml +97 -0
- package/rules/defined-term-form.yaml +113 -0
- package/rules/demonstrative-opener-run.yaml +2 -1
- package/rules/double-keigo.yaml +6 -1
- package/rules/double-negative.yaml +5 -1
- package/rules/doubled-nado.yaml +2 -1
- package/rules/doubled-punctuation.yaml +1 -1
- package/rules/doubled-word.yaml +4 -1
- package/rules/duplicate-definition.yaml +3 -1
- package/rules/duplicate-heading.yaml +1 -1
- package/rules/duration-mismatch.yaml +2 -1
- package/rules/elapsed-years-mismatch.yaml +4 -1
- package/rules/email-greeting-closing.yaml +122 -0
- package/rules/email-subject-length.yaml +112 -0
- package/rules/emoji-density.yaml +3 -1
- package/rules/emoji-heading.yaml +7 -1
- package/rules/empty-intensifier.yaml +4 -0
- package/rules/empty-list-item.yaml +2 -1
- package/rules/empty-section.yaml +1 -1
- package/rules/empty-table-cell.yaml +2 -1
- package/rules/excessive-hedging.yaml +7 -1
- package/rules/exclamation-width-consistency.yaml +64 -0
- package/rules/expletive-construction.yaml +7 -1
- package/rules/fact-conflict.yaml +4 -1
- package/rules/figure-reference-order.yaml +7 -1
- package/rules/fullwidth-alnum-consistency.yaml +2 -2
- package/rules/hankaku-kana.yaml +1 -1
- package/rules/heading-command-missing.yaml +109 -0
- package/rules/heading-echo.yaml +6 -1
- package/rules/heading-level-skip.yaml +3 -3
- package/rules/heading-numbering-mix.yaml +4 -3
- package/rules/hiragana-fukushi.yaml +4 -1
- package/rules/homophone-slip.yaml +5 -1
- package/rules/humble-for-others.yaml +63 -0
- package/rules/hyphen-as-dash.yaml +59 -0
- package/rules/idiom-misuse.yaml +4 -1
- package/rules/image-file-name-alt.yaml +83 -0
- package/rules/impossible-date.yaml +79 -0
- package/rules/impossible-time.yaml +76 -0
- package/rules/internal-jargon.yaml +1 -1
- package/rules/invisible-character.yaml +1 -1
- package/rules/katakana-density.yaml +2 -1
- package/rules/katakana-long-vowel.yaml +1 -1
- package/rules/known-misspelling.yaml +2 -1
- package/rules/kutoten-consistency.yaml +1 -1
- package/rules/latin-abbreviation-form.yaml +58 -0
- package/rules/latin-spacing.yaml +6 -2
- package/rules/link-text-url-mismatch.yaml +72 -0
- package/rules/list-item-form-mix.yaml +7 -1
- package/rules/max-kanji-continuous.yaml +10 -2
- package/rules/max-paragraph-length.yaml +4 -1
- package/rules/max-sentence-length.yaml +29 -21
- package/rules/max-ten.yaml +4 -1
- package/rules/misconversion.yaml +2 -1
- package/rules/modal-conflict.yaml +2 -1
- package/rules/name-variant.yaml +4 -3
- package/rules/ngram-repetition.yaml +5 -0
- package/rules/no-doubled-joshi.yaml +5 -1
- package/rules/no-em-dash.yaml +4 -1
- package/rules/no-lead.yaml +6 -1
- package/rules/no-mixed-desumasu.yaml +5 -2
- package/rules/no-nakaguro-parallel.yaml +3 -0
- package/rules/nominalization.yaml +5 -1
- package/rules/number-style-consistency.yaml +6 -1
- package/rules/number-without-unit.yaml +2 -1
- package/rules/numbering-gap.yaml +1 -1
- package/rules/one-sentence-paragraph-run.yaml +5 -1
- package/rules/orthographic-variant.yaml +4 -1
- package/rules/outside-announced-list.yaml +2 -1
- package/rules/oxford-comma-consistency.yaml +3 -1
- package/rules/padded-intro.yaml +7 -0
- package/rules/paragraph-length-variance.yaml +7 -1
- package/rules/paragraph-restatement.yaml +6 -1
- package/rules/percent-sum-mismatch.yaml +3 -1
- package/rules/person-consistency.yaml +4 -1
- package/rules/preamble-length.yaml +6 -0
- package/rules/preferred-term.yaml +1 -1
- package/rules/proper-noun-density.yaml +8 -1
- package/rules/quote-style-consistency.yaml +66 -0
- package/rules/ra-nuki.yaml +4 -1
- package/rules/redundant-expression.yaml +4 -1
- package/rules/reference-title-mismatch.yaml +4 -3
- package/rules/reference-topic-missing.yaml +2 -1
- package/rules/relative-date-mismatch.yaml +2 -1
- package/rules/repeated-conjunction.yaml +4 -1
- package/rules/repeated-sentence-ending.yaml +62 -0
- package/rules/repeated-sentence-head.yaml +4 -0
- package/rules/request-without-deadline.yaml +92 -0
- package/rules/required-sections.yaml +1 -1
- package/rules/requirement-modal.yaml +1 -1
- package/rules/requirement-smell.yaml +12 -4
- package/rules/rule-of-three.yaml +8 -1
- package/rules/sasete-itadaku.yaml +7 -1
- package/rules/section-length-uniformity.yaml +7 -1
- package/rules/sentence-initial-conjunction-run.yaml +5 -1
- package/rules/sentence-initial-so.yaml +6 -1
- package/rules/sentence-rhythm.yaml +8 -1
- package/rules/sentence-spacing-consistency.yaml +63 -0
- package/rules/shell-prompt-mix.yaml +128 -0
- package/rules/space-before-punctuation.yaml +1 -1
- package/rules/spelling-consistency.yaml +3 -1
- package/rules/step-statement-mix.yaml +76 -0
- package/rules/stock-transition.yaml +8 -1
- package/rules/stray-space.yaml +2 -2
- package/rules/summary-fact-mismatch.yaml +4 -1
- package/rules/table-header-variant.yaml +2 -1
- package/rules/taigen-dome-in-prose.yaml +6 -0
- package/rules/tari-unpaired.yaml +58 -0
- package/rules/teki-overuse.yaml +2 -1
- package/rules/title-case-consistency.yaml +1 -1
- package/rules/title-length.yaml +7 -1
- package/rules/topic-predicate-distance.yaml +6 -1
- package/rules/total-mismatch.yaml +3 -1
- package/rules/translationese-density.yaml +4 -1
- package/rules/unbalanced-bracket.yaml +3 -3
- package/rules/undefined-acronym.yaml +8 -1
- package/rules/unfilled-placeholder.yaml +1 -1
- package/rules/unit-mismatch.yaml +2 -1
- package/rules/unknown-word.yaml +4 -1
- package/rules/unqualified-superlative.yaml +6 -1
- package/rules/unused-definition.yaml +4 -3
- package/rules/url-run-on.yaml +1 -1
- package/rules/use-before-definition.yaml +4 -3
- package/rules/vague-figure-reference.yaml +3 -1
- package/rules/vague-link-text.yaml +2 -1
- package/rules/vague-word-density.yaml +2 -1
- package/rules/very-adjective.yaml +5 -1
- package/rules/weasel-word.yaml +6 -1
- package/rules/wordy-phrase.yaml +6 -1
- package/rules/yori-as-from.yaml +61 -0
- package/src/api.ts +13 -1
- package/src/cli-text.ts +18 -7
- package/src/cli.ts +3 -0
- package/src/command-help.ts +26 -0
- package/src/commands/eval.ts +2 -2
- package/src/commands/fix-plan.ts +13 -1
- package/src/commands/help.ts +8 -0
- package/src/config/load.ts +3 -0
- package/src/config/option-problems.ts +2 -2
- package/src/config/rule-problems.ts +2 -2
- package/src/custom/checked.ts +8 -0
- package/src/custom/dsl-fields.ts +102 -0
- package/src/custom/load.ts +2 -0
- package/src/custom/parse.ts +72 -50
- package/src/custom/problems.ts +18 -2
- package/src/detectors/blank-structure.ts +12 -4
- package/src/detectors/clause-shape.ts +199 -0
- package/src/detectors/code-fences.ts +113 -0
- package/src/detectors/cross-definitions.ts +49 -0
- package/src/detectors/cross-facts.ts +60 -0
- package/src/detectors/cross-registry/cross-doc-duplicate-definition.ts +4 -0
- package/src/detectors/cross-registry/cross-doc-fact-conflict.ts +4 -0
- package/src/detectors/custom.ts +10 -2
- package/src/detectors/defined-term-form.ts +200 -0
- package/src/detectors/email-letter.ts +262 -0
- package/src/detectors/impossible-date.ts +141 -0
- package/src/detectors/impossible-time.ts +151 -0
- package/src/detectors/link-shape.ts +70 -0
- package/src/detectors/mark-shape.ts +202 -0
- package/src/detectors/registry/adverb-polarity.ts +4 -0
- package/src/detectors/registry/attachment-missing.ts +4 -0
- package/src/detectors/registry/bare-url-mix.ts +4 -0
- package/src/detectors/registry/caps-shouting.ts +4 -0
- package/src/detectors/registry/code-fence-language.ts +4 -0
- package/src/detectors/registry/defined-name-repeated.ts +4 -0
- package/src/detectors/registry/defined-term-form.ts +4 -0
- package/src/detectors/registry/email-frame.ts +4 -0
- package/src/detectors/registry/email-subject.ts +4 -0
- package/src/detectors/registry/ending-run.ts +4 -0
- package/src/detectors/registry/heading-command.ts +4 -0
- package/src/detectors/registry/hyphen-dash.ts +4 -0
- package/src/detectors/registry/image-file-name-alt.ts +4 -0
- package/src/detectors/registry/impossible-date.ts +4 -0
- package/src/detectors/registry/impossible-time.ts +4 -0
- package/src/detectors/registry/latin-abbreviation.ts +4 -0
- package/src/detectors/registry/link-text-url-mismatch.ts +4 -0
- package/src/detectors/registry/mark-consistency.ts +4 -0
- package/src/detectors/registry/origin-particle.ts +4 -0
- package/src/detectors/registry/request-without-deadline.ts +4 -0
- package/src/detectors/registry/sentence-spacing.ts +4 -0
- package/src/detectors/registry/shell-prompt.ts +4 -0
- package/src/detectors/registry/step-statement.ts +4 -0
- package/src/detectors/registry/unpaired-parallel.ts +4 -0
- package/src/detectors/request-owner.ts +141 -0
- package/src/detectors/technical-docs.ts +230 -0
- package/src/extension/pack-read.ts +128 -0
- package/src/extension/plugin-load.ts +49 -9
- package/src/extension/plugin-parse.ts +10 -3
- package/src/facts/address-values.ts +74 -0
- package/src/facts/cross-facts.ts +54 -0
- package/src/fix-plan/chosen-depth.ts +28 -0
- package/src/fix-plan/mode.ts +57 -11
- package/src/fix-plan/plan.ts +37 -5
- package/src/fix-plan/render.ts +24 -0
- package/src/fix-plan/text.ts +47 -10
- package/src/genre-load.ts +7 -4
- package/src/genre-parse.ts +19 -0
- package/src/init.ts +10 -10
- package/src/known-genres.ts +19 -0
- package/src/link-shape.ts +62 -0
- package/src/reasons.ts +16 -4
- package/src/render/explain.ts +14 -2
- package/src/render/friendly.ts +1 -1
- package/src/render/rules-json.ts +14 -3
- package/src/render/rules-table.ts +6 -6
- package/src/render/semantic.ts +2 -2
- package/src/render/suppressions.ts +2 -2
- package/src/rewrite-depth.ts +60 -0
- package/src/rule-fields.ts +95 -0
- package/src/rule-guide.ts +9 -0
- package/src/rule-load.ts +10 -4
- package/src/rule-offs.ts +50 -0
- package/src/structure/build.ts +21 -1
- package/src/structure/cross-definitions.ts +60 -0
package/src/fix-plan/mode.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
+
import { depthIncludes, type RewriteDepth } from "../rewrite-depth.ts";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
* Which
|
|
4
|
+
* Which way to rewrite: the guide's Light, Bold and Full, and Register. Each is a rewrite depth (rewrite-depth.ts):
|
|
5
|
+
* Light and Bold keep the structure (light), Full reorganises it (structure), Register converts the style (register).
|
|
6
|
+
* chaff recommends a way from what it found; --depth or chaff.yaml's fix_plan.depth chooses the depth instead.
|
|
3
7
|
* A request that says "from scratch" also means Full; chaff cannot read the request, so the plan says so in words.
|
|
4
8
|
*/
|
|
5
|
-
export type FixMode = "none" | "light" | "bold" | "full";
|
|
9
|
+
export type FixMode = "none" | "light" | "bold" | "full" | "register";
|
|
6
10
|
|
|
7
|
-
/** Why the mode was chosen, so the plan can say it. */
|
|
8
|
-
export type ModeReason = "nothing-found" | "composite" | "structure" | "genre" | "signals" | "spots";
|
|
11
|
+
/** Why the mode was chosen, so the plan can say it. chosen: the depth was given; depth-limit: chaff would go deeper than the depth given. */
|
|
12
|
+
export type ModeReason = "nothing-found" | "all-deeper" | "composite" | "structure" | "genre" | "signals" | "spots" | "chosen" | "depth-limit";
|
|
9
13
|
|
|
10
|
-
export type ModeChoice = {
|
|
14
|
+
export type ModeChoice = {
|
|
15
|
+
readonly mode: FixMode;
|
|
16
|
+
readonly reason: ModeReason;
|
|
17
|
+
/** How deep the rewrite goes. None when there is nothing to fix. */
|
|
18
|
+
readonly depth: RewriteDepth | undefined;
|
|
19
|
+
};
|
|
11
20
|
|
|
12
21
|
export type ModeInput = {
|
|
13
22
|
readonly genre: string;
|
|
@@ -17,10 +26,25 @@ export type ModeInput = {
|
|
|
17
26
|
readonly signalRules: ReadonlySet<string>;
|
|
18
27
|
/** The structure score (measures past 90% of human articles) and the count at which the outline itself is the problem. */
|
|
19
28
|
readonly structure?: { readonly score: number; readonly limit: number };
|
|
29
|
+
/** The depth --depth or chaff.yaml chose. Without one, chaff recommends. */
|
|
30
|
+
readonly chosen?: RewriteDepth | undefined;
|
|
31
|
+
/** How many fired rules the chosen depth sets apart; they are not in firedRules. */
|
|
32
|
+
readonly setApart?: number;
|
|
20
33
|
};
|
|
21
34
|
|
|
22
35
|
export const COMPOSITE_RULE = "ai-generated-composite";
|
|
23
36
|
|
|
37
|
+
const DEPTH_OF: Readonly<Record<FixMode, RewriteDepth | undefined>> = {
|
|
38
|
+
none: undefined,
|
|
39
|
+
light: "light",
|
|
40
|
+
bold: "light",
|
|
41
|
+
full: "structure",
|
|
42
|
+
register: "register",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** The way that goes exactly as deep as a chosen depth, beyond light, where chaff's recommendation picks Light or Bold. */
|
|
46
|
+
const MODE_AT: Readonly<Record<Exclude<RewriteDepth, "light">, FixMode>> = { structure: "full", register: "register" };
|
|
47
|
+
|
|
24
48
|
/** Two document-wide signals mean the prose of the sections is the problem, not a spot or two. */
|
|
25
49
|
const MIN_SIGNALS_FOR_BOLD = 2;
|
|
26
50
|
|
|
@@ -30,11 +54,33 @@ const isFreeFormGenre = (genre: string): boolean => genre.startsWith("blog/") ||
|
|
|
30
54
|
/** An outline far from human articles is rewritten from the outline, in any genre and whether or not a rule fired. */
|
|
31
55
|
const hasStructureToFix = (input: ModeInput): boolean => input.structure !== undefined && input.structure.score >= input.structure.limit;
|
|
32
56
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (isFreeFormGenre(input.genre)) return { mode: "full", reason: "genre" };
|
|
57
|
+
const choice = (mode: FixMode, reason: ModeReason): ModeChoice => ({ mode, reason, depth: DEPTH_OF[mode] });
|
|
58
|
+
|
|
59
|
+
/** Two document-wide signals mean Bold, else Light: the deepest way that keeps the structure. */
|
|
60
|
+
const lightOrBold = (input: ModeInput): ModeChoice => {
|
|
38
61
|
const signals = [...input.firedRules].filter((rule) => input.signalRules.has(rule)).length;
|
|
39
|
-
return signals >= MIN_SIGNALS_FOR_BOLD ?
|
|
62
|
+
return signals >= MIN_SIGNALS_FOR_BOLD ? choice("bold", "signals") : choice("light", "spots");
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** What chaff recommends from its findings alone. */
|
|
66
|
+
const recommended = (input: ModeInput): ModeChoice => {
|
|
67
|
+
if (input.firedRules.has(COMPOSITE_RULE)) return choice("full", "composite");
|
|
68
|
+
if (hasStructureToFix(input)) return choice("full", "structure");
|
|
69
|
+
if (input.firedRules.size === 0) return choice("none", (input.setApart ?? 0) > 0 ? "all-deeper" : "nothing-found");
|
|
70
|
+
if (isFreeFormGenre(input.genre)) return choice("full", "genre");
|
|
71
|
+
return lightOrBold(input);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** A chosen depth sets how deep the rewrite goes. At light, chaff still picks Light or Bold, and says when it would go deeper. */
|
|
75
|
+
const atChosenDepth = (input: ModeInput, chosen: RewriteDepth, ownChoice: ModeChoice): ModeChoice => {
|
|
76
|
+
if (chosen !== "light") return ownChoice.depth === chosen ? ownChoice : choice(MODE_AT[chosen], "chosen");
|
|
77
|
+
if (ownChoice.depth !== undefined && depthIncludes(chosen, ownChoice.depth)) return ownChoice;
|
|
78
|
+
const within = lightOrBold(input);
|
|
79
|
+
return { ...within, reason: "depth-limit" };
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const recommendMode = (input: ModeInput): ModeChoice => {
|
|
83
|
+
const ownChoice = recommended(input);
|
|
84
|
+
if (input.chosen === undefined || ownChoice.mode === "none") return ownChoice;
|
|
85
|
+
return atChosenDepth(input, input.chosen, ownChoice);
|
|
40
86
|
};
|
package/src/fix-plan/plan.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { splitWords } from "../detectors/word-list.ts";
|
|
|
7
7
|
import { COMPOSITE_RULE, recommendMode, type ModeChoice } from "./mode.ts";
|
|
8
8
|
import type { StructureScore } from "../structure-shape/score.ts";
|
|
9
9
|
import { structureTargetsOf, type StructureTarget } from "./structure-targets.ts";
|
|
10
|
+
import { DEFAULT_DEPTH, depthIncludes, type RewriteDepth } from "../rewrite-depth.ts";
|
|
10
11
|
|
|
11
12
|
/** One flagged spot, where it is and what chaff said about it. */
|
|
12
13
|
export type PlanSpot = { readonly line: number; readonly column: number; readonly quote: string; readonly message: string };
|
|
@@ -18,6 +19,8 @@ export type PhraseHint = { readonly phrase: string; readonly rewrite: string };
|
|
|
18
19
|
export type RulePlan = {
|
|
19
20
|
readonly rule: string;
|
|
20
21
|
readonly name: string;
|
|
22
|
+
/** How deep the rule's direction reaches (rewrite.depth); light when the rule does not say. */
|
|
23
|
+
readonly depth: RewriteDepth;
|
|
21
24
|
/** The rule's rewrite direction, or its how_to_fix when it has no rewrite block. */
|
|
22
25
|
readonly direction: string;
|
|
23
26
|
readonly keep: readonly string[];
|
|
@@ -31,6 +34,12 @@ export type RulePlan = {
|
|
|
31
34
|
/** A rule that measures the whole document, with what it said. */
|
|
32
35
|
type DocumentSignal = { readonly rule: string; readonly message: string };
|
|
33
36
|
|
|
37
|
+
/** A rule that fired but reaches deeper than the chosen depth: named, not planned, so its silence is not read as fixed. */
|
|
38
|
+
export type DeeperRule = { readonly rule: string; readonly name: string; readonly depth: RewriteDepth; readonly spots: number };
|
|
39
|
+
|
|
40
|
+
/** The depth chosen for the plan, and where: --depth on the command line, or fix_plan.depth in chaff.yaml. */
|
|
41
|
+
export type ChosenDepth = { readonly depth: RewriteDepth; readonly from: "flag" | "config" };
|
|
42
|
+
|
|
34
43
|
export type FixPlan = {
|
|
35
44
|
readonly path: string;
|
|
36
45
|
/** Where the plan suggests saving the rewrite, so the check commands can be pasted as they are. */
|
|
@@ -39,6 +48,8 @@ export type FixPlan = {
|
|
|
39
48
|
readonly genre: string;
|
|
40
49
|
readonly experimental: boolean;
|
|
41
50
|
readonly mode: ModeChoice;
|
|
51
|
+
/** None when chaff recommends the depth itself. */
|
|
52
|
+
readonly chosenDepth: ChosenDepth | undefined;
|
|
42
53
|
readonly signals: readonly DocumentSignal[];
|
|
43
54
|
readonly outline: { readonly unit: LengthUnit; readonly shape: Shape };
|
|
44
55
|
/** The structure score, the count at which the plan rewrites from the outline, and how many measures were compared. */
|
|
@@ -46,6 +57,8 @@ export type FixPlan = {
|
|
|
46
57
|
/** The structure measures past 90% of human articles, each with the human numbers to bring it back to. */
|
|
47
58
|
readonly targets: readonly StructureTarget[];
|
|
48
59
|
readonly rules: readonly RulePlan[];
|
|
60
|
+
/** The rules that fired but reach deeper than the chosen depth. Empty without a chosen depth. */
|
|
61
|
+
readonly deeper: readonly DeeperRule[];
|
|
49
62
|
/** The rewrite and signal rules that did not run, and why: silence from them is not "checked and fine". */
|
|
50
63
|
readonly notRun: readonly Skipped[];
|
|
51
64
|
readonly checks: readonly string[];
|
|
@@ -66,6 +79,7 @@ export type PlanInput = {
|
|
|
66
79
|
readonly structure: StructureScore;
|
|
67
80
|
/** The ai-tell lexicon of the document's language, whose entries may carry their own hint. */
|
|
68
81
|
readonly phrases: Lexicon;
|
|
82
|
+
readonly chosenDepth?: ChosenDepth | undefined;
|
|
69
83
|
};
|
|
70
84
|
|
|
71
85
|
const BOLD_DENSITY_RULE = "bold-density";
|
|
@@ -114,6 +128,7 @@ const rulePlanOf = (rule: RuleDefinition, findings: readonly Finding[], input: P
|
|
|
114
128
|
return {
|
|
115
129
|
rule: rule.id,
|
|
116
130
|
name: localized(rule.name, input.language),
|
|
131
|
+
depth: depthOfRule(rule),
|
|
117
132
|
direction: rewrite?.direction ?? localized(rule.how_to_fix, input.language),
|
|
118
133
|
keep: rewrite?.keep ?? [],
|
|
119
134
|
avoid: rewrite?.avoid ?? [],
|
|
@@ -123,6 +138,18 @@ const rulePlanOf = (rule: RuleDefinition, findings: readonly Finding[], input: P
|
|
|
123
138
|
};
|
|
124
139
|
};
|
|
125
140
|
|
|
141
|
+
const depthOfRule = (rule: RuleDefinition): RewriteDepth => rule.guide?.rewriteDepth ?? DEFAULT_DEPTH;
|
|
142
|
+
|
|
143
|
+
/** Every rule that fired, planned; then the ones a chosen depth leaves out, set apart. */
|
|
144
|
+
const byDepth = (plans: readonly RulePlan[], chosen: ChosenDepth | undefined): Pick<FixPlan, "rules" | "deeper"> => {
|
|
145
|
+
if (chosen === undefined) return { rules: plans, deeper: [] };
|
|
146
|
+
const within = (plan: RulePlan): boolean => depthIncludes(chosen.depth, plan.depth);
|
|
147
|
+
return {
|
|
148
|
+
rules: plans.filter(within),
|
|
149
|
+
deeper: plans.filter((plan) => !within(plan)).map((plan) => ({ rule: plan.rule, name: plan.name, depth: plan.depth, spots: plan.spots.length })),
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
|
|
126
153
|
/** The rules in the order their first finding appears; the composite has no spot of its own and is a signal only. */
|
|
127
154
|
const rulePlansOf = (input: PlanInput): RulePlan[] => {
|
|
128
155
|
const order = [...new Set(input.findings.map((finding) => finding.rule))].filter((id) => id !== COMPOSITE_RULE);
|
|
@@ -159,12 +186,12 @@ const notRunOf = (input: PlanInput, signalRules: ReadonlySet<string>): Skipped[]
|
|
|
159
186
|
return input.skipped.filter((skipped) => watched.has(skipped.rule));
|
|
160
187
|
};
|
|
161
188
|
|
|
162
|
-
/** The commands to run on the rewrite. A
|
|
189
|
+
/** The commands to run on the rewrite. A rewrite past light cuts repeats and rebuilds headings, which compare is told. */
|
|
163
190
|
const checksOf = (input: PlanInput, choice: ModeChoice): string[] => {
|
|
164
191
|
const original = shellPath(input.path);
|
|
165
192
|
const rewritten = shellPath(rewrittenPathOf(input.path));
|
|
166
193
|
const lintFlags = [...(input.experimental ? ["--experimental"] : []), ...(input.genreFlag === undefined ? [] : ["--genre", input.genreFlag])];
|
|
167
|
-
const compareFlags = choice.
|
|
194
|
+
const compareFlags = choice.depth !== undefined && choice.depth !== "light" ? ["--distinct", "--allow-dropped heading", "--allow-added heading"] : [];
|
|
168
195
|
return [
|
|
169
196
|
["npx chaffjs", rewritten, ...lintFlags].join(" "),
|
|
170
197
|
["npx chaffjs compare", original, rewritten, ...compareFlags].join(" "),
|
|
@@ -182,8 +209,12 @@ const documentLength = (outline: Outline): number => outline.entries.reduce((sum
|
|
|
182
209
|
export const buildFixPlan = (input: PlanInput): FixPlan => {
|
|
183
210
|
const signalRules = signalRulesOf(input.rules);
|
|
184
211
|
const structure = { score: input.structure.score, limit: structureLimitOf(input.rules), compared: input.structure.compared };
|
|
185
|
-
const
|
|
186
|
-
|
|
212
|
+
const planned = byDepth(rulePlansOf(input), input.chosenDepth);
|
|
213
|
+
// A rule the depth sets apart is not something this rewrite fixes, so it does not decide the way either.
|
|
214
|
+
const setApart = new Set(planned.deeper.map((rule) => rule.rule));
|
|
215
|
+
const firedRules = new Set(input.findings.map((finding) => finding.rule).filter((rule) => !setApart.has(rule)));
|
|
216
|
+
const chosen = input.chosenDepth?.depth;
|
|
217
|
+
const mode = recommendMode({ genre: input.genre, firedRules, signalRules, structure, chosen, setApart: setApart.size });
|
|
187
218
|
return {
|
|
188
219
|
path: input.path,
|
|
189
220
|
rewrittenPath: rewrittenPathOf(input.path),
|
|
@@ -191,11 +222,12 @@ export const buildFixPlan = (input: PlanInput): FixPlan => {
|
|
|
191
222
|
genre: input.genre,
|
|
192
223
|
experimental: input.experimental,
|
|
193
224
|
mode,
|
|
225
|
+
chosenDepth: input.chosenDepth,
|
|
194
226
|
signals: signalsOf(input, signalRules),
|
|
195
227
|
outline: { unit: input.outline.unit, shape: input.outline.shape },
|
|
196
228
|
structure,
|
|
197
229
|
targets: structureTargetsOf(input.structure, documentLength(input.outline)),
|
|
198
|
-
|
|
230
|
+
...planned,
|
|
199
231
|
notRun: notRunOf(input, signalRules),
|
|
200
232
|
checks: checksOf(input, mode),
|
|
201
233
|
};
|
package/src/fix-plan/render.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { uiLanguageOf } from "../ui.ts";
|
|
|
5
5
|
import type { FixPlan, PhraseHint, PlanSpot, RulePlan } from "./plan.ts";
|
|
6
6
|
import { FIX_PLAN_TEXT, type FixPlanText } from "./text.ts";
|
|
7
7
|
import { STRUCTURE_TARGET_TEXT } from "./structure-target-text.ts";
|
|
8
|
+
import { DEFAULT_DEPTH, depthMeaning } from "../rewrite-depth.ts";
|
|
8
9
|
|
|
9
10
|
/** A phrase quoted the way the document's language quotes: 「…」 in Japanese, "…" otherwise. */
|
|
10
11
|
const quoted = (text: string, language: string): string => (language === "ja" ? `「${text}」` : `"${text}"`);
|
|
@@ -34,6 +35,7 @@ const rulePlanLines = (rule: RulePlan, text: FixPlanText, language: string): str
|
|
|
34
35
|
"",
|
|
35
36
|
`### \`${rule.rule}\` ${rule.name}`,
|
|
36
37
|
"",
|
|
38
|
+
...(rule.depth === DEFAULT_DEPTH ? [] : [`**${text.ruleDepth}**: ${rule.depth}`, ""]),
|
|
37
39
|
`**${text.direction}**: ${rule.direction}`,
|
|
38
40
|
...labelled(text.keep, bullets(rule.keep)),
|
|
39
41
|
...labelled(text.avoid, bullets(rule.avoid)),
|
|
@@ -48,15 +50,36 @@ const rulePlanLines = (rule: RulePlan, text: FixPlanText, language: string): str
|
|
|
48
50
|
),
|
|
49
51
|
];
|
|
50
52
|
|
|
53
|
+
const depthLines = (plan: FixPlan, text: FixPlanText): string[] => {
|
|
54
|
+
const { depth } = plan.mode;
|
|
55
|
+
if (depth === undefined) return [];
|
|
56
|
+
const setBy = plan.chosenDepth === undefined ? "" : text.depthSetBy[plan.chosenDepth.from];
|
|
57
|
+
return [text.depthLine(depth, depthMeaning(depth, uiLanguageOf(plan.language)), setBy), ""];
|
|
58
|
+
};
|
|
59
|
+
|
|
51
60
|
const modeLines = (plan: FixPlan, text: FixPlanText): string[] => [
|
|
52
61
|
"",
|
|
53
62
|
`## ${text.modeHeading}: ${text.modeName[plan.mode.mode]}`,
|
|
54
63
|
"",
|
|
55
64
|
text.modeReason[plan.mode.reason],
|
|
56
65
|
"",
|
|
66
|
+
...depthLines(plan, text),
|
|
57
67
|
...bullets(text.modeWays),
|
|
58
68
|
];
|
|
59
69
|
|
|
70
|
+
/** The rules a chosen depth leaves out, named with their depth, so their findings are not taken for fixed. */
|
|
71
|
+
const deeperLines = (plan: FixPlan, text: FixPlanText): string[] => {
|
|
72
|
+
if (plan.deeper.length === 0 || plan.chosenDepth === undefined) return [];
|
|
73
|
+
return [
|
|
74
|
+
"",
|
|
75
|
+
`## ${text.deeperHeading}`,
|
|
76
|
+
"",
|
|
77
|
+
text.deeperNote(plan.chosenDepth.depth),
|
|
78
|
+
"",
|
|
79
|
+
...bullets(plan.deeper.map((rule) => `\`${rule.rule}\` ${rule.name}: ${text.deeperLine(rule.depth, rule.spots)}`)),
|
|
80
|
+
];
|
|
81
|
+
};
|
|
82
|
+
|
|
60
83
|
const signalLines = (plan: FixPlan, text: FixPlanText): string[] => {
|
|
61
84
|
const outline = text.outlineLine(shapeMeasures(plan.outline.shape, plan.outline.unit, OUTLINE_TEXT[uiLanguageOf(plan.language)]));
|
|
62
85
|
const said = plan.signals.length === 0 ? [text.noSignals, ""] : [];
|
|
@@ -126,6 +149,7 @@ export const renderFixPlanMarkdown = (plan: FixPlan): string => {
|
|
|
126
149
|
...structureLines(plan),
|
|
127
150
|
...(plan.rules.length === 0 ? [] : ["", `## ${text.rulesHeading}`]),
|
|
128
151
|
...plan.rules.flatMap((rule) => rulePlanLines(rule, text, plan.language)),
|
|
152
|
+
...deeperLines(plan, text),
|
|
129
153
|
...notRunLines(plan, text),
|
|
130
154
|
...checkLines(plan, text),
|
|
131
155
|
].join("\n");
|
package/src/fix-plan/text.ts
CHANGED
|
@@ -13,6 +13,14 @@ export type FixPlanText = {
|
|
|
13
13
|
readonly modeName: Readonly<Record<FixMode, string>>;
|
|
14
14
|
readonly modeReason: Readonly<Record<ModeReason, string>>;
|
|
15
15
|
readonly modeWays: readonly string[];
|
|
16
|
+
/** The depth the plan goes to, with what it means, and where it was chosen when it was. */
|
|
17
|
+
readonly depthLine: (depth: string, meaning: string, setBy: string) => string;
|
|
18
|
+
readonly depthSetBy: Readonly<Record<"flag" | "config", string>>;
|
|
19
|
+
/** A rule's own depth, shown when it reaches past light. */
|
|
20
|
+
readonly ruleDepth: string;
|
|
21
|
+
readonly deeperHeading: string;
|
|
22
|
+
readonly deeperNote: (chosen: string) => string;
|
|
23
|
+
readonly deeperLine: (depth: string, spots: number) => string;
|
|
16
24
|
readonly signalsHeading: string;
|
|
17
25
|
readonly noSignals: string;
|
|
18
26
|
readonly outlineLine: (measures: string) => string;
|
|
@@ -35,7 +43,7 @@ export type FixPlanText = {
|
|
|
35
43
|
|
|
36
44
|
export const FIX_PLAN_TEXT: Texts<FixPlanText> = {
|
|
37
45
|
ja: {
|
|
38
|
-
usage: "使い方: chaff fix-plan <file> [--experimental] [--genre <ジャンル>] [--json]",
|
|
46
|
+
usage: "使い方: chaff fix-plan <file> [--depth light|structure|register] [--experimental] [--genre <ジャンル>] [--json]",
|
|
39
47
|
notFound: (path) => `${path} がありません。`,
|
|
40
48
|
title: (path) => `直す計画: ${path}`,
|
|
41
49
|
about: (language, genre) => `言語 ${language}、ジャンル ${genre}`,
|
|
@@ -49,9 +57,18 @@ export const FIX_PLAN_TEXT: Texts<FixPlanText> = {
|
|
|
49
57
|
"書き直しは 2 回まで。指摘を消すためだけに書き直しを繰り返さない。",
|
|
50
58
|
],
|
|
51
59
|
modeHeading: "勧める直し方",
|
|
52
|
-
modeName: {
|
|
60
|
+
modeName: {
|
|
61
|
+
none: "直すところなし",
|
|
62
|
+
light: "軽く直す(Light)",
|
|
63
|
+
bold: "大胆に直す(Bold)",
|
|
64
|
+
full: "全面書き直し(Full)",
|
|
65
|
+
register: "文体を変える(Register)",
|
|
66
|
+
},
|
|
53
67
|
modeReason: {
|
|
68
|
+
chosen: "書き直しの深さが決めてあるので、その深さで直します。",
|
|
69
|
+
"depth-limit": "chaff だけなら構成から書き直すことを勧めますが、決めてある深さが light なので、構成は残します。",
|
|
54
70
|
"nothing-found": "chaff は何も見つけませんでした。",
|
|
71
|
+
"all-deeper": "指摘はどれも、決めた深さより深く直すルールのものです。この深さで直すところはありません(下の「決めた深さより深いルール」)。",
|
|
55
72
|
composite: "ai-generated-composite が出ています。文の言い回しを直しても、生成文の骨組みが残ります。",
|
|
56
73
|
structure: "構成の項目のいくつもが、人の記事の 9 割を超えています(下の「構成の目標」)。文を直しても、見出しの立て方と節の形が残ります。",
|
|
57
74
|
genre: "ブログやエッセイは、構成から書き直すほうが書き手の望みに合います。",
|
|
@@ -59,10 +76,18 @@ export const FIX_PLAN_TEXT: Texts<FixPlanText> = {
|
|
|
59
76
|
spots: "指摘は箇所ごとです。指摘された所だけを直します。",
|
|
60
77
|
},
|
|
61
78
|
modeWays: [
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"
|
|
79
|
+
"軽く直す(深さ light): 指摘された箇所だけ。",
|
|
80
|
+
"大胆に直す(深さ light): 見出しの構成は残し、節ごとに文を書き直す。",
|
|
81
|
+
"全面書き直し(深さ structure): 事実と主張を控えてから、構成から書き直す。頼まれた内容が「全面的に」「一から」なら、これを選ぶ。",
|
|
82
|
+
"文体を変える(深さ register): 全面書き直しに加えて、文体も変える(です・ます → である など)。chaff からは勧めません。--depth register で選びます。",
|
|
65
83
|
],
|
|
84
|
+
depthLine: (depth, meaning, setBy) => `書き直しの深さ: ${depth}(${meaning})${setBy}。`,
|
|
85
|
+
depthSetBy: { flag: "。--depth で決めました", config: "。chaff.yaml の fix_plan.depth で決めました" },
|
|
86
|
+
ruleDepth: "深さ",
|
|
87
|
+
deeperHeading: "決めた深さより深いルール",
|
|
88
|
+
deeperNote: (chosen) =>
|
|
89
|
+
`次のルールも指摘を出しましたが、直す方向が深さ ${chosen} より深いので、この計画では直しません。直すときは、そのルールの深さを --depth に渡して計画を出し直してください。`,
|
|
90
|
+
deeperLine: (depth, spots) => `深さ ${depth}、${String(spots)} 箇所`,
|
|
66
91
|
signalsHeading: "文書全体の目印",
|
|
67
92
|
noSignals: "文書全体を測るルールは、何も言っていません。",
|
|
68
93
|
outlineLine: (measures) => `構成: ${measures}`,
|
|
@@ -84,7 +109,7 @@ export const FIX_PLAN_TEXT: Texts<FixPlanText> = {
|
|
|
84
109
|
"1 つめは書き直した文書の指摘、2 つめは事実が落ちていないか・足されていないか、3 つめは構成の変化です。数は chaff の出力から取り、形容詞で言わないでください。",
|
|
85
110
|
},
|
|
86
111
|
en: {
|
|
87
|
-
usage: "usage: chaff fix-plan <file> [--experimental] [--genre <genre>] [--json]",
|
|
112
|
+
usage: "usage: chaff fix-plan <file> [--depth light|structure|register] [--experimental] [--genre <genre>] [--json]",
|
|
88
113
|
notFound: (path) => `${path} does not exist.`,
|
|
89
114
|
title: (path) => `Fix plan: ${path}`,
|
|
90
115
|
about: (language, genre) => `language ${language}, genre ${genre}`,
|
|
@@ -98,9 +123,13 @@ export const FIX_PLAN_TEXT: Texts<FixPlanText> = {
|
|
|
98
123
|
"At most two passes. Do not rewrite again just to silence a finding.",
|
|
99
124
|
],
|
|
100
125
|
modeHeading: "Recommended way",
|
|
101
|
-
modeName: { none: "Nothing to fix", light: "Light", bold: "Bold", full: "Full rewrite" },
|
|
126
|
+
modeName: { none: "Nothing to fix", light: "Light", bold: "Bold", full: "Full rewrite", register: "Register change" },
|
|
102
127
|
modeReason: {
|
|
128
|
+
chosen: "A rewrite depth is set, so the plan goes to that depth.",
|
|
129
|
+
"depth-limit": "On its findings alone chaff would rewrite from the structure up, but the depth set is light, so the structure stays.",
|
|
103
130
|
"nothing-found": "chaff found nothing.",
|
|
131
|
+
"all-deeper":
|
|
132
|
+
'Every finding comes from a rule that rewrites deeper than the depth set. There is nothing to fix at this depth (see "Rules deeper than the depth set" below).',
|
|
104
133
|
composite: "ai-generated-composite fires. Fixing the wording would leave the skeleton of generated text.",
|
|
105
134
|
structure:
|
|
106
135
|
'Several structure measures lie past 90% of human articles (see "Structure targets" below). Fixing the sentences would leave the headings and the shape of the sections.',
|
|
@@ -109,10 +138,18 @@ export const FIX_PLAN_TEXT: Texts<FixPlanText> = {
|
|
|
109
138
|
spots: "The findings are single spots. Rewrite only those.",
|
|
110
139
|
},
|
|
111
140
|
modeWays: [
|
|
112
|
-
"Light: only the flagged spots.",
|
|
113
|
-
"Bold: keep the outline, rewrite the prose of each section.",
|
|
114
|
-
'Full: take an inventory of the facts and claims, then rewrite from the structure up. If the request says "from scratch", choose this.',
|
|
141
|
+
"Light (depth light): only the flagged spots.",
|
|
142
|
+
"Bold (depth light): keep the outline, rewrite the prose of each section.",
|
|
143
|
+
'Full (depth structure): take an inventory of the facts and claims, then rewrite from the structure up. If the request says "from scratch", choose this.',
|
|
144
|
+
"Register (depth register): a full rewrite that also converts the style (polite to plain endings, and so on). chaff never recommends it; choose it with --depth register.",
|
|
115
145
|
],
|
|
146
|
+
depthLine: (depth, meaning, setBy) => `Rewrite depth: ${depth} (${meaning})${setBy}.`,
|
|
147
|
+
depthSetBy: { flag: ", set by --depth", config: ", set by fix_plan.depth in chaff.yaml" },
|
|
148
|
+
ruleDepth: "Depth",
|
|
149
|
+
deeperHeading: "Rules deeper than the depth set",
|
|
150
|
+
deeperNote: (chosen) =>
|
|
151
|
+
`These rules fired too, but their direction goes deeper than ${chosen}, so this plan does not fix them. To fix them, give the rule's depth to --depth and print the plan again.`,
|
|
152
|
+
deeperLine: (depth, spots) => `depth ${depth}, ${String(spots)} ${spots === 1 ? "spot" : "spots"}`,
|
|
116
153
|
signalsHeading: "Document-level signals",
|
|
117
154
|
noSignals: "None of the rules that measure the whole document said anything.",
|
|
118
155
|
outlineLine: (measures) => `Outline: ${measures}`,
|
package/src/genre-load.ts
CHANGED
|
@@ -2,17 +2,20 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { parse } from "yaml";
|
|
5
|
-
import { parseGenres, presetLevelsOf, presetProfileOf, type GenreData, type PresetLevels } from "./genre-parse.ts";
|
|
5
|
+
import { parseGenres, presetLevelsOf, presetProfileOf, withRuleOffs, type GenreData, type PresetLevels } from "./genre-parse.ts";
|
|
6
|
+
import { loadRuleOffs } from "./rule-offs.ts";
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
+
const PACKAGE = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
|
+
const GENRES_FILE = join(PACKAGE, "genres.yaml");
|
|
10
|
+
const RULES_DIR = join(PACKAGE, "rules");
|
|
8
11
|
|
|
9
12
|
const loaded: { value: GenreData | undefined } = { value: undefined };
|
|
10
13
|
|
|
11
|
-
/** The bundled genres.yaml. Read once. */
|
|
14
|
+
/** The bundled genres.yaml, with the offs the rule files set (off_for). Read once. */
|
|
12
15
|
export const loadGenres = (): GenreData => {
|
|
13
16
|
if (loaded.value !== undefined) return loaded.value;
|
|
14
17
|
try {
|
|
15
|
-
loaded.value = parseGenres(parse(readFileSync(GENRES_FILE, "utf8")));
|
|
18
|
+
loaded.value = withRuleOffs(parseGenres(parse(readFileSync(GENRES_FILE, "utf8"))), loadRuleOffs(RULES_DIR));
|
|
16
19
|
} catch (error) {
|
|
17
20
|
throw new Error(`cannot read ${GENRES_FILE}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
18
21
|
}
|
package/src/genre-parse.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isLevel } from "./levels.ts";
|
|
2
2
|
import type { Level, Localized } from "./plugin.ts";
|
|
3
|
+
import type { RuleOff } from "./rule-offs.ts";
|
|
3
4
|
|
|
4
5
|
/** A preset's rules: the level each named rule runs at, under whatever chaff.yaml's rules say. */
|
|
5
6
|
export type PresetLevels = Readonly<Record<string, Level>>;
|
|
@@ -139,6 +140,24 @@ export const parseGenres = (raw: unknown): GenreData => {
|
|
|
139
140
|
return { groups, genres };
|
|
140
141
|
};
|
|
141
142
|
|
|
143
|
+
const withOffsFor = <T extends { readonly id: string; readonly rules: PresetLevels }>(entry: T, offs: readonly RuleOff[]): T => {
|
|
144
|
+
const mine = offs.filter((off) => off.target === entry.id);
|
|
145
|
+
const both = mine.find((off) => entry.rules[off.rule] !== undefined);
|
|
146
|
+
if (both !== undefined) throw new Error(`${both.rule}: its off_for and genres.yaml both set its level for ${entry.id}`);
|
|
147
|
+
return mine.length === 0 ? entry : { ...entry, rules: { ...entry.rules, ...Object.fromEntries(mine.map((off) => [off.rule, "off" as const])) } };
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* genres.yaml's data with each rule file's off_for merged in. An off for a group joins the group's rules and an off for
|
|
152
|
+
* a genre the genre's own, so a genre's own level still wins over its group's. An off naming no group or genre stops here.
|
|
153
|
+
*/
|
|
154
|
+
export const withRuleOffs = (data: GenreData, offs: readonly RuleOff[]): GenreData => {
|
|
155
|
+
const known = new Set([...data.groups, ...data.genres].map((entry) => entry.id));
|
|
156
|
+
const unknown = offs.find((off) => !known.has(off.target));
|
|
157
|
+
if (unknown !== undefined) throw new Error(`${unknown.rule}: off_for names ${unknown.target}, which is no group or genre`);
|
|
158
|
+
return { groups: data.groups.map((group) => withOffsFor(group, offs)), genres: data.genres.map((genre) => withOffsFor(genre, offs)) };
|
|
159
|
+
};
|
|
160
|
+
|
|
142
161
|
/** The levels a genre's preset sets: its group's, then its own on top. Empty for a genre chaff does not know. */
|
|
143
162
|
export const presetLevelsOf = (data: GenreData, genre: string): PresetLevels => {
|
|
144
163
|
const own = data.genres.find((entry) => entry.id === genre);
|
package/src/init.ts
CHANGED
|
@@ -30,9 +30,9 @@ const TEXT: Texts<{
|
|
|
30
30
|
#
|
|
31
31
|
# コマンドでも変更できます。理由がコメントとして自動で残ります。
|
|
32
32
|
#
|
|
33
|
-
# npx
|
|
34
|
-
# npx
|
|
35
|
-
# npx
|
|
33
|
+
# npx chaffjs relax bold-density --why "図の説明で太字を多用するため"
|
|
34
|
+
# npx chaffjs explain bold-density そのルールの意図を読む
|
|
35
|
+
# npx chaffjs rules --json AI に設定を書かせるときに渡す
|
|
36
36
|
|
|
37
37
|
# この場所に置く文書の種類(${summary})。ほかの種類: npx chaffjs genres
|
|
38
38
|
genre: ${genre}
|
|
@@ -50,9 +50,9 @@ rules:
|
|
|
50
50
|
exists: "chaff.yaml は既にあります。変更していません。",
|
|
51
51
|
createdHeading: "作成しました:",
|
|
52
52
|
genreChosen: (genre) => `ジャンルは ${genre} にしました。違う場合は chaff.yaml の genre を直してください。`,
|
|
53
|
-
genreList: " 一覧: npx
|
|
53
|
+
genreList: " 一覧: npx chaffjs genres",
|
|
54
54
|
next: "次:",
|
|
55
|
-
nextCheck: " npx
|
|
55
|
+
nextCheck: " npx chaffjs . この場所の Markdown を全部見る",
|
|
56
56
|
},
|
|
57
57
|
en: {
|
|
58
58
|
config: (genre, summary) => `# chaff.yaml — this team's writing rules
|
|
@@ -69,9 +69,9 @@ rules:
|
|
|
69
69
|
#
|
|
70
70
|
# Commands change it too, and leave the reason as a comment.
|
|
71
71
|
#
|
|
72
|
-
# npx
|
|
73
|
-
# npx
|
|
74
|
-
# npx
|
|
72
|
+
# npx chaffjs relax bold-density --why "figure captions use a lot of bold"
|
|
73
|
+
# npx chaffjs explain bold-density read what the rule is for
|
|
74
|
+
# npx chaffjs rules --json give this to an AI that writes the settings
|
|
75
75
|
|
|
76
76
|
# The kind of document kept here (${summary}). The others: npx chaffjs genres
|
|
77
77
|
genre: ${genre}
|
|
@@ -89,9 +89,9 @@ rules:
|
|
|
89
89
|
exists: "chaff.yaml already exists. Nothing was changed.",
|
|
90
90
|
createdHeading: "Created:",
|
|
91
91
|
genreChosen: (genre) => `The genre is ${genre}. If that is wrong, change genre in chaff.yaml.`,
|
|
92
|
-
genreList: " List: npx
|
|
92
|
+
genreList: " List: npx chaffjs genres",
|
|
93
93
|
next: "Next:",
|
|
94
|
-
nextCheck: " npx
|
|
94
|
+
nextCheck: " npx chaffjs . check every Markdown file here",
|
|
95
95
|
},
|
|
96
96
|
};
|
|
97
97
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse } from "yaml";
|
|
4
|
+
import { loadGenres } from "./genre-load.ts";
|
|
5
|
+
import { parseGenres, type GenreData } from "./genre-parse.ts";
|
|
6
|
+
|
|
7
|
+
const idsOf = (data: GenreData): string[] => [...data.groups.map((group) => group.id), ...data.genres.map((genre) => genre.id)];
|
|
8
|
+
|
|
9
|
+
/** Every genre and genre group genres.yaml names: what a rule's use_for may list. */
|
|
10
|
+
export const knownGenres = (): string[] => idsOf(loadGenres());
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The genres of the genres.yaml beside a rules folder (packages/chaff/rules → packages/chaff/genres.yaml), else chaff's
|
|
14
|
+
* own. The site reads the rules from the source tree after bundling, where chaff's own file is not where the module is.
|
|
15
|
+
*/
|
|
16
|
+
export const genresBeside = (rulesDir: string): string[] => {
|
|
17
|
+
const file = join(rulesDir, "..", "genres.yaml");
|
|
18
|
+
return existsSync(file) ? idsOf(parseGenres(parse(readFileSync(file, "utf8")))) : knownGenres();
|
|
19
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// What a link's text and an image's alt text look like: a URL, a host, a file name. Pure functions only.
|
|
2
|
+
|
|
3
|
+
/** Link text that reads as an address: it starts with a scheme or with `www.`. A bare `README.md` is a file, not a host. */
|
|
4
|
+
const URL_TEXT = /^(?:https?:\/\/|www\.)[^\s]+$/iu;
|
|
5
|
+
|
|
6
|
+
/** Marks around a link's text that are not part of the address it shows (`**https://…**`, `` `https://…` ``, `<…>`). */
|
|
7
|
+
const LEADING = new Set(" \t\n*_`<「『\"“'");
|
|
8
|
+
const TRAILING = new Set(" \t\n*_`>」』\"”'.。,,、");
|
|
9
|
+
|
|
10
|
+
/** The text as the reader sees the address, without emphasis, code marks, quotes or closing punctuation. */
|
|
11
|
+
export const shownAddress = (text: string): string => {
|
|
12
|
+
const chars = [...text];
|
|
13
|
+
const start = chars.findIndex((char) => !LEADING.has(char));
|
|
14
|
+
return start === -1 ? "" : chars.slice(start, chars.findLastIndex((char) => !TRAILING.has(char)) + 1).join("");
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Whether a link's text is itself an address. */
|
|
18
|
+
export const isUrlText = (text: string): boolean => URL_TEXT.test(shownAddress(text));
|
|
19
|
+
|
|
20
|
+
const WWW = /^www\./iu;
|
|
21
|
+
const WEB_SCHEME = /^https?:\/\//iu;
|
|
22
|
+
|
|
23
|
+
/** The host an address names, lower case and without `www.`; undefined for a relative path, a fragment or a mail address. */
|
|
24
|
+
export const hostOf = (address: string): string | undefined => {
|
|
25
|
+
const written = shownAddress(address);
|
|
26
|
+
const absolute = WWW.test(written) ? `https://${written}` : written;
|
|
27
|
+
if (!WEB_SCHEME.test(absolute)) return undefined;
|
|
28
|
+
try {
|
|
29
|
+
return new URL(absolute).hostname.toLowerCase().replace(WWW, "");
|
|
30
|
+
} catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The host a link's text shows, when it differs from the host the link goes to. Both must be absolute web addresses;
|
|
37
|
+
* a shortened display of the same host (`github.com/a` for `https://github.com/a/b/tree/main`) is the same host.
|
|
38
|
+
*/
|
|
39
|
+
export const mismatchedHost = (text: string, destination: string): { readonly shown: string; readonly target: string } | undefined => {
|
|
40
|
+
if (!isUrlText(text)) return undefined;
|
|
41
|
+
const shown = hostOf(text);
|
|
42
|
+
const target = hostOf(destination);
|
|
43
|
+
return shown === undefined || target === undefined || shown === target ? undefined : { shown, target };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** The extensions of image files. An alt text that ends in one is the file's name. */
|
|
47
|
+
const IMAGE_FILE = /\.(?:png|jpe?g|gif|svg|webp|bmp|tiff?|heic|heif|avif|ico)$/iu;
|
|
48
|
+
|
|
49
|
+
/** After a camera's or a screenshot tool's name: separators, then the number it counts with (`IMG_1234`, `Screenshot 2026-…`). */
|
|
50
|
+
const COUNTER = /^[\s_-]*\d/u;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether an alt text is a file's name rather than a description: it ends in an image extension (`IMG_1234.png`), or it
|
|
54
|
+
* starts with a name a camera or a screenshot tool gives (`prefixes`, from the language's lexicon) followed by its number.
|
|
55
|
+
*/
|
|
56
|
+
export const isFileNameAlt = (alt: string, prefixes: readonly string[]): boolean => {
|
|
57
|
+
const written = alt.trim();
|
|
58
|
+
if (written === "") return false;
|
|
59
|
+
if (IMAGE_FILE.test(written)) return true;
|
|
60
|
+
const lowered = written.toLowerCase();
|
|
61
|
+
return prefixes.some((prefix) => lowered.startsWith(prefix.toLowerCase()) && COUNTER.test(written.slice(prefix.length)));
|
|
62
|
+
};
|
package/src/reasons.ts
CHANGED
|
@@ -22,6 +22,12 @@ export type Reasons = {
|
|
|
22
22
|
readonly oneDocument: string;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
/** A plugin's word list is named <plugin>/<list> (@scope/foo/list too); a language package's has no slash. */
|
|
26
|
+
const pluginListOf = (list: string): { readonly plugin: string; readonly name: string } | undefined => {
|
|
27
|
+
const slash = list.lastIndexOf("/");
|
|
28
|
+
return slash === -1 ? undefined : { plugin: list.slice(0, slash), name: list.slice(slash + 1) };
|
|
29
|
+
};
|
|
30
|
+
|
|
25
31
|
const CAPABILITY_NAME: Texts<Readonly<Record<string, string>>> = {
|
|
26
32
|
ja: { pos: "品詞解析", lemma: "原形" },
|
|
27
33
|
en: { pos: "part-of-speech tagging", lemma: "lemmas" },
|
|
@@ -33,7 +39,7 @@ export const REASONS: Texts<Reasons> = {
|
|
|
33
39
|
noCapability: (capability) => `この言語では${CAPABILITY_NAME.ja[capability] ?? capability}が使えないため`,
|
|
34
40
|
noTags: "アダプタが品詞を返さなかったため",
|
|
35
41
|
unreadTags: "言語のパッケージがこの文書を読めなかったため(品詞の取れない段落があります)",
|
|
36
|
-
semantic: "意味を読む検査のため(npx
|
|
42
|
+
semantic: "意味を読む検査のため(npx chaffjs test で動きます)",
|
|
37
43
|
experimental: "まだ試験中のため",
|
|
38
44
|
turnedOff: "設定で止めているため",
|
|
39
45
|
presetOff: (genre) => `ジャンル ${genre} では見ないため`,
|
|
@@ -41,7 +47,10 @@ export const REASONS: Texts<Reasons> = {
|
|
|
41
47
|
unreadStructure: (clauses, units) =>
|
|
42
48
|
`条項の番号が本文に ${String(clauses)} 個あるのに、番号として読めたのは ${String(units)} 個のため(深い字下げや、行が本文につながった文書)`,
|
|
43
49
|
noDetector: (name) => `検出器 ${name} がないため`,
|
|
44
|
-
noLexicon: (language, list) =>
|
|
50
|
+
noLexicon: (language, list) => {
|
|
51
|
+
const owner = pluginListOf(list);
|
|
52
|
+
return owner === undefined ? `${language} の語彙表 ${list} が無いため` : `プラグイン ${owner.plugin} に ${language} の語彙表 ${owner.name} が無いため`;
|
|
53
|
+
},
|
|
45
54
|
noHeadings: "表題より下の見出しが無いため",
|
|
46
55
|
patternTimeout: (budgetMs) => `正規表現が ${String(budgetMs)} ms で終わらなかったため(chaff.yaml の pattern を単純にしてください)`,
|
|
47
56
|
notMarkdown: "Markdown の文書ではないため",
|
|
@@ -52,7 +61,7 @@ export const REASONS: Texts<Reasons> = {
|
|
|
52
61
|
noCapability: (capability) => `${CAPABILITY_NAME.en[capability] ?? capability} is not available for this language`,
|
|
53
62
|
noTags: "the language package returned no parts of speech",
|
|
54
63
|
unreadTags: "the language package could not read this document (some paragraphs have no parts of speech)",
|
|
55
|
-
semantic: "it reads meaning; npx
|
|
64
|
+
semantic: "it reads meaning; npx chaffjs test runs it",
|
|
56
65
|
experimental: "still experimental",
|
|
57
66
|
turnedOff: "turned off in the settings",
|
|
58
67
|
presetOff: (genre) => `the ${genre} genre does not check it`,
|
|
@@ -60,7 +69,10 @@ export const REASONS: Texts<Reasons> = {
|
|
|
60
69
|
unreadStructure: (clauses, units) =>
|
|
61
70
|
`the text has ${String(clauses)} clause numbers but only ${String(units)} ${formFor(units, "was read as a numbered line", "were read as numbered lines")} (deep indents, or lines run into the text)`,
|
|
62
71
|
noDetector: (name) => `no detector named ${name}`,
|
|
63
|
-
noLexicon: (language, list) =>
|
|
72
|
+
noLexicon: (language, list) => {
|
|
73
|
+
const owner = pluginListOf(list);
|
|
74
|
+
return owner === undefined ? `the ${language} package has no word list ${list}` : `the plugin ${owner.plugin} has no ${language} word list ${owner.name}`;
|
|
75
|
+
},
|
|
64
76
|
noHeadings: "the document has no headings below its title",
|
|
65
77
|
patternTimeout: (budgetMs) => `the pattern did not finish within ${String(budgetMs)} ms (simplify the pattern in chaff.yaml)`,
|
|
66
78
|
notMarkdown: "the document is not Markdown",
|