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.
- package/README.md +28 -2
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/bin/sks.js +1 -1
- package/dist/cli/command-registry.js +1 -0
- package/dist/core/agents/agent-runner-ollama.js +2 -0
- package/dist/core/agents/native-worker-backend-router.js +3 -0
- package/dist/core/bench.js +115 -0
- package/dist/core/code-structure.js +399 -11
- package/dist/core/codex-control/codex-fake-sdk-adapter.js +67 -9
- package/dist/core/codex-control/gpt-final-arbiter.js +4 -1
- package/dist/core/codex-control/gpt-final-review-schema.js +58 -0
- package/dist/core/codex-native/core-skill-manifest.js +23 -0
- package/dist/core/commands/bench-command.js +11 -2
- package/dist/core/commands/code-structure-command.js +34 -2
- package/dist/core/commands/run-command.js +92 -2
- package/dist/core/commands/seo-command.js +130 -0
- package/dist/core/feature-fixtures.js +6 -0
- package/dist/core/feature-registry.js +3 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +8 -0
- package/dist/core/init.js +8 -6
- package/dist/core/lean-engineering-policy.js +159 -0
- package/dist/core/pipeline-internals/runtime-core.js +15 -5
- package/dist/core/proof/auto-finalize.js +3 -2
- package/dist/core/proof/proof-schema.js +2 -1
- package/dist/core/proof/proof-writer.js +1 -0
- package/dist/core/proof/route-adapter.js +4 -2
- package/dist/core/proof/route-finalizer.js +35 -3
- package/dist/core/routes.js +75 -9
- package/dist/core/search-visibility/adapter-registry.js +26 -0
- package/dist/core/search-visibility/adapters/next-app.js +6 -0
- package/dist/core/search-visibility/adapters/next-pages.js +6 -0
- package/dist/core/search-visibility/adapters/static-site.js +6 -0
- package/dist/core/search-visibility/analyzers.js +377 -0
- package/dist/core/search-visibility/artifacts.js +183 -0
- package/dist/core/search-visibility/discovery.js +347 -0
- package/dist/core/search-visibility/index.js +199 -0
- package/dist/core/search-visibility/mission.js +67 -0
- package/dist/core/search-visibility/mutation.js +314 -0
- package/dist/core/search-visibility/types.js +2 -0
- package/dist/core/search-visibility/verifier.js +60 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/check-architecture.js +40 -7
- package/dist/scripts/check-command-module-budget.js +43 -5
- package/dist/scripts/check-pipeline-budget.js +17 -30
- package/dist/scripts/check-publish-tag.js +33 -6
- package/dist/scripts/check-route-modularity.js +25 -33
- package/dist/scripts/check-runtime-schemas.js +22 -0
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +2 -2
- package/dist/scripts/core-skill-immutable-sync-check.js +3 -2
- package/dist/scripts/core-skill-integrity-blackbox.js +3 -2
- package/dist/scripts/core-skill-manifest-check.js +7 -2
- package/dist/scripts/geo-claim-evidence-check.js +18 -0
- package/dist/scripts/geo-cli-blackbox-check.js +18 -0
- package/dist/scripts/geo-crawler-policy-check.js +16 -0
- package/dist/scripts/geo-llms-txt-optional-check.js +19 -0
- package/dist/scripts/gpt-final-arbiter-check.js +4 -1
- package/dist/scripts/release-parallel-check.js +15 -0
- package/dist/scripts/release-registry-check.js +33 -14
- package/dist/scripts/search-visibility-gate-lib.js +124 -0
- package/dist/scripts/seo-audit-fixture-check.js +16 -0
- package/dist/scripts/seo-canonical-locale-check.js +19 -0
- package/dist/scripts/seo-cli-blackbox-check.js +18 -0
- package/dist/scripts/seo-geo-feature-fixture-quality-check.js +18 -0
- package/dist/scripts/seo-geo-geo-disambiguation-check.js +12 -0
- package/dist/scripts/seo-geo-no-unsupported-ranking-claims-check.js +18 -0
- package/dist/scripts/seo-geo-route-identity-check.js +12 -0
- package/dist/scripts/seo-geo-skill-rich-content-check.js +22 -0
- package/dist/scripts/seo-mutation-rollback-check.js +23 -0
- package/dist/scripts/seo-no-mutation-by-default-check.js +17 -0
- package/dist/scripts/seo-structured-data-visible-content-check.js +19 -0
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +10 -1
- package/package.json +19 -1
- package/schemas/search-visibility/finding-ledger.schema.json +36 -0
- package/schemas/search-visibility/gate.schema.json +22 -0
- package/schemas/search-visibility/mutation-plan.schema.json +27 -0
- package/schemas/search-visibility/site-inventory.schema.json +21 -0
- package/schemas/search-visibility/verification-report.schema.json +23 -0
package/dist/core/routes.js
CHANGED
|
@@ -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
|
|
284
|
+
return leanEngineeringCompactText();
|
|
281
285
|
}
|
|
282
286
|
export function outcomeRubricPolicyText() {
|
|
283
|
-
return 'Outcome rubric policy:
|
|
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>`.
|
|
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', '
|
|
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: '
|
|
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 (
|
|
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|
|
|
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
|