chaffjs 0.15.0 → 0.17.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.js +1 -1
- 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 +31 -0
- package/dist/calendar-number.d.ts.map +1 -0
- package/dist/calendar-number.js +58 -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 +6 -2
- package/dist/cli-text.d.ts.map +1 -1
- package/dist/cli-text.js +19 -12
- package/dist/cli-text.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +43 -37
- package/dist/cli.js.map +1 -1
- package/dist/closing-numbers.d.ts +20 -0
- package/dist/closing-numbers.d.ts.map +1 -0
- package/dist/closing-numbers.js +56 -0
- package/dist/closing-numbers.js.map +1 -0
- 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.js +1 -1
- package/dist/commands/eval.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 +78 -44
- 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 +7 -6
- 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-context.d.ts +2 -0
- package/dist/detectors/acronym-context.d.ts.map +1 -1
- package/dist/detectors/acronym-context.js +13 -0
- package/dist/detectors/acronym-context.js.map +1 -1
- package/dist/detectors/acronym-expansion.d.ts +2 -0
- package/dist/detectors/acronym-expansion.d.ts.map +1 -1
- package/dist/detectors/acronym-expansion.js +104 -1
- package/dist/detectors/acronym-expansion.js.map +1 -1
- package/dist/detectors/agreement-slip.d.ts +32 -0
- package/dist/detectors/agreement-slip.d.ts.map +1 -0
- package/dist/detectors/agreement-slip.js +137 -0
- package/dist/detectors/agreement-slip.js.map +1 -0
- package/dist/detectors/char-shape.d.ts.map +1 -1
- package/dist/detectors/char-shape.js +16 -6
- package/dist/detectors/char-shape.js.map +1 -1
- package/dist/detectors/content-morphemes.d.ts +4 -0
- package/dist/detectors/content-morphemes.d.ts.map +1 -0
- package/dist/detectors/content-morphemes.js +12 -0
- package/dist/detectors/content-morphemes.js.map +1 -0
- package/dist/detectors/en-shape.d.ts +0 -1
- package/dist/detectors/en-shape.d.ts.map +1 -1
- package/dist/detectors/en-shape.js +2 -143
- 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.js +1 -1
- package/dist/detectors/gram-place.js.map +1 -1
- package/dist/detectors/heading-case.d.ts.map +1 -1
- package/dist/detectors/heading-case.js +3 -1
- package/dist/detectors/heading-case.js.map +1 -1
- package/dist/detectors/heading-echo.d.ts.map +1 -1
- package/dist/detectors/heading-echo.js +12 -18
- package/dist/detectors/heading-echo.js.map +1 -1
- package/dist/detectors/heading-overlap.d.ts +13 -0
- package/dist/detectors/heading-overlap.d.ts.map +1 -0
- package/dist/detectors/heading-overlap.js +52 -0
- package/dist/detectors/heading-overlap.js.map +1 -0
- package/dist/detectors/index.d.ts.map +1 -1
- package/dist/detectors/index.js +10 -4
- package/dist/detectors/index.js.map +1 -1
- package/dist/detectors/letter-joined-name.d.ts +13 -0
- package/dist/detectors/letter-joined-name.d.ts.map +1 -0
- package/dist/detectors/letter-joined-name.js +11 -0
- package/dist/detectors/letter-joined-name.js.map +1 -0
- 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/orthography.d.ts.map +1 -1
- package/dist/detectors/orthography.js +24 -6
- package/dist/detectors/orthography.js.map +1 -1
- package/dist/detectors/oxford-comma.d.ts +3 -0
- package/dist/detectors/oxford-comma.d.ts.map +1 -0
- package/dist/detectors/oxford-comma.js +340 -0
- package/dist/detectors/oxford-comma.js.map +1 -0
- 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/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/signals.d.ts.map +1 -1
- package/dist/detectors/signals.js +45 -16
- package/dist/detectors/signals.js.map +1 -1
- package/dist/detectors/stacked-hedge.d.ts +16 -0
- package/dist/detectors/stacked-hedge.d.ts.map +1 -0
- package/dist/detectors/stacked-hedge.js +67 -0
- package/dist/detectors/stacked-hedge.js.map +1 -0
- package/dist/detectors/stray-space.d.ts +7 -0
- package/dist/detectors/stray-space.d.ts.map +1 -0
- package/dist/detectors/stray-space.js +34 -0
- package/dist/detectors/stray-space.js.map +1 -0
- package/dist/detectors/structure.d.ts +3 -1
- package/dist/detectors/structure.d.ts.map +1 -1
- package/dist/detectors/structure.js +4 -2
- package/dist/detectors/structure.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-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 +8 -6
- 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 +33 -6
- 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.js +1 -1
- package/dist/genre-check.js.map +1 -1
- 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 +44 -0
- package/dist/genre-parse.d.ts.map +1 -0
- package/dist/genre-parse.js +117 -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 +40 -0
- package/dist/genre-suggest.js.map +1 -0
- package/dist/genre.d.ts +1 -1
- package/dist/genre.d.ts.map +1 -1
- package/dist/genre.js +3 -13
- 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.map +1 -1
- package/dist/init.js +16 -5
- package/dist/init.js.map +1 -1
- 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-paragraphs.d.ts +4 -0
- package/dist/line-paragraphs.d.ts.map +1 -0
- package/dist/line-paragraphs.js +71 -0
- package/dist/line-paragraphs.js.map +1 -0
- package/dist/mask.d.ts.map +1 -1
- package/dist/mask.js +2 -10
- package/dist/mask.js.map +1 -1
- package/dist/number-name.js +1 -1
- package/dist/number-name.js.map +1 -1
- package/dist/opening-line.d.ts +3 -0
- package/dist/opening-line.d.ts.map +1 -0
- package/dist/opening-line.js +14 -0
- package/dist/opening-line.js.map +1 -0
- package/dist/orthography.d.ts +5 -1
- package/dist/orthography.d.ts.map +1 -1
- package/dist/orthography.js +7 -1
- package/dist/orthography.js.map +1 -1
- package/dist/paragraph-length.d.ts +7 -0
- package/dist/paragraph-length.d.ts.map +1 -0
- package/dist/paragraph-length.js +10 -0
- package/dist/paragraph-length.js.map +1 -0
- package/dist/plugin.d.ts +12 -0
- 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 +13 -3
- package/dist/quoted-span.d.ts.map +1 -1
- package/dist/quoted-span.js +16 -11
- package/dist/quoted-span.js.map +1 -1
- 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.map +1 -1
- package/dist/render/eval.js +4 -3
- 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/rule-load.d.ts.map +1 -1
- package/dist/rule-load.js +12 -1
- package/dist/rule-load.js.map +1 -1
- package/dist/run-semantic.d.ts +3 -1
- package/dist/run-semantic.d.ts.map +1 -1
- package/dist/run-semantic.js +9 -4
- package/dist/run-semantic.js.map +1 -1
- package/dist/run.d.ts +4 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +29 -16
- package/dist/run.js.map +1 -1
- package/dist/semantic.d.ts +1 -1
- package/dist/semantic.d.ts.map +1 -1
- package/dist/semantic.js +10 -3
- package/dist/semantic.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/span-merge.d.ts +4 -0
- package/dist/span-merge.d.ts.map +1 -0
- package/dist/span-merge.js +12 -0
- package/dist/span-merge.js.map +1 -0
- package/dist/speaker-labels.d.ts +4 -0
- package/dist/speaker-labels.d.ts.map +1 -0
- package/dist/speaker-labels.js +106 -0
- package/dist/speaker-labels.js.map +1 -0
- package/dist/stray-space.d.ts +17 -0
- package/dist/stray-space.d.ts.map +1 -0
- package/dist/stray-space.js +66 -0
- package/dist/stray-space.js.map +1 -0
- package/dist/structure/build.js +5 -5
- 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/issues.d.ts.map +1 -1
- package/dist/structure/issues.js +5 -5
- package/dist/structure/issues.js.map +1 -1
- 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/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/relative-resolve.d.ts.map +1 -1
- package/dist/structure/relative-resolve.js +26 -20
- package/dist/structure/relative-resolve.js.map +1 -1
- package/dist/structure/sexp.js +1 -1
- package/dist/structure/sexp.js.map +1 -1
- package/dist/structure/unnumbered.js +2 -2
- package/dist/structure/unnumbered.js.map +1 -1
- package/dist/team-names.d.ts +9 -0
- package/dist/team-names.d.ts.map +1 -0
- package/dist/team-names.js +31 -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 +395 -0
- package/package.json +4 -3
- package/rules/adverb-overuse.yaml +2 -2
- package/rules/agentless-passive.yaml +1 -1
- package/rules/agreement-slip.yaml +36 -0
- 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 +1 -1
- package/rules/date-order.yaml +1 -1
- 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 +3 -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 +2 -1
- package/rules/hiragana-fukushi.yaml +1 -1
- package/rules/internal-jargon.yaml +2 -2
- package/rules/latin-spacing.yaml +5 -4
- package/rules/max-kanji-continuous.yaml +4 -3
- package/rules/max-paragraph-length.yaml +18 -4
- package/rules/max-sentence-length.yaml +18 -2
- package/rules/ngram-repetition.yaml +4 -4
- package/rules/no-doubled-joshi.yaml +2 -2
- 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/stray-space.yaml +38 -0
- package/rules/taigen-dome-in-prose.yaml +2 -2
- package/rules/title-case-consistency.yaml +1 -1
- package/rules/total-mismatch.yaml +1 -1
- package/rules/undefined-acronym.yaml +5 -4
- package/rules/unqualified-superlative.yaml +2 -2
- package/skills/chaff/SKILL.md +28 -9
- package/src/address-chain.ts +1 -1
- 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 +84 -0
- package/src/cli-args.ts +2 -2
- package/src/cli-text.ts +29 -15
- package/src/cli.ts +42 -41
- package/src/closing-numbers.ts +72 -0
- package/src/commands/cite.ts +4 -4
- package/src/commands/eval.ts +1 -1
- package/src/commands/init-ask.ts +53 -0
- package/src/commands/test.ts +118 -56
- package/src/commands/tree.ts +7 -6
- 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-context.ts +16 -0
- package/src/detectors/acronym-expansion.ts +132 -1
- package/src/detectors/agreement-slip.ts +191 -0
- package/src/detectors/char-shape.ts +21 -9
- package/src/detectors/content-morphemes.ts +19 -0
- package/src/detectors/en-shape.ts +2 -173
- package/src/detectors/enumerated-runs.ts +3 -2
- package/src/detectors/gram-place.ts +1 -1
- package/src/detectors/heading-case.ts +4 -1
- package/src/detectors/heading-echo.ts +12 -20
- package/src/detectors/heading-overlap.ts +65 -0
- package/src/detectors/index.ts +10 -4
- package/src/detectors/letter-joined-name.ts +15 -0
- package/src/detectors/lettered-index.ts +78 -0
- package/src/detectors/lexicon.ts +69 -8
- package/src/detectors/orthography.ts +31 -7
- package/src/detectors/oxford-comma.ts +402 -0
- package/src/detectors/phrase-match.ts +23 -12
- package/src/detectors/repeated-head.ts +3 -4
- package/src/detectors/signals.ts +47 -15
- package/src/detectors/stacked-hedge.ts +96 -0
- package/src/detectors/stray-space.ts +40 -0
- package/src/detectors/structure.ts +10 -2
- package/src/detectors/superlative-amount.ts +24 -0
- package/src/detectors/superlative-clause.ts +82 -0
- package/src/detectors/superlative-scope.ts +3 -3
- package/src/detectors/team.ts +18 -2
- package/src/detectors/token-shape.ts +8 -10
- package/src/document.ts +54 -7
- 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 +1 -1
- package/src/genre-load.ts +24 -0
- package/src/genre-parse.ts +151 -0
- package/src/genre-suggest.ts +50 -0
- package/src/genre.ts +4 -13
- package/src/init-choice.ts +47 -0
- package/src/init.ts +20 -6
- package/src/judge.ts +3 -1
- package/src/line-paragraphs.ts +86 -0
- package/src/mask.ts +2 -12
- package/src/number-name.ts +1 -1
- package/src/opening-line.ts +17 -0
- package/src/orthography.ts +10 -2
- package/src/paragraph-length.ts +9 -0
- package/src/plugin.ts +12 -0
- 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 +26 -12
- package/src/reasons.ts +5 -1
- package/src/render/compact.ts +1 -3
- package/src/render/eval.ts +4 -3
- 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/rule-load.ts +14 -1
- package/src/run-semantic.ts +29 -21
- package/src/run.ts +33 -16
- package/src/semantic.ts +11 -3
- package/src/sentence-shape.ts +3 -7
- package/src/setting-problems.ts +24 -0
- package/src/span-merge.ts +13 -0
- package/src/speaker-labels.ts +129 -0
- package/src/stray-space.ts +81 -0
- package/src/structure/build.ts +5 -5
- package/src/structure/cite.ts +1 -1
- package/src/structure/issues.ts +6 -5
- package/src/structure/markdown-path.ts +2 -2
- package/src/structure/numbering.ts +1 -1
- package/src/structure/relative-find.ts +1 -1
- package/src/structure/relative-resolve.ts +34 -19
- package/src/structure/sexp.ts +1 -1
- package/src/structure/unnumbered.ts +2 -2
- package/src/team-names.ts +39 -0
- package/src/ui.ts +7 -0
- package/src/version.ts +1 -1
- package/src/watch.ts +5 -5
package/src/cli.ts
CHANGED
|
@@ -16,14 +16,17 @@ import { clock, describeChange, snapshotOf, watchPaths, type Snapshot } from "./
|
|
|
16
16
|
import { runEval } from "./commands/eval.ts";
|
|
17
17
|
import { runTest } from "./commands/test.ts";
|
|
18
18
|
import { GENRES } from "./genre.ts";
|
|
19
|
-
import { unknownGenres, writtenGenres } from "./genre-check.ts";
|
|
20
19
|
import { resolveGenre } from "./resolve-genre.ts";
|
|
21
20
|
import { runInit } from "./init.ts";
|
|
21
|
+
import { initGenre } from "./commands/init-ask.ts";
|
|
22
22
|
import { targetsOf } from "./cli-args.ts";
|
|
23
23
|
import { loadRules } from "./rule-load.ts";
|
|
24
24
|
import { renderCompact } from "./render/compact.ts";
|
|
25
25
|
import { renderExplain } from "./render/explain.ts";
|
|
26
26
|
import { renderFriendly } from "./render/friendly.ts";
|
|
27
|
+
import { renderGenres } from "./render/genres.ts";
|
|
28
|
+
import { loadGenres, presetLevels } from "./genre-load.ts";
|
|
29
|
+
import { fileHeader } from "./file-header.ts";
|
|
27
30
|
import { rulesJson } from "./render/rules-json.ts";
|
|
28
31
|
import { renderSarif } from "./render/sarif.ts";
|
|
29
32
|
import { VERSION, VERSION_LINES } from "./version.ts";
|
|
@@ -33,26 +36,20 @@ import { runSkill } from "./commands/skill.ts";
|
|
|
33
36
|
import { runFeedback, settingsOf } from "./commands/feedback.ts";
|
|
34
37
|
import { homedir } from "node:os";
|
|
35
38
|
import { ruleProblems } from "./config/rule-problems.ts";
|
|
39
|
+
import { nameProblems } from "./config/name-problems.ts";
|
|
36
40
|
import { renderSummary, type FileOutcome } from "./render/summary.ts";
|
|
37
41
|
import { neededBy, runRules } from "./run.ts";
|
|
38
42
|
import type { Level, RuleDefinition } from "./plugin.ts";
|
|
39
43
|
import { CLI_TEXT, type CliText, type GenreSource } from "./cli-text.ts";
|
|
40
|
-
import { hostLanguage, uiLanguageOf, type UiLanguage } from "./ui.ts";
|
|
44
|
+
import { hostLanguage, sharedLanguage, uiLanguageOf, type UiLanguage } from "./ui.ts";
|
|
41
45
|
import { profileFor } from "./profile/for-file.ts";
|
|
46
|
+
import { settingProblems } from "./setting-problems.ts";
|
|
42
47
|
|
|
43
48
|
/** Text for output that is not about one document. */
|
|
44
49
|
const hostText = (config: Config): CliText => CLI_TEXT[hostLanguage(config.language, process.env)];
|
|
45
50
|
|
|
46
51
|
const readConfig = (): Config => (existsSync(join(process.cwd(), CONFIG_FILE)) ? loadConfig(join(process.cwd(), CONFIG_FILE)) : EMPTY);
|
|
47
52
|
|
|
48
|
-
const genreProblems = (command: string, argv: readonly string[]): string[] => {
|
|
49
|
-
const config = readConfig();
|
|
50
|
-
const text = hostText(config);
|
|
51
|
-
return unknownGenres(writtenGenres(command, flag(argv, "--genre"), config), GENRES).map((entry) =>
|
|
52
|
-
text.unknownGenre(entry.genre, text.genreWhere(entry.where, entry.files), GENRES),
|
|
53
|
-
);
|
|
54
|
-
};
|
|
55
|
-
|
|
56
53
|
/** resolveGenre with this run's --genre, for the commands that take it as a dependency. */
|
|
57
54
|
const genreFrom =
|
|
58
55
|
(argv: readonly string[]) =>
|
|
@@ -90,12 +87,6 @@ type Inspected = {
|
|
|
90
87
|
readonly all: readonly string[];
|
|
91
88
|
};
|
|
92
89
|
|
|
93
|
-
/** The first line for a file, in the file's language. */
|
|
94
|
-
const headerFor = (path: string, genre: string, from: GenreSource, language: string, shelved: number, hushed: number): string => {
|
|
95
|
-
const text = CLI_TEXT[uiLanguageOf(language)];
|
|
96
|
-
return text.header(path, genre, text.languageName(language), text.genreSource[from], shelved, hushed);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
90
|
const inspect = async (path: string, config: Config, argv: readonly string[]): Promise<Inspected> => {
|
|
100
91
|
const source = plainSource(await readFile(path, "utf8"));
|
|
101
92
|
const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
|
|
@@ -105,7 +96,7 @@ const inspect = async (path: string, config: Config, argv: readonly string[]): P
|
|
|
105
96
|
const rules = loadRules(language);
|
|
106
97
|
const experimental = config.experimental || argv.includes("--experimental");
|
|
107
98
|
await adapter.prepare?.(neededBy(rules, config.rules, experimental, genre, language));
|
|
108
|
-
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language));
|
|
99
|
+
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language, genre));
|
|
109
100
|
const raw = runRules(doc, rules, config.rules, experimental, genre, config.limits);
|
|
110
101
|
// 応答は 3 つ。stet で黙らせたものは、ここで落とす。
|
|
111
102
|
const applied = applySuppressions(
|
|
@@ -116,8 +107,8 @@ const inspect = async (path: string, config: Config, argv: readonly string[]): P
|
|
|
116
107
|
const baseline = argv.includes("--show-baseline") ? undefined : readBaseline(join(process.cwd(), BASELINE_FILE));
|
|
117
108
|
const split = splitByBaseline(path, applied.kept, baseline);
|
|
118
109
|
const result = { ...raw, findings: split.fresh };
|
|
119
|
-
const header =
|
|
120
|
-
const text = argv.includes("--compact") ? renderCompact(header, result, rules, language) : renderFriendly(header, result, rules, language);
|
|
110
|
+
const { header, notes } = fileHeader(path, source, language, { genre, from, unread }, { shelved: split.shelved, hushed: applied.suppressed.length });
|
|
111
|
+
const text = argv.includes("--compact") ? renderCompact(header, result, rules, language) : renderFriendly(header, result, rules, language, notes);
|
|
121
112
|
return {
|
|
122
113
|
text,
|
|
123
114
|
rules,
|
|
@@ -147,14 +138,14 @@ const writeSarif = (results: readonly Inspected[], argv: readonly string[]): voi
|
|
|
147
138
|
|
|
148
139
|
/** 効いていない設定は、結果の前に一度だけ言う。標準エラーに出すので、JSON や SARIF の出力は汚さない。 */
|
|
149
140
|
const warnRuleProblems = (config: Config, language: string): void => {
|
|
150
|
-
|
|
141
|
+
const ui = hostLanguage(config.language, process.env);
|
|
142
|
+
[...ruleProblems(config, loadRules(language), ui), ...nameProblems(config, ui)].forEach((problem) => console.error(`chaff: ${problem}`));
|
|
151
143
|
};
|
|
152
144
|
|
|
153
145
|
/** Several files end with one summary: in their language when they share one, else the host's. */
|
|
154
146
|
const summaryLanguage = (results: readonly Inspected[], config: Config): UiLanguage => {
|
|
155
|
-
const languages =
|
|
156
|
-
|
|
157
|
-
return languages.size === 1 && only !== undefined ? only : hostLanguage(config.language, process.env);
|
|
147
|
+
const languages = results.map((result) => result.language);
|
|
148
|
+
return sharedLanguage(languages, hostLanguage(config.language, process.env));
|
|
158
149
|
};
|
|
159
150
|
|
|
160
151
|
const lint = async (targets: readonly string[], argv: readonly string[]): Promise<number> => {
|
|
@@ -216,8 +207,10 @@ const runWatch = async (targets: readonly string[], argv: readonly string[]): Pr
|
|
|
216
207
|
return new Promise(() => undefined);
|
|
217
208
|
};
|
|
218
209
|
|
|
219
|
-
|
|
210
|
+
/** genreFlag: --genre, which wins over chaff.yaml's genre here as it does in a run. */
|
|
211
|
+
const explain = (ruleId: string | undefined, genreFlag: string | undefined): number => {
|
|
220
212
|
const config = readConfig();
|
|
213
|
+
const genre = genreFlag ?? config.genre;
|
|
221
214
|
// The rule's limits differ by language (characters for Japanese, words for English): explain in the one being written.
|
|
222
215
|
const language = config.language ?? hostLanguage(undefined, process.env);
|
|
223
216
|
const text = CLI_TEXT[uiLanguageOf(language)];
|
|
@@ -228,8 +221,9 @@ const explain = (ruleId: string | undefined): number => {
|
|
|
228
221
|
console.error(text.unknownRuleWithList(ruleId ?? text.unnamed, list));
|
|
229
222
|
return 1;
|
|
230
223
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
224
|
+
const preset = genre === undefined ? {} : presetLevels(genre);
|
|
225
|
+
const current = config.rules[rule.id] ?? preset[rule.id] ?? (rule.status === "experimental" && !config.experimental ? "off" : "normal");
|
|
226
|
+
console.log(renderExplain(rule, current, language, text.unit(rule.id, language), genre));
|
|
233
227
|
return 0;
|
|
234
228
|
};
|
|
235
229
|
|
|
@@ -266,16 +260,16 @@ type Handler = (argv: readonly string[]) => number | Promise<number>;
|
|
|
266
260
|
/** `--` で始まらない引数。対象のパス。 */
|
|
267
261
|
const positional = (argv: readonly string[]): string[] => targetsOf(argv.slice(1));
|
|
268
262
|
|
|
269
|
-
const showRules = (): number => {
|
|
263
|
+
const showRules = (genreFlag: string | undefined): number => {
|
|
270
264
|
const config = readConfig();
|
|
271
265
|
const language = config.language ?? hostLanguage(undefined, process.env);
|
|
272
266
|
warnRuleProblems(config, language);
|
|
273
|
-
console.log(rulesJson(loadRules(language), config, language, config.genre ?? "blog/tech"));
|
|
267
|
+
console.log(rulesJson(loadRules(language), config, language, genreFlag ?? config.genre ?? "blog/tech"));
|
|
274
268
|
return 0;
|
|
275
269
|
};
|
|
276
270
|
|
|
277
271
|
const showGenres = (): number => {
|
|
278
|
-
console.log(
|
|
272
|
+
console.log(renderGenres(loadGenres(), hostLanguage(readConfig().language, process.env)));
|
|
279
273
|
return 0;
|
|
280
274
|
};
|
|
281
275
|
|
|
@@ -284,22 +278,28 @@ const treeContext = (): TreeContext => {
|
|
|
284
278
|
return { config, flag, ui: hostLanguage(config.language, process.env) };
|
|
285
279
|
};
|
|
286
280
|
|
|
281
|
+
/** What eval and test share: the settings, this run's genre, and the language for what is not about one document. */
|
|
282
|
+
const measureContext = (argv: readonly string[]): { config: Config; resolveGenre: ReturnType<typeof genreFrom>; ui: UiLanguage } => {
|
|
283
|
+
const config = readConfig();
|
|
284
|
+
return { config, resolveGenre: genreFrom(argv), ui: hostLanguage(config.language, process.env) };
|
|
285
|
+
};
|
|
286
|
+
|
|
287
287
|
/** 分岐を数珠つなぎにせず表にする。足すときに main を太らせない。 */
|
|
288
288
|
const HANDLERS: Readonly<Record<string, Handler>> = {
|
|
289
|
-
init: (argv) => {
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
init: async (argv) => {
|
|
290
|
+
const ui = hostLanguage(readConfig().language, process.env);
|
|
291
|
+
const chosen = await initGenre(flag(argv, "--genre"), ui, process.cwd());
|
|
292
|
+
if ("error" in chosen) console.error(chosen.error);
|
|
293
|
+
else runInit(process.cwd(), chosen.genre, ui).forEach((line) => console.log(line));
|
|
294
|
+
return "error" in chosen ? 1 : 0;
|
|
292
295
|
},
|
|
293
296
|
genres: showGenres,
|
|
294
|
-
rules: showRules,
|
|
295
|
-
explain: (argv) => explain(argv[1]),
|
|
296
|
-
eval: (argv) => {
|
|
297
|
-
const config = readConfig();
|
|
298
|
-
return runEval(positional(argv), argv, { config, resolveGenre: genreFrom(argv), flag, ui: hostLanguage(config.language, process.env) });
|
|
299
|
-
},
|
|
297
|
+
rules: (argv) => showRules(flag(argv, "--genre")),
|
|
298
|
+
explain: (argv) => explain(argv[1], flag(argv, "--genre")),
|
|
299
|
+
eval: (argv) => runEval(positional(argv), argv, { ...measureContext(argv), flag }),
|
|
300
300
|
tree: (argv) => runTree(treeTargets(argv), argv, treeContext()),
|
|
301
301
|
cite: (argv) => runCite(citeTargets(argv), argv, treeContext()),
|
|
302
|
-
test: (argv) => runTest(positional(argv), argv, {
|
|
302
|
+
test: (argv) => runTest(positional(argv), argv, { ...measureContext(argv), inspect }),
|
|
303
303
|
baseline: (argv) => runBaseline(positional(argv), argv),
|
|
304
304
|
suppressions: (argv) => runSuppressions(positional(argv), argv),
|
|
305
305
|
relax: (argv) => changeSetting("relaxed", argv[1], flag(argv, "--why")),
|
|
@@ -336,8 +336,9 @@ export const main = async (argv: readonly string[]): Promise<number> => {
|
|
|
336
336
|
console.log(VERSION_LINES.join("\n"));
|
|
337
337
|
return 0;
|
|
338
338
|
}
|
|
339
|
-
//
|
|
340
|
-
const
|
|
339
|
+
// 知らないジャンルではどの rule も当たらず、知らない文書の種類では種類の知識が外れる。どちらも素通りに見えるので、何かする前に止める。
|
|
340
|
+
const config = readConfig();
|
|
341
|
+
const problems = settingProblems(first, flag(argv, "--genre"), config, hostText(config));
|
|
341
342
|
problems.forEach((problem) => console.error(problem));
|
|
342
343
|
if (problems.length > 0) return 1;
|
|
343
344
|
const handler = HANDLERS[first];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { calendarRuns, digitRuns, type CalendarUnits } from "./calendar-number.ts";
|
|
2
|
+
import type { ProseDocument, Section, Sentence, Span } from "./plugin.ts";
|
|
3
|
+
|
|
4
|
+
/** 文書の一続きの文字列と、その中の文。base は text の先頭の位置で、文の span は文書全体の座標。 */
|
|
5
|
+
export type Passage = { readonly text: string; readonly base: number; readonly sentences: readonly Sentence[] };
|
|
6
|
+
|
|
7
|
+
/** 結びの節と、それより前の文書全体(見出し・表・コードも)。 */
|
|
8
|
+
export const closingAndEarlier = (doc: ProseDocument, last: Section): { closing: Passage; earlier: Passage } => ({
|
|
9
|
+
closing: { text: doc.source.slice(last.span.start, last.span.end), base: last.span.start, sentences: last.sentences },
|
|
10
|
+
earlier: { text: doc.source.slice(0, last.span.start), base: 0, sentences: doc.sentences.filter((sentence) => sentence.span.end <= last.span.start) },
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const DIGIT = /\d/u;
|
|
14
|
+
|
|
15
|
+
/** 最後の数字まで。文末の「412.」の点や、範囲の「3-」のハイフンは数の一部ではない。前のハイフンは符号(-1%)なので残す。 */
|
|
16
|
+
const valueOf = (text: string, run: Span): string => {
|
|
17
|
+
const raw = text.slice(run.start, run.end);
|
|
18
|
+
const lastDigit = [...raw].findLastIndex((char) => DIGIT.test(char));
|
|
19
|
+
return raw.slice(0, lastDigit + 1);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** passage の中で、品詞の付いた文の字。見出しや表の字は文ではないので付かない。 */
|
|
23
|
+
const taggedChars = (passage: Passage): Uint8Array => {
|
|
24
|
+
const tagged = new Uint8Array(passage.text.length);
|
|
25
|
+
passage.sentences
|
|
26
|
+
.filter((sentence) => sentence.tokens !== undefined)
|
|
27
|
+
.forEach((sentence) => tagged.fill(1, Math.max(0, sentence.span.start - passage.base), Math.max(0, sentence.span.end - passage.base)));
|
|
28
|
+
return tagged;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** 文の中の日付・時刻の数の単位を、passage の中の位置で。文ごとに読むので、長い文書でも語を探す範囲は文の中だけ。 */
|
|
32
|
+
const calendarUnitsAt = (passage: Passage, units: CalendarUnits): ReadonlyMap<number, string> =>
|
|
33
|
+
new Map(
|
|
34
|
+
passage.sentences.flatMap((sentence) =>
|
|
35
|
+
calendarRuns(sentence.text, sentence.tokens, sentence.span.start, units).map(({ run, unit }): [number, string] => [
|
|
36
|
+
sentence.span.start - passage.base + run.start,
|
|
37
|
+
unit,
|
|
38
|
+
]),
|
|
39
|
+
),
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
/** 品詞の無いところの数の、すぐ後ろ(空白 1 つまで)に字面で続く日付・時刻の単位のうち、いちばん長いもの。無ければ空。 */
|
|
43
|
+
const unitAsWritten = (text: string, run: Span, units: CalendarUnits): string => {
|
|
44
|
+
const at = text[run.end] === " " ? run.end + 1 : run.end;
|
|
45
|
+
const following = [...units.chained, ...units.positional, ...units.year].filter((unit) => text.startsWith(unit, at));
|
|
46
|
+
return following.reduce((longest, unit) => (unit.length > longest.length ? unit : longest), "");
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* passage の中の数の、比べるときの名前を左から。日付・時刻の数は単位ごと(9月)、それ以外は数そのもの(412)。
|
|
51
|
+
* 文の中は品詞で見分ける(calendar-number.ts)。見出しや表は品詞が無いので、数の後ろの字で見分ける(題の「9月の報告」)。
|
|
52
|
+
*/
|
|
53
|
+
const numberKeys = (passage: Passage, units: CalendarUnits): string[] => {
|
|
54
|
+
const unitAt = calendarUnitsAt(passage, units);
|
|
55
|
+
const tagged = taggedChars(passage);
|
|
56
|
+
return digitRuns(passage.text).map((run) => {
|
|
57
|
+
const value = valueOf(passage.text, run);
|
|
58
|
+
const firstDigit = run.start + passage.text.slice(run.start, run.end).search(DIGIT);
|
|
59
|
+
const unit = tagged[firstDigit] === 1 ? (unitAt.get(run.start) ?? "") : unitAsWritten(passage.text, run, units);
|
|
60
|
+
return `${value}${unit}`;
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 結びの数のうち、それより前(見出し・表も)に書いていないもの(重ならないように、書いた順)。
|
|
66
|
+
* 前に同じ名前で書いた数(題の「9月」と結びの「9月」、本文の「412 件」と結びの「412 件」)は新しくない。
|
|
67
|
+
* 日付・時刻と数量は別のもので、本文の「9 件」があっても結びの「9月末」は新しく、題の「9月」があっても結びの「9 件」は新しい。
|
|
68
|
+
*/
|
|
69
|
+
export const newNumbers = (closing: Passage, earlier: Passage, units: CalendarUnits): string[] => {
|
|
70
|
+
const stated = new Set(numberKeys(earlier, units));
|
|
71
|
+
return [...new Set(numberKeys(closing, units).filter((key) => !stated.has(key)))];
|
|
72
|
+
};
|
package/src/commands/cite.ts
CHANGED
|
@@ -2,12 +2,12 @@ import { checkCitations, type Citation, type CitationResult } from "../structure
|
|
|
2
2
|
import { readSource, readTree, type TreeContext } from "./tree.ts";
|
|
3
3
|
import type { Texts, UiLanguage } from "../ui.ts";
|
|
4
4
|
|
|
5
|
-
const FORMATS = ["text", "json"];
|
|
5
|
+
const FORMATS: ReadonlySet<string> = new Set(["text", "json"]);
|
|
6
6
|
const QUOTE_WIDTH = 40;
|
|
7
|
-
const VALUED = ["--format", "--language"];
|
|
7
|
+
const VALUED: ReadonlySet<string> = new Set(["--format", "--language", "--genre"]);
|
|
8
8
|
|
|
9
9
|
export const citeTargets = (argv: readonly string[]): string[] =>
|
|
10
|
-
argv.slice(1).filter((arg, index, all) => !arg.startsWith("--") && !VALUED.
|
|
10
|
+
argv.slice(1).filter((arg, index, all) => !arg.startsWith("--") && !VALUED.has(all[index - 1] ?? ""));
|
|
11
11
|
|
|
12
12
|
const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
13
|
|
|
@@ -88,7 +88,7 @@ const describe = (result: CitationResult, text: CiteText): string => {
|
|
|
88
88
|
export const runCite = async (targets: readonly string[], argv: readonly string[], context: TreeContext): Promise<number> => {
|
|
89
89
|
const [sourcePath, citationsPath] = targets;
|
|
90
90
|
const format = context.flag(argv, "--format") ?? "text";
|
|
91
|
-
if (sourcePath === undefined || citationsPath === undefined || targets.length !== 2 || !FORMATS.
|
|
91
|
+
if (sourcePath === undefined || citationsPath === undefined || targets.length !== 2 || !FORMATS.has(format)) {
|
|
92
92
|
console.error(TEXT[context.ui ?? "ja"].usage);
|
|
93
93
|
return 1;
|
|
94
94
|
}
|
package/src/commands/eval.ts
CHANGED
|
@@ -56,7 +56,7 @@ export const runEval = async (targets: readonly string[], argv: readonly string[
|
|
|
56
56
|
const adapter = await loadAdapter(language);
|
|
57
57
|
const { genre } = resolveGenre(path, source, config);
|
|
58
58
|
await adapter.prepare?.(neededBy(loadRules(language), config.rules, config.experimental, genre, language));
|
|
59
|
-
return { doc: buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language)), language, genre };
|
|
59
|
+
return { doc: buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language, genre)), language, genre };
|
|
60
60
|
}),
|
|
61
61
|
);
|
|
62
62
|
const language = docs[0]?.language ?? "ja";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createInterface, type Interface } from "node:readline";
|
|
4
|
+
import { CONFIG_FILE } from "../config/load.ts";
|
|
5
|
+
import { loadGenres } from "../genre-load.ts";
|
|
6
|
+
import { choicePrompt, DEFAULT_GENRE, genreChoices, genreFromAnswer, noAnswer, notAGenre } from "../init-choice.ts";
|
|
7
|
+
import type { UiLanguage } from "../ui.ts";
|
|
8
|
+
|
|
9
|
+
type Terminal = { readonly isTTY?: boolean | undefined };
|
|
10
|
+
|
|
11
|
+
/** Where init asks: the terminal, or streams a test gives it. */
|
|
12
|
+
export type Prompter = { readonly input: NodeJS.ReadableStream & Terminal; readonly output: NodeJS.WritableStream & Terminal };
|
|
13
|
+
|
|
14
|
+
/** Ask only at a terminal, and only when init will write chaff.yaml: a script or CI gets the default without waiting. */
|
|
15
|
+
export const shouldAsk = (stdin: Terminal, stdout: Terminal, configExists: boolean): boolean => stdin.isTTY === true && stdout.isTTY === true && !configExists;
|
|
16
|
+
|
|
17
|
+
/** The first line typed, or undefined when the input ends with none (Ctrl-D): waiting for a question's answer would never end then. */
|
|
18
|
+
const answerOf = (reader: Interface, prompt: string): Promise<string | undefined> => {
|
|
19
|
+
const answer = new Promise<string | undefined>((resolve) => {
|
|
20
|
+
reader.once("line", resolve);
|
|
21
|
+
reader.once("close", () => resolve(undefined));
|
|
22
|
+
});
|
|
23
|
+
reader.setPrompt(prompt);
|
|
24
|
+
reader.prompt();
|
|
25
|
+
return answer;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const ask = async (ui: UiLanguage, io: Prompter): Promise<{ readonly genre: string | undefined; readonly answer: string | undefined }> => {
|
|
29
|
+
const data = loadGenres();
|
|
30
|
+
io.output.write(`${genreChoices(data, ui).join("\n")}\n`);
|
|
31
|
+
const reader = createInterface({ input: io.input, output: io.output });
|
|
32
|
+
try {
|
|
33
|
+
const answer = await answerOf(reader, choicePrompt(ui));
|
|
34
|
+
const ids = data.genres.map((genre) => genre.id);
|
|
35
|
+
return { genre: answer === undefined ? undefined : genreFromAnswer(answer, ids), answer };
|
|
36
|
+
} finally {
|
|
37
|
+
reader.close();
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** The genre init writes: --genre, else the one picked at the prompt, else the default. An answer that is no genre is an error. */
|
|
42
|
+
export const initGenre = async (
|
|
43
|
+
flagged: string | undefined,
|
|
44
|
+
ui: UiLanguage,
|
|
45
|
+
dir: string,
|
|
46
|
+
io: Prompter = { input: process.stdin, output: process.stdout },
|
|
47
|
+
): Promise<{ readonly genre: string } | { readonly error: string }> => {
|
|
48
|
+
if (flagged !== undefined) return { genre: flagged };
|
|
49
|
+
if (!shouldAsk(io.input, io.output, existsSync(join(dir, CONFIG_FILE)))) return { genre: DEFAULT_GENRE };
|
|
50
|
+
const picked = await ask(ui, io);
|
|
51
|
+
if (picked.answer === undefined) return { error: noAnswer(ui) };
|
|
52
|
+
return picked.genre === undefined ? { error: notAGenre(picked.answer.trim(), ui) } : { genre: picked.genre };
|
|
53
|
+
};
|
package/src/commands/test.ts
CHANGED
|
@@ -7,20 +7,94 @@ import { buildDocument, teamRules } from "../document.ts";
|
|
|
7
7
|
import { guessLanguage } from "../detect.ts";
|
|
8
8
|
import { collectTargets } from "../files.ts";
|
|
9
9
|
import { loadRules } from "../rule-load.ts";
|
|
10
|
+
import { counted } from "../render/plural.ts";
|
|
10
11
|
import { CHECKS_FILE, loadChecks, type UserCheck } from "../checks.ts";
|
|
11
|
-
import { CACHE_DIR, credentialHint, describeFailure, hasCredentials } from "../judge.ts";
|
|
12
|
+
import { CACHE_DIR, credentialHint, describeFailure, hasCredentials, type JudgeOptions } from "../judge.ts";
|
|
12
13
|
import { ENV_FILE, loadEnvFile } from "../env.ts";
|
|
13
|
-
import { planSemantic, runSemantic } from "../run-semantic.ts";
|
|
14
|
-
import {
|
|
14
|
+
import { planSemantic, runSemantic, semanticNeeds } from "../run-semantic.ts";
|
|
15
|
+
import { machineBanner, renderPlan, renderSemantic } from "../render/semantic.ts";
|
|
15
16
|
import type { Config } from "../config/load.ts";
|
|
16
|
-
import type { Finding, RuleDefinition } from "../plugin.ts";
|
|
17
|
-
import type { Failure } from "../backends/types.ts";
|
|
17
|
+
import type { Finding, ProseDocument, RuleDefinition } from "../plugin.ts";
|
|
18
|
+
import type { BackendName, Failure } from "../backends/types.ts";
|
|
18
19
|
import { profileFor } from "../profile/for-file.ts";
|
|
20
|
+
import { CLI_TEXT } from "../cli-text.ts";
|
|
21
|
+
import { sharedLanguage, type Texts, type UiLanguage } from "../ui.ts";
|
|
22
|
+
|
|
23
|
+
const TEXT: Texts<{
|
|
24
|
+
readonly total: (total: number) => string;
|
|
25
|
+
readonly sentTo: (backend: string, model: string, found: boolean) => string;
|
|
26
|
+
readonly envRead: (path: string) => string;
|
|
27
|
+
readonly envWhere: (found: string | undefined) => string;
|
|
28
|
+
readonly notRun: (why: string, what: string, where: string) => readonly string[];
|
|
29
|
+
readonly noCredentials: (backend: BackendName) => string;
|
|
30
|
+
readonly refused: (backend: BackendName, status: string) => string;
|
|
31
|
+
readonly checkKey: string;
|
|
32
|
+
readonly quota: (backend: BackendName, status: string) => string;
|
|
33
|
+
readonly returned: (backend: BackendName, status: string) => string;
|
|
34
|
+
readonly noCause: string;
|
|
35
|
+
readonly tally: (machine: number, ai: number) => string;
|
|
36
|
+
readonly untouched: string;
|
|
37
|
+
}> = {
|
|
38
|
+
ja: {
|
|
39
|
+
total: (total) => ` 合計 ${total} 箇所を送ります。--dry-run なので API は呼んでいません。`,
|
|
40
|
+
sentTo: (backend, model, found) => ` 送り先: ${backend} / ${model}(認証${found ? "あり" : "なし"})`,
|
|
41
|
+
envRead: (path) => ` ${path} を読みました。`,
|
|
42
|
+
envWhere: (found) => (found === undefined ? `${ENV_FILE} に書いても読みます(いまは ${ENV_FILE} がありません)` : `${found} は読みました`),
|
|
43
|
+
notRun: (why, what, where) => [` 意味を読む検査は動かしていません。${why}`, ` ${what}`, ` ${where}。`, " 機械による判定はすべて動いています。"],
|
|
44
|
+
noCredentials: (backend) => `${backend} の認証情報がありません。`,
|
|
45
|
+
refused: (backend, status) => `${backend} が鍵を受け付けませんでした(${status})。`,
|
|
46
|
+
checkKey: "鍵が正しいか、その provider のものかを確かめてください。",
|
|
47
|
+
quota: (backend, status) => `${backend} の残高か上限に達しました(${status})。`,
|
|
48
|
+
returned: (backend, status) => `${backend} が ${status} を返しました。`,
|
|
49
|
+
noCause: "原因は返ってきませんでした",
|
|
50
|
+
tally: (machine, ai) => ` 機械 ${machine} 件 / AI ${ai} 件`,
|
|
51
|
+
untouched: " 文章は書き換えていません。直すのは書いた人です。",
|
|
52
|
+
},
|
|
53
|
+
en: {
|
|
54
|
+
total: (total) => ` ${counted(total, "passage")} in all would be sent. With --dry-run the API was not called.`,
|
|
55
|
+
sentTo: (backend, model, found) => ` Sent to: ${backend} / ${model} (${found ? "credentials found" : "no credentials"})`,
|
|
56
|
+
envRead: (path) => ` Read ${path}.`,
|
|
57
|
+
envWhere: (found) => (found === undefined ? `A key written in ${ENV_FILE} is read too (there is no ${ENV_FILE} now)` : `${found} was read`),
|
|
58
|
+
notRun: (why, what, where) => [` The checks that read meaning did not run. ${why}`, ` ${what}`, ` ${where}.`, " Every machine check ran."],
|
|
59
|
+
noCredentials: (backend) => `There are no credentials for ${backend}.`,
|
|
60
|
+
refused: (backend, status) => `${backend} did not accept the key (${status}).`,
|
|
61
|
+
checkKey: "Check that the key is right and that it is for that provider.",
|
|
62
|
+
quota: (backend, status) => `${backend} has run out of balance or hit a limit (${status}).`,
|
|
63
|
+
returned: (backend, status) => `${backend} returned ${status}.`,
|
|
64
|
+
noCause: "No reason was given.",
|
|
65
|
+
tally: (machine, ai) => ` ${machine} by machine / ${ai} by AI`,
|
|
66
|
+
untouched: " The text was not changed. Fixing it is the writer's job.",
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const DIVIDER = "─".repeat(60);
|
|
19
71
|
|
|
20
72
|
export type TestContext = {
|
|
21
73
|
readonly config: Config;
|
|
22
74
|
readonly resolveGenre: (path: string, source: string, config: Config) => { genre: string; from: string };
|
|
23
|
-
readonly inspect: (
|
|
75
|
+
readonly inspect: (
|
|
76
|
+
path: string,
|
|
77
|
+
config: Config,
|
|
78
|
+
argv: readonly string[],
|
|
79
|
+
) => Promise<{ text: string; language: string; outcome: { findings: readonly Finding[] } }>;
|
|
80
|
+
/** The language of what is not about the documents: chaff.yaml's language, else the locale. */
|
|
81
|
+
readonly ui: UiLanguage;
|
|
82
|
+
/** The judges to call instead of the providers' own clients. Tests pass stand-ins; the command line never does. */
|
|
83
|
+
readonly clients?: Pick<JudgeOptions, "anthropicClient" | "openaiClient">;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
type SemanticDocument = { readonly doc: ProseDocument; readonly rules: RuleDefinition[]; readonly language: string; readonly genre: string };
|
|
87
|
+
|
|
88
|
+
/** 意味を読む検査に渡す文書。絞り込みが品詞を使うなら、解析器を読み込んでから作る。dry-run も本番も同じ文書を見る。 */
|
|
89
|
+
const semanticDocument = async (path: string, config: Config, resolveGenre: TestContext["resolveGenre"]): Promise<SemanticDocument> => {
|
|
90
|
+
const source = plainSource(await readFile(path, "utf8"));
|
|
91
|
+
const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
|
|
92
|
+
const adapter = await loadAdapter(language);
|
|
93
|
+
const { genre } = resolveGenre(path, source, config);
|
|
94
|
+
const rules = loadRules(language);
|
|
95
|
+
await adapter.prepare?.(semanticNeeds(rules, config.rules, genre));
|
|
96
|
+
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language, genre));
|
|
97
|
+
return { doc, rules, language, genre };
|
|
24
98
|
};
|
|
25
99
|
|
|
26
100
|
type Judged = { path: string; outcome: Awaited<ReturnType<typeof runSemantic>>; rules: RuleDefinition[]; language: string };
|
|
@@ -34,12 +108,7 @@ const judgeAll = async (
|
|
|
34
108
|
): Promise<Judged[]> =>
|
|
35
109
|
Promise.all(
|
|
36
110
|
paths.map(async (path) => {
|
|
37
|
-
const
|
|
38
|
-
const language = applyByPath(config.byPath, config.baseDir, path).language ?? config.language ?? guessLanguage(source).language;
|
|
39
|
-
const adapter = await loadAdapter(language);
|
|
40
|
-
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language));
|
|
41
|
-
const { genre } = resolveGenre(path, source, config);
|
|
42
|
-
const rules = loadRules(language);
|
|
111
|
+
const { doc, rules, language, genre } = await semanticDocument(path, config, resolveGenre);
|
|
43
112
|
return { path, outcome: await runSemantic(doc, rules, checks, config.rules, genre, options), rules, language };
|
|
44
113
|
}),
|
|
45
114
|
);
|
|
@@ -50,43 +119,47 @@ const dryRun = async (
|
|
|
50
119
|
config: Config,
|
|
51
120
|
checks: readonly UserCheck[],
|
|
52
121
|
resolveGenre: TestContext["resolveGenre"],
|
|
53
|
-
envFile: string | undefined,
|
|
122
|
+
closing: { readonly envFile: string | undefined; readonly language: UiLanguage },
|
|
54
123
|
): Promise<void> => {
|
|
55
124
|
const plans = await Promise.all(
|
|
56
125
|
paths.map(async (path) => {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const adapter = await loadAdapter(language);
|
|
60
|
-
const doc = buildDocument(path, source, adapter, teamRules(config), profileFor(config, path, source, language));
|
|
61
|
-
const { genre } = resolveGenre(path, source, config);
|
|
62
|
-
return { path, jobs: planSemantic(doc, loadRules(language), checks, config.rules, genre), sentences: doc.sentences.length };
|
|
126
|
+
const { doc, rules, language, genre } = await semanticDocument(path, config, resolveGenre);
|
|
127
|
+
return { path, jobs: planSemantic(doc, rules, checks, config.rules, genre), sentences: doc.sentences.length, language };
|
|
63
128
|
}),
|
|
64
129
|
);
|
|
65
|
-
plans.forEach(({ path, jobs, sentences }) => console.log(renderPlan(path, jobs, sentences).join("\n")));
|
|
130
|
+
plans.forEach(({ path, jobs, sentences, language }) => console.log(renderPlan(path, jobs, sentences, language).join("\n")));
|
|
66
131
|
const total = plans.reduce((sum, plan) => sum + plan.jobs.reduce((count, job) => count + job.candidates.length, 0), 0);
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
console.log(
|
|
70
|
-
[
|
|
71
|
-
"",
|
|
72
|
-
"─".repeat(60),
|
|
73
|
-
"",
|
|
74
|
-
` 合計 ${total} 箇所を送ります。--dry-run なので API は呼んでいません。`,
|
|
75
|
-
` 送り先: ${where}`,
|
|
76
|
-
...(from === "" ? [] : [from]),
|
|
77
|
-
"",
|
|
78
|
-
].join("\n"),
|
|
79
|
-
);
|
|
132
|
+
const text = TEXT[closing.language];
|
|
133
|
+
const from = closing.envFile === undefined ? [] : [text.envRead(closing.envFile)];
|
|
134
|
+
console.log(["", DIVIDER, "", text.total(total), text.sentTo(config.aiBackend, config.aiModel, hasCredentials(config.aiBackend)), ...from, ""].join("\n"));
|
|
80
135
|
};
|
|
81
136
|
|
|
137
|
+
/**
|
|
138
|
+
* API が返した失敗を人の言葉にする。生のスタックトレースを人に見せない。
|
|
139
|
+
* 鍵はあるのに弾かれたときに「ありません」と言うと、設定済みの鍵をもう一度設定しに行かせる。
|
|
140
|
+
* 残高切れも同じで、「鍵を確かめてください」と言われても鍵は正しい。
|
|
141
|
+
*/
|
|
142
|
+
const whyFailed = (failure: Failure, backend: BackendName, language: UiLanguage): { why: string; what: string } => {
|
|
143
|
+
const text = TEXT[language];
|
|
144
|
+
const status = String(failure.status ?? "?");
|
|
145
|
+
if (failure.kind === "auth") return { why: text.refused(backend, status), what: text.checkKey };
|
|
146
|
+
const what = failure.message ?? text.noCause;
|
|
147
|
+
return failure.kind === "quota" ? { why: text.quota(backend, status), what } : { why: text.returned(backend, status), what };
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/** Why the checks that read meaning did not run, and where a key would be read from. */
|
|
151
|
+
const notRunNotice = (why: string, what: string, envFile: string | undefined, language: UiLanguage): string =>
|
|
152
|
+
["", ...TEXT[language].notRun(why, what, TEXT[language].envWhere(envFile)), ""].join("\n");
|
|
153
|
+
|
|
82
154
|
/**
|
|
83
155
|
* 機械と AI を見出しで分けて出す。読む人が「これは揺れる判定か」を知らないと、
|
|
84
156
|
* AI の誤検知に振り回される。workflow spec §7、samples/README の軸 2。
|
|
157
|
+
* 見出しと指摘は文書の言語、締めは文書がそろっていればその言語、混ざっていれば ui の言語。
|
|
85
158
|
*/
|
|
86
159
|
export const runTest = async (targets: readonly string[], argv: readonly string[], context: TestContext): Promise<number> => {
|
|
87
160
|
const paths = collectTargets(targets.length > 0 ? targets : ["."]);
|
|
88
161
|
if (paths.length === 0) {
|
|
89
|
-
console.error(
|
|
162
|
+
console.error(CLI_TEXT[context.ui].noMarkdown(targets.join(", ")));
|
|
90
163
|
return 1;
|
|
91
164
|
}
|
|
92
165
|
const { config, resolveGenre, inspect } = context;
|
|
@@ -95,31 +168,20 @@ export const runTest = async (targets: readonly string[], argv: readonly string[
|
|
|
95
168
|
const checks = loadChecks(join(process.cwd(), CHECKS_FILE));
|
|
96
169
|
const results = await Promise.all(paths.map((path) => inspect(path, config, argv)));
|
|
97
170
|
results.forEach((result) => {
|
|
98
|
-
console.log(result.text.replace(/\n/u, `\n${
|
|
171
|
+
console.log(result.text.replace(/\n/u, `\n${machineBanner(result.language).join("\n")}`));
|
|
99
172
|
});
|
|
173
|
+
const closing = sharedLanguage(
|
|
174
|
+
results.map((result) => result.language),
|
|
175
|
+
context.ui,
|
|
176
|
+
);
|
|
100
177
|
const machineOnly = results.some((result) => result.outcome.findings.some((finding) => finding.severity === "error")) ? 1 : 0;
|
|
101
|
-
const where = envFile === undefined ? `${ENV_FILE} に書いても読みます(いまは ${ENV_FILE} がありません)` : `${envFile} は読みました`;
|
|
102
|
-
const notRun = (why: string, what: string): string =>
|
|
103
|
-
["", ` 意味を読む検査は動かしていません。${why}`, ` ${what}`, ` ${where}。`, " 機械による判定はすべて動いています。", ""].join("\n");
|
|
104
|
-
const noCredentials = notRun(`${config.aiBackend} の認証情報がありません。`, credentialHint(config.aiBackend));
|
|
105
|
-
/**
|
|
106
|
-
* API が返した失敗を人の言葉にする。生のスタックトレースを人に見せない。
|
|
107
|
-
* 鍵はあるのに弾かれたときに「ありません」と言うと、設定済みの鍵をもう一度設定しに行かせる。
|
|
108
|
-
* 残高切れも同じで、「鍵を確かめてください」と言われても鍵は正しい。
|
|
109
|
-
*/
|
|
110
|
-
const failed = (failure: Failure): string => {
|
|
111
|
-
if (failure.kind === "auth")
|
|
112
|
-
return notRun(`${config.aiBackend} が鍵を受け付けませんでした(${failure.status ?? "?"})。`, "鍵が正しいか、その provider のものかを確かめてください。");
|
|
113
|
-
if (failure.kind === "quota") return notRun(`${config.aiBackend} の残高か上限に達しました(${failure.status ?? "?"})。`, failure.message);
|
|
114
|
-
return notRun(`${config.aiBackend} が ${failure.status ?? "?"} を返しました。`, failure.message);
|
|
115
|
-
};
|
|
116
178
|
if (argv.includes("--dry-run")) {
|
|
117
|
-
await dryRun(paths, config, checks, resolveGenre, envFile);
|
|
179
|
+
await dryRun(paths, config, checks, resolveGenre, { envFile, language: closing });
|
|
118
180
|
return machineOnly;
|
|
119
181
|
}
|
|
120
182
|
// 呼んでから落ちるのを待たない。認証が無いときの SDK の例外は型で判別できない。
|
|
121
183
|
if (!hasCredentials(config.aiBackend)) {
|
|
122
|
-
console.log(noCredentials);
|
|
184
|
+
console.log(notRunNotice(TEXT[closing].noCredentials(config.aiBackend), credentialHint(config.aiBackend, closing), envFile, closing));
|
|
123
185
|
return machineOnly;
|
|
124
186
|
}
|
|
125
187
|
const options = {
|
|
@@ -127,12 +189,14 @@ export const runTest = async (targets: readonly string[], argv: readonly string[
|
|
|
127
189
|
backend: config.aiBackend,
|
|
128
190
|
cacheDir: join(process.cwd(), CACHE_DIR),
|
|
129
191
|
confidenceThreshold: config.confidenceThreshold,
|
|
192
|
+
...context.clients,
|
|
130
193
|
};
|
|
131
194
|
const semantic = await judgeAll(paths, config, checks, options, resolveGenre).catch((error: unknown) => {
|
|
132
195
|
// API が返した失敗だけを受け止める。ネットワーク断やコードの誤りは握りつぶさない。
|
|
133
196
|
const failure = describeFailure(config.aiBackend, error);
|
|
134
197
|
if (failure === undefined) throw error;
|
|
135
|
-
|
|
198
|
+
const { why, what } = whyFailed(failure, config.aiBackend, closing);
|
|
199
|
+
console.log(notRunNotice(why, what, envFile, closing));
|
|
136
200
|
return undefined;
|
|
137
201
|
});
|
|
138
202
|
if (semantic === undefined) return machineOnly;
|
|
@@ -142,8 +206,6 @@ export const runTest = async (targets: readonly string[], argv: readonly string[
|
|
|
142
206
|
});
|
|
143
207
|
const machine = results.flatMap((result) => result.outcome.findings);
|
|
144
208
|
const ai = semantic.flatMap((entry) => entry.outcome.findings);
|
|
145
|
-
console.log(
|
|
146
|
-
["", "─".repeat(60), "", ` 機械 ${machine.length} 件 / AI ${ai.length} 件`, " 文章は書き換えていません。直すのは書いた人です。", ""].join("\n"),
|
|
147
|
-
);
|
|
209
|
+
console.log(["", DIVIDER, "", TEXT[closing].tally(machine.length, ai.length), TEXT[closing].untouched, ""].join("\n"));
|
|
148
210
|
return [...machine, ...ai].some((finding) => finding.severity === "error") ? 1 : 0;
|
|
149
211
|
};
|