@softure-ai/blog 0.0.0-stage → 0.1.6
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/CHANGELOG.md +10 -0
- package/LICENSE +21 -0
- package/README.md +604 -2
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +5 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/command.d.ts +9 -0
- package/dist/cli/command.d.ts.map +1 -0
- package/dist/cli/command.js +40 -0
- package/dist/cli/command.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/run.d.ts +62 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +555 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/skill.d.ts +47 -0
- package/dist/cli/skill.d.ts.map +1 -0
- package/dist/cli/skill.js +221 -0
- package/dist/cli/skill.js.map +1 -0
- package/dist/content/article-file.d.ts +27 -0
- package/dist/content/article-file.d.ts.map +1 -0
- package/dist/content/article-file.js +179 -0
- package/dist/content/article-file.js.map +1 -0
- package/dist/contract.d.ts +77 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +5 -0
- package/dist/contract.js.map +1 -0
- package/dist/db/articles.d.ts +30 -0
- package/dist/db/articles.d.ts.map +1 -0
- package/dist/db/articles.js +138 -0
- package/dist/db/articles.js.map +1 -0
- package/dist/db/publish-run.d.ts +50 -0
- package/dist/db/publish-run.d.ts.map +1 -0
- package/dist/db/publish-run.js +168 -0
- package/dist/db/publish-run.js.map +1 -0
- package/dist/db/schema.d.ts +403 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +33 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/discovery/dates.d.ts +9 -0
- package/dist/discovery/dates.d.ts.map +1 -0
- package/dist/discovery/dates.js +19 -0
- package/dist/discovery/dates.js.map +1 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +11 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/indexnow.d.ts +11 -0
- package/dist/discovery/indexnow.d.ts.map +1 -0
- package/dist/discovery/indexnow.js +23 -0
- package/dist/discovery/indexnow.js.map +1 -0
- package/dist/discovery/refresh.d.ts +49 -0
- package/dist/discovery/refresh.d.ts.map +1 -0
- package/dist/discovery/refresh.js +55 -0
- package/dist/discovery/refresh.js.map +1 -0
- package/dist/discovery/related.d.ts +17 -0
- package/dist/discovery/related.d.ts.map +1 -0
- package/dist/discovery/related.js +28 -0
- package/dist/discovery/related.js.map +1 -0
- package/dist/discovery/rss.d.ts +30 -0
- package/dist/discovery/rss.d.ts.map +1 -0
- package/dist/discovery/rss.js +48 -0
- package/dist/discovery/rss.js.map +1 -0
- package/dist/discovery/sitemap.d.ts +25 -0
- package/dist/discovery/sitemap.d.ts.map +1 -0
- package/dist/discovery/sitemap.js +31 -0
- package/dist/discovery/sitemap.js.map +1 -0
- package/dist/discovery/submit.d.ts +44 -0
- package/dist/discovery/submit.d.ts.map +1 -0
- package/dist/discovery/submit.js +46 -0
- package/dist/discovery/submit.js.map +1 -0
- package/dist/index.d.ts +371 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/en.d.ts +83 -0
- package/dist/messages/en.d.ts.map +1 -0
- package/dist/messages/en.js +83 -0
- package/dist/messages/en.js.map +1 -0
- package/dist/messages/index.d.ts +158 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +5 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/messages/pl.d.ts +78 -0
- package/dist/messages/pl.d.ts.map +1 -0
- package/dist/messages/pl.js +78 -0
- package/dist/messages/pl.js.map +1 -0
- package/dist/next/context.d.ts +6 -0
- package/dist/next/context.d.ts.map +1 -0
- package/dist/next/context.js +27 -0
- package/dist/next/context.js.map +1 -0
- package/dist/next/data.d.ts +11 -0
- package/dist/next/data.d.ts.map +1 -0
- package/dist/next/data.js +40 -0
- package/dist/next/data.js.map +1 -0
- package/dist/next/discovery.d.ts +6 -0
- package/dist/next/discovery.d.ts.map +1 -0
- package/dist/next/discovery.js +49 -0
- package/dist/next/discovery.js.map +1 -0
- package/dist/next/index.d.ts +9 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +10 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/og-fonts.d.ts +6 -0
- package/dist/next/og-fonts.d.ts.map +1 -0
- package/dist/next/og-fonts.js +8 -0
- package/dist/next/og-fonts.js.map +1 -0
- package/dist/next/og-image.d.ts +36 -0
- package/dist/next/og-image.d.ts.map +1 -0
- package/dist/next/og-image.js +77 -0
- package/dist/next/og-image.js.map +1 -0
- package/dist/next/pages.d.ts +45 -0
- package/dist/next/pages.d.ts.map +1 -0
- package/dist/next/pages.js +189 -0
- package/dist/next/pages.js.map +1 -0
- package/dist/next/refresh.d.ts +9 -0
- package/dist/next/refresh.d.ts.map +1 -0
- package/dist/next/refresh.js +81 -0
- package/dist/next/refresh.js.map +1 -0
- package/dist/options.d.ts +320 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +184 -0
- package/dist/options.js.map +1 -0
- package/dist/pages/body.d.ts +18 -0
- package/dist/pages/body.d.ts.map +1 -0
- package/dist/pages/body.js +21 -0
- package/dist/pages/body.js.map +1 -0
- package/dist/pages/dates.d.ts +21 -0
- package/dist/pages/dates.d.ts.map +1 -0
- package/dist/pages/dates.js +24 -0
- package/dist/pages/dates.js.map +1 -0
- package/dist/pages/index.d.ts +7 -0
- package/dist/pages/index.d.ts.map +1 -0
- package/dist/pages/index.js +9 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/json-ld.d.ts +33 -0
- package/dist/pages/json-ld.d.ts.map +1 -0
- package/dist/pages/json-ld.js +95 -0
- package/dist/pages/json-ld.js.map +1 -0
- package/dist/pages/listing.d.ts +46 -0
- package/dist/pages/listing.d.ts.map +1 -0
- package/dist/pages/listing.js +57 -0
- package/dist/pages/listing.js.map +1 -0
- package/dist/pages/paths.d.ts +37 -0
- package/dist/pages/paths.d.ts.map +1 -0
- package/dist/pages/paths.js +61 -0
- package/dist/pages/paths.js.map +1 -0
- package/dist/pages/redirects.d.ts +57 -0
- package/dist/pages/redirects.d.ts.map +1 -0
- package/dist/pages/redirects.js +78 -0
- package/dist/pages/redirects.js.map +1 -0
- package/dist/proxy/index.d.ts +15 -0
- package/dist/proxy/index.d.ts.map +1 -0
- package/dist/proxy/index.js +60 -0
- package/dist/proxy/index.js.map +1 -0
- package/dist/quality/blocks.d.ts +21 -0
- package/dist/quality/blocks.d.ts.map +1 -0
- package/dist/quality/blocks.js +113 -0
- package/dist/quality/blocks.js.map +1 -0
- package/dist/quality/catalog.d.ts +10 -0
- package/dist/quality/catalog.d.ts.map +1 -0
- package/dist/quality/catalog.js +71 -0
- package/dist/quality/catalog.js.map +1 -0
- package/dist/quality/check-article.d.ts +34 -0
- package/dist/quality/check-article.d.ts.map +1 -0
- package/dist/quality/check-article.js +74 -0
- package/dist/quality/check-article.js.map +1 -0
- package/dist/quality/check-files.d.ts +23 -0
- package/dist/quality/check-files.d.ts.map +1 -0
- package/dist/quality/check-files.js +44 -0
- package/dist/quality/check-files.js.map +1 -0
- package/dist/quality/external-links.d.ts +19 -0
- package/dist/quality/external-links.d.ts.map +1 -0
- package/dist/quality/external-links.js +30 -0
- package/dist/quality/external-links.js.map +1 -0
- package/dist/quality/finding.d.ts +17 -0
- package/dist/quality/finding.d.ts.map +1 -0
- package/dist/quality/finding.js +15 -0
- package/dist/quality/finding.js.map +1 -0
- package/dist/quality/gate.d.ts +5 -0
- package/dist/quality/gate.d.ts.map +1 -0
- package/dist/quality/gate.js +10 -0
- package/dist/quality/gate.js.map +1 -0
- package/dist/quality/index.d.ts +15 -0
- package/dist/quality/index.d.ts.map +1 -0
- package/dist/quality/index.js +16 -0
- package/dist/quality/index.js.map +1 -0
- package/dist/quality/link-targets.d.ts +44 -0
- package/dist/quality/link-targets.d.ts.map +1 -0
- package/dist/quality/link-targets.js +91 -0
- package/dist/quality/link-targets.js.map +1 -0
- package/dist/quality/options.d.ts +113 -0
- package/dist/quality/options.d.ts.map +1 -0
- package/dist/quality/options.js +93 -0
- package/dist/quality/options.js.map +1 -0
- package/dist/quality/plugin.d.ts +34 -0
- package/dist/quality/plugin.d.ts.map +1 -0
- package/dist/quality/plugin.js +7 -0
- package/dist/quality/plugin.js.map +1 -0
- package/dist/quality/rules/blocks.d.ts +5 -0
- package/dist/quality/rules/blocks.d.ts.map +1 -0
- package/dist/quality/rules/blocks.js +35 -0
- package/dist/quality/rules/blocks.js.map +1 -0
- package/dist/quality/rules/images.d.ts +4 -0
- package/dist/quality/rules/images.d.ts.map +1 -0
- package/dist/quality/rules/images.js +33 -0
- package/dist/quality/rules/images.js.map +1 -0
- package/dist/quality/rules/input.d.ts +11 -0
- package/dist/quality/rules/input.d.ts.map +1 -0
- package/dist/quality/rules/input.js +2 -0
- package/dist/quality/rules/input.js.map +1 -0
- package/dist/quality/rules/links.d.ts +17 -0
- package/dist/quality/rules/links.d.ts.map +1 -0
- package/dist/quality/rules/links.js +46 -0
- package/dist/quality/rules/links.js.map +1 -0
- package/dist/quality/rules/structure.d.ts +13 -0
- package/dist/quality/rules/structure.d.ts.map +1 -0
- package/dist/quality/rules/structure.js +139 -0
- package/dist/quality/rules/structure.js.map +1 -0
- package/dist/quality/rules/style.d.ts +11 -0
- package/dist/quality/rules/style.d.ts.map +1 -0
- package/dist/quality/rules/style.js +129 -0
- package/dist/quality/rules/style.js.map +1 -0
- package/dist/quality/rules/ymyl.d.ts +5 -0
- package/dist/quality/rules/ymyl.d.ts.map +1 -0
- package/dist/quality/rules/ymyl.js +51 -0
- package/dist/quality/rules/ymyl.js.map +1 -0
- package/dist/quality/rulesets/en/ruleset.d.ts +3 -0
- package/dist/quality/rulesets/en/ruleset.d.ts.map +1 -0
- package/dist/quality/rulesets/en/ruleset.js +106 -0
- package/dist/quality/rulesets/en/ruleset.js.map +1 -0
- package/dist/quality/rulesets/index.d.ts +7 -0
- package/dist/quality/rulesets/index.d.ts.map +1 -0
- package/dist/quality/rulesets/index.js +7 -0
- package/dist/quality/rulesets/index.js.map +1 -0
- package/dist/quality/rulesets/pl/ruleset.d.ts +3 -0
- package/dist/quality/rulesets/pl/ruleset.d.ts.map +1 -0
- package/dist/quality/rulesets/pl/ruleset.js +114 -0
- package/dist/quality/rulesets/pl/ruleset.js.map +1 -0
- package/dist/quality/rulesets/types.d.ts +28 -0
- package/dist/quality/rulesets/types.d.ts.map +1 -0
- package/dist/quality/rulesets/types.js +2 -0
- package/dist/quality/rulesets/types.js.map +1 -0
- package/dist/quality/settings.d.ts +26 -0
- package/dist/quality/settings.d.ts.map +1 -0
- package/dist/quality/settings.js +32 -0
- package/dist/quality/settings.js.map +1 -0
- package/dist/quality/text.d.ts +43 -0
- package/dist/quality/text.d.ts.map +1 -0
- package/dist/quality/text.js +85 -0
- package/dist/quality/text.js.map +1 -0
- package/dist/render/glossary.d.ts +27 -0
- package/dist/render/glossary.d.ts.map +1 -0
- package/dist/render/glossary.js +71 -0
- package/dist/render/glossary.js.map +1 -0
- package/dist/render/images.d.ts +42 -0
- package/dist/render/images.d.ts.map +1 -0
- package/dist/render/images.js +84 -0
- package/dist/render/images.js.map +1 -0
- package/dist/render/index.d.ts +6 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +8 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/reading-time.d.ts +8 -0
- package/dist/render/reading-time.d.ts.map +1 -0
- package/dist/render/reading-time.js +17 -0
- package/dist/render/reading-time.js.map +1 -0
- package/dist/render/render-article.d.ts +97 -0
- package/dist/render/render-article.d.ts.map +1 -0
- package/dist/render/render-article.js +373 -0
- package/dist/render/render-article.js.map +1 -0
- package/dist/render/slugify-heading.d.ts +2 -0
- package/dist/render/slugify-heading.d.ts.map +1 -0
- package/dist/render/slugify-heading.js +21 -0
- package/dist/render/slugify-heading.js.map +1 -0
- package/dist/server/health.d.ts +3 -0
- package/dist/server/health.d.ts.map +1 -0
- package/dist/server/health.js +11 -0
- package/dist/server/health.js.map +1 -0
- package/dist/server/index.d.ts +10 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +13 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/og-fonts.d.ts +24 -0
- package/dist/server/og-fonts.d.ts.map +1 -0
- package/dist/server/og-fonts.js +85 -0
- package/dist/server/og-fonts.js.map +1 -0
- package/dist/server/options.d.ts +17 -0
- package/dist/server/options.d.ts.map +1 -0
- package/dist/server/options.js +56 -0
- package/dist/server/options.js.map +1 -0
- package/dist/sitemap.d.ts +11 -0
- package/dist/sitemap.d.ts.map +1 -0
- package/dist/sitemap.js +35 -0
- package/dist/sitemap.js.map +1 -0
- package/dist/ui/blog-article.d.ts +49 -0
- package/dist/ui/blog-article.d.ts.map +1 -0
- package/dist/ui/blog-article.js +45 -0
- package/dist/ui/blog-article.js.map +1 -0
- package/dist/ui/blog-glossary.d.ts +27 -0
- package/dist/ui/blog-glossary.d.ts.map +1 -0
- package/dist/ui/blog-glossary.js +20 -0
- package/dist/ui/blog-glossary.js.map +1 -0
- package/dist/ui/blog-layout.d.ts +31 -0
- package/dist/ui/blog-layout.d.ts.map +1 -0
- package/dist/ui/blog-layout.js +25 -0
- package/dist/ui/blog-layout.js.map +1 -0
- package/dist/ui/blog-listing.d.ts +15 -0
- package/dist/ui/blog-listing.d.ts.map +1 -0
- package/dist/ui/blog-listing.js +29 -0
- package/dist/ui/blog-listing.js.map +1 -0
- package/dist/ui/blog-method.d.ts +5 -0
- package/dist/ui/blog-method.d.ts.map +1 -0
- package/dist/ui/blog-method.js +21 -0
- package/dist/ui/blog-method.js.map +1 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +8 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/page-context.d.ts +15 -0
- package/dist/ui/page-context.d.ts.map +1 -0
- package/dist/ui/page-context.js +2 -0
- package/dist/ui/page-context.js.map +1 -0
- package/migrations/0001_create_articles.sql +67 -0
- package/migrations/README.md +8 -0
- package/module.json +28 -0
- package/package.json +102 -4
- package/skill/SKILL.md +72 -0
- package/skill/references/reviewer.md +39 -0
- package/skill/references/rules.md +122 -0
- package/skill/references/structure.md +69 -0
- package/skill/references/template.md +86 -0
- package/src/cli/bin.ts +5 -0
- package/src/cli/command.ts +49 -0
- package/src/cli/index.ts +20 -0
- package/src/cli/run.ts +591 -0
- package/src/cli/skill.ts +242 -0
- package/src/content/article-file.ts +184 -0
- package/src/contract.ts +88 -0
- package/src/db/articles.ts +160 -0
- package/src/db/publish-run.ts +224 -0
- package/src/db/schema.ts +36 -0
- package/src/discovery/dates.ts +27 -0
- package/src/discovery/index.ts +18 -0
- package/src/discovery/indexnow.ts +25 -0
- package/src/discovery/refresh.ts +79 -0
- package/src/discovery/related.ts +35 -0
- package/src/discovery/rss.ts +77 -0
- package/src/discovery/sitemap.ts +59 -0
- package/src/discovery/submit.ts +69 -0
- package/src/index.ts +99 -0
- package/src/messages/en.ts +82 -0
- package/src/messages/index.ts +7 -0
- package/src/messages/pl.ts +77 -0
- package/src/next/context.ts +30 -0
- package/src/next/data.ts +55 -0
- package/src/next/discovery.ts +49 -0
- package/src/next/index.ts +25 -0
- package/src/next/next-modules.d.ts +15 -0
- package/src/next/og-fonts.ts +14 -0
- package/src/next/og-image.tsx +108 -0
- package/src/next/pages.tsx +251 -0
- package/src/next/refresh.ts +85 -0
- package/src/options.ts +224 -0
- package/src/pages/body.ts +38 -0
- package/src/pages/dates.ts +39 -0
- package/src/pages/index.ts +37 -0
- package/src/pages/json-ld.ts +120 -0
- package/src/pages/listing.ts +95 -0
- package/src/pages/paths.ts +84 -0
- package/src/pages/redirects.ts +120 -0
- package/src/proxy/index.ts +66 -0
- package/src/quality/blocks.ts +130 -0
- package/src/quality/catalog.ts +86 -0
- package/src/quality/check-article.ts +112 -0
- package/src/quality/check-files.ts +63 -0
- package/src/quality/external-links.ts +37 -0
- package/src/quality/finding.ts +29 -0
- package/src/quality/gate.ts +15 -0
- package/src/quality/index.ts +28 -0
- package/src/quality/link-targets.ts +107 -0
- package/src/quality/options.ts +105 -0
- package/src/quality/plugin.ts +43 -0
- package/src/quality/rules/blocks.ts +43 -0
- package/src/quality/rules/images.ts +34 -0
- package/src/quality/rules/input.ts +12 -0
- package/src/quality/rules/links.ts +56 -0
- package/src/quality/rules/structure.ts +137 -0
- package/src/quality/rules/style.ts +137 -0
- package/src/quality/rules/ymyl.ts +57 -0
- package/src/quality/rulesets/en/ruleset.ts +117 -0
- package/src/quality/rulesets/index.ts +9 -0
- package/src/quality/rulesets/pl/ruleset.ts +126 -0
- package/src/quality/rulesets/types.ts +31 -0
- package/src/quality/settings.ts +60 -0
- package/src/quality/text.ts +113 -0
- package/src/render/glossary.ts +103 -0
- package/src/render/images.ts +110 -0
- package/src/render/index.ts +29 -0
- package/src/render/reading-time.ts +18 -0
- package/src/render/render-article.ts +487 -0
- package/src/render/slugify-heading.ts +22 -0
- package/src/server/health.ts +12 -0
- package/src/server/index.ts +28 -0
- package/src/server/og-fonts.ts +102 -0
- package/src/server/options.ts +62 -0
- package/src/sitemap.ts +36 -0
- package/src/ui/blog-article.tsx +186 -0
- package/src/ui/blog-glossary.tsx +104 -0
- package/src/ui/blog-layout.tsx +89 -0
- package/src/ui/blog-listing.tsx +95 -0
- package/src/ui/blog-method.tsx +34 -0
- package/src/ui/index.ts +8 -0
- package/src/ui/page-context.ts +17 -0
- package/styles.css +498 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.d.ts","sourceRoot":"","sources":["../../../src/quality/rules/links.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,SAAS;QAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACnF,QAAQ,CAAC,QAAQ,EAAE,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/E;AAED,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;AAEjE,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,QAAQ,GAAG,UAAU,CAAC,GAAG,WAAW,CAkBtG;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,mBAAmB,EAAE,oBAAoB,GAAG,cAAc,EAAE,CAoB5H"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { findBareUrls, findLinks } from "../text.js";
|
|
2
|
+
export function collectLinks({ blocks, settings }) {
|
|
3
|
+
const internal = [];
|
|
4
|
+
const external = [];
|
|
5
|
+
for (const block of blocks) {
|
|
6
|
+
if (block.kind === "directive")
|
|
7
|
+
continue;
|
|
8
|
+
const urls = findLinks(block.text).map((link) => link.url);
|
|
9
|
+
if (block.kind === "footnote")
|
|
10
|
+
urls.push(...findBareUrls(block.text));
|
|
11
|
+
for (const url of new Set(urls)) {
|
|
12
|
+
const own = settings.ownOrigins.find((origin) => url === origin || url.startsWith(`${origin}/`) || url.startsWith(`${origin}?`) || url.startsWith(`${origin}#`));
|
|
13
|
+
if (url.startsWith("/") || own !== undefined) {
|
|
14
|
+
const rest = own === undefined ? url : url.slice(own.length);
|
|
15
|
+
internal.push({ pathname: rest.split(/[?#]/)[0] || "/", line: block.line });
|
|
16
|
+
}
|
|
17
|
+
else if (/^https?:\/\//.test(url)) {
|
|
18
|
+
external.push({ url, line: block.line });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return { internal, external };
|
|
23
|
+
}
|
|
24
|
+
export function checkLinks(input, links, resolveInternalLink) {
|
|
25
|
+
const findings = [];
|
|
26
|
+
const { kind } = input.article;
|
|
27
|
+
const minimum = input.settings.options.limits.internalLinks[kind];
|
|
28
|
+
if (links.internal.length < minimum) {
|
|
29
|
+
findings.push({
|
|
30
|
+
rule: "internal-links",
|
|
31
|
+
severity: kind === "article" ? "error" : "warning",
|
|
32
|
+
message: `${String(links.internal.length)} internal links; at least ${String(minimum)} needed (other texts, the glossary, the app's tools)`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
for (const link of links.internal) {
|
|
36
|
+
if (!resolveInternalLink(link.pathname)) {
|
|
37
|
+
findings.push({ rule: "internal-link-target", severity: "error", message: `an internal link leads nowhere: ${link.pathname}`, line: link.line });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
for (const link of links.external) {
|
|
41
|
+
if (link.url.startsWith("http://"))
|
|
42
|
+
findings.push({ rule: "external-link-https", severity: "warning", message: `a link without https: ${link.url}`, line: link.line });
|
|
43
|
+
}
|
|
44
|
+
return findings;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=links.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../../src/quality/rules/links.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Block } from "../blocks.js";
|
|
2
|
+
import type { QualityFinding } from "../finding.js";
|
|
3
|
+
import type { RuleInput } from "./input.js";
|
|
4
|
+
export declare function checkMetadata({ article, settings, today }: RuleInput): QualityFinding[];
|
|
5
|
+
/** The text opens with a paragraph that answers; an article puts a concrete number in its first two paragraphs. */
|
|
6
|
+
export declare function checkLead({ article, blocks, settings }: RuleInput): QualityFinding[];
|
|
7
|
+
/** No H1 (the page title is `title`), no skipped heading level; an article has question sections with short answers. */
|
|
8
|
+
export declare function checkSections({ article, blocks, settings }: RuleInput): QualityFinding[];
|
|
9
|
+
export declare function checkLength({ article, blocks, settings }: RuleInput): QualityFinding[];
|
|
10
|
+
/** Every footnote reference has a definition, and every definition is used. */
|
|
11
|
+
export declare function checkFootnotes({ blocks }: RuleInput): QualityFinding[];
|
|
12
|
+
export declare function getFootnoteDefinitions(blocks: readonly Block[]): Map<string, Block>;
|
|
13
|
+
//# sourceMappingURL=structure.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structure.d.ts","sourceRoot":"","sources":["../../../src/quality/rules/structure.ts"],"names":[],"mappings":"AAGA,OAAO,EAAkB,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,SAAS,GAAG,cAAc,EAAE,CAmBvF;AAED,mHAAmH;AACnH,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,SAAS,GAAG,cAAc,EAAE,CAyBpF;AAED,wHAAwH;AACxH,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,SAAS,GAAG,cAAc,EAAE,CAwCxF;AAED,wBAAgB,WAAW,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,SAAS,GAAG,cAAc,EAAE,CAMtF;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,GAAG,cAAc,EAAE,CAetE;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAMnF"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Structure rules (FIRE_TRACKER `src/lib/blog/quality/rules-structure.ts`): metadata lengths and
|
|
2
|
+
// freshness, a summary, the answer first, heading order, question sections, length and footnotes.
|
|
3
|
+
// Required keys, kinds, slug shape and slug = file name are BL-2's parser's, not repeated here.
|
|
4
|
+
import { getProseBlocks } from "../blocks.js";
|
|
5
|
+
import { countWords, findFootnoteRefs, findSignificantNumbers, toProse } from "../text.js";
|
|
6
|
+
export function checkMetadata({ article, settings, today }) {
|
|
7
|
+
const findings = [];
|
|
8
|
+
const { limits } = settings.options;
|
|
9
|
+
if (article.title.length > limits.titleChars) {
|
|
10
|
+
findings.push({ rule: "title-length", severity: "warning", message: `the title has ${String(article.title.length)} characters; search engines cut it after about ${String(limits.titleChars)}` });
|
|
11
|
+
}
|
|
12
|
+
const { min, max } = limits.descriptionChars;
|
|
13
|
+
if (article.description.length < min || article.description.length > max) {
|
|
14
|
+
findings.push({ rule: "description-length", severity: "warning", message: `the description has ${String(article.description.length)} characters; aim for ${String(min)}–${String(max)}` });
|
|
15
|
+
}
|
|
16
|
+
if (article.currentAsOf > today) {
|
|
17
|
+
findings.push({ rule: "as-of-future", severity: "error", message: `current_as_of ${article.currentAsOf} is in the future (today is ${today})` });
|
|
18
|
+
}
|
|
19
|
+
else if (daysBetween(article.currentAsOf, today) > limits.staleAfterDays) {
|
|
20
|
+
findings.push({ rule: "stale", severity: "warning", message: `current_as_of ${article.currentAsOf} is older than ${String(limits.staleAfterDays)} days; the text waits for fresh numbers` });
|
|
21
|
+
}
|
|
22
|
+
if (article.kind === "article" && article.summary === null) {
|
|
23
|
+
findings.push({ rule: "summary-missing", severity: "error", message: "the article has no summary: a few sentences with the answer for the \"in short\" box" });
|
|
24
|
+
}
|
|
25
|
+
return findings;
|
|
26
|
+
}
|
|
27
|
+
/** The text opens with a paragraph that answers; an article puts a concrete number in its first two paragraphs. */
|
|
28
|
+
export function checkLead({ article, blocks, settings }) {
|
|
29
|
+
const first = blocks[0];
|
|
30
|
+
if (first === undefined)
|
|
31
|
+
return [{ rule: "lead", severity: "error", message: "the text is empty" }];
|
|
32
|
+
if (first.kind !== "paragraph") {
|
|
33
|
+
return [{ rule: "lead", severity: "error", message: "the text starts with a heading or a list; the first block is a paragraph with the answer", line: first.line }];
|
|
34
|
+
}
|
|
35
|
+
const findings = [];
|
|
36
|
+
const limit = settings.options.limits.leadWords[article.kind];
|
|
37
|
+
const words = countWords(toProse(first.text));
|
|
38
|
+
if (words > limit) {
|
|
39
|
+
findings.push({ rule: "lead-length", severity: "warning", message: `the first paragraph has ${String(words)} words; the answer fits in ${String(limit)}`, line: first.line });
|
|
40
|
+
}
|
|
41
|
+
if (article.kind === "article") {
|
|
42
|
+
const opening = blocks.slice(0, 4).filter((block) => block.kind === "paragraph").slice(0, 2);
|
|
43
|
+
const hasNumber = opening.some((block) => findSignificantNumbers(toProse(block.text), settings.ruleset.notation).length > 0);
|
|
44
|
+
if (!hasNumber) {
|
|
45
|
+
findings.push({
|
|
46
|
+
rule: "lead-number",
|
|
47
|
+
severity: "error",
|
|
48
|
+
message: "the first two paragraphs hold no concrete number (an amount or a percentage); the claim with its number goes first",
|
|
49
|
+
line: first.line,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return findings;
|
|
54
|
+
}
|
|
55
|
+
/** No H1 (the page title is `title`), no skipped heading level; an article has question sections with short answers. */
|
|
56
|
+
export function checkSections({ article, blocks, settings }) {
|
|
57
|
+
const findings = [];
|
|
58
|
+
let previousLevel = 1;
|
|
59
|
+
for (const block of blocks) {
|
|
60
|
+
if (block.kind !== "heading" || block.level === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
if (block.level === 1) {
|
|
63
|
+
findings.push({ rule: "heading-h1", severity: "error", message: "a # heading in the body; the page title comes from title, sections start at ##", line: block.line });
|
|
64
|
+
}
|
|
65
|
+
else if (block.level > previousLevel + 1) {
|
|
66
|
+
findings.push({ rule: "heading-order", severity: "error", message: `heading level ${String(block.level)} follows level ${String(previousLevel)}; do not skip a level`, line: block.line });
|
|
67
|
+
}
|
|
68
|
+
previousLevel = block.level;
|
|
69
|
+
}
|
|
70
|
+
if (article.kind !== "article")
|
|
71
|
+
return findings;
|
|
72
|
+
const sections = blocks.filter((block) => block.kind === "heading" && block.level === 2);
|
|
73
|
+
if (sections.length < 2) {
|
|
74
|
+
findings.push({ rule: "sections", severity: "error", message: `the article has ${String(sections.length)} ## sections; it needs at least 2` });
|
|
75
|
+
}
|
|
76
|
+
const questions = sections.filter((block) => block.text.trim().endsWith("?"));
|
|
77
|
+
if (questions.length === 0) {
|
|
78
|
+
findings.push({ rule: "section-question", severity: "error", message: "no ## heading is a question; answer sections are what AI assistants quote" });
|
|
79
|
+
}
|
|
80
|
+
const limit = settings.options.limits.answerWords;
|
|
81
|
+
for (const question of questions) {
|
|
82
|
+
const answer = blocks[blocks.indexOf(question) + 1];
|
|
83
|
+
if (answer?.kind !== "paragraph") {
|
|
84
|
+
findings.push({ rule: "section-answer", severity: "error", message: `no paragraph with the answer under "${question.text}"`, line: question.line });
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const words = countWords(toProse(answer.text));
|
|
88
|
+
if (words > limit) {
|
|
89
|
+
findings.push({
|
|
90
|
+
rule: "section-answer-length",
|
|
91
|
+
severity: "warning",
|
|
92
|
+
message: `the answer under "${question.text}" has ${String(words)} words; the first sentences answer directly (at most ${String(limit)})`,
|
|
93
|
+
line: answer.line,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return findings;
|
|
98
|
+
}
|
|
99
|
+
export function checkLength({ article, blocks, settings }) {
|
|
100
|
+
const words = getProseBlocks(blocks).reduce((sum, block) => sum + countWords(toProse(block.text)), 0);
|
|
101
|
+
const { min, max } = settings.options.limits.words[article.kind];
|
|
102
|
+
if (words < min)
|
|
103
|
+
return [{ rule: "length", severity: "error", message: `the text has ${String(words)} words; ${article.kind === "article" ? "an article" : "a term"} needs at least ${String(min)}` }];
|
|
104
|
+
if (words > max)
|
|
105
|
+
return [{ rule: "length", severity: "warning", message: `the text has ${String(words)} words; above ${String(max)} readers and assistants lose the answer` }];
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
/** Every footnote reference has a definition, and every definition is used. */
|
|
109
|
+
export function checkFootnotes({ blocks }) {
|
|
110
|
+
const findings = [];
|
|
111
|
+
const definitions = getFootnoteDefinitions(blocks);
|
|
112
|
+
const used = new Set();
|
|
113
|
+
for (const block of blocks) {
|
|
114
|
+
if (block.kind === "footnote" || block.kind === "directive")
|
|
115
|
+
continue;
|
|
116
|
+
for (const ref of findFootnoteRefs(block.text)) {
|
|
117
|
+
used.add(ref);
|
|
118
|
+
if (!definitions.has(ref))
|
|
119
|
+
findings.push({ rule: "footnote-undefined", severity: "error", message: `footnote [^${ref}] has no definition`, line: block.line });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const [id, block] of definitions) {
|
|
123
|
+
if (!used.has(id))
|
|
124
|
+
findings.push({ rule: "footnote-unused", severity: "warning", message: `footnote [^${id}] is not used in the text`, line: block.line });
|
|
125
|
+
}
|
|
126
|
+
return findings;
|
|
127
|
+
}
|
|
128
|
+
export function getFootnoteDefinitions(blocks) {
|
|
129
|
+
const definitions = new Map();
|
|
130
|
+
for (const block of blocks) {
|
|
131
|
+
if (block.kind === "footnote" && block.footnoteId !== undefined)
|
|
132
|
+
definitions.set(block.footnoteId, block);
|
|
133
|
+
}
|
|
134
|
+
return definitions;
|
|
135
|
+
}
|
|
136
|
+
function daysBetween(fromIso, toIso) {
|
|
137
|
+
return Math.round((Date.parse(`${toIso}T00:00:00Z`) - Date.parse(`${fromIso}T00:00:00Z`)) / 86_400_000);
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=structure.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"structure.js","sourceRoot":"","sources":["../../../src/quality/rules/structure.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { QualityFinding } from "../finding.js";
|
|
2
|
+
import type { StylePattern } from "../rulesets/types.js";
|
|
3
|
+
import type { RuleInput } from "./input.js";
|
|
4
|
+
/** The patterns the prose is matched against: ruleset style, app voice, and profit promises when YMYL is on. */
|
|
5
|
+
export declare function getActivePatterns({ settings }: Pick<RuleInput, "settings">): StylePattern[];
|
|
6
|
+
/** Title, description and summary show in search results and above the text: the error patterns apply to them too. */
|
|
7
|
+
export declare function checkMetadataStyle(input: RuleInput): QualityFinding[];
|
|
8
|
+
export declare function checkStyle(input: RuleInput): QualityFinding[];
|
|
9
|
+
/** Rhythm and formatting: dashes (an error over the limit), bold, bold labels, triads, sentence length and variety, openings, headings. */
|
|
10
|
+
export declare function checkRhythm({ blocks, settings }: RuleInput): QualityFinding[];
|
|
11
|
+
//# sourceMappingURL=style.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.d.ts","sourceRoot":"","sources":["../../../src/quality/rules/style.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C,gHAAgH;AAChH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,GAAG,YAAY,EAAE,CAM3F;AAED,sHAAsH;AACtH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,cAAc,EAAE,CAQrE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,cAAc,EAAE,CAE7D;AAgCD,2IAA2I;AAC3I,wBAAgB,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,SAAS,GAAG,cAAc,EAAE,CAuE7E"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Style rules (FIRE_TRACKER `src/lib/blog/quality/rules-style.ts`): the ruleset's patterns, the app's
|
|
2
|
+
// voice and the YMYL profit promises over prose, then rhythm and formatting measures.
|
|
3
|
+
import { getProseBlocks } from "../blocks.js";
|
|
4
|
+
import { countWords, splitSentences, toProse } from "../text.js";
|
|
5
|
+
const PROFIT_PROMISE_MESSAGE = "a promise of profit or an order to buy; not acceptable in a your-money-or-your-life text";
|
|
6
|
+
/** The patterns the prose is matched against: ruleset style, app voice, and profit promises when YMYL is on. */
|
|
7
|
+
export function getActivePatterns({ settings }) {
|
|
8
|
+
const patterns = [...settings.ruleset.patterns, ...settings.voicePatterns];
|
|
9
|
+
if (settings.options.ymyl !== null) {
|
|
10
|
+
patterns.push({ id: "profit-promise", severity: "error", pattern: settings.ruleset.profitPromises, message: PROFIT_PROMISE_MESSAGE });
|
|
11
|
+
}
|
|
12
|
+
return patterns;
|
|
13
|
+
}
|
|
14
|
+
/** Title, description and summary show in search results and above the text: the error patterns apply to them too. */
|
|
15
|
+
export function checkMetadataStyle(input) {
|
|
16
|
+
const { title, description, summary } = input.article;
|
|
17
|
+
const blocks = [title, description, ...(summary === null ? [] : [summary])].map((text) => ({ kind: "paragraph", text, line: 1 }));
|
|
18
|
+
return matchPatterns(blocks, getActivePatterns(input).filter((pattern) => pattern.severity === "error")).map((finding) => ({
|
|
19
|
+
rule: finding.rule,
|
|
20
|
+
severity: finding.severity,
|
|
21
|
+
message: `title, description or summary: ${finding.message}`,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
export function checkStyle(input) {
|
|
25
|
+
return matchPatterns(getProseBlocks(input.blocks), getActivePatterns(input));
|
|
26
|
+
}
|
|
27
|
+
/** One error per pattern and block; warnings aggregated to one finding per pattern with a count. */
|
|
28
|
+
function matchPatterns(blocks, patterns) {
|
|
29
|
+
const findings = [];
|
|
30
|
+
const warningHits = new Map();
|
|
31
|
+
for (const block of blocks) {
|
|
32
|
+
const prose = toProse(block.text);
|
|
33
|
+
for (const style of patterns) {
|
|
34
|
+
const matches = [...prose.matchAll(style.pattern)];
|
|
35
|
+
const first = matches[0]?.[0];
|
|
36
|
+
if (first === undefined)
|
|
37
|
+
continue;
|
|
38
|
+
if (style.severity === "error") {
|
|
39
|
+
findings.push({ rule: style.id, severity: "error", message: `"${first}": ${style.message}`, line: block.line });
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
const hit = warningHits.get(style.id) ?? { count: 0, line: block.line, sample: first };
|
|
43
|
+
hit.count += matches.length;
|
|
44
|
+
warningHits.set(style.id, hit);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const style of patterns) {
|
|
49
|
+
const hit = warningHits.get(style.id);
|
|
50
|
+
if (hit !== undefined) {
|
|
51
|
+
findings.push({ rule: style.id, severity: "warning", message: `${String(hit.count)}x (e.g. "${hit.sample}"): ${style.message}`, line: hit.line });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return findings;
|
|
55
|
+
}
|
|
56
|
+
const DASH = /\s[—–-]\s|—/g;
|
|
57
|
+
/** Rhythm and formatting: dashes (an error over the limit), bold, bold labels, triads, sentence length and variety, openings, headings. */
|
|
58
|
+
export function checkRhythm({ blocks, settings }) {
|
|
59
|
+
const findings = [];
|
|
60
|
+
const { limits } = settings.options;
|
|
61
|
+
const prose = getProseBlocks(blocks).filter((block) => block.kind !== "heading" && block.kind !== "table");
|
|
62
|
+
const proseText = prose.map((block) => toProse(block.text));
|
|
63
|
+
const totalWords = proseText.reduce((sum, text) => sum + countWords(text), 0);
|
|
64
|
+
const dashes = proseText.reduce((sum, text) => sum + (text.match(DASH)?.length ?? 0), 0);
|
|
65
|
+
if (dashes >= 3 && dashes > totalWords / limits.wordsPerDash) {
|
|
66
|
+
findings.push({
|
|
67
|
+
rule: "dashes",
|
|
68
|
+
severity: "error",
|
|
69
|
+
message: `${String(dashes)} dashes in ${String(totalWords)} words; at most one per ${String(limits.wordsPerDash)}: use a full stop, a comma or a colon`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
for (const [index, block] of prose.entries()) {
|
|
73
|
+
if ((proseText[index]?.match(DASH)?.length ?? 0) >= 2)
|
|
74
|
+
findings.push({ rule: "dashes-paragraph", severity: "warning", message: "two dashes in one paragraph", line: block.line });
|
|
75
|
+
}
|
|
76
|
+
const bold = blocks.reduce((sum, block) => sum + (block.text.match(/\*\*[^*]+\*\*/g)?.length ?? 0), 0);
|
|
77
|
+
if (bold > 0 && bold > totalWords / limits.wordsPerBold) {
|
|
78
|
+
findings.push({ rule: "bold-density", severity: "warning", message: `${String(bold)} bold phrases in ${String(totalWords)} words; bold stops standing out` });
|
|
79
|
+
}
|
|
80
|
+
const boldLabels = blocks.filter((block) => block.kind === "listItem" && /^\*\*[^*]+:\*\*|^\*\*[^*]+\*\*:/.test(block.text));
|
|
81
|
+
const firstLabel = boldLabels[0];
|
|
82
|
+
if (firstLabel !== undefined && boldLabels.length >= 3) {
|
|
83
|
+
findings.push({
|
|
84
|
+
rule: "bold-labels",
|
|
85
|
+
severity: "warning",
|
|
86
|
+
message: `${String(boldLabels.length)} list items start with a bold label; the "**Label:** text" template`,
|
|
87
|
+
line: firstLabel.line,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const triads = proseText.reduce((sum, text) => sum + (text.match(settings.ruleset.triad)?.length ?? 0), 0);
|
|
91
|
+
if (triads > 3)
|
|
92
|
+
findings.push({ rule: "triads", severity: "warning", message: `${String(triads)} lists of three; two items sound less like a template than three` });
|
|
93
|
+
const sentences = proseText.flatMap((text) => splitSentences(text));
|
|
94
|
+
const lengths = sentences.map((sentence) => countWords(sentence));
|
|
95
|
+
const long = lengths.filter((length) => length > limits.sentenceWords).length;
|
|
96
|
+
if (long > 0)
|
|
97
|
+
findings.push({ rule: "long-sentences", severity: "warning", message: `${String(long)} sentences longer than ${String(limits.sentenceWords)} words` });
|
|
98
|
+
if (lengths.length >= 12) {
|
|
99
|
+
const mean = lengths.reduce((sum, length) => sum + length, 0) / lengths.length;
|
|
100
|
+
const deviation = Math.sqrt(lengths.reduce((sum, length) => sum + (length - mean) ** 2, 0) / lengths.length);
|
|
101
|
+
if (deviation < 4) {
|
|
102
|
+
findings.push({
|
|
103
|
+
rule: "monotone-rhythm",
|
|
104
|
+
severity: "warning",
|
|
105
|
+
message: `the sentences have similar lengths (mean ${mean.toFixed(0)} words, deviation ${deviation.toFixed(1)}); mix short ones with long ones`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (let index = 2; index < sentences.length; index += 1) {
|
|
110
|
+
const openings = sentences.slice(index - 2, index + 1).map((sentence) => sentence.split(" ")[0]?.toLowerCase());
|
|
111
|
+
const opening = openings[0];
|
|
112
|
+
if (opening !== undefined && openings.every((other) => other === opening)) {
|
|
113
|
+
findings.push({ rule: "repeated-openings", severity: "warning", message: `three sentences in a row start with "${opening}"` });
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (settings.ruleset.flagsTitleCaseHeadings) {
|
|
118
|
+
for (const block of blocks) {
|
|
119
|
+
if (block.kind !== "heading")
|
|
120
|
+
continue;
|
|
121
|
+
const words = block.text.split(/\s+/).filter((word) => /^\p{L}/u.test(word));
|
|
122
|
+
if (words.length >= 3 && words.every((word) => /^\p{Lu}/u.test(word))) {
|
|
123
|
+
findings.push({ rule: "title-case-heading", severity: "warning", message: `heading "${block.text}": only the first word starts with a capital letter`, line: block.line });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return findings;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"style.js","sourceRoot":"","sources":["../../../src/quality/rules/style.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { QualityFinding } from "../finding.js";
|
|
2
|
+
import type { RuleInput } from "./input.js";
|
|
3
|
+
export declare function checkSources({ article }: RuleInput): QualityFinding[];
|
|
4
|
+
export declare function checkNumberSources({ article, blocks, settings }: RuleInput): QualityFinding[];
|
|
5
|
+
//# sourceMappingURL=ymyl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ymyl.d.ts","sourceRoot":"","sources":["../../../src/quality/rules/ymyl.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAG5C,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE,SAAS,GAAG,cAAc,EAAE,CAWrE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,SAAS,GAAG,cAAc,EAAE,CA2B7F"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { findBareUrls, findFootnoteRefs, findSignificantNumbers, toProse } from "../text.js";
|
|
2
|
+
import { getFootnoteDefinitions } from "./structure.js";
|
|
3
|
+
export function checkSources({ article }) {
|
|
4
|
+
const findings = [];
|
|
5
|
+
if (article.kind === "article" && article.sources.length === 0) {
|
|
6
|
+
findings.push({ rule: "sources-missing", severity: "error", message: "the article lists no sources (name and address of each)" });
|
|
7
|
+
}
|
|
8
|
+
for (const source of article.sources) {
|
|
9
|
+
if (!source.url.startsWith("https://")) {
|
|
10
|
+
findings.push({ rule: "source-https", severity: "error", message: `source "${source.name}" has an address without https: ${source.url}` });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return findings;
|
|
14
|
+
}
|
|
15
|
+
export function checkNumberSources({ article, blocks, settings }) {
|
|
16
|
+
const findings = [];
|
|
17
|
+
const { notation } = settings.ruleset;
|
|
18
|
+
const mark = settings.options.ymyl?.ownCalculationMark ?? null;
|
|
19
|
+
for (const block of blocks) {
|
|
20
|
+
if (block.kind === "footnote" || block.kind === "directive" || block.kind === "heading")
|
|
21
|
+
continue;
|
|
22
|
+
const numbers = findSignificantNumbers(toProse(block.text), notation);
|
|
23
|
+
if (numbers.length > 0 && findFootnoteRefs(block.text).length === 0 && !isTableSourcedNearby(blocks, block)) {
|
|
24
|
+
const remedy = mark === null ? "add a footnote with the source's address" : `add a footnote with the source's address or "${mark}"`;
|
|
25
|
+
findings.push({ rule: "number-source", severity: "error", message: `a number without a source: ${numbers.slice(0, 3).join(", ")}; ${remedy}`, line: block.line });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const sourceUrls = new Set(article.sources.map((source) => source.url));
|
|
29
|
+
for (const [id, block] of getFootnoteDefinitions(blocks)) {
|
|
30
|
+
const urls = findBareUrls(block.text);
|
|
31
|
+
const isOwnCalculation = mark !== null && block.text.toLowerCase().includes(mark.toLowerCase());
|
|
32
|
+
if (urls.length === 0 && !isOwnCalculation) {
|
|
33
|
+
const message = mark === null ? `footnote [^${id}] has no source address` : `footnote [^${id}] has neither a source address nor "${mark}"`;
|
|
34
|
+
findings.push({ rule: "footnote-source", severity: "error", message, line: block.line });
|
|
35
|
+
}
|
|
36
|
+
for (const url of urls) {
|
|
37
|
+
if (!sourceUrls.has(url)) {
|
|
38
|
+
findings.push({ rule: "footnote-not-in-sources", severity: "error", message: `the address in footnote [^${id}] is not in sources: ${url}`, line: block.line });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return findings;
|
|
43
|
+
}
|
|
44
|
+
/** A table has no room for a footnote per cell, so the paragraph right above or below may carry it. */
|
|
45
|
+
function isTableSourcedNearby(blocks, table) {
|
|
46
|
+
if (table.kind !== "table")
|
|
47
|
+
return false;
|
|
48
|
+
const index = blocks.indexOf(table);
|
|
49
|
+
return [blocks[index - 1], blocks[index + 1]].some((neighbour) => neighbour?.kind === "paragraph" && findFootnoteRefs(neighbour.text).length > 0);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=ymyl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ymyl.js","sourceRoot":"","sources":["../../../src/quality/rules/ymyl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ruleset.d.ts","sourceRoot":"","sources":["../../../../src/quality/rulesets/en/ruleset.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,SAAS,EAAE,eA8GvB,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// The English ruleset: the same habits as the Polish one, in English, after `blader/humanizer` and
|
|
2
|
+
// `hardikpandya/stop-slop`. Errors are habits a careful author uses rarely and on purpose; warnings
|
|
3
|
+
// are words that say nothing alone but betray a template in a cluster.
|
|
4
|
+
import { wordPattern } from "../../text.js";
|
|
5
|
+
export const enRuleset = {
|
|
6
|
+
language: "en",
|
|
7
|
+
patterns: [
|
|
8
|
+
{
|
|
9
|
+
id: "announcement",
|
|
10
|
+
severity: "error",
|
|
11
|
+
pattern: wordPattern("it(?:'s| is) (?:worth|important to) (?:noting|note|mentioning|mention|pointing out|highlighting|emphasizing)|it should be noted|needless to say"),
|
|
12
|
+
message: "an announcement instead of a fact; write the fact",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "these-days",
|
|
16
|
+
severity: "error",
|
|
17
|
+
pattern: wordPattern("in today's (?:fast-paced |ever-changing |digital |modern )?(?:world|age|era|landscape)|in this day and age|in the modern era|now more than ever"),
|
|
18
|
+
message: "an empty opener about \"these times\"; start with a number or the answer",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "not-only-but-also",
|
|
22
|
+
severity: "error",
|
|
23
|
+
pattern: wordPattern("not only[^.!?]{1,120}? but also"),
|
|
24
|
+
message: "\"not only…, but also\"; name both things plainly",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "not-x-but-y",
|
|
28
|
+
severity: "error",
|
|
29
|
+
pattern: wordPattern("it(?:'s| is) not (?:just |only |about )?[^.!?,;]{1,60}[,;] it(?:'s| is)|this isn't (?:just )?[^.!?,;]{1,60}[,;] it's"),
|
|
30
|
+
message: "the \"it is not X, it is Y\" contrast; say Y right away",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "meta-commentary",
|
|
34
|
+
severity: "error",
|
|
35
|
+
pattern: wordPattern("in this (?:article|post|guide|piece|blog post)|let's (?:dive|delve|take a (?:closer )?look|explore|break (?:it|this) down)|without further ado|as (?:we )?(?:mentioned|discussed) (?:earlier|above|before)|as you'll see|you'll learn|read on to"),
|
|
36
|
+
message: "the text talks about itself instead of the topic; remove the announcement",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "throat-clearing",
|
|
40
|
+
severity: "error",
|
|
41
|
+
pattern: wordPattern("the truth is|here's (?:why|how|what|the thing)|here's where|interestingly|importantly|undoubtedly|without a doubt|it goes without saying|make no mistake|the reality is"),
|
|
42
|
+
message: "a run-up before the point; cut it and start with the point",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "empty-conclusion",
|
|
46
|
+
severity: "error",
|
|
47
|
+
pattern: wordPattern("in conclusion|to sum up|to summarize|in summary|all in all|at the end of the day|the bottom line is"),
|
|
48
|
+
message: "an empty conclusion; end with the last concrete fact",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "crucial",
|
|
52
|
+
severity: "error",
|
|
53
|
+
pattern: wordPattern("crucial|pivotal|delve|delves|delving|tapestry|testament to"),
|
|
54
|
+
message: "a favourite word of language models; name what depends on the thing",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "plays-a-role",
|
|
58
|
+
severity: "error",
|
|
59
|
+
pattern: wordPattern("plays? an? (?:\\p{L}+ )?role|playing an? (?:\\p{L}+ )?role"),
|
|
60
|
+
message: "\"plays a role\"; write what the thing does",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "puffery",
|
|
64
|
+
severity: "error",
|
|
65
|
+
pattern: wordPattern("game[- ]changer|groundbreaking|revolutionary|cutting-edge|unparalleled|invaluable|holistic|comprehensive guide|ultimate guide|a wide (?:range|array|variety) of|navigat(?:e|ing) the (?:complex |ever-changing )?(?:world|landscape|maze) of|ever-evolving|ever-changing landscape|seamless(?:ly)?|there(?:'s| is) no one-size-fits-all|depends on many factors"),
|
|
66
|
+
message: "an inflated or empty phrase; replace it with a fact",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "chatbot-phrases",
|
|
70
|
+
severity: "error",
|
|
71
|
+
pattern: wordPattern("I hope this helps|let me know if|feel free to|great question|as an AI(?: language model)?|as of my (?:last|knowledge) (?:update|cutoff)|happy to help|I'd be happy to"),
|
|
72
|
+
message: "a leftover of a chat with an assistant; remove it",
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "emoji",
|
|
76
|
+
severity: "error",
|
|
77
|
+
pattern: /\p{Extended_Pictographic}/gu,
|
|
78
|
+
message: "an emoji in the text; remove it",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "filler-words",
|
|
82
|
+
severity: "warning",
|
|
83
|
+
pattern: wordPattern("moreover|furthermore|additionally|in addition|in other words|simply put|very|really|truly|absolutely|definitely|literally|basically|essentially|actually|just|optimal|leverage|robust|remember that"),
|
|
84
|
+
message: "a filler word; check whether the sentence says the same without it",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "exclamation",
|
|
88
|
+
severity: "warning",
|
|
89
|
+
pattern: /!/g,
|
|
90
|
+
message: "an exclamation mark; a guide speaks calmly",
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
firstPersonSingular: wordPattern("I|I'm|I've|I'd|I'll|me|my|mine|myself|in my (?:opinion|view|experience)|personally"),
|
|
94
|
+
profitPromises: wordPattern("guaranteed (?:returns?|profits?|income|gains?)|(?:a )?sure (?:profit|bet|thing)|(?<!not |never |isn't |aren't )risk[- ]free|zero risk|no risk|(?:you )?can(?:'t|not) lose|you will (?:definitely |surely )?(?:profit|earn|get rich)|buy (?:now|today)|you (?:must|need to|have to) (?:buy|invest)|get rich quick|quick (?:profits?|riches)|easy money|effortless (?:income|returns?|wealth)"),
|
|
95
|
+
notation: {
|
|
96
|
+
number: /\d{1,3}(?:,\d{3})+(?:\.\d+)?|\d+(?:\.\d+)?/g,
|
|
97
|
+
unitAfter: /^\s?(?:%|percent\b|per cent\b|USD\b|EUR\b|GBP\b|dollars?\b|euros?\b|pounds?\b|k\b|thousand\b|million\b|billion\b|bn\b)/iu,
|
|
98
|
+
unitBefore: /(?:[$€£]|USD ?|EUR ?|GBP ?)$/u,
|
|
99
|
+
referenceBefore: /(?:§|section|sec\.|art\.|article|no\.|chapter|clause|rule|part)\s*$/iu,
|
|
100
|
+
thousandsSeparator: /,/,
|
|
101
|
+
decimalSeparator: ".",
|
|
102
|
+
},
|
|
103
|
+
triad: /(?<![\p{L}])\p{L}+, \p{L}+,? (?:and|or) \p{L}+(?![\p{L}])/gu,
|
|
104
|
+
flagsTitleCaseHeadings: false,
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=ruleset.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ruleset.js","sourceRoot":"","sources":["../../../../src/quality/rulesets/en/ruleset.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { enRuleset } from "./en/ruleset.js";
|
|
2
|
+
import { plRuleset } from "./pl/ruleset.js";
|
|
3
|
+
import type { LanguageRuleset, QualityLanguage } from "./types.js";
|
|
4
|
+
export declare const QUALITY_RULESETS: Readonly<Record<QualityLanguage, LanguageRuleset>>;
|
|
5
|
+
export { enRuleset, plRuleset };
|
|
6
|
+
export { QUALITY_LANGUAGES, type LanguageRuleset, type QualityLanguage, type StylePattern } from "./types.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/quality/rulesets/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEnE,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,eAAe,CAAC,CAAoC,CAAC;AAErH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// The language rulesets an app chooses from with `blog({ quality: { language } })`.
|
|
2
|
+
import { enRuleset } from "./en/ruleset.js";
|
|
3
|
+
import { plRuleset } from "./pl/ruleset.js";
|
|
4
|
+
export const QUALITY_RULESETS = { en: enRuleset, pl: plRuleset };
|
|
5
|
+
export { enRuleset, plRuleset };
|
|
6
|
+
export { QUALITY_LANGUAGES } from "./types.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/quality/rulesets/index.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,MAAM,CAAC,MAAM,gBAAgB,GAAuD,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC;AAErH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAiE,MAAM,YAAY,CAAC"}
|