sneakoscope 4.2.0 → 4.3.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 +35 -8
- 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 +3 -1
- package/dist/cli/ultra-search-command.js +163 -0
- package/dist/cli/xai-command.js +28 -168
- package/dist/core/agents/agent-codex-cockpit.js +3 -3
- package/dist/core/agents/agent-runner-ollama.js +2 -0
- package/dist/core/agents/agent-wrongness.js +1 -1
- 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-app-server-v2-client.js +86 -2
- package/dist/core/codex-control/codex-fake-sdk-adapter.js +67 -9
- package/dist/core/codex-control/codex-reliability-shield.js +26 -5
- package/dist/core/codex-control/codex-task-runner.js +7 -1
- 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-control/model-call-concurrency.js +1 -1
- 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/qa-loop-command.js +23 -7
- 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 +9 -1
- 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/qa-loop/qa-app-server-driver.js +134 -0
- package/dist/core/qa-loop/qa-contract-v2.js +231 -0
- package/dist/core/qa-loop/qa-gate-v2.js +132 -0
- package/dist/core/qa-loop/qa-runtime-artifacts.js +53 -0
- package/dist/core/qa-loop/qa-surface-router.js +114 -0
- package/dist/core/qa-loop/qa-types.js +18 -0
- package/dist/core/qa-loop.js +83 -26
- package/dist/core/release/gate-manifest.js +1 -0
- package/dist/core/release/sla-scheduler.js +1 -1
- package/dist/core/release-parallel-full-coverage.js +1 -1
- package/dist/core/routes.js +96 -14
- 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/source-intelligence/source-intelligence-policy.js +45 -26
- package/dist/core/source-intelligence/source-intelligence-proof.js +10 -16
- package/dist/core/source-intelligence/source-intelligence-runner.js +56 -42
- package/dist/core/triwiki/triwiki-affected-graph.js +3 -2
- package/dist/core/trust-kernel/trust-report.js +3 -5
- package/dist/core/ultra-search/index.js +3 -0
- package/dist/core/ultra-search/runtime.js +502 -0
- package/dist/core/ultra-search/types.js +3 -0
- package/dist/core/version.js +1 -1
- package/dist/scripts/agent-visual-consistency-check.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/codex-control-all-pipelines-check.js +1 -0
- package/dist/scripts/codex-control-model-capacity-fallback-check.js +53 -0
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +7 -1
- 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/loop-directive-check-lib.js +78 -1
- package/dist/scripts/qa-loop-app-server-driver-check.js +74 -0
- package/dist/scripts/qa-loop-surface-router-check.js +49 -0
- package/dist/scripts/release-check-dynamic-execute.js +1 -1
- package/dist/scripts/release-metadata-1-19-check.js +2 -2
- package/dist/scripts/release-parallel-check.js +17 -2
- package/dist/scripts/release-parallel-full-coverage-check.js +1 -1
- package/dist/scripts/release-readiness-report.js +6 -6
- package/dist/scripts/release-registry-check.js +33 -14
- package/dist/scripts/runtime-ts-rust-boundary-check.js +1 -1
- 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-1-18-gate-lib.js +2 -2
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +10 -1
- package/dist/scripts/source-intelligence-all-modes-check.js +9 -19
- package/dist/scripts/source-intelligence-policy-check.js +6 -6
- package/dist/scripts/triwiki-affected-graph-check.js +2 -2
- package/dist/scripts/ultra-search-provider-interface-check.js +27 -0
- package/package.json +26 -5
- 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/mcp/xai-mcp-detector.js +0 -157
- package/dist/core/mcp/xai-search-adapter.js +0 -100
- package/dist/scripts/xai-mcp-capability-check.js +0 -14
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { appendJsonl, ensureDir, exists, readJson, readText, sha256, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
5
|
+
import { routeForMode } from './mission.js';
|
|
6
|
+
export async function buildMutationPlan(mode, missionId, artifactDir, inventory, findings, options, entityFacts) {
|
|
7
|
+
const operations = [];
|
|
8
|
+
const blockers = [];
|
|
9
|
+
if (inventory.detected_adapter.confidence < 0.6)
|
|
10
|
+
blockers.push('adapter_detection_confidence_too_low_for_mutation');
|
|
11
|
+
if (inventory.detected_adapter.adapterId === 'unsupported')
|
|
12
|
+
blockers.push('unsupported_framework_mutation_blocked');
|
|
13
|
+
if (!blockers.length && mode === 'seo') {
|
|
14
|
+
operations.push(...await seoOperations(inventory, findings, options));
|
|
15
|
+
}
|
|
16
|
+
if (!blockers.length && mode === 'geo') {
|
|
17
|
+
operations.push(...await geoOperations(inventory, findings, options, entityFacts));
|
|
18
|
+
}
|
|
19
|
+
const plan = {
|
|
20
|
+
schema: 'sks.search-visibility.mutation-plan.v1',
|
|
21
|
+
generated_at: new Date().toISOString(),
|
|
22
|
+
mission_id: missionId,
|
|
23
|
+
route: routeForMode(mode),
|
|
24
|
+
mode,
|
|
25
|
+
adapter: inventory.detected_adapter.adapterId,
|
|
26
|
+
detection_confidence: inventory.detected_adapter.confidence,
|
|
27
|
+
status: blockers.length ? 'blocked' : 'planned',
|
|
28
|
+
operations: operations.filter((op) => scopeAllowed(op, options.scope)),
|
|
29
|
+
blockers,
|
|
30
|
+
unverified: ['production deployment and measured outcomes are outside mutation apply'],
|
|
31
|
+
};
|
|
32
|
+
await writeJsonAtomic(path.join(artifactDir, 'mutation-plan.json'), plan);
|
|
33
|
+
return plan;
|
|
34
|
+
}
|
|
35
|
+
export async function applyMutationPlan(root, missionId, artifactDir, plan, options) {
|
|
36
|
+
const blockers = [...plan.blockers];
|
|
37
|
+
if (!options.apply)
|
|
38
|
+
blockers.push('apply_requires_explicit_--apply');
|
|
39
|
+
const previousRollback = await readJson(path.join(artifactDir, 'rollback-manifest.json'), null);
|
|
40
|
+
const rollback = {
|
|
41
|
+
schema: 'sks.search-visibility.rollback-manifest.v1',
|
|
42
|
+
generated_at: new Date().toISOString(),
|
|
43
|
+
mission_id: missionId,
|
|
44
|
+
route: plan.route,
|
|
45
|
+
operations: previousRollback?.operations || [],
|
|
46
|
+
blockers,
|
|
47
|
+
};
|
|
48
|
+
if (blockers.length) {
|
|
49
|
+
await writeJsonAtomic(path.join(artifactDir, 'rollback-manifest.json'), rollback);
|
|
50
|
+
await appendJournal(artifactDir, blockedEvent(plan.operations[0] || null, 'mutation preconditions failed'));
|
|
51
|
+
return { ok: false, status: 'blocked', applied: 0, rollback, blockers };
|
|
52
|
+
}
|
|
53
|
+
await ensureDir(path.join(artifactDir, 'backups'));
|
|
54
|
+
let applied = 0;
|
|
55
|
+
let idempotent = 0;
|
|
56
|
+
for (const op of plan.operations) {
|
|
57
|
+
const full = path.resolve(root, op.path);
|
|
58
|
+
if (!full.startsWith(path.resolve(root) + path.sep) && full !== path.resolve(root)) {
|
|
59
|
+
blockers.push(`path_outside_root:${op.path}`);
|
|
60
|
+
await appendJournal(artifactDir, blockedEvent(op, 'path outside root'));
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const beforeExists = await exists(full);
|
|
64
|
+
const before = beforeExists ? await readText(full, '') : null;
|
|
65
|
+
const beforeSha = before == null ? null : sha256(before);
|
|
66
|
+
if (op.baseSha256 !== beforeSha) {
|
|
67
|
+
if (op.kind === 'create' && beforeExists && beforeSha === op.proposedSha256) {
|
|
68
|
+
idempotent += 1;
|
|
69
|
+
await appendJournal(artifactDir, {
|
|
70
|
+
schema: 'sks.search-visibility.mutation-journal-event.v1',
|
|
71
|
+
ts: new Date().toISOString(),
|
|
72
|
+
operation_id: op.id,
|
|
73
|
+
event: 'applied',
|
|
74
|
+
path: op.path,
|
|
75
|
+
before_sha256: beforeSha,
|
|
76
|
+
after_sha256: beforeSha,
|
|
77
|
+
message: 'operation already applied; idempotent no-op preserved existing rollback manifest',
|
|
78
|
+
});
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
blockers.push(`base_hash_mismatch:${op.path}`);
|
|
82
|
+
await appendJournal(artifactDir, blockedEvent(op, 'base hash mismatch'));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const dirty = await gitDirtyStatus(root, op.path);
|
|
86
|
+
if (dirty && !options.allowDirtyTouched) {
|
|
87
|
+
blockers.push(`dirty_touched_path:${op.path}`);
|
|
88
|
+
await appendJournal(artifactDir, blockedEvent(op, `dirty touched path blocked: ${dirty}`));
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (op.kind === 'create' && beforeExists) {
|
|
92
|
+
blockers.push(`create_would_overwrite_existing:${op.path}`);
|
|
93
|
+
await appendJournal(artifactDir, blockedEvent(op, 'existing user-authored path'));
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const backupPath = before == null ? null : path.join('backups', `${op.id}-${path.basename(op.path)}.bak`);
|
|
97
|
+
if (backupPath && before != null)
|
|
98
|
+
await writeTextAtomic(path.join(artifactDir, backupPath), before);
|
|
99
|
+
if (op.content == null) {
|
|
100
|
+
blockers.push(`operation_content_missing:${op.id}`);
|
|
101
|
+
await appendJournal(artifactDir, blockedEvent(op, 'operation content missing'));
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
await writeTextAtomic(full, op.content);
|
|
105
|
+
const after = await readText(full, '');
|
|
106
|
+
const afterSha = sha256(after);
|
|
107
|
+
rollback.operations.push({
|
|
108
|
+
operation_id: op.id,
|
|
109
|
+
path: op.path,
|
|
110
|
+
inverse: before == null ? 'delete-created' : 'restore-content',
|
|
111
|
+
before_sha256: beforeSha,
|
|
112
|
+
after_sha256: afterSha,
|
|
113
|
+
backup_path: backupPath,
|
|
114
|
+
});
|
|
115
|
+
applied += 1;
|
|
116
|
+
await appendJournal(artifactDir, {
|
|
117
|
+
schema: 'sks.search-visibility.mutation-journal-event.v1',
|
|
118
|
+
ts: new Date().toISOString(),
|
|
119
|
+
operation_id: op.id,
|
|
120
|
+
event: 'applied',
|
|
121
|
+
path: op.path,
|
|
122
|
+
before_sha256: beforeSha,
|
|
123
|
+
after_sha256: afterSha,
|
|
124
|
+
message: 'operation applied with base hash verification',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
rollback.blockers = blockers;
|
|
128
|
+
await writeJsonAtomic(path.join(artifactDir, 'rollback-manifest.json'), rollback);
|
|
129
|
+
const completed = applied + idempotent;
|
|
130
|
+
return { ok: blockers.length === 0 && completed === plan.operations.length, status: blockers.length ? 'blocked' : 'applied', applied, rollback, blockers };
|
|
131
|
+
}
|
|
132
|
+
export async function rollbackMutationPlan(root, artifactDir, apply) {
|
|
133
|
+
const manifest = await readJson(path.join(artifactDir, 'rollback-manifest.json'), {
|
|
134
|
+
schema: 'sks.search-visibility.rollback-manifest.v1',
|
|
135
|
+
generated_at: new Date().toISOString(),
|
|
136
|
+
mission_id: 'unknown',
|
|
137
|
+
route: '$SEO-GEO-OPTIMIZER',
|
|
138
|
+
operations: [],
|
|
139
|
+
blockers: ['rollback_manifest_missing'],
|
|
140
|
+
});
|
|
141
|
+
const blockers = [...(manifest.blockers || [])].filter((blocker) => blocker !== 'apply_requires_explicit_--apply');
|
|
142
|
+
if (!apply)
|
|
143
|
+
return { ok: true, status: 'planned', rolled_back: 0, blockers: ['rollback_requires_explicit_--apply'] };
|
|
144
|
+
let rolledBack = 0;
|
|
145
|
+
for (const op of [...manifest.operations].reverse()) {
|
|
146
|
+
const full = path.resolve(root, op.path);
|
|
147
|
+
const current = await exists(full) ? await readText(full, '') : null;
|
|
148
|
+
const currentSha = current == null ? null : sha256(current);
|
|
149
|
+
if (op.after_sha256 !== currentSha) {
|
|
150
|
+
blockers.push(`rollback_hash_mismatch:${op.path}`);
|
|
151
|
+
await appendJournal(artifactDir, {
|
|
152
|
+
schema: 'sks.search-visibility.mutation-journal-event.v1',
|
|
153
|
+
ts: new Date().toISOString(),
|
|
154
|
+
operation_id: op.operation_id,
|
|
155
|
+
event: 'blocked',
|
|
156
|
+
path: op.path,
|
|
157
|
+
before_sha256: currentSha,
|
|
158
|
+
after_sha256: null,
|
|
159
|
+
message: 'rollback blocked because current hash differs from manifest',
|
|
160
|
+
});
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (op.inverse === 'delete-created') {
|
|
164
|
+
await fs.rm(full, { force: true });
|
|
165
|
+
}
|
|
166
|
+
else if (op.inverse === 'restore-content' && op.backup_path) {
|
|
167
|
+
const backup = await readText(path.join(artifactDir, op.backup_path), null);
|
|
168
|
+
if (backup == null) {
|
|
169
|
+
blockers.push(`rollback_backup_missing:${op.path}`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
await writeTextAtomic(full, backup);
|
|
173
|
+
}
|
|
174
|
+
rolledBack += 1;
|
|
175
|
+
await appendJournal(artifactDir, {
|
|
176
|
+
schema: 'sks.search-visibility.mutation-journal-event.v1',
|
|
177
|
+
ts: new Date().toISOString(),
|
|
178
|
+
operation_id: op.operation_id,
|
|
179
|
+
event: 'rolled_back',
|
|
180
|
+
path: op.path,
|
|
181
|
+
before_sha256: op.after_sha256,
|
|
182
|
+
after_sha256: op.before_sha256,
|
|
183
|
+
message: 'operation rolled back from manifest',
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return { ok: blockers.length === 0, status: blockers.length ? 'blocked' : 'rolled_back', rolled_back: rolledBack, blockers };
|
|
187
|
+
}
|
|
188
|
+
async function seoOperations(inventory, findings, options) {
|
|
189
|
+
const operations = [];
|
|
190
|
+
const robotsMissing = findings.some((finding) => finding.ruleId === 'seo-robots-missing');
|
|
191
|
+
const sitemapMissing = findings.some((finding) => finding.ruleId === 'seo-sitemap-missing');
|
|
192
|
+
if (robotsMissing && inventory.detected_adapter.capabilities.robotsMutation) {
|
|
193
|
+
const rel = await preferredPolicyPath(options.root, 'robots.txt');
|
|
194
|
+
const content = managedHeader('robots.txt') + [
|
|
195
|
+
'User-agent: *',
|
|
196
|
+
'Allow: /',
|
|
197
|
+
'',
|
|
198
|
+
inventory.origin ? `Sitemap: ${trimSlash(inventory.origin)}/sitemap.xml` : '# Sitemap: add verified origin before publishing sitemap directive',
|
|
199
|
+
'',
|
|
200
|
+
].join('\n');
|
|
201
|
+
operations.push(createOperation('seo-create-robots', rel, content, ['F-seo-robots-missing'], ['sks seo-geo-optimizer verify <mission> --mode seo --strict']));
|
|
202
|
+
}
|
|
203
|
+
if (sitemapMissing && inventory.detected_adapter.capabilities.sitemapMutation && inventory.origin) {
|
|
204
|
+
const rel = await preferredPolicyPath(options.root, 'sitemap.xml');
|
|
205
|
+
const urls = (inventory.routes.length ? inventory.routes : [{ path: '/', source: 'fallback', kind: 'static', locale: null }]).filter((route) => route.kind === 'static').slice(0, 500);
|
|
206
|
+
const content = [
|
|
207
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
208
|
+
'<!-- sks-search-visibility managed sitemap; sitemap is discovery evidence, not an indexing guarantee. -->',
|
|
209
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
|
210
|
+
...urls.map((route) => ` <url><loc>${xmlEscape(trimSlash(inventory.origin || '') + (route.path === '/' ? '/' : route.path))}</loc></url>`),
|
|
211
|
+
'</urlset>',
|
|
212
|
+
'',
|
|
213
|
+
].join('\n');
|
|
214
|
+
operations.push(createOperation('seo-create-sitemap', rel, content, ['F-seo-sitemap-missing'], ['sks seo-geo-optimizer verify <mission> --mode seo --strict']));
|
|
215
|
+
}
|
|
216
|
+
return operations;
|
|
217
|
+
}
|
|
218
|
+
async function geoOperations(inventory, findings, options, entityFacts) {
|
|
219
|
+
if (!options.includeLlmsTxt)
|
|
220
|
+
return [];
|
|
221
|
+
const existing = inventory.policy_files.find((file) => file.kind === 'llms' && file.exists);
|
|
222
|
+
if (existing && !existing.managed)
|
|
223
|
+
return [];
|
|
224
|
+
if (existing)
|
|
225
|
+
return [];
|
|
226
|
+
const facts = entityFacts?.facts || [];
|
|
227
|
+
const rel = await preferredRootPath(options.root, 'llms.txt');
|
|
228
|
+
const factLines = facts.slice(0, 20).map((fact) => `- ${fact.key}: ${fact.value} (source: ${fact.source})`);
|
|
229
|
+
const content = managedHeader('llms.txt') + [
|
|
230
|
+
`# ${entityFacts?.canonical_name || inventory.package.name || 'Project'} llms.txt`,
|
|
231
|
+
'',
|
|
232
|
+
'> Optional experimental assistant surface generated from source-backed facts. It does not guarantee AI search visibility, citation, ranking, or traffic.',
|
|
233
|
+
'',
|
|
234
|
+
'## Official Sources',
|
|
235
|
+
...(inventory.package.repository ? [`- Repository: ${inventory.package.repository}`] : []),
|
|
236
|
+
...(inventory.package.homepage ? [`- Homepage: ${inventory.package.homepage}`] : []),
|
|
237
|
+
'',
|
|
238
|
+
'## Source-Backed Facts',
|
|
239
|
+
...(factLines.length ? factLines : ['- No publish-safe facts were available.']),
|
|
240
|
+
'',
|
|
241
|
+
].join('\n');
|
|
242
|
+
return [createOperation('geo-create-llms-txt', rel, content, findings.filter((finding) => finding.ruleId === 'geo-llms-txt-optional-missing').map((finding) => finding.id), ['sks seo-geo-optimizer verify <mission> --mode geo --strict'])];
|
|
243
|
+
}
|
|
244
|
+
function createOperation(id, rel, content, findingIds, requiredVerification) {
|
|
245
|
+
return {
|
|
246
|
+
id,
|
|
247
|
+
path: rel,
|
|
248
|
+
baseSha256: null,
|
|
249
|
+
proposedSha256: sha256(content),
|
|
250
|
+
kind: 'create',
|
|
251
|
+
owner: 'sks-search-visibility',
|
|
252
|
+
findingIds,
|
|
253
|
+
reversible: true,
|
|
254
|
+
preview: `Create ${rel} with SKS managed search-visibility content.`,
|
|
255
|
+
content,
|
|
256
|
+
risk: 'low',
|
|
257
|
+
requiredVerification,
|
|
258
|
+
scopeAuthorization: [id, rel],
|
|
259
|
+
ownershipStrategy: 'create-only; never overwrite user-authored files',
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async function preferredPolicyPath(root, file) {
|
|
263
|
+
return await exists(path.join(root, 'public')) ? `public/${file}` : file;
|
|
264
|
+
}
|
|
265
|
+
async function preferredRootPath(_root, file) {
|
|
266
|
+
return file;
|
|
267
|
+
}
|
|
268
|
+
function scopeAllowed(op, scope) {
|
|
269
|
+
if (!scope.length)
|
|
270
|
+
return true;
|
|
271
|
+
return scope.some((item) => op.scopeAuthorization.includes(item) || op.path === item || op.id === item);
|
|
272
|
+
}
|
|
273
|
+
function managedHeader(label) {
|
|
274
|
+
return [
|
|
275
|
+
`# sks-search-visibility managed ${label}`,
|
|
276
|
+
'# owner: sks-search-visibility',
|
|
277
|
+
'# edit policy: generated only after explicit --apply; safe to remove through sks seo-geo-optimizer rollback',
|
|
278
|
+
'',
|
|
279
|
+
].join('\n');
|
|
280
|
+
}
|
|
281
|
+
function trimSlash(value) {
|
|
282
|
+
return value.replace(/\/+$/, '');
|
|
283
|
+
}
|
|
284
|
+
function xmlEscape(value) {
|
|
285
|
+
return value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
286
|
+
}
|
|
287
|
+
async function appendJournal(artifactDir, event) {
|
|
288
|
+
await appendJsonl(path.join(artifactDir, 'mutation-journal.jsonl'), event);
|
|
289
|
+
}
|
|
290
|
+
function blockedEvent(op, message) {
|
|
291
|
+
return {
|
|
292
|
+
schema: 'sks.search-visibility.mutation-journal-event.v1',
|
|
293
|
+
ts: new Date().toISOString(),
|
|
294
|
+
operation_id: op?.id || 'none',
|
|
295
|
+
event: 'blocked',
|
|
296
|
+
path: op?.path || '',
|
|
297
|
+
before_sha256: null,
|
|
298
|
+
after_sha256: null,
|
|
299
|
+
message,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
async function gitDirtyStatus(root, rel) {
|
|
303
|
+
if (!(await exists(path.join(root, '.git'))))
|
|
304
|
+
return '';
|
|
305
|
+
const result = spawnSync('git', ['status', '--porcelain', '--', rel], {
|
|
306
|
+
cwd: root,
|
|
307
|
+
encoding: 'utf8',
|
|
308
|
+
stdio: 'pipe',
|
|
309
|
+
});
|
|
310
|
+
if (result.status !== 0)
|
|
311
|
+
return '';
|
|
312
|
+
return String(result.stdout || '').trim();
|
|
313
|
+
}
|
|
314
|
+
//# sourceMappingURL=mutation.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { exists } from '../fsx.js';
|
|
3
|
+
import { SEARCH_VISIBILITY_DIR, routeForMode } from './mission.js';
|
|
4
|
+
const COMMON_REQUIRED = [
|
|
5
|
+
'intake.json',
|
|
6
|
+
'adapter-detection.json',
|
|
7
|
+
'site-inventory.json',
|
|
8
|
+
'route-graph.json',
|
|
9
|
+
'robots-policy.json',
|
|
10
|
+
'structured-data-ledger.json',
|
|
11
|
+
];
|
|
12
|
+
export async function verifySearchVisibility(ctx, inventory, mission) {
|
|
13
|
+
const route = routeForMode(ctx.mode);
|
|
14
|
+
const required = [
|
|
15
|
+
...COMMON_REQUIRED,
|
|
16
|
+
ctx.mode === 'seo' ? 'seo-findings.json' : 'geo-findings.json',
|
|
17
|
+
];
|
|
18
|
+
const checked = mission
|
|
19
|
+
? await Promise.all(required.map(async (artifact) => {
|
|
20
|
+
const file = path.join(mission.artifactDir, artifact);
|
|
21
|
+
const present = await exists(file);
|
|
22
|
+
return { path: path.relative(mission.dir, file).split(path.sep).join('/'), ok: present, message: present ? 'present' : 'missing' };
|
|
23
|
+
}))
|
|
24
|
+
: [];
|
|
25
|
+
const blockers = checked.filter((item) => !item.ok).map((item) => `missing_artifact:${item.path}`);
|
|
26
|
+
const productionVerified = Boolean(ctx.origin && !ctx.offline);
|
|
27
|
+
const unverified = [
|
|
28
|
+
...(ctx.origin && !ctx.offline ? [] : ['production_http_not_verified']),
|
|
29
|
+
...(ctx.framework === 'unsupported' ? ['framework_specific_mutation_not_verified'] : []),
|
|
30
|
+
...(ctx.mode === 'geo' ? ['external_ai_answer_observation_not_verified', 'measured_outcome_pending'] : ['search_ranking_or_traffic_outcome_not_measured']),
|
|
31
|
+
...(!ctx.strict ? ['strict_mode_not_requested'] : []),
|
|
32
|
+
];
|
|
33
|
+
const status = blockers.length
|
|
34
|
+
? 'blocked'
|
|
35
|
+
: productionVerified
|
|
36
|
+
? 'production_verified'
|
|
37
|
+
: 'verified_partial';
|
|
38
|
+
return {
|
|
39
|
+
schema: 'sks.search-visibility.verification-report.v1',
|
|
40
|
+
generated_at: new Date().toISOString(),
|
|
41
|
+
mission_id: mission?.id || 'ad-hoc',
|
|
42
|
+
route,
|
|
43
|
+
status,
|
|
44
|
+
source_verified: inventory.detected_adapter.capabilities.sourceAudit,
|
|
45
|
+
build_verified: false,
|
|
46
|
+
http_verified: productionVerified,
|
|
47
|
+
browser_verified: false,
|
|
48
|
+
production_verified: productionVerified,
|
|
49
|
+
measured_outcome: 'pending',
|
|
50
|
+
checked_artifacts: checked,
|
|
51
|
+
blockers,
|
|
52
|
+
unverified,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function expectedArtifactPath(missionId, artifact) {
|
|
56
|
+
if (artifact.endsWith('-gate.json') || artifact === 'completion-proof.json')
|
|
57
|
+
return `.sneakoscope/missions/${missionId}/${artifact}`;
|
|
58
|
+
return `.sneakoscope/missions/${missionId}/${SEARCH_VISIBILITY_DIR}/${artifact}`;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=verifier.js.map
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
3
|
-
export const SOURCE_INTELLIGENCE_POLICY_SCHEMA = 'sks.source-intelligence-policy.
|
|
3
|
+
export const SOURCE_INTELLIGENCE_POLICY_SCHEMA = 'sks.source-intelligence-policy.v2';
|
|
4
4
|
export function buildSourceIntelligencePolicy(input = {}) {
|
|
5
5
|
const route = input.route || 'unknown';
|
|
6
6
|
const offline = input.offline === true;
|
|
7
|
+
const query = input.query || '';
|
|
8
|
+
const docsIntent = /\b(package|npm|SDK|API|MCP|framework|library|docs?|문서|React|Next\.js|Prisma|Tailwind)\b/i.test(query);
|
|
9
|
+
const xIntent = /\b(?:x\.com|twitter\.com|X\/Twitter|트위터|엑스|site:x\.com|site:twitter\.com)\b/i.test(query);
|
|
10
|
+
const urlIntent = /https?:\/\/[^\s)"']+/i.test(query);
|
|
7
11
|
const context7Available = input.context7Available !== false;
|
|
8
12
|
const codex = input.codexWebCapability || {
|
|
9
13
|
schema: 'sks.codex-web-search-capability.v1',
|
|
@@ -11,43 +15,65 @@ export function buildSourceIntelligencePolicy(input = {}) {
|
|
|
11
15
|
status: 'degraded_unverified',
|
|
12
16
|
reason: 'capability_not_checked'
|
|
13
17
|
};
|
|
14
|
-
const xai = input.xaiDetection;
|
|
15
|
-
const xaiSearchCapable = xai?.search_capable === true;
|
|
16
|
-
const xaiConfigured = xai?.configured === true;
|
|
17
18
|
const blockers = [];
|
|
18
19
|
const warnings = [];
|
|
19
20
|
const wrongnessKinds = [];
|
|
20
|
-
if (!context7Available) {
|
|
21
|
+
if (docsIntent && !context7Available) {
|
|
21
22
|
blockers.push('docs_context_missing');
|
|
22
23
|
wrongnessKinds.push('context7_missing');
|
|
23
24
|
}
|
|
24
25
|
if (!offline && codex.status === 'unavailable') {
|
|
25
|
-
warnings.push('
|
|
26
|
+
warnings.push('codex_web_search_unavailable_degraded_to_ultra_cache_or_docs');
|
|
26
27
|
wrongnessKinds.push('codex_web_search_missing');
|
|
27
28
|
}
|
|
28
|
-
if (
|
|
29
|
-
warnings.push('
|
|
30
|
-
|
|
31
|
-
warnings.push('xai_mcp_configured_but_search_capability_unverified');
|
|
32
|
-
const mode = !context7Available
|
|
29
|
+
if (input.xaiDetection)
|
|
30
|
+
warnings.push('xai_detection_input_ignored_by_source_intelligence_v2');
|
|
31
|
+
const mode = input.mode || (!context7Available && docsIntent
|
|
33
32
|
? 'blocked'
|
|
34
33
|
: offline
|
|
35
|
-
? '
|
|
36
|
-
:
|
|
37
|
-
? '
|
|
38
|
-
:
|
|
39
|
-
? '
|
|
40
|
-
:
|
|
34
|
+
? 'offline_cache'
|
|
35
|
+
: urlIntent
|
|
36
|
+
? 'url_acquisition'
|
|
37
|
+
: xIntent
|
|
38
|
+
? 'x_search'
|
|
39
|
+
: /deep|exhaustive|가능한 전부|누락 없이|완벽하게 조사/i.test(query)
|
|
40
|
+
? 'ultra_deep'
|
|
41
|
+
: 'ultra_balanced');
|
|
42
|
+
const selected = new Set();
|
|
43
|
+
if (docsIntent && context7Available)
|
|
44
|
+
selected.add('context7');
|
|
45
|
+
if (!offline && codex.status !== 'unavailable')
|
|
46
|
+
selected.add('codex_web');
|
|
47
|
+
if (xIntent || mode === 'x_search')
|
|
48
|
+
selected.add('x_public');
|
|
49
|
+
if (offline)
|
|
50
|
+
selected.add('offline_cache');
|
|
41
51
|
return {
|
|
42
52
|
schema: SOURCE_INTELLIGENCE_POLICY_SCHEMA,
|
|
43
53
|
generated_at: nowIso(),
|
|
44
54
|
ok: blockers.length === 0,
|
|
45
55
|
route,
|
|
46
56
|
mode,
|
|
57
|
+
requirements: {
|
|
58
|
+
official_sources: docsIntent,
|
|
59
|
+
full_content: mode !== 'ultra_fast',
|
|
60
|
+
counter_search: mode === 'ultra_deep' || mode === 'ultra_exhaustive',
|
|
61
|
+
claim_ledger: true,
|
|
62
|
+
social_recency: xIntent || mode === 'x_search',
|
|
63
|
+
code_execution_verification: /\b(code|implementation|test|runtime|구현)\b/i.test(query)
|
|
64
|
+
},
|
|
65
|
+
capabilities: {
|
|
66
|
+
docs: context7Available ? ['context7', 'official_web'] : ['official_web'],
|
|
67
|
+
web_search: !offline ? ['codex_web'] : [],
|
|
68
|
+
repo_search: ['github'],
|
|
69
|
+
social: xIntent || mode === 'x_search' ? ['x_public', 'authenticated_chrome_optional', 'official_x_api_optional'] : [],
|
|
70
|
+
browser: ['codex_browser_optional', 'codex_chrome_optional']
|
|
71
|
+
},
|
|
72
|
+
selected_providers: [...selected],
|
|
47
73
|
context7: {
|
|
48
|
-
required:
|
|
74
|
+
required: docsIntent,
|
|
49
75
|
available: context7Available,
|
|
50
|
-
status: context7Available ? (offline ? 'offline_only' : 'available') : 'missing'
|
|
76
|
+
status: docsIntent ? (context7Available ? (offline ? 'offline_only' : 'available') : 'missing') : 'not_required'
|
|
51
77
|
},
|
|
52
78
|
codex_web_search: {
|
|
53
79
|
required: !offline,
|
|
@@ -55,13 +81,6 @@ export function buildSourceIntelligencePolicy(input = {}) {
|
|
|
55
81
|
status: codex.status,
|
|
56
82
|
reason: codex.reason
|
|
57
83
|
},
|
|
58
|
-
xai_mcp: {
|
|
59
|
-
required: xaiSearchCapable,
|
|
60
|
-
configured: xaiConfigured,
|
|
61
|
-
search_capable: xaiSearchCapable,
|
|
62
|
-
configured_but_unverified: xai?.configured_but_unverified === true,
|
|
63
|
-
status: xai?.status || 'not_checked'
|
|
64
|
-
},
|
|
65
84
|
wrongness_kinds: wrongnessKinds,
|
|
66
85
|
blockers,
|
|
67
86
|
warnings
|
|
@@ -1,25 +1,21 @@
|
|
|
1
|
-
export const SOURCE_INTELLIGENCE_PROOF_SCHEMA = 'sks.source-intelligence-proof.
|
|
1
|
+
export const SOURCE_INTELLIGENCE_PROOF_SCHEMA = 'sks.source-intelligence-proof.v2';
|
|
2
2
|
export function buildSourceIntelligenceProof(policy, evidence) {
|
|
3
3
|
const blockers = [...policy.blockers];
|
|
4
4
|
const wrongnessKinds = [...policy.wrongness_kinds];
|
|
5
|
-
const context7Ok = evidence.context7?.ok === true;
|
|
6
|
-
const
|
|
7
|
-
const xaiOk = policy.xai_mcp.required ? evidence.xai_search?.ok === true : true;
|
|
8
|
-
const xaiMissingIsBlocker = policy.xai_mcp.required && !xaiOk;
|
|
5
|
+
const context7Ok = policy.context7.required ? evidence.context7?.ok === true : true;
|
|
6
|
+
const ultraOk = evidence.ultra_search?.proof.provider_independent === true && evidence.ultra_search?.proof.xai_runtime_dependency === false;
|
|
9
7
|
const appshotsRequired = evidence.appshots?.capability.visual_required === true;
|
|
10
8
|
const appshotsOk = appshotsRequired ? evidence.appshots?.ok === true : true;
|
|
11
9
|
if (policy.context7.required && !context7Ok) {
|
|
12
10
|
blockers.push('context7_missing');
|
|
13
11
|
wrongnessKinds.push('context7_missing');
|
|
14
12
|
}
|
|
15
|
-
if (
|
|
16
|
-
blockers.push('
|
|
17
|
-
wrongnessKinds.push('
|
|
18
|
-
}
|
|
19
|
-
if (policy.xai_mcp.required && !xaiOk) {
|
|
20
|
-
blockers.push('xai_available_not_used');
|
|
21
|
-
wrongnessKinds.push('xai_available_not_used');
|
|
13
|
+
if (!ultraOk) {
|
|
14
|
+
blockers.push('ultra_search_provider_independent_proof_missing');
|
|
15
|
+
wrongnessKinds.push('ultra_search_provider_independent_proof_missing');
|
|
22
16
|
}
|
|
17
|
+
if (evidence.ultra_search?.proof.blockers.length)
|
|
18
|
+
blockers.push(...evidence.ultra_search.proof.blockers);
|
|
23
19
|
if (appshotsRequired && !appshotsOk) {
|
|
24
20
|
blockers.push('appshots_operator_action_missing');
|
|
25
21
|
wrongnessKinds.push('appshots_operator_action_missing');
|
|
@@ -32,10 +28,8 @@ export function buildSourceIntelligenceProof(policy, evidence) {
|
|
|
32
28
|
context7_required: policy.context7.required,
|
|
33
29
|
context7_ok: context7Ok,
|
|
34
30
|
codex_web_required: policy.codex_web_search.required,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
xai_ok: xaiOk,
|
|
38
|
-
xai_missing_is_blocker: xaiMissingIsBlocker,
|
|
31
|
+
ultra_search_ok: ultraOk,
|
|
32
|
+
provider_independent: evidence.ultra_search?.proof.provider_independent === true,
|
|
39
33
|
appshots_required: appshotsRequired,
|
|
40
34
|
appshots_ok: appshotsOk
|
|
41
35
|
},
|