sneakoscope 4.4.0 → 4.6.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.
Files changed (80) hide show
  1. package/README.md +28 -2
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -0
  7. package/dist/core/agents/agent-runner-ollama.js +2 -0
  8. package/dist/core/agents/native-worker-backend-router.js +3 -0
  9. package/dist/core/bench.js +115 -0
  10. package/dist/core/code-structure.js +399 -11
  11. package/dist/core/codex-control/codex-fake-sdk-adapter.js +67 -9
  12. package/dist/core/codex-control/gpt-final-arbiter.js +4 -1
  13. package/dist/core/codex-control/gpt-final-review-schema.js +58 -0
  14. package/dist/core/codex-native/core-skill-manifest.js +23 -0
  15. package/dist/core/commands/bench-command.js +11 -2
  16. package/dist/core/commands/code-structure-command.js +34 -2
  17. package/dist/core/commands/run-command.js +92 -2
  18. package/dist/core/commands/seo-command.js +130 -0
  19. package/dist/core/feature-fixtures.js +6 -0
  20. package/dist/core/feature-registry.js +3 -1
  21. package/dist/core/fsx.js +1 -1
  22. package/dist/core/hooks-runtime.js +8 -0
  23. package/dist/core/init.js +8 -6
  24. package/dist/core/lean-engineering-policy.js +159 -0
  25. package/dist/core/pipeline-internals/runtime-core.js +15 -5
  26. package/dist/core/proof/auto-finalize.js +3 -2
  27. package/dist/core/proof/proof-schema.js +2 -1
  28. package/dist/core/proof/proof-writer.js +1 -0
  29. package/dist/core/proof/route-adapter.js +4 -2
  30. package/dist/core/proof/route-finalizer.js +35 -3
  31. package/dist/core/routes.js +75 -9
  32. package/dist/core/search-visibility/adapter-registry.js +26 -0
  33. package/dist/core/search-visibility/adapters/next-app.js +6 -0
  34. package/dist/core/search-visibility/adapters/next-pages.js +6 -0
  35. package/dist/core/search-visibility/adapters/static-site.js +6 -0
  36. package/dist/core/search-visibility/analyzers.js +377 -0
  37. package/dist/core/search-visibility/artifacts.js +183 -0
  38. package/dist/core/search-visibility/discovery.js +347 -0
  39. package/dist/core/search-visibility/index.js +199 -0
  40. package/dist/core/search-visibility/mission.js +67 -0
  41. package/dist/core/search-visibility/mutation.js +314 -0
  42. package/dist/core/search-visibility/types.js +2 -0
  43. package/dist/core/search-visibility/verifier.js +60 -0
  44. package/dist/core/version.js +1 -1
  45. package/dist/scripts/check-architecture.js +40 -7
  46. package/dist/scripts/check-command-module-budget.js +43 -5
  47. package/dist/scripts/check-pipeline-budget.js +17 -30
  48. package/dist/scripts/check-publish-tag.js +33 -6
  49. package/dist/scripts/check-route-modularity.js +25 -33
  50. package/dist/scripts/check-runtime-schemas.js +22 -0
  51. package/dist/scripts/config-managed-merge-callsite-coverage-check.js +2 -2
  52. package/dist/scripts/core-skill-immutable-sync-check.js +3 -2
  53. package/dist/scripts/core-skill-integrity-blackbox.js +3 -2
  54. package/dist/scripts/core-skill-manifest-check.js +7 -2
  55. package/dist/scripts/geo-claim-evidence-check.js +18 -0
  56. package/dist/scripts/geo-cli-blackbox-check.js +18 -0
  57. package/dist/scripts/geo-crawler-policy-check.js +16 -0
  58. package/dist/scripts/geo-llms-txt-optional-check.js +19 -0
  59. package/dist/scripts/gpt-final-arbiter-check.js +4 -1
  60. package/dist/scripts/release-parallel-check.js +15 -0
  61. package/dist/scripts/release-registry-check.js +33 -14
  62. package/dist/scripts/search-visibility-gate-lib.js +124 -0
  63. package/dist/scripts/seo-audit-fixture-check.js +16 -0
  64. package/dist/scripts/seo-canonical-locale-check.js +19 -0
  65. package/dist/scripts/seo-cli-blackbox-check.js +18 -0
  66. package/dist/scripts/seo-geo-feature-fixture-quality-check.js +18 -0
  67. package/dist/scripts/seo-geo-geo-disambiguation-check.js +12 -0
  68. package/dist/scripts/seo-geo-no-unsupported-ranking-claims-check.js +18 -0
  69. package/dist/scripts/seo-geo-route-identity-check.js +12 -0
  70. package/dist/scripts/seo-geo-skill-rich-content-check.js +22 -0
  71. package/dist/scripts/seo-mutation-rollback-check.js +23 -0
  72. package/dist/scripts/seo-no-mutation-by-default-check.js +17 -0
  73. package/dist/scripts/seo-structured-data-visible-content-check.js +19 -0
  74. package/dist/scripts/sks-3-1-5-directive-check-lib.js +10 -1
  75. package/package.json +19 -1
  76. package/schemas/search-visibility/finding-ledger.schema.json +36 -0
  77. package/schemas/search-visibility/gate.schema.json +22 -0
  78. package/schemas/search-visibility/mutation-plan.schema.json +27 -0
  79. package/schemas/search-visibility/site-inventory.schema.json +21 -0
  80. package/schemas/search-visibility/verification-report.schema.json +23 -0
@@ -1,5 +1,7 @@
1
1
  import { PRODUCT_DESIGN_LEGACY_DESIGN_FALLBACK_SKILLS, PRODUCT_DESIGN_PLUGIN, PRODUCT_DESIGN_REQUIRED_SKILLS, productDesignPluginPolicyText } from './product-design-plugin.js';
2
+ import { leanEngineeringCompactText, leanEngineeringLongText } from './lean-engineering-policy.js';
2
3
  export { productDesignPluginPolicyText };
4
+ export { leanEngineeringCompactText, leanEngineeringLongText };
3
5
  const REFLECTION_SKILL_NAME = 'reflection';
4
6
  export const SOLUTION_SCOUT_SKILL_NAME = 'solution-scout';
5
7
  export const SOLUTION_SCOUT_STAGE_ID = 'solution_scout';
@@ -30,7 +32,7 @@ export const FROM_CHAT_IMG_CHECKLIST_ARTIFACT = 'from-chat-img-checklist.md';
30
32
  export const FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT = 'from-chat-img-temp-triwiki.json';
31
33
  export const FROM_CHAT_IMG_QA_LOOP_ARTIFACT = 'from-chat-img-qa-loop.json';
32
34
  export const FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS = 5;
33
- export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|zellij|tmux|auto-review|team|qa-loop|ppt|image-ux-review|computer-use|goal|fast-mode|research|db|git|codex|codex-app|codex-native|hooks|features|all-features|dfix|commit|commit-and-push|design|imagegen|dollar|context7|ultra-search|xai|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|wrongness|code-structure|proof-field|skill-dream|rust';
35
+ export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|zellij|tmux|auto-review|team|qa-loop|ppt|image-ux-review|computer-use|goal|fast-mode|research|seo-geo-optimizer|db|git|codex|codex-app|codex-native|hooks|features|all-features|dfix|commit|commit-and-push|design|imagegen|dollar|context7|ultra-search|xai|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|wrongness|code-structure|proof-field|skill-dream|rust';
34
36
  export const CODEX_COMPUTER_USE_EVIDENCE_SOURCE = 'codex_computer_use';
35
37
  export const CODEX_IN_APP_BROWSER_EVIDENCE_SOURCE = 'codex_in_app_browser';
36
38
  export const CODEX_CHROME_EXTENSION_EVIDENCE_SOURCE = 'codex_chrome_extension';
@@ -181,6 +183,8 @@ export const RECOMMENDED_SKILLS = [
181
183
  'pipeline-runner',
182
184
  'solution-scout',
183
185
  'context7-docs',
186
+ 'ultra-search',
187
+ 'search-visibility-core',
184
188
  'seo-geo-optimizer',
185
189
  'autoresearch-loop',
186
190
  'performance-evaluator',
@@ -277,13 +281,13 @@ export function chatCaptureIntakeText() {
277
281
  return `From-Chat-IMG intake: explicit signal only. Select forensic visual effort. Treat uploads as chat screenshot plus originals. For web/browser/webapp targets, use the Codex Chrome Extension path first; for native Mac/non-web app surfaces, use Codex Computer Use visual inspection when available. List requirements first in source order, match regions to attachments with confidence, and write ${FROM_CHAT_IMG_WORK_ORDER_ARTIFACT}, ${FROM_CHAT_IMG_SOURCE_INVENTORY_ARTIFACT}, ${FROM_CHAT_IMG_VISUAL_MAP_ARTIFACT}, ${FROM_CHAT_IMG_COVERAGE_ARTIFACT}, ${FROM_CHAT_IMG_CHECKLIST_ARTIFACT}, ${FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT}, and ${FROM_CHAT_IMG_QA_LOOP_ARTIFACT}. ${CODEX_WEB_VERIFICATION_POLICY} ${CODEX_COMPUTER_USE_ONLY_POLICY} Preserve each visible customer request as source-bound text, account for every screenshot image region and separate attachment, map each item to work-order actions, perform the customer-request work, then run a scoped QA-LOOP over that exact work-order range before Team completion. Update checklist checkboxes as work proceeds until all boxes are checked, unresolved_items is empty, scoped_qa_loop_completed=true, QA unresolved findings are zero, and schema validation passes. ${FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT} is temporary TriWiki-backed session context with expires_after_sessions=${FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS}, so it can be forgotten by retention after enough later sessions. Do not assume ordinary image prompts are chat captures.`;
278
282
  }
279
283
  export function noUnrequestedFallbackCodePolicyText() {
280
- return 'No unrequested fallback implementation code: every pipeline stage, executor, reviewer, auto-review profile, and MAD/MAD-SKS invocation must implement only the requested contract. Do not invent alternate code paths, substitute features, compatibility shims, mock behavior, or hidden fallbacks unless the user explicitly requested them or the sealed decision contract names them; if the requested path is impossible, block with evidence instead.';
284
+ return leanEngineeringCompactText();
281
285
  }
282
286
  export function outcomeRubricPolicyText() {
283
- return 'Outcome rubric policy: before adding pipeline stages, use the existing Proof Field, route gate, reflection, and Honest Mode evidence as a compact rubric: goal fit, minimum touched surface, bounded verification, and explicit escalation triggers. Apply Hyperplan-derived adversarial lenses inside that rubric: challenge framing, subtract surface, demand evidence, test integration risk, and consider one simpler alternative. Prefer deleting or skipping unrelated work with evidence over adding a background loop; only add a new mechanism when it reduces net route weight or closes a proven gate gap.';
287
+ return 'Outcome rubric policy: use the Lean Engineering Policy plus existing Proof Field, route gate, reflection, and Honest Mode evidence as the compact rubric: goal fit, minimum touched surface, bounded verification, and explicit escalation triggers.';
284
288
  }
285
289
  export function speedLanePolicyText() {
286
- return 'Proof Field speed lane policy: after the intended write scope is known, run or mentally apply `sks proof-field scan --intent "<goal>" --changed <files>`. If `execution_lane.lane` is `proof_field_fast_lane`, keep the parent-owned minimal patch, listed verification, TriWiki validate, and Honest Mode while skipping Team debate, fresh executor teams, broad route rework, and unrelated checks. If blockers include database, security, visual-forensic, unknown surface, broad change set, failed verification, or unsupported claims, fail closed to the normal Team/Honest path.';
290
+ return 'Proof Field speed lane policy: after the intended write scope is known, run or mentally apply `sks proof-field scan --intent "<goal>" --changed <files>`. Fast lanes keep the parent-owned minimal patch, listed verification, TriWiki validate, and Honest Mode; DB, security, visual-forensic, unknown surface, broad changes, failed verification, or unsupported claims fail closed to the normal Team/Honest path.';
287
291
  }
288
292
  export function hasFromChatImgSignal(prompt = '') {
289
293
  return /(?:^|\s)\$?from-chat-img(?:\s|:|$)/i.test(String(prompt || ''));
@@ -536,13 +540,42 @@ export const ROUTES = [
536
540
  cliEntrypoint: 'sks research prepare|run',
537
541
  examples: ['$Research investigate this idea']
538
542
  },
543
+ {
544
+ id: 'UltraSearch',
545
+ command: '$Ultra-Search',
546
+ mode: 'ULTRA_SEARCH',
547
+ route: 'provider-independent source intelligence',
548
+ description: 'Run UltraSearch source acquisition, source normalization, claim/proof ledgers, and provider-independent citation evidence without requiring xAI/Grok.',
549
+ requiredSkills: ['ultra-search', 'pipeline-runner', 'context7-docs', 'honest-mode'],
550
+ dollarAliases: ['$UltraSearch'],
551
+ lifecycle: ['source_intent', 'query_variants', 'provider_plan', 'source_ledgers', 'claim_ledgers', 'ultra_search_gate', 'honest_mode'],
552
+ context7Policy: 'if_external_docs',
553
+ reasoningPolicy: 'high',
554
+ stopGate: 'ultra-search/ultra-search-gate.json',
555
+ cliEntrypoint: 'sks ultra-search doctor|run|x|fetch|status|inspect|sources|claims|cache|bench|migrate-xai',
556
+ examples: ['$Ultra-Search run "current package release notes"', '$UltraSearch x "site:x.com product launch"']
557
+ },
558
+ {
559
+ id: 'SEOGEOOptimizer',
560
+ command: '$SEO-GEO-OPTIMIZER',
561
+ mode: 'SEO_GEO_OPTIMIZER',
562
+ route: 'search visibility optimization audit/apply/verify',
563
+ description: 'Unified SEO/GEO optimizer route for Search Engine Optimization and Generative Engine Optimization. Uses one shared kernel with mode-specific evidence, gates, safe apply, rollback, and Completion Proof. Not a ranking, traffic, or AI citation guarantee.',
564
+ requiredSkills: ['seo-geo-optimizer', 'search-visibility-core', 'pipeline-runner', REFLECTION_SKILL_NAME, 'honest-mode'],
565
+ lifecycle: ['doctor', 'read_only_audit', 'mode_specific_evidence', 'mutation_plan', 'explicit_apply_only', 'rollback_manifest', 'source_verify', 'seo_or_geo_gate', 'completion_proof', 'honest_mode'],
566
+ context7Policy: 'if_external_docs',
567
+ reasoningPolicy: 'high',
568
+ stopGate: 'seo-gate.json|geo-gate.json',
569
+ cliEntrypoint: 'sks seo-geo-optimizer doctor|audit|plan|apply|verify|status|rollback|fixture --mode seo|geo',
570
+ examples: ['$SEO-GEO-OPTIMIZER audit this site', 'sks seo-geo-optimizer audit --mode seo --target package --json', 'sks seo-geo-optimizer apply latest --mode geo --include-llms-txt --apply']
571
+ },
539
572
  {
540
573
  id: 'AutoResearch',
541
574
  command: '$AutoResearch',
542
575
  mode: 'AUTORESEARCH',
543
576
  route: 'iterative experiment loop',
544
577
  description: 'Program, hypothesize, test, measure, keep/discard, falsify, and report evidence.',
545
- requiredSkills: ['autoresearch', 'autoresearch-loop', 'seo-geo-optimizer', 'performance-evaluator', 'pipeline-runner', 'context7-docs', REFLECTION_SKILL_NAME, 'honest-mode'],
578
+ requiredSkills: ['autoresearch', 'autoresearch-loop', 'performance-evaluator', 'pipeline-runner', 'context7-docs', REFLECTION_SKILL_NAME, 'honest-mode'],
546
579
  lifecycle: ['experiment_ledger', 'metric', 'keep_or_discard', 'falsification', 'post_route_reflection', 'honest_conclusion'],
547
580
  context7Policy: 'required',
548
581
  reasoningPolicy: 'xhigh',
@@ -674,7 +707,7 @@ export const COMMAND_CATALOG = [
674
707
  { name: 'hooks', usage: 'sks hooks explain|status|trust-report|replay|codex-validate|warning-check ... [--json]', description: 'Explain Codex hook events, validate vendored latest 10-event output schemas, replay fixtures, and enforce warning-zero SKS hook policies under the 0.134 compatibility matrix.' },
675
708
  { name: 'codex-lb', usage: 'sks codex-lb status|health|metrics|doctor|circuit|repair|setup ...', description: 'Configure, health-check, repair, and record circuit evidence for codex-lb provider auth without confusing ChatGPT OAuth and proxy keys.' },
676
709
  { name: 'zellij', usage: 'sks zellij status|repair [--json] | sks naruto dashboard latest | sks --mad', description: 'Inspect Zellij runtime status, explain repair (no auto-install), and open the SKS Zellij runtime used by MAD and Naruto lane UI.' },
677
- { name: 'tmux', usage: 'sks tmux [--json]', description: 'Show the removed-runtime migration notice and point operators to Zellij.' },
710
+ { name: 'tmux', usage: 'removed-runtime migration notice (replacement: zellij)', description: 'Show the removed-runtime migration notice and point operators to Zellij.' },
678
711
  { name: 'mad-sks', usage: 'sks mad-sks plan|run|status|proof ... | sks --mad [--high]', description: 'Open or inspect MAD-SKS scoped permission workflows and the Zellij permission launcher.' },
679
712
  { name: 'auto-review', usage: 'sks auto-review status|enable|start [--high] | sks --Auto-review --high', description: 'Enable Codex automatic approval review and launch SKS Zellij with the auto-review profile.' },
680
713
  { name: 'dollar-commands', usage: 'sks dollar-commands [--json]', description: 'List Codex App $ commands such as $DFix and $Naruto.' },
@@ -707,6 +740,7 @@ export const COMMAND_CATALOG = [
707
740
  { name: 'init', usage: 'sks init [--force] [--local-only] [--install-scope global|project]', description: 'Initialize the local SKS control surface.' },
708
741
  { name: 'selftest', usage: 'sks selftest [--mock]', description: 'Run local smoke tests without calling a model.' },
709
742
  { name: 'goal', usage: 'sks goal create|pause|resume|clear|status ...', description: 'Prepare and control the fast SKS bridge overlay for Codex native persisted /goal workflows.' },
743
+ { name: 'seo-geo-optimizer', usage: 'sks seo-geo-optimizer [seo|geo] doctor|audit|plan|apply|verify|status|rollback|fixture [mission|latest] [--mode seo|geo] [--target auto|website|docs|package] [--json]', description: 'Run the unified SEO/GEO optimizer on the shared search-visibility kernel with mode-specific gates and proof.' },
710
744
  { name: 'research', usage: 'sks research prepare|run|status ...', description: 'Run long-form real research missions with xhigh agent Eureka ideas, debate, layered sources, paper, novelty, and falsification gates.' },
711
745
  { name: 'db', usage: 'sks db policy|scan|mcp-config|classify|check ...', description: 'Inspect and enforce database/Supabase safety policy.' },
712
746
  { name: 'eval', usage: 'sks eval run|compare|thresholds ...', description: 'Run deterministic context-quality and performance evidence checks.' },
@@ -748,6 +782,7 @@ export function routeByDollarCommand(commandName) {
748
782
  return ROUTES.find((route) => [
749
783
  dollarSkillName(route.command),
750
784
  ...(route.dollarAliases || []).map((alias) => dollarSkillName(alias)),
785
+ ...(route.hiddenDollarAliases || []).map((alias) => dollarSkillName(alias)),
751
786
  ...(route.appSkillAliases || [])
752
787
  ].includes(key)) || null;
753
788
  }
@@ -849,6 +884,23 @@ export function looksLikeImageUxReviewRequest(prompt = '') {
849
884
  const commandCue = /\$?(?:image-ux-review|ux-review|visual-review|ui-ux-review)\b/i.test(text);
850
885
  return commandCue || (reviewCue && imagegenCue);
851
886
  }
887
+ export function looksLikeGeoLocationRequest(prompt = '') {
888
+ return /\b(?:geolocation|geoip|geo\s*ip|map\s+coordinates?|coordinates?|latitude|longitude|location\s+permission|cdn\s+edge\s+geography|regional?\s+redirect|country\s+routing|지도\s*좌표|위치\s*권한|지역\s*리다이렉트|국가별\s*라우팅)\b/i.test(String(prompt || ''));
889
+ }
890
+ export function looksLikeSeoRequest(prompt = '') {
891
+ const text = String(prompt || '');
892
+ return /\b(?:SEO|search\s+engine\s+optimization|technical\s+seo|canonical|sitemap|robots\.txt|hreflang|structured\s+data|json-ld|indexability|crawlability|metadata|meta\s+description|npm\s+seo|package\s+seo|검색\s*엔진\s*최적화|검색\s*노출|사이트맵|캐노니컬|구조화\s*데이터)\b/i.test(text);
893
+ }
894
+ export function looksLikeGenerativeEngineOptimizationRequest(prompt = '') {
895
+ if (looksLikeGeoLocationRequest(prompt))
896
+ return false;
897
+ const text = String(prompt || '');
898
+ return /\b(?:GEO|generative\s+engine\s+optimization|AI\s+(?:answer|search)\s+(?:visibility|discoverability)|LLM\s+(?:citation|answer|visibility|discoverability)|answerability|entity\s+(?:facts?|clarity)|claim\s+evidence|crawler\s+policy|OAI-SearchBot|GPTBot|ChatGPT-User|Claude-SearchBot|ClaudeBot|Claude-User|llms\.txt|AI\s*검색\s*가시성|AI\s*답변\s*가시성|생성형\s*엔진\s*최적화)\b/i.test(text);
899
+ }
900
+ export function looksLikeUltraSearchRequest(prompt = '') {
901
+ const text = String(prompt || '');
902
+ return /\b(?:UltraSearch|Ultra-Search|ultra\s*search|source\s+intelligence|provider-independent\s+source|source\s+acquisition|citation\s+proof|x-search)\b|울트라\s*서치|소스\s*인텔리전스/i.test(text);
903
+ }
852
904
  export function routePrompt(prompt) {
853
905
  const text = stripVisibleDecisionAnswerBlocks(prompt);
854
906
  const command = dollarCommand(text);
@@ -896,7 +948,13 @@ export function routePrompt(prompt) {
896
948
  return routeById('Team');
897
949
  if (/\b(qa[-\s]?loop|qaloop|e2e\s+qa|qa\s+e2e)\b/i.test(text))
898
950
  return routeById('QALoop');
899
- if (/\b(autoresearch|experiment|benchmark|SEO|GEO|ranking|optimi[sz]e|improve metric|discoverability|visibility|github stars?|npm downloads?|검색|노출|스타|다운로드)\b/i.test(text))
951
+ if (looksLikeUltraSearchRequest(text) && !looksLikeCodeChangingWork(text) && !looksLikeAnswerOnlyRequest(text))
952
+ return routeById('UltraSearch');
953
+ if (looksLikeGenerativeEngineOptimizationRequest(text))
954
+ return routeById('SEOGEOOptimizer');
955
+ if (looksLikeSeoRequest(text))
956
+ return routeById('SEOGEOOptimizer');
957
+ if (/\b(autoresearch|experiment|benchmark|ranking|optimi[sz]e|improve metric|github stars?|npm downloads?|스타|다운로드)\b/i.test(text))
900
958
  return routeById('AutoResearch');
901
959
  if (/\b(research|hypothesis|falsify|novelty|frontier|조사|연구)\b/i.test(text))
902
960
  return routeById('Research');
@@ -993,6 +1051,10 @@ export function routeRequiresSubagents(route, prompt = '') {
993
1051
  return false;
994
1052
  if (route.id === 'ImageUXReview')
995
1053
  return false;
1054
+ if (route.id === 'UltraSearch')
1055
+ return false;
1056
+ if (route.id === 'SEOGEOOptimizer')
1057
+ return false;
996
1058
  if (route.id === 'MadDB')
997
1059
  return false;
998
1060
  if (route.id === 'Research' || route.id === 'AutoResearch')
@@ -1027,7 +1089,7 @@ export function simpleGitOnlyRouteId(prompt = '') {
1027
1089
  }
1028
1090
  export function reflectionRequiredForRoute(route) {
1029
1091
  const id = String(route?.id || route?.mode || route?.route || route || '').replace(/^\$/, '');
1030
- return /^(team|naruto|shadowclone|shadow-clone|kagebunshin|kage-bunshin|qaloop|qa-loop|ppt|imageuxreview|image-ux-review|research|autoresearch|db|database|madsks|mad-sks|maddb|mad-db|gx)$/i.test(id);
1092
+ return /^(team|naruto|shadowclone|shadow-clone|kagebunshin|kage-bunshin|qaloop|qa-loop|ppt|imageuxreview|image-ux-review|research|autoresearch|seo|geo|db|database|madsks|mad-sks|maddb|mad-db|gx)$/i.test(id);
1031
1093
  }
1032
1094
  export function looksLikeCodeChangingWork(prompt = '') {
1033
1095
  const text = String(prompt || '');
@@ -1075,9 +1137,13 @@ export function routeReasoning(route, prompt = '') {
1075
1137
  return teamRouteReasoning(text);
1076
1138
  if (route?.id === 'Research' || route?.id === 'AutoResearch')
1077
1139
  return reasoning('xhigh', 'research_or_experiment_route');
1140
+ if (route?.id === 'UltraSearch')
1141
+ return reasoning('high', 'source_intelligence_route');
1142
+ if (route?.id === 'SEOGEOOptimizer')
1143
+ return reasoning('high', 'search_visibility_route');
1078
1144
  if (route?.id === 'ImageUXReview')
1079
1145
  return reasoning('high', 'image_generation_visual_review_route');
1080
- if (/\b(research|autoresearch|hypothesis|falsify|novelty|frontier|benchmark|experiment|SEO|GEO|ranking|연구|실험|가설|검증)\b/i.test(text))
1146
+ if (/\b(research|autoresearch|hypothesis|falsify|novelty|frontier|benchmark|experiment|ranking|연구|실험|가설|검증)\b/i.test(text))
1081
1147
  return reasoning('xhigh', 'research_level_prompt');
1082
1148
  if (base === 'xhigh')
1083
1149
  return reasoning('xhigh', 'route_policy_xhigh');
@@ -0,0 +1,26 @@
1
+ import { auditSeo } from './analyzers.js';
2
+ import { detectProject, discoverSiteInventory } from './discovery.js';
3
+ import { verifySearchVisibility } from './verifier.js';
4
+ class GenericSearchVisibilityAdapter {
5
+ id = 'generic-search-visibility';
6
+ async detect(ctx) {
7
+ return detectProject(ctx);
8
+ }
9
+ async discover(ctx, detection) {
10
+ return discoverSiteInventory(ctx, detection);
11
+ }
12
+ async audit(ctx, inventory) {
13
+ return auditSeo(ctx.root, inventory);
14
+ }
15
+ async verify(ctx, inventory) {
16
+ return verifySearchVisibility(ctx, inventory, null);
17
+ }
18
+ }
19
+ const GENERIC_ADAPTER = new GenericSearchVisibilityAdapter();
20
+ export function adapterForDetection(_detection) {
21
+ return GENERIC_ADAPTER;
22
+ }
23
+ export function searchVisibilityAdapters() {
24
+ return [GENERIC_ADAPTER];
25
+ }
26
+ //# sourceMappingURL=adapter-registry.js.map
@@ -0,0 +1,6 @@
1
+ export const NEXT_APP_SEARCH_VISIBILITY_ADAPTER_ID = 'next-app';
2
+ export const NEXT_APP_SEARCH_VISIBILITY_SUPPORT = {
3
+ sourceAudit: true,
4
+ mutation: 'guarded_metadata_robots_sitemap_jsonld_when_source_patterns_are_unambiguous',
5
+ };
6
+ //# sourceMappingURL=next-app.js.map
@@ -0,0 +1,6 @@
1
+ export const NEXT_PAGES_SEARCH_VISIBILITY_ADAPTER_ID = 'next-pages';
2
+ export const NEXT_PAGES_SEARCH_VISIBILITY_SUPPORT = {
3
+ sourceAudit: true,
4
+ mutation: 'guarded_metadata_robots_sitemap_jsonld_when_source_patterns_are_unambiguous',
5
+ };
6
+ //# sourceMappingURL=next-pages.js.map
@@ -0,0 +1,6 @@
1
+ export const STATIC_SITE_SEARCH_VISIBILITY_ADAPTER_ID = 'static-site';
2
+ export const STATIC_SITE_SEARCH_VISIBILITY_SUPPORT = {
3
+ sourceAudit: true,
4
+ mutation: 'create_only_policy_files_and_managed_head_blocks_when_ownership_is_clear',
5
+ };
6
+ //# sourceMappingURL=static-site.js.map
@@ -0,0 +1,377 @@
1
+ import path from 'node:path';
2
+ import { readText, sha256 } from '../fsx.js';
3
+ import { officialEvidence, sourceEvidence } from './discovery.js';
4
+ export const GOOGLE_AI_FEATURES_URL = 'https://developers.google.com/search/docs/appearance/ai-features';
5
+ export const GOOGLE_AI_OPTIMIZATION_URL = 'https://developers.google.com/search/docs/fundamentals/ai-optimization-guide';
6
+ export const GOOGLE_STRUCTURED_DATA_URL = 'https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data';
7
+ export const GOOGLE_STRUCTURED_DATA_POLICIES_URL = 'https://developers.google.com/search/docs/appearance/structured-data/sd-policies';
8
+ export const GOOGLE_SITEMAP_URL = 'https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview';
9
+ export const GOOGLE_HREFLANG_URL = 'https://developers.google.com/search/docs/specialty/international/localized-versions';
10
+ export const GOOGLE_CANONICAL_URL = 'https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls';
11
+ export const GOOGLE_ROBOTS_URL = 'https://developers.google.com/search/docs/crawling-indexing/robots/intro';
12
+ export const GOOGLE_SPAM_POLICIES_URL = 'https://developers.google.com/search/docs/essentials/spam-policies';
13
+ export const OPENAI_BOTS_URL = 'https://developers.openai.com/api/docs/bots';
14
+ export const ANTHROPIC_CRAWLERS_URL = 'https://support.claude.com/en/articles/8896518-does-anthropic-crawl-data-from-the-web-and-how-can-site-owners-block-the-crawler';
15
+ export const LLMS_TXT_URL = 'https://llmstxt.org/';
16
+ export async function auditSeo(root, inventory) {
17
+ const findings = [];
18
+ const evidenceBase = packageOrInventoryEvidence(inventory);
19
+ if (inventory.target === 'package' || inventory.package.path) {
20
+ if (!inventory.package.description)
21
+ findings.push(finding('seo-package-description', 'metadata', 'medium', 'Package description is missing.', evidenceBase, ['package.json'], 'Add a concise factual package description.', true));
22
+ if (!inventory.package.keywords.length)
23
+ findings.push(finding('seo-package-keywords', 'metadata', 'low', 'Package keywords are missing.', evidenceBase, ['package.json'], 'Add source-backed npm keywords without stuffing.', true));
24
+ if (!inventory.package.repository)
25
+ findings.push(finding('seo-package-repository', 'metadata', 'medium', 'Repository metadata is missing.', evidenceBase, ['package.json'], 'Set package.json repository to the public project URL.', true));
26
+ if (!inventory.readme.h1)
27
+ findings.push(finding('seo-readme-h1', 'package-docs', 'medium', 'README H1 is missing.', evidenceBase, [inventory.readme.path || 'README.md'], 'Add a clear H1 matching the package/entity name.', false));
28
+ if (!inventory.readme.command_mentions.length)
29
+ findings.push(finding('seo-readme-quickstart', 'package-docs', 'low', 'README quickstart command surface is hard to detect.', evidenceBase, [inventory.readme.path || 'README.md'], 'Include exact install and command spellings.', false));
30
+ }
31
+ const robots = inventory.policy_files.filter((file) => file.kind === 'robots' && file.exists);
32
+ const sitemap = inventory.policy_files.filter((file) => file.kind === 'sitemap' && file.exists);
33
+ if (inventory.target !== 'package') {
34
+ if (!robots.length)
35
+ findings.push(finding('seo-robots-missing', 'indexability', 'low', 'robots.txt was not found; this is not a security issue, but crawl policy is undocumented.', [officialEvidence(GOOGLE_ROBOTS_URL, 'robots.txt is crawl management, not a private-content protection mechanism')], ['robots.txt'], 'Create a managed robots.txt only when project ownership is clear.', true, 'robotsMutation'));
36
+ if (!sitemap.length)
37
+ findings.push(finding('seo-sitemap-missing', 'sitemap', 'medium', 'Sitemap was not found; sitemap discovery signals are absent.', [officialEvidence(GOOGLE_SITEMAP_URL, 'Sitemaps help discovery but do not guarantee indexing')], ['sitemap.xml'], 'Create a sitemap from confirmed canonical/indexable routes.', true, 'sitemapMutation'));
38
+ }
39
+ for (const html of inventory.html_files) {
40
+ const evidence = [sourceEvidence(html.path, 'HTML file inspected')];
41
+ if (!html.title)
42
+ findings.push(finding(`seo-title-missing-${slug(html.path)}`, 'metadata', 'medium', `HTML page ${html.path} is missing a title.`, evidence, [html.path], 'Add a factual title aligned with visible page intent.', true, 'metadataMutation'));
43
+ if (!html.description)
44
+ findings.push(finding(`seo-description-missing-${slug(html.path)}`, 'metadata', 'low', `HTML page ${html.path} is missing a meta description.`, evidence, [html.path], 'Add a concise factual meta description.', true, 'metadataMutation'));
45
+ if (!html.canonical && inventory.origin)
46
+ findings.push(finding(`seo-canonical-missing-${slug(html.path)}`, 'canonical', 'medium', `HTML page ${html.path} has no canonical URL.`, [sourceEvidence(html.path, 'No canonical link found'), officialEvidence(GOOGLE_CANONICAL_URL, 'Canonical links are signals for preferred duplicate URLs')], [html.path], 'Add an absolute canonical URL only from a verified canonical host.', true, 'metadataMutation'));
47
+ if (html.canonical && !/^https?:\/\//i.test(html.canonical))
48
+ findings.push(finding(`seo-canonical-relative-${slug(html.path)}`, 'canonical', 'medium', `HTML page ${html.path} uses a non-absolute canonical URL.`, [sourceEvidence(html.path, `canonical=${html.canonical}`)], [html.path], 'Prefer absolute canonical URLs derived from the verified origin.', true, 'metadataMutation'));
49
+ if (html.jsonLdParseErrors.length)
50
+ findings.push(finding(`seo-jsonld-parse-${slug(html.path)}`, 'structured-data', 'high', `HTML page ${html.path} has invalid JSON-LD.`, evidence, [html.path], 'Fix JSON syntax before rich-result eligibility claims.', false, 'structuredDataMutation'));
51
+ if (html.jsonLdCount && !html.visibleTextSample)
52
+ findings.push(finding(`seo-jsonld-visible-parity-${slug(html.path)}`, 'structured-data', 'high', `HTML page ${html.path} has JSON-LD but little visible text evidence.`, [sourceEvidence(html.path, 'Structured data must describe visible/source-backed content'), officialEvidence(GOOGLE_STRUCTURED_DATA_POLICIES_URL, 'Structured data quality guidelines require truthful page content')], [html.path], 'Verify visible-content parity before adding or relying on structured data.', false));
53
+ for (const href of html.links) {
54
+ if (href.startsWith('/') && href !== '/' && href.includes('//'))
55
+ findings.push(finding(`seo-link-variant-${slug(html.path)}-${slug(href)}`, 'internal-links', 'low', `Internal link ${href} may contain a malformed duplicate slash variant.`, evidence, [html.path], 'Normalize internal links to canonical route variants.', true));
56
+ }
57
+ }
58
+ if (inventory.locale_candidates.length > 1 && !inventory.html_files.some((html) => /alternate/i.test(html.visibleTextSample) || html.links.some((href) => /hreflang/i.test(href)))) {
59
+ findings.push(finding('seo-locale-hreflang-unverified', 'locale', 'medium', 'Multiple locale candidates exist, but hreflang reciprocity is not verified from source inventory.', [officialEvidence(GOOGLE_HREFLANG_URL, 'Localized versions should provide self/reciprocal alternate signals where applicable')], inventory.locale_candidates.map((candidate) => candidate.source), 'Verify self, reciprocal, and x-default locale graph before mutating locale metadata.', false, 'localeMutation'));
60
+ }
61
+ const unsupportedClaims = unsupportedRankingClaims(inventory);
62
+ for (const claim of unsupportedClaims) {
63
+ findings.push(finding(`seo-unsupported-claim-${slug(claim.path)}`, 'truthfulness', 'critical', `Unsupported ranking/traffic guarantee found in ${claim.path}.`, [sourceEvidence(claim.path, claim.text)], [claim.path], 'Remove ranking, traffic, citation, or indexing guarantees unless backed by measured evidence.', false));
64
+ }
65
+ return findings;
66
+ }
67
+ export async function auditGeo(root, inventory) {
68
+ const entityFacts = await buildEntityFacts(root, inventory);
69
+ const claims = await buildClaimEvidence(root, inventory, entityFacts);
70
+ const crawlers = buildCrawlerPolicyRegistry();
71
+ const findings = [];
72
+ const baseEvidence = packageOrInventoryEvidence(inventory);
73
+ if (!entityFacts.canonical_name)
74
+ findings.push(finding('geo-entity-name-missing', 'entity-facts', 'high', 'Canonical entity name could not be established from source evidence.', baseEvidence, ['package.json', 'README.md'], 'Add or align source-backed entity naming before GEO publishing claims.', false));
75
+ if (!entityFacts.facts.length)
76
+ findings.push(finding('geo-entity-facts-empty', 'entity-facts', 'high', 'No source-backed entity facts were found.', baseEvidence, ['package.json', 'README.md'], 'Record official entity facts with visible source locations.', false));
77
+ if (entityFacts.conflicts.length)
78
+ findings.push(finding('geo-entity-fact-conflict', 'entity-facts', 'high', 'Entity facts conflict across package/docs/source evidence.', baseEvidence, entityFacts.conflicts.flatMap((conflict) => conflict.sources), 'Resolve conflicting names, URLs, or claims before generating structured or llms.txt outputs.', false));
79
+ const unsafeClaims = claims.filter((claim) => !claim.safe_to_publish);
80
+ for (const claim of unsafeClaims) {
81
+ findings.push(finding(`geo-unsafe-claim-${slug(claim.id)}`, 'claim-evidence', 'critical', `Claim is not safe to publish: ${claim.claim}`, [sourceEvidence(claim.supporting_source, claim.claim, claim.source_hash)], [claim.supporting_source], 'Do not publish commercial, ranking, pricing, review, or availability claims without source evidence and visible parity.', false));
82
+ }
83
+ if (!inventory.html_files.length && !inventory.readme.path)
84
+ findings.push(finding('geo-answerability-no-visible-source', 'answerability', 'medium', 'No visible README or HTML answer surface was found.', baseEvidence, ['README.md', 'index.html'], 'Add human-visible source content before AI answerability claims.', false));
85
+ const llms = inventory.policy_files.find((file) => file.kind === 'llms' && file.exists);
86
+ if (!llms) {
87
+ findings.push(finding('geo-llms-txt-optional-missing', 'llms-txt', 'info', 'llms.txt is absent; this is not a blocker because llms.txt is optional and experimental.', [officialEvidence(LLMS_TXT_URL, 'llms.txt is a proposal for optional inference-time guidance'), officialEvidence(GOOGLE_AI_OPTIMIZATION_URL, 'Google generative AI search does not require special AI schema or files')], ['llms.txt'], 'Only plan llms.txt when explicitly requested with --include-llms-txt --apply.', true));
88
+ }
89
+ else if (!llms.managed) {
90
+ findings.push(finding('geo-llms-txt-user-authored', 'llms-txt', 'medium', 'Existing llms.txt has no SKS managed marker; full overwrite is blocked.', [sourceEvidence(llms.path, 'Existing llms.txt is user-authored or unmanaged', llms.hash)], [llms.path], 'Preserve user-authored content and use managed merge only when ownership is clear.', false));
91
+ }
92
+ const answerability = buildAnswerabilityReport(inventory, entityFacts, claims);
93
+ return { findings, entityFacts, claims, crawlers, answerability };
94
+ }
95
+ export function buildRouteGraph(inventory) {
96
+ return {
97
+ schema: 'sks.search-visibility.route-graph.v1',
98
+ routes: inventory.routes,
99
+ canonical_edges: inventory.html_files.filter((html) => html.canonical).map((html) => ({ from: routeFromHtmlPath(html.path), to: String(html.canonical), source: html.path })),
100
+ internal_links: inventory.html_files.flatMap((html) => html.links.filter((href) => href.startsWith('/')).map((href) => ({ from: routeFromHtmlPath(html.path), to: href, source: html.path }))),
101
+ redirects: [],
102
+ generated_at: new Date().toISOString(),
103
+ };
104
+ }
105
+ export function buildCanonicalMap(inventory) {
106
+ return {
107
+ schema: 'sks.search-visibility.canonical-map.v1',
108
+ generated_at: new Date().toISOString(),
109
+ origin: inventory.origin,
110
+ groups: inventory.html_files.map((html) => ({
111
+ source: html.path,
112
+ route: routeFromHtmlPath(html.path),
113
+ canonical: html.canonical,
114
+ confidence: html.canonical ? 0.9 : 0.3,
115
+ note: html.canonical ? 'source canonical observed' : 'canonical missing or not verified',
116
+ })),
117
+ warning: 'Canonical signals express preference; final search-engine canonical selection is not guaranteed.',
118
+ };
119
+ }
120
+ export function buildLocaleGraph(inventory) {
121
+ return {
122
+ schema: 'sks.search-visibility.locale-graph.v1',
123
+ generated_at: new Date().toISOString(),
124
+ locales: inventory.locale_candidates,
125
+ checks: {
126
+ self_hreflang_verified: false,
127
+ reciprocal_hreflang_verified: false,
128
+ x_default_verified: false,
129
+ localized_sitemap_rows_verified: false,
130
+ },
131
+ unverified: inventory.locale_candidates.length ? ['hreflang reciprocity requires framework/source-specific verification'] : [],
132
+ };
133
+ }
134
+ export function buildSitemapAudit(inventory) {
135
+ const files = inventory.policy_files.filter((file) => file.kind === 'sitemap' && file.exists);
136
+ return {
137
+ schema: 'sks.search-visibility.sitemap-audit.v1',
138
+ generated_at: new Date().toISOString(),
139
+ sitemap_files: files,
140
+ route_count: inventory.routes.length,
141
+ status: files.length ? 'present_unverified_rows' : 'missing',
142
+ indexing_guarantee: false,
143
+ note: 'Sitemaps are discovery signals and do not guarantee indexing.',
144
+ };
145
+ }
146
+ export function buildRobotsPolicy(inventory, crawlers = buildCrawlerPolicyRegistry()) {
147
+ return {
148
+ schema: 'sks.search-visibility.robots-policy.v1',
149
+ generated_at: new Date().toISOString(),
150
+ robots_files: inventory.policy_files.filter((file) => file.kind === 'robots'),
151
+ crawler_policy_sources: crawlers,
152
+ security_note: 'robots.txt is crawl management, not authentication or private-content protection.',
153
+ mutations_blocked_when_unmanaged: true,
154
+ };
155
+ }
156
+ export function buildStructuredDataLedger(inventory) {
157
+ return {
158
+ schema: 'sks.search-visibility.structured-data-ledger.v1',
159
+ generated_at: new Date().toISOString(),
160
+ pages: inventory.html_files.map((html) => ({
161
+ path: html.path,
162
+ json_ld_count: html.jsonLdCount,
163
+ parse_errors: html.jsonLdParseErrors,
164
+ visible_text_sample_present: Boolean(html.visibleTextSample),
165
+ visible_content_parity: html.jsonLdCount ? (html.visibleTextSample ? 'needs_field_level_review' : 'blocked') : 'not_applicable',
166
+ })),
167
+ policies: [GOOGLE_STRUCTURED_DATA_URL, GOOGLE_STRUCTURED_DATA_POLICIES_URL],
168
+ };
169
+ }
170
+ export function buildInternalLinkGraph(inventory) {
171
+ return {
172
+ schema: 'sks.search-visibility.internal-link-graph.v1',
173
+ generated_at: new Date().toISOString(),
174
+ links: inventory.html_files.flatMap((html) => html.links.map((href) => ({ source: html.path, href, internal: href.startsWith('/') }))),
175
+ orphan_routes: inventory.routes.filter((route) => !inventory.html_files.some((html) => html.links.includes(route.path))).map((route) => route.path),
176
+ };
177
+ }
178
+ export function buildAiCrawlerPolicy() {
179
+ return {
180
+ schema: 'sks.search-visibility.ai-crawler-policy.v1',
181
+ generated_at: new Date().toISOString(),
182
+ entries: buildCrawlerPolicyRegistry(),
183
+ policy: {
184
+ single_allow_ai_toggle: false,
185
+ training_auto_allow: false,
186
+ purpose_split_required: true,
187
+ stale_registry_blocks_mutation: true,
188
+ },
189
+ };
190
+ }
191
+ export function buildLlmsTxtPlan(inventory, facts) {
192
+ const existing = inventory.policy_files.find((file) => file.kind === 'llms' && file.exists);
193
+ return {
194
+ schema: 'sks.search-visibility.llms-txt-plan.v1',
195
+ generated_at: new Date().toISOString(),
196
+ status: existing && !existing.managed ? 'blocked_user_authored_file' : 'optional_candidate',
197
+ experimental_assistive_surface: true,
198
+ required_for_gate: false,
199
+ source: LLMS_TXT_URL,
200
+ candidate_facts: facts.facts,
201
+ privacy_check: {
202
+ private_urls_included: false,
203
+ credentials_included: false,
204
+ },
205
+ blockers: existing && !existing.managed ? ['existing_llms_txt_without_managed_marker'] : [],
206
+ };
207
+ }
208
+ async function buildEntityFacts(root, inventory) {
209
+ const now = new Date().toISOString();
210
+ const facts = [];
211
+ if (inventory.package.name)
212
+ facts.push(entityFact('official_package_name', inventory.package.name, 'package.json#name', [inventory.package.path || 'package.json'], now));
213
+ if (inventory.package.description)
214
+ facts.push(entityFact('official_description', inventory.package.description, 'package.json#description', [inventory.package.path || 'package.json'], now));
215
+ if (inventory.package.repository)
216
+ facts.push(entityFact('official_repository', inventory.package.repository, 'package.json#repository', [inventory.package.path || 'package.json'], now));
217
+ if (inventory.package.homepage)
218
+ facts.push(entityFact('official_homepage', inventory.package.homepage, 'package.json#homepage', [inventory.package.path || 'package.json'], now));
219
+ if (inventory.readme.h1)
220
+ facts.push(entityFact('readme_heading', inventory.readme.h1, `${inventory.readme.path || 'README.md'}#h1`, [inventory.readme.path || 'README.md'], now));
221
+ const conflicts = detectFactConflicts(facts);
222
+ const canonicalName = inventory.readme.h1 || inventory.package.name;
223
+ const canonicalUrl = inventory.package.homepage || inventory.package.repository || inventory.origin;
224
+ return {
225
+ schema: 'sks.search-visibility.entity-facts.v1',
226
+ entity_id: canonicalName ? `entity:${slug(canonicalName)}` : 'entity:unknown',
227
+ type: inventory.package.bin.length ? 'SoftwareApplication' : 'Unknown',
228
+ canonical_name: canonicalName,
229
+ canonical_url: canonicalUrl,
230
+ facts,
231
+ conflicts,
232
+ };
233
+ }
234
+ async function buildClaimEvidence(root, inventory, facts) {
235
+ const claims = facts.facts.map((fact, index) => ({
236
+ id: `claim-${String(index + 1).padStart(3, '0')}`,
237
+ claim: `${fact.key}: ${fact.value}`,
238
+ claim_type: fact.key.includes('repository') || fact.key.includes('homepage') ? 'supporting_source' : 'identity',
239
+ supporting_source: fact.source,
240
+ source_hash: null,
241
+ visible_location: fact.visible_on[0] || null,
242
+ confidence: fact.confidence,
243
+ freshness: fact.freshness,
244
+ contradiction: null,
245
+ safe_to_publish: true,
246
+ verification_level: 'locally_verified',
247
+ }));
248
+ const unsupported = unsupportedRankingClaims(inventory);
249
+ for (const item of unsupported) {
250
+ const full = path.join(root, item.path);
251
+ const text = await readText(full, '');
252
+ claims.push({
253
+ id: `unsafe-${slug(item.path)}`,
254
+ claim: item.text,
255
+ claim_type: 'capability',
256
+ supporting_source: item.path,
257
+ source_hash: text ? sha256(text) : null,
258
+ visible_location: item.path,
259
+ confidence: 0.9,
260
+ freshness: 'unknown',
261
+ contradiction: 'Unsupported ranking, traffic, indexing, or AI citation outcome claim.',
262
+ safe_to_publish: false,
263
+ verification_level: 'implemented',
264
+ });
265
+ }
266
+ return claims;
267
+ }
268
+ function buildAnswerabilityReport(inventory, facts, claims) {
269
+ const questions = [
270
+ {
271
+ representative_question: `What is ${facts.canonical_name || inventory.package.name || 'this project'}?`,
272
+ intent_class: 'entity_identity',
273
+ official_answer_page: inventory.readme.path || inventory.html_files[0]?.path || null,
274
+ answer_section: inventory.readme.h1 || inventory.html_files[0]?.title || null,
275
+ supporting_claim_ids: claims.filter((claim) => claim.safe_to_publish).slice(0, 5).map((claim) => claim.id),
276
+ source_freshness: 'stable',
277
+ internal_discovery_path: inventory.readme.path ? ['README.md'] : inventory.routes.map((route) => route.path).slice(0, 5),
278
+ text_availability: Boolean(inventory.readme.path || inventory.html_files.some((html) => html.visibleTextSample)),
279
+ structured_entity_linkage: facts.canonical_url ? 'present_source_backed' : 'missing',
280
+ gaps: facts.facts.length ? [] : ['entity_facts_missing'],
281
+ },
282
+ ];
283
+ return {
284
+ schema: 'sks.search-visibility.answerability-report.v1',
285
+ generated_at: new Date().toISOString(),
286
+ questions,
287
+ ranking_or_citation_claim: false,
288
+ external_observation: 'not_verified',
289
+ };
290
+ }
291
+ function buildCrawlerPolicyRegistry() {
292
+ const observedAt = '2026-06-26T00:00:00.000Z';
293
+ const expiresAt = '2026-09-24T00:00:00.000Z';
294
+ return [
295
+ crawler('OAI-SearchBot', 'OpenAI', 'search', OPENAI_BOTS_URL, observedAt, expiresAt, 'Used for ChatGPT search visibility; robots.txt can express crawl preference for this user agent.'),
296
+ crawler('GPTBot', 'OpenAI', 'training', OPENAI_BOTS_URL, observedAt, expiresAt, 'Used for model training related crawling; do not auto-allow training crawler without user choice.'),
297
+ crawler('ChatGPT-User', 'OpenAI', 'user_retrieval', OPENAI_BOTS_URL, observedAt, expiresAt, 'User-directed retrieval agent; not the same as search-index eligibility.'),
298
+ crawler('OAI-AdsBot', 'OpenAI', 'ads_validation', OPENAI_BOTS_URL, observedAt, expiresAt, 'Ads/policy validation crawler; separated from search and training purposes.'),
299
+ crawler('Claude-SearchBot', 'Anthropic', 'search', ANTHROPIC_CRAWLERS_URL, observedAt, expiresAt, 'Search indexing crawler for Claude search surfaces.'),
300
+ crawler('ClaudeBot', 'Anthropic', 'training', ANTHROPIC_CRAWLERS_URL, observedAt, expiresAt, 'Model development/training crawler; do not auto-allow without user choice.'),
301
+ crawler('Claude-User', 'Anthropic', 'user_retrieval', ANTHROPIC_CRAWLERS_URL, observedAt, expiresAt, 'User-directed retrieval agent; policy semantics differ from search crawler.'),
302
+ ];
303
+ }
304
+ function crawler(userAgent, provider, purpose, officialSource, observedAt, expiresAt, robotsSemantics) {
305
+ return { userAgent, provider, purpose, officialSource, observedAt, expiresAt, robotsSemantics };
306
+ }
307
+ function packageOrInventoryEvidence(inventory) {
308
+ if (inventory.package.path)
309
+ return [sourceEvidence(inventory.package.path, 'package metadata inspected')];
310
+ if (inventory.readme.path)
311
+ return [sourceEvidence(inventory.readme.path, 'README inspected')];
312
+ return [sourceEvidence('.', 'project inventory inspected')];
313
+ }
314
+ function finding(ruleId, category, severity, summary, evidence, affected, remediation, autoFixable, requiredCapability) {
315
+ const result = {
316
+ id: `F-${ruleId}`,
317
+ ruleId,
318
+ domain: ruleId.startsWith('geo') ? 'geo' : ruleId.startsWith('seo') ? 'seo' : 'shared',
319
+ category,
320
+ severity,
321
+ confidence: evidence.length ? 0.9 : 0.4,
322
+ blocking: severity === 'critical',
323
+ summary,
324
+ evidence,
325
+ affected,
326
+ remediation,
327
+ autoFixable,
328
+ status: evidence.length ? 'confirmed' : 'not_verified',
329
+ };
330
+ if (requiredCapability)
331
+ result.requiredCapability = requiredCapability;
332
+ return result;
333
+ }
334
+ function unsupportedRankingClaims(inventory) {
335
+ const out = [];
336
+ const candidates = [
337
+ ...(inventory.package.description ? [{ path: inventory.package.path || 'package.json', text: inventory.package.description }] : []),
338
+ ...(inventory.readme.h1 ? [{ path: inventory.readme.path || 'README.md', text: inventory.readme.h1 }] : []),
339
+ ];
340
+ for (const html of inventory.html_files) {
341
+ if (html.visibleTextSample)
342
+ candidates.push({ path: html.path, text: html.visibleTextSample });
343
+ }
344
+ for (const candidate of candidates) {
345
+ if (/(guarantee[ds]?|보장|1위|#1|top\s*rank|rank\s*#?1|traffic\s+(?:lift|increase)|AI\s+(?:citation|answer)\s+guarantee|검색\s*유입\s*증가\s*보장|인용\s*보장)/i.test(candidate.text))
346
+ out.push(candidate);
347
+ }
348
+ return out;
349
+ }
350
+ function detectFactConflicts(facts) {
351
+ const byKey = new Map();
352
+ for (const fact of facts) {
353
+ const key = fact.key.replace(/^readme_heading$/, 'official_package_name');
354
+ const current = byKey.get(key) || [];
355
+ current.push(fact);
356
+ byKey.set(key, current);
357
+ }
358
+ const conflicts = [];
359
+ for (const [key, rows] of byKey) {
360
+ const values = Array.from(new Set(rows.map((row) => row.value).filter(Boolean)));
361
+ if (values.length > 1)
362
+ conflicts.push({ key, values, sources: rows.map((row) => row.source) });
363
+ }
364
+ return conflicts;
365
+ }
366
+ function entityFact(key, value, source, visibleOn, observedAt) {
367
+ return { key, value, source, visible_on: visibleOn, observed_at: observedAt, confidence: 1, freshness: 'stable' };
368
+ }
369
+ function routeFromHtmlPath(pathValue) {
370
+ const clean = pathValue.replace(/^public\//, '').replace(/index\.html$/, '').replace(/\.html$/, '');
371
+ const route = `/${clean}`.replace(/\/+/g, '/');
372
+ return route === '/' ? '/' : route.replace(/\/$/, '');
373
+ }
374
+ function slug(value) {
375
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 64) || 'item';
376
+ }
377
+ //# sourceMappingURL=analyzers.js.map