diff --git a/site/docs/reports/INDEX_POLICY_2026-10-25.md b/site/docs/reports/INDEX_POLICY_2026-10-25.md new file mode 100644 index 0000000..5a079d0 --- /dev/null +++ b/site/docs/reports/INDEX_POLICY_2026-10-25.md @@ -0,0 +1,34 @@ +# Toolify365 — search-index configuration inventory, 2026-10-25 + +## Verified by reading GitHub configuration (NOT live Google inspection) + +- All tool definitions in `src/lib/tools/registry.ts`: **223**. +- Active locales in `src/lib/i18n/locales.ts`: **17**. +- Therefore generated tool-locale combinations: **3,791**. +- `CORE_TOOLS_BY_DEMAND`: **29** tools; default `INDEXED_LOCALES = ["en", "ja"]` means **58** core eligible tool-locale URLs. +- `INDEX_EXCEPTIONS`: **22** additional locale/slugs (Thai 4, Russian 4, Turkish 6, Arabic 5, French 1, Spanish 2). +- Total tool-locale URLs explicitly intended to be eligible for index: **80**. +- Tool-locale pages intentionally excluded by configuration: **3,711** (97.89%). This counts tool pages only, not the home, categories, directory, shared routes, or prompts. +- These 3,711 are **intentional noindex**, NOT 3,711 Google crawl/index failures. + +The archived 50-URL Search Console inspection sample in `appdevelopsk/app-infra/gsc/latest.json` reported only 1 indexed and 36 crawled-not-indexed. Since the 50 exact URLs and their chosen indexable status were not available, do not equate the 36 with intentionally noindexed URLs; Google distinguishes these verdicts and requires per-URL inspection. Do not quote `1/50` as a site-wide indexing ratio. + +New reproducible read-only commands from `site/`: + +```sh +npm run audit:index-policy +npm run audit:index-policy -- --json +npm run audit:index-policy -- --urls +``` + +The script loads the actual locale registry and tool metadata, checks duplicate slugs, invalid exception slugs/locales, and that each per-tool hreflang locale list agrees with the indexable decision. It emits the exact 80 **intended** candidate tool URLs for later official Search Console URL Inspection. It never changes robots, meta-noindex, sitemaps, canonical, hreflang, deployed content or Google APIs. + +## Prioritized diagnosis + +1. Inspect exact `en/ja` **29 core pairs** and 22 exceptions with GSC URL Inspection (authenticated owner account). Record `PASS`/coverageState, lastCrawlTime, Google-selected canonical, declared canonical, robots/fetch status and referring sitemap for each. +2. Partition the prior 50 sample by **intended index eligibility** before making any "deindexing" statement. Without exact URLs, the 36 crawled-not-indexed cannot be attributed. +3. Compare 28-day finalized GSC impressions/clicks per 80 intended URL candidates and current public HTML after verified deployment. 2026-10-20..22 recent SMEPost GSC (free 3-day window) returned no opportunities for `https://toolify365.com/`; no inference of zero index coverage. +4. Improve tool-specific original utility and on-page examples before considering changes to the October 11 allowlist. +5. **Do not bulk change indexability/robots/sitemap/canonical before the prior 2026-12-06 freeze end without a new evidence-backed decision.** A code merge or a site: search is not URL Inspection evidence. + +Note: the source inventory counts are accurate for this commit, not a forecast of what Google will index. diff --git a/site/package.json b/site/package.json index 57d0f1a..f8127de 100644 --- a/site/package.json +++ b/site/package.json @@ -21,7 +21,8 @@ "report:calculate": "node scripts/calculate-ranking.mjs", "postbuild": "node scripts/check-built-titles.mjs && node scripts/check-built-canonical.mjs && node scripts/check-built-descriptions.mjs", "check:pressure": "node scripts/check-pressure-reference.mjs", - "check:currency": "node scripts/check-currency-rate-freshness.mjs" + "check:currency": "node scripts/check-currency-rate-freshness.mjs", + "audit:index-policy": "tsx scripts/report-index-policy.ts" }, "dependencies": { "@types/qrcode": "^1.5.6", diff --git a/site/scripts/report-index-policy.ts b/site/scripts/report-index-policy.ts new file mode 100644 index 0000000..e4e9cca --- /dev/null +++ b/site/scripts/report-index-policy.ts @@ -0,0 +1,74 @@ +/** + * Read-only diagnosis of deliberately indexable tool/locale URLs. + * Does NOT call Google APIs, submit sitemaps, or change SEO directives. + * Usage: npm run audit:index-policy [-- --json] [-- --urls] + * + * These are *intended* robots/index candidates, never actual Google coverage. + */ +import { LOCALES, INDEXED_LOCALES } from "../src/lib/i18n/locales"; +import { listTools, INDEXED_SLUGS, INDEX_EXCEPTIONS, isIndexableIn, indexableLocalesFor } from "../src/lib/tools/registry"; + +function inspect() { + const tools = listTools(); + const all = tools.flatMap(tool => LOCALES.map(locale => ({ + url: `https://toolify365.com/${locale}/tools/${tool.slug}`, + slug: tool.slug, + locale, + intendedIndex: isIndexableIn(locale, tool.slug) + }))); + const allowed = all.filter(row => row.intendedIndex); + const byLocale = Object.fromEntries(LOCALES.map(l => [l, { + toolPages: tools.length, + intendedIndex: allowed.filter(x => x.locale === l).length, + intentionallyNoindex: tools.length - allowed.filter(x => x.locale === l).length + }])); + const invalid = []; + const slugs = tools.map(t=>t.slug); + if (new Set(slugs).size !== slugs.length) invalid.push("Duplicate tool slug"); + for (const slug of INDEXED_SLUGS) if (!slugs.includes(slug)) invalid.push("Core slug absent from registry: "+slug); + for (const [locale, exceptions] of Object.entries(INDEX_EXCEPTIONS)) { + if (!LOCALES.includes(locale as (typeof LOCALES)[number])) invalid.push("Unknown exception locale: "+locale); + for(const slug of exceptions) if(!slugs.includes(slug)) invalid.push("Unknown exception tool: "+locale+"/"+slug); + if(new Set(exceptions).size !== exceptions.length) invalid.push("Duplicate exceptions in "+locale); + } + for (const tool of tools) { + const expected = indexableLocalesFor(tool.slug); + for (const locale of LOCALES) { + if (isIndexableIn(locale, tool.slug) !== expected.includes(locale)) { + invalid.push("Mismatch registry indexability: "+locale+"/"+tool.slug); + } + } + } + if (invalid.length) throw Error(invalid.join(" | ")); + return { + runAtUtc: new Date().toISOString(), + warning: "This is a SOURCE CONFIGURATION INTENT audit, NOT a GSC URL Inspection response. 3711 intentionally noindexed pages must not be counted as Google exclusions.", + fullToolLocalePages: all.length, + tools: tools.length, + locales: LOCALES.length, + coreTools: INDEXED_SLUGS.size, + indexedCoreLocales: INDEXED_LOCALES, + explicitlyIndexedExceptionPairs: Object.values(INDEX_EXCEPTIONS).reduce((n,v)=>n+v.length,0), + indexCandidates: allowed.length, + deliberatelyNoindex: all.length - allowed.length, + byLocale, + urls: allowed.map(x=>x.url) + }; +} + +try { + const report=inspect(); + if (process.argv.includes("--json")) { + console.log(JSON.stringify(report,null,2)); + } else if (process.argv.includes("--urls")) { + console.log(report.urls.join("\n")); + } else { + console.log("Toolify intended index candidates: "+report.indexCandidates+"/"+report.fullToolLocalePages+ + " tool-locale pages; intentionally noindex "+report.deliberatelyNoindex); + console.table(report.byLocale); + console.log("Use --urls to get exact 80 intended inspection targets. Actual Google status requires GSC URL Inspection."); + } +} catch (e) { + console.error("[index-policy] FAIL: "+String(e)); + process.exitCode=1; +}