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
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @ts-nocheck
|
|
3
2
|
import { spawnSync } from 'node:child_process';
|
|
4
3
|
import fs from 'node:fs';
|
|
5
4
|
import os from 'node:os';
|
|
@@ -27,7 +26,7 @@ function readJson(file) {
|
|
|
27
26
|
return JSON.parse(fs.readFileSync(path.join(root, file), 'utf8'));
|
|
28
27
|
}
|
|
29
28
|
catch (err) {
|
|
30
|
-
fail(`unable to read ${file}`, err.message);
|
|
29
|
+
fail(`unable to read ${file}`, err instanceof Error ? err.message : String(err));
|
|
31
30
|
}
|
|
32
31
|
}
|
|
33
32
|
function run(cmd, args, options = {}) {
|
|
@@ -58,17 +57,23 @@ function checkPackagePublishConfig(pkg) {
|
|
|
58
57
|
if (isPrerelease && pkg.publishConfig?.tag !== 'rc') {
|
|
59
58
|
fail('package.json publishConfig.tag must be rc for prerelease versions', `found: ${pkg.publishConfig?.tag || 'missing'}\nversion: ${pkg.version}`);
|
|
60
59
|
}
|
|
61
|
-
if (!isPrerelease && pkg.publishConfig?.tag && pkg.publishConfig.tag !== 'latest') {
|
|
62
|
-
fail('package.json publishConfig.tag must be latest or
|
|
60
|
+
if (!isPrerelease && pkg.publishConfig?.tag && pkg.publishConfig.tag !== 'latest' && !isBackfillTag(pkg.publishConfig.tag)) {
|
|
61
|
+
fail('package.json publishConfig.tag must be latest, omitted, or explicit backfill-* for stable backfill versions', `found: ${pkg.publishConfig.tag}\nversion: ${pkg.version}`);
|
|
63
62
|
}
|
|
64
63
|
}
|
|
65
64
|
function checkRootNpmrc(pkg) {
|
|
66
65
|
const npmrcPath = path.join(root, '.npmrc');
|
|
67
|
-
|
|
66
|
+
const publishConfigTag = String(pkg.publishConfig?.tag || '');
|
|
67
|
+
if (!fs.existsSync(npmrcPath)) {
|
|
68
|
+
if (isBackfillTag(publishConfigTag)) {
|
|
69
|
+
fail('root .npmrc must pin the backfill publish tag for npm publish --ignore-scripts', `expected: tag=${publishConfigTag}`);
|
|
70
|
+
}
|
|
68
71
|
return;
|
|
72
|
+
}
|
|
69
73
|
const text = fs.readFileSync(npmrcPath, 'utf8');
|
|
70
74
|
const unsafe = [];
|
|
71
75
|
const isPrerelease = /-/.test(String(pkg.version || ''));
|
|
76
|
+
let npmrcTag = '';
|
|
72
77
|
for (const [index, line] of text.split(/\r?\n/).entries()) {
|
|
73
78
|
const trimmed = line.trim();
|
|
74
79
|
if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith(';'))
|
|
@@ -78,16 +83,23 @@ function checkRootNpmrc(pkg) {
|
|
|
78
83
|
unsafe.push(`${index + 1}: ${trimmed}`);
|
|
79
84
|
const tagMatch = trimmed.match(/^tag\s*=\s*(.+)$/);
|
|
80
85
|
if (tagMatch) {
|
|
81
|
-
const tag = tagMatch[1].trim();
|
|
86
|
+
const tag = String(tagMatch[1] || '').trim();
|
|
87
|
+
npmrcTag = tag;
|
|
82
88
|
if (isPrerelease && tag !== 'rc')
|
|
83
89
|
unsafe.push(`${index + 1}: ${trimmed}`);
|
|
84
|
-
if (!isPrerelease && tag !== 'latest')
|
|
90
|
+
if (!isPrerelease && tag !== 'latest' && !isBackfillTag(tag))
|
|
85
91
|
unsafe.push(`${index + 1}: ${trimmed}`);
|
|
86
92
|
}
|
|
87
93
|
}
|
|
88
94
|
if (unsafe.length) {
|
|
89
95
|
fail('root .npmrc contains publish config incompatible with this release', unsafe.join('\n'));
|
|
90
96
|
}
|
|
97
|
+
if (publishConfigTag && npmrcTag && publishConfigTag !== npmrcTag) {
|
|
98
|
+
fail('root .npmrc tag disagrees with package.json publishConfig.tag', `.npmrc tag: ${npmrcTag}\npublishConfig.tag: ${publishConfigTag}`);
|
|
99
|
+
}
|
|
100
|
+
if (isBackfillTag(publishConfigTag) && npmrcTag !== publishConfigTag) {
|
|
101
|
+
fail('root .npmrc must pin the backfill publish tag for npm publish --ignore-scripts', `.npmrc tag: ${npmrcTag || 'missing'}\nexpected: ${publishConfigTag}`);
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
104
|
function checkLockfile(pkg) {
|
|
93
105
|
const lock = readJson('package-lock.json');
|
|
@@ -147,8 +159,10 @@ function compareVersions(a, b) {
|
|
|
147
159
|
const pb = String(b || '').split(/[.-]/).map((part) => Number.parseInt(part, 10));
|
|
148
160
|
const len = Math.max(pa.length, pb.length);
|
|
149
161
|
for (let i = 0; i < len; i += 1) {
|
|
150
|
-
const
|
|
151
|
-
const
|
|
162
|
+
const paValue = pa[i];
|
|
163
|
+
const pbValue = pb[i];
|
|
164
|
+
const da = Number.isFinite(paValue) ? paValue || 0 : 0;
|
|
165
|
+
const db = Number.isFinite(pbValue) ? pbValue || 0 : 0;
|
|
152
166
|
if (da !== db)
|
|
153
167
|
return da > db ? 1 : -1;
|
|
154
168
|
}
|
|
@@ -193,14 +207,17 @@ function checkPublishedVersion(pkg) {
|
|
|
193
207
|
fail('package version is already published on npm', `${pkg.name}@${pkg.version}`);
|
|
194
208
|
}
|
|
195
209
|
const cmp = compareVersions(pkg.version, latest);
|
|
196
|
-
if (requireUnpublished && cmp <= 0) {
|
|
210
|
+
if (requireUnpublished && cmp <= 0 && !isBackfillTag(pkg.publishConfig?.tag || '')) {
|
|
197
211
|
fail('package version is not newer than the npm latest dist-tag', `package.json: ${pkg.version}\nnpm latest: ${latest}`);
|
|
198
212
|
}
|
|
199
213
|
const note = exactPublished
|
|
200
214
|
? `exact version already exists; current npm latest is ${latest}`
|
|
201
|
-
: (cmp > 0 ? `ready for new publish over npm latest ${latest}` : `current npm latest
|
|
215
|
+
: (cmp > 0 ? `ready for new publish over npm latest ${latest}` : `ready as backfill over current npm latest ${latest} with tag ${pkg.publishConfig?.tag || 'missing'}`);
|
|
202
216
|
console.log(`Registry metadata check passed: ${pkg.name}@${pkg.version}; ${note}.`);
|
|
203
217
|
}
|
|
218
|
+
function isBackfillTag(value) {
|
|
219
|
+
return /^backfill(?:[-_][a-z0-9.-]+)?$/i.test(String(value || ''));
|
|
220
|
+
}
|
|
204
221
|
function checkPublishAuth(pkg) {
|
|
205
222
|
if (skipNetwork) {
|
|
206
223
|
fail('publish auth check cannot run when SKS_SKIP_REGISTRY_NETWORK_CHECK=1 is set');
|
|
@@ -324,8 +341,10 @@ function packageMaintainers(pkg, env) {
|
|
|
324
341
|
function normalizeNpmUser(value) {
|
|
325
342
|
if (!value)
|
|
326
343
|
return '';
|
|
327
|
-
if (typeof value === 'object')
|
|
328
|
-
|
|
344
|
+
if (typeof value === 'object') {
|
|
345
|
+
const record = value;
|
|
346
|
+
return normalizeNpmUser(record.name || record.username || '');
|
|
347
|
+
}
|
|
329
348
|
const text = String(value).trim();
|
|
330
349
|
if (/^["[{]/.test(text)) {
|
|
331
350
|
try {
|
|
@@ -335,7 +354,7 @@ function normalizeNpmUser(value) {
|
|
|
335
354
|
// Fall through for legacy plain-text npm output.
|
|
336
355
|
}
|
|
337
356
|
}
|
|
338
|
-
return text.replace(/^@/, '').split(/\s+/)[0].toLowerCase();
|
|
357
|
+
return String(text.replace(/^@/, '').split(/\s+/)[0] || '').toLowerCase();
|
|
339
358
|
}
|
|
340
359
|
function tail(value, limit = 1200) {
|
|
341
360
|
const text = String(value || '').trim();
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { assertGate, emitGate, root } from './sks-1-18-gate-lib.js';
|
|
8
|
+
export { assertGate, emitGate, root };
|
|
9
|
+
export function makeSearchVisibilityFixture(name = 'search-visibility', options = {}) {
|
|
10
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `sks-${name}-`));
|
|
11
|
+
fs.mkdirSync(path.join(dir, 'public'), { recursive: true });
|
|
12
|
+
fs.mkdirSync(path.join(dir, 'bin'), { recursive: true });
|
|
13
|
+
const packageJson = {
|
|
14
|
+
name: `sks-${name}-fixture`,
|
|
15
|
+
version: '0.0.0',
|
|
16
|
+
description: options.description || 'Source-backed search visibility fixture for Sneakoscope gates.',
|
|
17
|
+
keywords: ['sneakoscope', 'search-visibility'],
|
|
18
|
+
repository: { type: 'git', url: 'https://example.test/repo.git' },
|
|
19
|
+
homepage: 'https://example.test/',
|
|
20
|
+
bugs: { url: 'https://example.test/issues' },
|
|
21
|
+
bin: { [`${name}-fixture`]: 'bin/fixture.js' },
|
|
22
|
+
scripts: { build: 'node -e "console.log(1)"' },
|
|
23
|
+
};
|
|
24
|
+
fs.writeFileSync(path.join(dir, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
25
|
+
fs.writeFileSync(path.join(dir, 'bin', 'fixture.js'), '#!/usr/bin/env node\nconsole.log("fixture")\n');
|
|
26
|
+
fs.writeFileSync(path.join(dir, 'README.md'), `# ${options.title || 'Search Visibility Fixture'}\n\nInstall with \`npm i\` and run \`${name}-fixture\`.\n`);
|
|
27
|
+
fs.writeFileSync(path.join(dir, 'public', 'index.html'), options.html || [
|
|
28
|
+
'<!doctype html>',
|
|
29
|
+
'<html lang="en">',
|
|
30
|
+
'<head><meta charset="utf-8"><title>Search Visibility Fixture</title><meta name="description" content="A source-backed fixture."></head>',
|
|
31
|
+
'<body><main><h1>Search Visibility Fixture</h1><p>Official source-backed content.</p><a href="/docs">Docs</a></main></body>',
|
|
32
|
+
'</html>',
|
|
33
|
+
'',
|
|
34
|
+
].join('\n'));
|
|
35
|
+
return dir;
|
|
36
|
+
}
|
|
37
|
+
export function runSks(args, options = {}) {
|
|
38
|
+
const result = spawnSync(process.execPath, [path.join(root, 'dist', 'bin', 'sks.js'), ...args], {
|
|
39
|
+
cwd: root,
|
|
40
|
+
encoding: 'utf8',
|
|
41
|
+
stdio: 'pipe',
|
|
42
|
+
env: {
|
|
43
|
+
...process.env,
|
|
44
|
+
SKS_REQUIRE_ZELLIJ: '0',
|
|
45
|
+
SKS_TEST_REAL_IMAGEGEN: '0',
|
|
46
|
+
SKS_REAL_IMAGEGEN: '0',
|
|
47
|
+
SKS_REQUIRE_REAL_COMPUTER_USE: '0',
|
|
48
|
+
...(options.env || {}),
|
|
49
|
+
},
|
|
50
|
+
timeout: options.timeoutMs || 120000,
|
|
51
|
+
});
|
|
52
|
+
if (!options.allowFailure) {
|
|
53
|
+
assertGate(result.status === 0, `sks command failed: ${args.join(' ')}`, {
|
|
54
|
+
status: result.status,
|
|
55
|
+
stdout: result.stdout,
|
|
56
|
+
stderr: result.stderr,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
export function runSksJson(args, options = {}) {
|
|
62
|
+
const result = runSks(args, options);
|
|
63
|
+
const parsed = parseJsonOutput(result.stdout);
|
|
64
|
+
assertGate(parsed && typeof parsed === 'object', `sks command did not emit JSON: ${args.join(' ')}`, {
|
|
65
|
+
status: result.status,
|
|
66
|
+
stdout: result.stdout,
|
|
67
|
+
stderr: result.stderr,
|
|
68
|
+
});
|
|
69
|
+
return { result, json: parsed };
|
|
70
|
+
}
|
|
71
|
+
export function parseJsonOutput(stdout) {
|
|
72
|
+
const text = String(stdout || '').trim();
|
|
73
|
+
if (!text)
|
|
74
|
+
return null;
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(text);
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
const start = text.indexOf('{');
|
|
80
|
+
const end = text.lastIndexOf('}');
|
|
81
|
+
if (start >= 0 && end > start) {
|
|
82
|
+
try {
|
|
83
|
+
return JSON.parse(text.slice(start, end + 1));
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
export function missionDir(missionId, baseRoot = root) {
|
|
90
|
+
return path.join(baseRoot, '.sneakoscope', 'missions', missionId);
|
|
91
|
+
}
|
|
92
|
+
export function artifactPath(missionId, rel, baseRoot = root) {
|
|
93
|
+
return path.join(missionDir(missionId, baseRoot), rel.startsWith('search-visibility/') ? rel : path.join('search-visibility', rel));
|
|
94
|
+
}
|
|
95
|
+
export function readJsonFile(file) {
|
|
96
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
97
|
+
}
|
|
98
|
+
export function assertMissionArtifact(missionId, rel, baseRoot = root) {
|
|
99
|
+
const file = rel === 'completion-proof.json' || rel.endsWith('-gate.json')
|
|
100
|
+
? path.join(missionDir(missionId, baseRoot), rel)
|
|
101
|
+
: artifactPath(missionId, rel, baseRoot);
|
|
102
|
+
assertGate(fs.existsSync(file), `missing mission artifact: ${rel}`, { missionId, file });
|
|
103
|
+
return readJsonFile(file);
|
|
104
|
+
}
|
|
105
|
+
export function listSourceFiles(dir) {
|
|
106
|
+
const out = [];
|
|
107
|
+
walk(dir, out);
|
|
108
|
+
return out
|
|
109
|
+
.map((file) => path.relative(dir, file).split(path.sep).join('/'))
|
|
110
|
+
.filter((rel) => !rel.startsWith('.sneakoscope/'))
|
|
111
|
+
.sort();
|
|
112
|
+
}
|
|
113
|
+
function walk(dir, out) {
|
|
114
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
115
|
+
if (entry.name === '.sneakoscope' || entry.name === 'node_modules' || entry.name === '.git')
|
|
116
|
+
continue;
|
|
117
|
+
const full = path.join(dir, entry.name);
|
|
118
|
+
if (entry.isDirectory())
|
|
119
|
+
walk(full, out);
|
|
120
|
+
else if (entry.isFile())
|
|
121
|
+
out.push(full);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=search-visibility-gate-lib.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, assertMissionArtifact, emitGate, makeSearchVisibilityFixture, runSksJson } from './search-visibility-gate-lib.js';
|
|
4
|
+
const fixture = makeSearchVisibilityFixture('seo-audit');
|
|
5
|
+
const { json } = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'seo', '--root', fixture, '--target', 'website', '--framework', 'static', '--url', 'https://example.test', '--offline', '--json']);
|
|
6
|
+
assertGate(json.route === '$SEO-GEO-OPTIMIZER' && json.mission_id, 'seo audit must create a unified optimizer mission', json);
|
|
7
|
+
const inventory = assertMissionArtifact(json.mission_id, 'site-inventory.json', fixture);
|
|
8
|
+
const findings = assertMissionArtifact(json.mission_id, 'seo-findings.json', fixture);
|
|
9
|
+
const gate = assertMissionArtifact(json.mission_id, 'seo-gate.json', fixture);
|
|
10
|
+
const proof = assertMissionArtifact(json.mission_id, 'completion-proof.json', fixture);
|
|
11
|
+
assertGate(inventory.detected_adapter.adapterId === 'static-site', 'fixture should detect static-site adapter', inventory.detected_adapter);
|
|
12
|
+
assertGate(Array.isArray(findings.findings) && findings.findings.every((finding) => finding.evidence?.length > 0), 'SEO findings must be atomic and evidence-backed', findings);
|
|
13
|
+
assertGate(gate.route === '$SEO-GEO-OPTIMIZER' && gate.completion_proof.endsWith('/completion-proof.json'), 'seo gate must link completion proof', gate);
|
|
14
|
+
assertGate(proof.schema === 'sks.completion-proof.v1' && proof.route === '$SEO-GEO-OPTIMIZER', 'completion proof must retain unified route identity', proof);
|
|
15
|
+
emitGate('seo:audit-fixture', { mission_id: json.mission_id, findings: findings.findings.length });
|
|
16
|
+
//# sourceMappingURL=seo-audit-fixture-check.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { assertGate, assertMissionArtifact, emitGate, makeSearchVisibilityFixture, runSksJson } from './search-visibility-gate-lib.js';
|
|
6
|
+
const fixture = makeSearchVisibilityFixture('seo-locale');
|
|
7
|
+
fs.mkdirSync(path.join(fixture, 'public', 'fr'), { recursive: true });
|
|
8
|
+
fs.writeFileSync(path.join(fixture, 'public', 'fr', 'index.html'), '<!doctype html><html lang="fr"><head><title>FR</title><link rel="canonical" href="/fr"></head><body><h1>FR</h1></body></html>\n');
|
|
9
|
+
const audit = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'seo', '--root', fixture, '--target', 'website', '--framework', 'static', '--url', 'https://example.test', '--offline', '--json']).json;
|
|
10
|
+
const canonical = assertMissionArtifact(audit.mission_id, 'canonical-map.json', fixture);
|
|
11
|
+
const locale = assertMissionArtifact(audit.mission_id, 'locale-graph.json', fixture);
|
|
12
|
+
const sitemap = assertMissionArtifact(audit.mission_id, 'sitemap-audit.json', fixture);
|
|
13
|
+
const findings = assertMissionArtifact(audit.mission_id, 'seo-findings.json', fixture);
|
|
14
|
+
assertGate(/not guaranteed/i.test(canonical.warning), 'canonical report must avoid search-engine selection guarantees', canonical);
|
|
15
|
+
assertGate(locale.checks.self_hreflang_verified === false && Array.isArray(locale.unverified), 'locale report must separate unverified hreflang checks', locale);
|
|
16
|
+
assertGate(sitemap.indexing_guarantee === false, 'sitemap report must not claim indexing guarantee', sitemap);
|
|
17
|
+
assertGate(findings.findings.some((finding) => finding.ruleId.includes('canonical') || finding.category === 'locale'), 'fixture should produce canonical or locale findings', findings);
|
|
18
|
+
emitGate('seo:canonical-sitemap-locale', { mission_id: audit.mission_id });
|
|
19
|
+
//# sourceMappingURL=seo-canonical-locale-check.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, emitGate, runSks, runSksJson } from './search-visibility-gate-lib.js';
|
|
4
|
+
const commands = runSksJson(['commands', '--json']).json;
|
|
5
|
+
assertGate(commands.commands.some((row) => row.name === 'seo-geo-optimizer' && /unified SEO\/GEO optimizer/i.test(row.description)), 'commands --json must expose seo-geo-optimizer');
|
|
6
|
+
assertGate(!commands.commands.some((row) => row.name === 'seo' || row.name === 'geo'), 'commands --json must not expose split seo/geo command surfaces');
|
|
7
|
+
const optimizerUsage = runSks(['usage', 'seo-geo-optimizer']);
|
|
8
|
+
assertGate(/sks seo-geo-optimizer \[seo\|geo\] doctor\|audit\|plan\|apply\|verify\|status\|rollback\|fixture/.test(optimizerUsage.stdout), 'usage seo-geo-optimizer must expose unified lifecycle');
|
|
9
|
+
const doctor = runSksJson(['seo-geo-optimizer', 'doctor', '--mode', 'seo', '--json']).json;
|
|
10
|
+
assertGate(doctor.route === '$SEO-GEO-OPTIMIZER' && doctor.schema === 'sks.search-visibility.doctor-command.v1', 'optimizer seo doctor must preserve unified route identity', doctor);
|
|
11
|
+
const invalid = runSks(['seo-geo-optimizer', 'nonsense-subcommand'], { allowFailure: true });
|
|
12
|
+
assertGate(invalid.status === 2 && /Usage: sks seo-geo-optimizer/.test(invalid.stderr), 'invalid optimizer subcommand must exit 2 with usage', {
|
|
13
|
+
status: invalid.status,
|
|
14
|
+
stdout: invalid.stdout,
|
|
15
|
+
stderr: invalid.stderr,
|
|
16
|
+
});
|
|
17
|
+
emitGate('seo:cli-blackbox', { adapter: doctor.adapter, confidence: doctor.confidence });
|
|
18
|
+
//# sourceMappingURL=seo-cli-blackbox-check.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, emitGate, importDist, root } from './sks-1-18-gate-lib.js';
|
|
4
|
+
const registryMod = await importDist('core/feature-registry.js');
|
|
5
|
+
const registry = await registryMod.buildFeatureRegistry({ root });
|
|
6
|
+
const byId = new Map(registry.features.map((feature) => [feature.id, feature]));
|
|
7
|
+
for (const id of ['cli-seo-geo-optimizer', 'route-seo-geo-optimizer']) {
|
|
8
|
+
const feature = byId.get(id);
|
|
9
|
+
assertGate(feature, `feature missing: ${id}`);
|
|
10
|
+
assertGate(feature.fixture?.status === 'pass', `feature fixture must pass: ${id}`, feature);
|
|
11
|
+
assertGate(feature.fixture?.quality !== 'static_contract', `SEO/GEO feature must not rely on static contract: ${id}`, feature.fixture);
|
|
12
|
+
assertGate(['runtime_verified', 'runtime_mock_verified'].includes(feature.fixture?.quality), `SEO/GEO fixture quality must be runtime-backed: ${id}`, feature.fixture);
|
|
13
|
+
}
|
|
14
|
+
const selftest = registryMod.buildAllFeaturesSelftest(registry, {});
|
|
15
|
+
const runtimeCheck = selftest.checks.find((check) => check.id === 'runtime_routes_not_static_contract');
|
|
16
|
+
assertGate(runtimeCheck?.ok === true, 'runtime route static-contract guard must pass', runtimeCheck);
|
|
17
|
+
emitGate('seo-geo:feature-fixture-quality', { features: 2 });
|
|
18
|
+
//# sourceMappingURL=seo-geo-feature-fixture-quality-check.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
4
|
+
const routes = await importDist('core/routes.js');
|
|
5
|
+
const commandFor = (prompt) => routes.routePrompt(prompt)?.command;
|
|
6
|
+
assertGate(commandFor('$SEO-GEO-OPTIMIZER improve AI answer visibility') === '$SEO-GEO-OPTIMIZER', 'explicit optimizer should route to unified route');
|
|
7
|
+
assertGate(commandFor('Generative Engine Optimization crawler policy audit') === '$SEO-GEO-OPTIMIZER', 'generative engine optimization should route to unified optimizer');
|
|
8
|
+
assertGate(commandFor('SEO audit canonical sitemap metadata') === '$SEO-GEO-OPTIMIZER', 'SEO intent should route to unified optimizer');
|
|
9
|
+
assertGate(commandFor('fix GeoIP regional redirect bug') !== '$SEO-GEO-OPTIMIZER', 'GeoIP/geolocation request must not route to optimizer');
|
|
10
|
+
assertGate(commandFor('map coordinates and location permission issue') !== '$SEO-GEO-OPTIMIZER', 'location permission request must not route to optimizer');
|
|
11
|
+
emitGate('seo-geo:geo-disambiguation');
|
|
12
|
+
//# sourceMappingURL=seo-geo-geo-disambiguation-check.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { assertGate, assertMissionArtifact, emitGate, makeSearchVisibilityFixture, runSksJson } from './search-visibility-gate-lib.js';
|
|
6
|
+
const fixture = makeSearchVisibilityFixture('unsupported-claims', {
|
|
7
|
+
description: 'Guarantees top rank and AI citation for every site.',
|
|
8
|
+
});
|
|
9
|
+
fs.appendFileSync(path.join(fixture, 'README.md'), '\nWe guarantee rank #1, traffic lift, and AI answer citation.\n');
|
|
10
|
+
const seo = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'seo', '--root', fixture, '--target', 'package', '--offline', '--json'], { allowFailure: true }).json;
|
|
11
|
+
const geo = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'geo', '--root', fixture, '--target', 'package', '--offline', '--json'], { allowFailure: true }).json;
|
|
12
|
+
const seoGate = assertMissionArtifact(seo.mission_id, 'seo-gate.json', fixture);
|
|
13
|
+
const geoGate = assertMissionArtifact(geo.mission_id, 'geo-gate.json', fixture);
|
|
14
|
+
const claims = assertMissionArtifact(geo.mission_id, 'claim-evidence-ledger.json', fixture);
|
|
15
|
+
assertGate(seoGate.ok === false && seoGate.unsupported_claims.length > 0, 'SEO gate must reject unsupported ranking/traffic/citation guarantees', seoGate);
|
|
16
|
+
assertGate(geoGate.ok === false && claims.claims.some((claim) => claim.safe_to_publish === false), 'GEO gate must reject unsupported AI citation claims', { geoGate, claims });
|
|
17
|
+
emitGate('seo-geo:no-unsupported-ranking-claims', { seo_mission: seo.mission_id, geo_mission: geo.mission_id });
|
|
18
|
+
//# sourceMappingURL=seo-geo-no-unsupported-ranking-claims-check.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, emitGate, runSksJson } from './search-visibility-gate-lib.js';
|
|
4
|
+
const seo = runSksJson(['run', '$SEO-GEO-OPTIMIZER SEO audit this fixture', '--execute', '--json']).json;
|
|
5
|
+
const geo = runSksJson(['run', '$SEO-GEO-OPTIMIZER Generative Engine Optimization audit AI search visibility', '--execute', '--json']).json;
|
|
6
|
+
const seoText = JSON.stringify(seo);
|
|
7
|
+
const geoText = JSON.stringify(geo);
|
|
8
|
+
assertGate(/\$SEO-GEO-OPTIMIZER/.test(seoText) && /sks seo-geo-optimizer/.test(seoText) && /--mode seo/.test(seoText), 'sks run --execute must preserve unified optimizer route and execute seo mode path', seo);
|
|
9
|
+
assertGate(/\$SEO-GEO-OPTIMIZER/.test(geoText) && /sks seo-geo-optimizer/.test(geoText) && /--mode geo/.test(geoText), 'sks run --execute must preserve unified optimizer route and execute geo mode path', geo);
|
|
10
|
+
assertGate(!/\$AutoResearch/.test(seoText) && !/\$AutoResearch/.test(geoText), 'SEO/GEO route identity must not collapse into AutoResearch', { seo, geo });
|
|
11
|
+
emitGate('seo-geo:route-identity', { seo_status: seo.status, geo_status: geo.status });
|
|
12
|
+
//# sourceMappingURL=seo-geo-route-identity-check.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-seo-geo-skills-'));
|
|
8
|
+
const init = await importDist('core/init.js');
|
|
9
|
+
await init.installSkills(root);
|
|
10
|
+
for (const name of ['search-visibility-core', 'seo-geo-optimizer']) {
|
|
11
|
+
const file = path.join(root, '.agents', 'skills', name, 'SKILL.md');
|
|
12
|
+
assertGate(fs.existsSync(file), `generated skill missing: ${name}`);
|
|
13
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
14
|
+
assertGate(/^---\nname:/m.test(text) && /description:/.test(text), `skill frontmatter missing: ${name}`, text);
|
|
15
|
+
for (const token of ['Purpose:', 'Use when', 'Workflow:', 'Safety:', 'Evidence/artifacts:', 'Failure/recovery:']) {
|
|
16
|
+
assertGate(text.includes(token), `skill missing rich token ${token}: ${name}`, text);
|
|
17
|
+
}
|
|
18
|
+
assertGate(/CLI entrypoint:/i.test(text), `skill missing CLI entrypoint: ${name}`, text);
|
|
19
|
+
assertGate(/ranking|citation|traffic|guarantee|보장/i.test(text), `skill must name forbidden guarantee boundary: ${name}`, text);
|
|
20
|
+
}
|
|
21
|
+
emitGate('seo-geo:skill-rich-content', { skills_checked: 2 });
|
|
22
|
+
//# sourceMappingURL=seo-geo-skill-rich-content-check.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { assertGate, assertMissionArtifact, emitGate, makeSearchVisibilityFixture, runSksJson } from './search-visibility-gate-lib.js';
|
|
6
|
+
const fixture = makeSearchVisibilityFixture('seo-rollback');
|
|
7
|
+
const audit = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'seo', '--root', fixture, '--target', 'website', '--framework', 'static', '--url', 'https://example.test', '--offline', '--json']).json;
|
|
8
|
+
const plan = runSksJson(['seo-geo-optimizer', 'plan', audit.mission_id, '--mode', 'seo', '--root', fixture, '--json']).json;
|
|
9
|
+
assertGate(plan.operations >= 2, 'SEO rollback fixture should plan robots and sitemap creation', plan);
|
|
10
|
+
const apply = runSksJson(['seo-geo-optimizer', 'apply', audit.mission_id, '--mode', 'seo', '--root', fixture, '--apply', '--json']).json;
|
|
11
|
+
assertGate(apply.ok === true && apply.applied >= 2, 'SEO apply must create planned managed files', apply);
|
|
12
|
+
assertGate(fs.existsSync(path.join(fixture, 'public', 'robots.txt')) && fs.existsSync(path.join(fixture, 'public', 'sitemap.xml')), 'managed robots and sitemap must exist after apply');
|
|
13
|
+
const second = runSksJson(['seo-geo-optimizer', 'apply', audit.mission_id, '--mode', 'seo', '--root', fixture, '--apply', '--json']).json;
|
|
14
|
+
assertGate(second.ok === true, 'second apply must be idempotent', second);
|
|
15
|
+
const journal = fs.readFileSync(path.join(fixture, '.sneakoscope', 'missions', audit.mission_id, 'search-visibility', 'mutation-journal.jsonl'), 'utf8');
|
|
16
|
+
assertGate(/applied/.test(journal), 'mutation journal must record applied/idempotent events', journal);
|
|
17
|
+
const rollbackManifest = assertMissionArtifact(audit.mission_id, 'rollback-manifest.json', fixture);
|
|
18
|
+
assertGate(rollbackManifest.operations.length >= 2, 'rollback manifest must preserve inverse operations after idempotent apply', rollbackManifest);
|
|
19
|
+
const rollback = runSksJson(['seo-geo-optimizer', 'rollback', audit.mission_id, '--mode', 'seo', '--root', fixture, '--apply', '--json']).json;
|
|
20
|
+
assertGate(rollback.ok === true && rollback.rolled_back >= 2, 'rollback must reverse mission-owned operations', rollback);
|
|
21
|
+
assertGate(!fs.existsSync(path.join(fixture, 'public', 'robots.txt')) && !fs.existsSync(path.join(fixture, 'public', 'sitemap.xml')), 'rollback must remove mission-created files');
|
|
22
|
+
emitGate('seo:mutation-rollback', { mission_id: audit.mission_id, applied: apply.applied, rolled_back: rollback.rolled_back });
|
|
23
|
+
//# sourceMappingURL=seo-mutation-rollback-check.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { assertGate, emitGate, listSourceFiles, makeSearchVisibilityFixture, runSksJson } from './search-visibility-gate-lib.js';
|
|
6
|
+
const fixture = makeSearchVisibilityFixture('seo-readonly');
|
|
7
|
+
const before = listSourceFiles(fixture);
|
|
8
|
+
const audit = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'seo', '--root', fixture, '--target', 'website', '--framework', 'static', '--url', 'https://example.test', '--offline', '--json']).json;
|
|
9
|
+
const plan = runSksJson(['seo-geo-optimizer', 'plan', audit.mission_id, '--mode', 'seo', '--root', fixture, '--json']).json;
|
|
10
|
+
const blockedApply = runSksJson(['seo-geo-optimizer', 'apply', audit.mission_id, '--mode', 'seo', '--root', fixture, '--json'], { allowFailure: true }).json;
|
|
11
|
+
const after = listSourceFiles(fixture);
|
|
12
|
+
assertGate(JSON.stringify(before) === JSON.stringify(after), 'audit/plan/apply-without-flag must not mutate project source files', { before, after });
|
|
13
|
+
assertGate(plan.operations > 0, 'readonly fixture should still compile a mutation plan', plan);
|
|
14
|
+
assertGate(blockedApply.ok === false && blockedApply.blockers.includes('apply_requires_explicit_--apply'), 'apply must require explicit --apply', blockedApply);
|
|
15
|
+
assertGate(!fs.existsSync(path.join(fixture, 'public', 'robots.txt')) && !fs.existsSync(path.join(fixture, 'public', 'sitemap.xml')), 'policy files must not be created without --apply');
|
|
16
|
+
emitGate('seo:no-mutation-by-default', { mission_id: audit.mission_id, planned_operations: plan.operations });
|
|
17
|
+
//# sourceMappingURL=seo-no-mutation-by-default-check.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, assertMissionArtifact, emitGate, makeSearchVisibilityFixture, runSksJson } from './search-visibility-gate-lib.js';
|
|
4
|
+
const html = [
|
|
5
|
+
'<!doctype html>',
|
|
6
|
+
'<html lang="en"><head><title>Structured Data Fixture</title>',
|
|
7
|
+
'<script type="application/ld+json">{"@context":"https://schema.org","@type":"Product","aggregateRating":{"ratingValue":5,}}</script>',
|
|
8
|
+
'</head><body><h1>Structured Data Fixture</h1><p>Visible source content only.</p></body></html>',
|
|
9
|
+
'',
|
|
10
|
+
].join('\n');
|
|
11
|
+
const fixture = makeSearchVisibilityFixture('seo-structured', { html });
|
|
12
|
+
const audit = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'seo', '--root', fixture, '--target', 'website', '--framework', 'static', '--url', 'https://example.test', '--offline', '--json']).json;
|
|
13
|
+
const ledger = assertMissionArtifact(audit.mission_id, 'structured-data-ledger.json', fixture);
|
|
14
|
+
const findings = assertMissionArtifact(audit.mission_id, 'seo-findings.json', fixture);
|
|
15
|
+
assertGate(ledger.policies.some((url) => /structured-data/.test(url)), 'structured data ledger must cite structured-data policy sources', ledger);
|
|
16
|
+
assertGate(ledger.pages.some((page) => page.parse_errors.length > 0), 'invalid JSON-LD must be recorded as parse error', ledger);
|
|
17
|
+
assertGate(findings.findings.some((finding) => finding.ruleId.includes('jsonld-parse') && finding.evidence.length > 0), 'invalid JSON-LD finding must carry evidence', findings);
|
|
18
|
+
emitGate('seo:structured-data-visible-content', { mission_id: audit.mission_id });
|
|
19
|
+
//# sourceMappingURL=seo-structured-data-visible-content-check.js.map
|
|
@@ -251,7 +251,16 @@ async function richContentGate(id) {
|
|
|
251
251
|
const report = await mod.syncCodexSksSkills({ root: rootDir, skillsRoot, apply: true });
|
|
252
252
|
const skill = fs.readFileSync(path.join(skillsRoot, 'loop', 'SKILL.md'), 'utf8');
|
|
253
253
|
assertGate(/Purpose:|Evidence:|Fallback:/.test(skill), 'managed skill must include rich route content', { skill, report });
|
|
254
|
-
|
|
254
|
+
for (const name of ['search-visibility-core', 'seo-geo-optimizer']) {
|
|
255
|
+
const file = path.join(skillsRoot, name, 'SKILL.md');
|
|
256
|
+
assertGate(fs.existsSync(file), `managed skill missing: ${name}`, { report });
|
|
257
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
258
|
+
assertGate(/Purpose:|Use when:|Workflow:|Safety:|Evidence\/artifacts:|Failure\/recovery:|CLI entrypoint:/i.test(text), `managed skill lacks rich content: ${name}`, { text });
|
|
259
|
+
assertGate(/ranking|citation|traffic|guarantee|보장/i.test(text), `managed skill must name forbidden guarantee boundary: ${name}`, { text });
|
|
260
|
+
if (name === 'seo-geo-optimizer')
|
|
261
|
+
assertGate(text.includes('sks seo-geo-optimizer doctor|audit|plan|apply|verify|status|rollback|fixture') && /not geolocation|GeoIP/i.test(text), 'seo-geo-optimizer skill must expose CLI entrypoint and geolocation disambiguation', { text });
|
|
262
|
+
}
|
|
263
|
+
return emitGate(id, { skills: report.created.length, search_visibility_skills_checked: 2 });
|
|
255
264
|
}
|
|
256
265
|
const previous = swapEnv({ SKS_CODEX_AGENT_TYPE_FIXTURE: 'supported' });
|
|
257
266
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.6.0",
|
|
5
5
|
"description": "Sneakoscope Codex: fast proof-first Codex trust layer with image-based Voxel TriWiki.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -68,6 +68,21 @@
|
|
|
68
68
|
"typescript:migration-report": "node ./dist/scripts/typescript-migration-report.js",
|
|
69
69
|
"typecheck:contracts": "node ./dist/scripts/check-ts-contracts.js",
|
|
70
70
|
"schema:check": "node ./dist/scripts/check-runtime-schemas.js",
|
|
71
|
+
"seo:cli-blackbox": "node ./dist/scripts/seo-cli-blackbox-check.js",
|
|
72
|
+
"seo:audit-fixture": "node ./dist/scripts/seo-audit-fixture-check.js",
|
|
73
|
+
"seo:no-mutation-by-default": "node ./dist/scripts/seo-no-mutation-by-default-check.js",
|
|
74
|
+
"seo:mutation-rollback": "node ./dist/scripts/seo-mutation-rollback-check.js",
|
|
75
|
+
"seo:canonical-sitemap-locale": "node ./dist/scripts/seo-canonical-locale-check.js",
|
|
76
|
+
"seo:structured-data-visible-content": "node ./dist/scripts/seo-structured-data-visible-content-check.js",
|
|
77
|
+
"geo:cli-blackbox": "node ./dist/scripts/geo-cli-blackbox-check.js",
|
|
78
|
+
"geo:claim-evidence": "node ./dist/scripts/geo-claim-evidence-check.js",
|
|
79
|
+
"geo:crawler-purpose-policy": "node ./dist/scripts/geo-crawler-policy-check.js",
|
|
80
|
+
"geo:llms-txt-optional": "node ./dist/scripts/geo-llms-txt-optional-check.js",
|
|
81
|
+
"seo-geo:route-identity": "node ./dist/scripts/seo-geo-route-identity-check.js",
|
|
82
|
+
"seo-geo:geo-disambiguation": "node ./dist/scripts/seo-geo-geo-disambiguation-check.js",
|
|
83
|
+
"seo-geo:skill-rich-content": "node ./dist/scripts/seo-geo-skill-rich-content-check.js",
|
|
84
|
+
"seo-geo:no-unsupported-ranking-claims": "node ./dist/scripts/seo-geo-no-unsupported-ranking-claims-check.js",
|
|
85
|
+
"seo-geo:feature-fixture-quality": "node ./dist/scripts/seo-geo-feature-fixture-quality-check.js",
|
|
71
86
|
"package-boundary:check": "node ./dist/scripts/check-package-boundary.js",
|
|
72
87
|
"blackbox:command-import-smoke": "node ./dist/scripts/blackbox-command-import-smoke.js",
|
|
73
88
|
"repo-audit": "node ./dist/scripts/repo-audit.js",
|
|
@@ -1082,6 +1097,9 @@
|
|
|
1082
1097
|
"goal",
|
|
1083
1098
|
"research",
|
|
1084
1099
|
"autoresearch",
|
|
1100
|
+
"seo",
|
|
1101
|
+
"search-visibility",
|
|
1102
|
+
"generative-engine-optimization",
|
|
1085
1103
|
"honest-mode",
|
|
1086
1104
|
"update-check",
|
|
1087
1105
|
"llm-wiki",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://sneakoscope.dev/schemas/search-visibility/finding-ledger.schema.json",
|
|
4
|
+
"title": "SKS Search Visibility Finding Ledger",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schema", "mission_id", "route", "findings", "counts"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schema": { "enum": ["sks.search-visibility.seo-findings.v1", "sks.search-visibility.geo-findings.v1"] },
|
|
9
|
+
"mission_id": { "type": "string" },
|
|
10
|
+
"route": { "const": "$SEO-GEO-OPTIMIZER" },
|
|
11
|
+
"findings": {
|
|
12
|
+
"type": "array",
|
|
13
|
+
"items": {
|
|
14
|
+
"type": "object",
|
|
15
|
+
"required": ["id", "ruleId", "domain", "category", "severity", "confidence", "blocking", "summary", "evidence", "affected", "remediation", "autoFixable"],
|
|
16
|
+
"properties": {
|
|
17
|
+
"id": { "type": "string" },
|
|
18
|
+
"ruleId": { "type": "string" },
|
|
19
|
+
"domain": { "enum": ["seo", "geo", "shared"] },
|
|
20
|
+
"category": { "type": "string" },
|
|
21
|
+
"severity": { "enum": ["info", "low", "medium", "high", "critical"] },
|
|
22
|
+
"confidence": { "type": "number" },
|
|
23
|
+
"blocking": { "type": "boolean" },
|
|
24
|
+
"summary": { "type": "string" },
|
|
25
|
+
"evidence": { "type": "array", "minItems": 1 },
|
|
26
|
+
"affected": { "type": "array" },
|
|
27
|
+
"remediation": { "type": "string" },
|
|
28
|
+
"autoFixable": { "type": "boolean" }
|
|
29
|
+
},
|
|
30
|
+
"additionalProperties": true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"counts": { "type": "object" }
|
|
34
|
+
},
|
|
35
|
+
"additionalProperties": true
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://sneakoscope.dev/schemas/search-visibility/gate.schema.json",
|
|
4
|
+
"title": "SKS Search Visibility Gate",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schema", "mission_id", "route", "ok", "passed", "status", "command_identity", "required_artifacts", "unsupported_claims", "blockers", "unverified", "completion_proof"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schema": { "const": "sks.search-visibility.gate.v1" },
|
|
9
|
+
"mission_id": { "type": "string" },
|
|
10
|
+
"route": { "const": "$SEO-GEO-OPTIMIZER" },
|
|
11
|
+
"ok": { "type": "boolean" },
|
|
12
|
+
"passed": { "type": "boolean" },
|
|
13
|
+
"status": { "enum": ["verified_partial", "blocked", "locally_verified", "production_verified"] },
|
|
14
|
+
"command_identity": { "type": "boolean" },
|
|
15
|
+
"required_artifacts": { "type": "array" },
|
|
16
|
+
"unsupported_claims": { "type": "array" },
|
|
17
|
+
"blockers": { "type": "array" },
|
|
18
|
+
"unverified": { "type": "array" },
|
|
19
|
+
"completion_proof": { "type": "string" }
|
|
20
|
+
},
|
|
21
|
+
"additionalProperties": true
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://sneakoscope.dev/schemas/search-visibility/mutation-plan.schema.json",
|
|
4
|
+
"title": "SKS Search Visibility Mutation Plan",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schema", "mission_id", "route", "mode", "adapter", "detection_confidence", "status", "operations", "blockers", "unverified"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"schema": { "const": "sks.search-visibility.mutation-plan.v1" },
|
|
9
|
+
"mission_id": { "type": "string" },
|
|
10
|
+
"route": { "const": "$SEO-GEO-OPTIMIZER" },
|
|
11
|
+
"mode": { "enum": ["seo", "geo"] },
|
|
12
|
+
"adapter": { "type": "string" },
|
|
13
|
+
"detection_confidence": { "type": "number" },
|
|
14
|
+
"status": { "enum": ["planned", "blocked"] },
|
|
15
|
+
"operations": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"items": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"required": ["id", "path", "baseSha256", "proposedSha256", "kind", "owner", "findingIds", "reversible", "preview", "risk", "requiredVerification", "scopeAuthorization", "ownershipStrategy"],
|
|
20
|
+
"additionalProperties": true
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"blockers": { "type": "array" },
|
|
24
|
+
"unverified": { "type": "array" }
|
|
25
|
+
},
|
|
26
|
+
"additionalProperties": true
|
|
27
|
+
}
|