sneakoscope 4.4.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +28 -2
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/bin/sks.js +1 -1
  6. package/dist/cli/command-registry.js +1 -0
  7. package/dist/core/agents/agent-runner-ollama.js +2 -0
  8. package/dist/core/agents/native-worker-backend-router.js +3 -0
  9. package/dist/core/bench.js +115 -0
  10. package/dist/core/code-structure.js +399 -11
  11. package/dist/core/codex-control/codex-fake-sdk-adapter.js +67 -9
  12. package/dist/core/codex-control/gpt-final-arbiter.js +4 -1
  13. package/dist/core/codex-control/gpt-final-review-schema.js +58 -0
  14. package/dist/core/codex-native/core-skill-manifest.js +23 -0
  15. package/dist/core/commands/bench-command.js +11 -2
  16. package/dist/core/commands/code-structure-command.js +34 -2
  17. package/dist/core/commands/run-command.js +92 -2
  18. package/dist/core/commands/seo-command.js +130 -0
  19. package/dist/core/feature-fixtures.js +6 -0
  20. package/dist/core/feature-registry.js +3 -1
  21. package/dist/core/fsx.js +1 -1
  22. package/dist/core/hooks-runtime.js +8 -0
  23. package/dist/core/init.js +8 -6
  24. package/dist/core/lean-engineering-policy.js +159 -0
  25. package/dist/core/pipeline-internals/runtime-core.js +15 -5
  26. package/dist/core/proof/auto-finalize.js +3 -2
  27. package/dist/core/proof/proof-schema.js +2 -1
  28. package/dist/core/proof/proof-writer.js +1 -0
  29. package/dist/core/proof/route-adapter.js +4 -2
  30. package/dist/core/proof/route-finalizer.js +35 -3
  31. package/dist/core/routes.js +75 -9
  32. package/dist/core/search-visibility/adapter-registry.js +26 -0
  33. package/dist/core/search-visibility/adapters/next-app.js +6 -0
  34. package/dist/core/search-visibility/adapters/next-pages.js +6 -0
  35. package/dist/core/search-visibility/adapters/static-site.js +6 -0
  36. package/dist/core/search-visibility/analyzers.js +377 -0
  37. package/dist/core/search-visibility/artifacts.js +183 -0
  38. package/dist/core/search-visibility/discovery.js +347 -0
  39. package/dist/core/search-visibility/index.js +199 -0
  40. package/dist/core/search-visibility/mission.js +67 -0
  41. package/dist/core/search-visibility/mutation.js +314 -0
  42. package/dist/core/search-visibility/types.js +2 -0
  43. package/dist/core/search-visibility/verifier.js +60 -0
  44. package/dist/core/version.js +1 -1
  45. package/dist/scripts/check-architecture.js +40 -7
  46. package/dist/scripts/check-command-module-budget.js +43 -5
  47. package/dist/scripts/check-pipeline-budget.js +17 -30
  48. package/dist/scripts/check-publish-tag.js +33 -6
  49. package/dist/scripts/check-route-modularity.js +25 -33
  50. package/dist/scripts/check-runtime-schemas.js +22 -0
  51. package/dist/scripts/config-managed-merge-callsite-coverage-check.js +2 -2
  52. package/dist/scripts/core-skill-immutable-sync-check.js +3 -2
  53. package/dist/scripts/core-skill-integrity-blackbox.js +3 -2
  54. package/dist/scripts/core-skill-manifest-check.js +7 -2
  55. package/dist/scripts/geo-claim-evidence-check.js +18 -0
  56. package/dist/scripts/geo-cli-blackbox-check.js +18 -0
  57. package/dist/scripts/geo-crawler-policy-check.js +16 -0
  58. package/dist/scripts/geo-llms-txt-optional-check.js +19 -0
  59. package/dist/scripts/gpt-final-arbiter-check.js +4 -1
  60. package/dist/scripts/release-parallel-check.js +15 -0
  61. package/dist/scripts/release-registry-check.js +33 -14
  62. package/dist/scripts/search-visibility-gate-lib.js +124 -0
  63. package/dist/scripts/seo-audit-fixture-check.js +16 -0
  64. package/dist/scripts/seo-canonical-locale-check.js +19 -0
  65. package/dist/scripts/seo-cli-blackbox-check.js +18 -0
  66. package/dist/scripts/seo-geo-feature-fixture-quality-check.js +18 -0
  67. package/dist/scripts/seo-geo-geo-disambiguation-check.js +12 -0
  68. package/dist/scripts/seo-geo-no-unsupported-ranking-claims-check.js +18 -0
  69. package/dist/scripts/seo-geo-route-identity-check.js +12 -0
  70. package/dist/scripts/seo-geo-skill-rich-content-check.js +22 -0
  71. package/dist/scripts/seo-mutation-rollback-check.js +23 -0
  72. package/dist/scripts/seo-no-mutation-by-default-check.js +17 -0
  73. package/dist/scripts/seo-structured-data-visible-content-check.js +19 -0
  74. package/dist/scripts/sks-3-1-5-directive-check-lib.js +10 -1
  75. package/package.json +19 -1
  76. package/schemas/search-visibility/finding-ledger.schema.json +36 -0
  77. package/schemas/search-visibility/gate.schema.json +22 -0
  78. package/schemas/search-visibility/mutation-plan.schema.json +27 -0
  79. package/schemas/search-visibility/site-inventory.schema.json +21 -0
  80. package/schemas/search-visibility/verification-report.schema.json +23 -0
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from 'node:fs';
4
3
  import path from 'node:path';
5
4
  import { spawnSync } from 'node:child_process';
6
5
  const root = process.cwd();
7
6
  const failures = [];
8
7
  const waivers = loadWaivers();
8
+ const changedFiles = changedFileSet();
9
9
  runGate('pipeline-budget:check');
10
10
  runGate('pipeline-runtime:check');
11
11
  checkFacade('src/core/pipeline-runtime.ts', 300);
@@ -44,10 +44,16 @@ function checkLargeFiles() {
44
44
  if (isWaivedGenerated(relPath))
45
45
  continue;
46
46
  const max = architectureLineLimit(relPath);
47
- if (lines > max)
48
- failures.push(`${relPath}: handwritten file ${lines} lines > ${max} architecture gate`);
49
- if (!isRouteDomainAggregator(relPath) && importsUnrelatedRouteDomains(file))
50
- failures.push(`${relPath}: imports 5+ unrelated route domains`);
47
+ const baselineLines = changedFiles.has(relPath) ? baseLineCount(relPath) : lines;
48
+ if (lines >= 3000)
49
+ failures.push(`${relPath}: handwritten file ${lines} lines >= 3000 split-review risk`);
50
+ if (baselineLines === null && lines > 400)
51
+ failures.push(`${relPath}: new source file ${lines} lines > 400`);
52
+ if (changedFiles.has(relPath) && lines > max && (baselineLines === null || lines - baselineLines > 80)) {
53
+ failures.push(`${relPath}: changed file grew to ${lines} lines > ${max} architecture gate`);
54
+ }
55
+ if (changedFiles.has(relPath) && !isRouteDomainAggregator(relPath) && importsUnrelatedRouteDomains(file))
56
+ failures.push(`${relPath}: changed file imports 5+ unrelated route domains`);
51
57
  }
52
58
  }
53
59
  function architectureLineLimit(relPath) {
@@ -79,8 +85,11 @@ function importSpecs(text) {
79
85
  const specs = [];
80
86
  const re = /^\s*import\s+(?:[^'"]*?\s+from\s+)?['"]([^'"]+)['"]/gm;
81
87
  let match;
82
- while ((match = re.exec(text)))
83
- specs.push(match[1]);
88
+ while ((match = re.exec(text))) {
89
+ const spec = match[1];
90
+ if (spec)
91
+ specs.push(spec);
92
+ }
84
93
  return specs;
85
94
  }
86
95
  function walk(dir, out) {
@@ -132,4 +141,28 @@ function isWaivedGenerated(relPath) {
132
141
  && waiver.reason === 'generated'
133
142
  && waiver.expires_version);
134
143
  }
144
+ function changedFileSet() {
145
+ const out = new Set();
146
+ for (const line of gitLines(['diff', '--name-only', 'HEAD', '--']))
147
+ out.add(normalize(line));
148
+ for (const line of gitLines(['ls-files', '--others', '--exclude-standard']))
149
+ out.add(normalize(line));
150
+ return out;
151
+ }
152
+ function baseLineCount(relPath) {
153
+ const result = spawnSync('git', ['show', `HEAD:${relPath}`], { cwd: root, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024 });
154
+ if (result.status !== 0)
155
+ return null;
156
+ const text = String(result.stdout || '');
157
+ return text ? text.split(/\r?\n/).length : 0;
158
+ }
159
+ function gitLines(args) {
160
+ const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
161
+ if (result.status !== 0)
162
+ return [];
163
+ return String(result.stdout || '').split(/\r?\n/).map(normalize).filter(Boolean);
164
+ }
165
+ function normalize(file) {
166
+ return String(file || '').replace(/\\/g, '/');
167
+ }
135
168
  //# sourceMappingURL=check-architecture.js.map
@@ -1,19 +1,30 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from 'node:fs';
4
3
  import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
5
  const root = process.cwd();
6
6
  const dir = path.join(root, 'src', 'core', 'commands');
7
7
  const failures = [];
8
+ const changedFiles = changedFileSet();
8
9
  for (const file of fs.readdirSync(dir).filter((name) => name.endsWith('-command.ts'))) {
9
10
  const absolute = path.join(dir, file);
11
+ const relPath = rel(absolute);
10
12
  const text = fs.readFileSync(absolute, 'utf8');
11
13
  const lines = text.split(/\r?\n/).length;
12
14
  const imports = [...text.matchAll(/^\s*import\s+/gm)].length;
13
- if (lines > 1200)
14
- failures.push(`${file}: line count ${lines} > 1200`);
15
- if (imports > 35)
16
- failures.push(`${file}: import count ${imports} > 35`);
15
+ const changed = changedFiles.has(relPath);
16
+ const baselineLines = baseLineCount(relPath);
17
+ if (lines > 1800)
18
+ failures.push(`${file}: line count ${lines} > 1800 hard ceiling`);
19
+ if (baselineLines === null && lines > 400)
20
+ failures.push(`${file}: new command module ${lines} lines > 400`);
21
+ if (changed && lines > 1200 && (baselineLines === null || lines - baselineLines > 50)) {
22
+ failures.push(`${file}: changed command module grew to ${lines} lines > 1200`);
23
+ }
24
+ if (imports > 60)
25
+ failures.push(`${file}: import count ${imports} > 60 hard ceiling`);
26
+ if (changed && imports > 35)
27
+ failures.push(`${file}: changed command module import count ${imports} > 35`);
17
28
  }
18
29
  if (failures.length) {
19
30
  console.error('Command module budget check failed:');
@@ -22,4 +33,31 @@ if (failures.length) {
22
33
  process.exit(1);
23
34
  }
24
35
  console.log('Command module budget check passed');
36
+ function changedFileSet() {
37
+ const out = new Set();
38
+ for (const line of gitLines(['diff', '--name-only', 'HEAD', '--']))
39
+ out.add(normalize(line));
40
+ for (const line of gitLines(['ls-files', '--others', '--exclude-standard']))
41
+ out.add(normalize(line));
42
+ return out;
43
+ }
44
+ function baseLineCount(relPath) {
45
+ const result = spawnSync('git', ['show', `HEAD:${relPath}`], { cwd: root, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
46
+ if (result.status !== 0)
47
+ return null;
48
+ const text = String(result.stdout || '');
49
+ return text ? text.split(/\r?\n/).length : 0;
50
+ }
51
+ function gitLines(args) {
52
+ const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' });
53
+ if (result.status !== 0)
54
+ return [];
55
+ return String(result.stdout || '').split(/\r?\n/).map(normalize).filter(Boolean);
56
+ }
57
+ function rel(file) {
58
+ return normalize(path.relative(root, file));
59
+ }
60
+ function normalize(file) {
61
+ return String(file || '').replace(/\\/g, '/');
62
+ }
25
63
  //# sourceMappingURL=check-command-module-budget.js.map
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from 'node:fs';
4
3
  import path from 'node:path';
5
4
  const root = process.cwd();
@@ -8,6 +7,19 @@ const facade = path.join(root, 'src', 'core', 'pipeline.ts');
8
7
  const facadeLines = lineCount(facade);
9
8
  if (facadeLines > 200)
10
9
  failures.push(`src/core/pipeline.ts: line count ${facadeLines} > 200`);
10
+ const facadeText = fs.readFileSync(facade, 'utf8');
11
+ for (const exportName of [
12
+ 'buildPipelinePlan',
13
+ 'writePipelinePlan',
14
+ 'validatePipelinePlan',
15
+ 'promptPipelineContext',
16
+ 'prepareRoute',
17
+ 'activeRouteContext',
18
+ 'evaluateStop'
19
+ ]) {
20
+ if (!facadeText.includes(exportName))
21
+ failures.push(`src/core/pipeline.ts: missing exported pipeline API ${exportName}`);
22
+ }
11
23
  const runtimeFacade = path.join(root, 'src', 'core', 'pipeline-runtime.ts');
12
24
  if (fs.existsSync(runtimeFacade)) {
13
25
  const runtimeLines = lineCount(runtimeFacade);
@@ -19,7 +31,10 @@ if (fs.existsSync(runtimeFacade)) {
19
31
  }
20
32
  }
21
33
  const moduleDir = path.join(root, 'src', 'core', 'pipeline');
22
- for (const file of fs.readdirSync(moduleDir).filter((name) => name.endsWith('.ts'))) {
34
+ const moduleFiles = fs.readdirSync(moduleDir).filter((name) => name.endsWith('.ts'));
35
+ if (moduleFiles.length < 8)
36
+ failures.push(`src/core/pipeline: split module count ${moduleFiles.length} < 8`);
37
+ for (const file of moduleFiles) {
23
38
  const absolute = path.join(moduleDir, file);
24
39
  const lines = lineCount(absolute);
25
40
  if (lines > 1000)
@@ -28,34 +43,6 @@ for (const file of fs.readdirSync(moduleDir).filter((name) => name.endsWith('.ts
28
43
  if (imports.length > 35)
29
44
  failures.push(`src/core/pipeline/${file}: import count ${imports.length} > 35`);
30
45
  }
31
- const required = [
32
- 'plan-schema.ts',
33
- 'stage-policy.ts',
34
- 'agent-stage-policy.ts',
35
- 'route-prep.ts',
36
- 'route-prep-team.ts',
37
- 'route-prep-research.ts',
38
- 'route-prep-qa.ts',
39
- 'route-prep-ppt.ts',
40
- 'route-prep-image-ux.ts',
41
- 'route-prep-db.ts',
42
- 'route-prep-gx.ts',
43
- 'stop-gate.ts',
44
- 'stop-gate-context7.ts',
45
- 'stop-gate-subagents.ts',
46
- 'stop-gate-proof.ts',
47
- 'active-context.ts',
48
- 'prompt-context.ts',
49
- 'prompt-context-dfix.ts',
50
- 'prompt-context-answer.ts',
51
- 'prompt-context-computer-use.ts',
52
- 'pipeline-plan-writer.ts',
53
- 'validation.ts'
54
- ];
55
- for (const file of required) {
56
- if (!fs.existsSync(path.join(moduleDir, file)))
57
- failures.push(`src/core/pipeline/${file}: missing`);
58
- }
59
46
  if (failures.length) {
60
47
  console.error('Pipeline budget check failed:');
61
48
  for (const failure of failures)
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from 'node:fs';
4
3
  import path from 'node:path';
5
4
  import { fileURLToPath } from 'node:url';
@@ -10,21 +9,49 @@ const isPrerelease = version.includes('-');
10
9
  const publishLifecycle = process.env.npm_lifecycle_event === 'prepublishOnly';
11
10
  const npmTag = process.env.npm_config_tag || process.env.NPM_CONFIG_TAG || '';
12
11
  const publishConfigTag = pkg.publishConfig?.tag || '';
12
+ const npmrcTag = readRootNpmrcTag();
13
13
  const configuredTag = publishLifecycle && (!npmTag || npmTag === 'latest')
14
- ? (publishConfigTag || npmTag || 'latest')
15
- : (npmTag || publishConfigTag || 'latest');
16
- const expectedTag = isPrerelease ? 'rc' : 'latest';
14
+ ? (npmrcTag || publishConfigTag || npmTag || 'latest')
15
+ : (npmTag || npmrcTag || publishConfigTag || 'latest');
16
+ const isBackfill = !isPrerelease && /^backfill(?:[-_][a-z0-9.-]+)?$/i.test(configuredTag);
17
+ const expectedTag = isPrerelease ? 'rc' : isBackfill ? configuredTag : 'latest';
18
+ if (publishConfigTag && npmrcTag && publishConfigTag !== npmrcTag) {
19
+ console.error('package.json publishConfig.tag and root .npmrc tag disagree.');
20
+ console.error(`publishConfig.tag: ${publishConfigTag}`);
21
+ console.error(`.npmrc tag: ${npmrcTag}`);
22
+ process.exit(2);
23
+ }
24
+ if (publishConfigTag && /^backfill(?:[-_][a-z0-9.-]+)?$/i.test(publishConfigTag) && !npmrcTag && !npmTag) {
25
+ console.error('Backfill releases must set root .npmrc tag so `npm publish --ignore-scripts` does not use latest.');
26
+ console.error(`Expected .npmrc: tag=${publishConfigTag}`);
27
+ process.exit(2);
28
+ }
17
29
  if (configuredTag !== expectedTag) {
18
30
  if (isPrerelease) {
19
31
  console.error(`Prerelease ${pkg.name}@${version} must be published with the rc dist-tag.`);
20
32
  console.error('Set package.json publishConfig.tag to rc or pass `--tag rc` to npm publish.');
21
33
  }
22
34
  else {
23
- console.error(`Stable release ${pkg.name}@${version} must be published with the latest dist-tag.`);
24
- console.error('Remove prerelease tag config and use plain `npm publish`.');
35
+ console.error(`Stable release ${pkg.name}@${version} must be published with the latest dist-tag unless it is an explicit backfill.`);
36
+ console.error('Use latest for forward releases, or a backfill-* publishConfig.tag for intentional unpublished lower-version backfills.');
25
37
  }
26
38
  console.error(`Current npm tag config: ${configuredTag || 'missing'}`);
27
39
  process.exit(2);
28
40
  }
29
41
  console.log(`Publish tag check passed: ${pkg.name}@${version} -> ${configuredTag}`);
42
+ function readRootNpmrcTag() {
43
+ const npmrcPath = path.join(root, '.npmrc');
44
+ if (!fs.existsSync(npmrcPath))
45
+ return '';
46
+ const text = fs.readFileSync(npmrcPath, 'utf8');
47
+ for (const line of text.split(/\r?\n/)) {
48
+ const trimmed = line.trim();
49
+ if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith(';'))
50
+ continue;
51
+ const match = trimmed.match(/^tag\s*=\s*(.+)$/);
52
+ if (match)
53
+ return String(match[1] || '').trim();
54
+ }
55
+ return '';
56
+ }
30
57
  //# sourceMappingURL=check-publish-tag.js.map
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from 'node:fs';
4
3
  import path from 'node:path';
5
4
  const root = process.cwd();
@@ -7,37 +6,23 @@ const failures = [];
7
6
  const routeCli = path.join(root, 'src', 'core', 'commands', 'route-cli.mjs');
8
7
  if (fs.existsSync(routeCli))
9
8
  failures.push('src/core/commands/route-cli.mjs must not exist in runtime source');
10
- for (const file of listFiles(path.join(root, 'src', 'commands'), '.mjs')) {
11
- const text = fs.readFileSync(file, 'utf8');
12
- if (text.includes('route-cli.mjs'))
13
- failures.push(`${rel(file)} imports route-cli.mjs`);
9
+ const legacyCommandsDir = path.join(root, 'src', 'commands');
10
+ if (fs.existsSync(legacyCommandsDir)) {
11
+ for (const file of listFiles(legacyCommandsDir, '.mjs')) {
12
+ const text = fs.readFileSync(file, 'utf8');
13
+ if (text.includes('route-cli.mjs'))
14
+ failures.push(`${rel(file)} imports route-cli.mjs`);
15
+ }
14
16
  }
15
- const requiredModules = [
16
- 'team-command.mjs',
17
- 'qa-loop-command.mjs',
18
- 'research-command.mjs',
19
- 'autoresearch-command.mjs',
20
- 'ppt-command.mjs',
21
- 'image-ux-review-command.mjs',
22
- 'computer-use-command.mjs',
23
- 'db-command.mjs',
24
- 'wiki-command.mjs',
25
- 'gx-command.mjs',
26
- 'goal-command.mjs',
27
- 'pipeline-command.mjs',
28
- 'recallpulse-command.mjs',
29
- 'hproof-command.mjs',
30
- 'validate-artifacts-command.mjs'
31
- ];
32
- for (const name of requiredModules) {
33
- if (!commandModuleExists(name))
34
- failures.push(`missing ${name}`);
17
+ for (const relModule of registeredCommandModules()) {
18
+ if (!fs.existsSync(path.join(root, relModule)))
19
+ failures.push(`registered command module missing: ${relModule}`);
35
20
  }
36
21
  const disallowedImports = {
37
- 'qa-loop-command.mjs': ['research-command', 'ppt-command', 'team-command', 'gx-command'],
38
- 'research-command.mjs': ['qa-loop-command', 'ppt-command', 'team-command', 'gx-command'],
39
- 'db-command.mjs': ['image-ux-review-command', 'ppt-command', 'computer-use-command'],
40
- 'wiki-command.mjs': ['team-command', 'research-command', 'qa-loop-command', 'ppt-command']
22
+ 'qa-loop-command.ts': ['research-command', 'ppt-command', 'team-command', 'gx-command'],
23
+ 'research-command.ts': ['qa-loop-command', 'ppt-command', 'team-command', 'gx-command'],
24
+ 'db-command.ts': ['image-ux-review-command', 'ppt-command', 'computer-use-command'],
25
+ 'wiki-command.ts': ['team-command', 'research-command', 'qa-loop-command', 'ppt-command']
41
26
  };
42
27
  for (const [name, needles] of Object.entries(disallowedImports)) {
43
28
  const file = commandModulePath(name);
@@ -56,6 +41,16 @@ if (failures.length) {
56
41
  process.exit(1);
57
42
  }
58
43
  console.log('Route modularity check passed');
44
+ function registeredCommandModules() {
45
+ const registry = fs.readFileSync(path.join(root, 'src', 'cli', 'command-registry.ts'), 'utf8');
46
+ const out = new Set();
47
+ for (const match of registry.matchAll(/['"]dist\/core\/commands\/([^'"]+)\.js['"]/g)) {
48
+ const stem = match[1];
49
+ if (stem)
50
+ out.add(`src/core/commands/${stem}.ts`);
51
+ }
52
+ return [...out].sort();
53
+ }
59
54
  function listFiles(dir, ext) {
60
55
  const out = [];
61
56
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -72,11 +67,8 @@ function rel(file) {
72
67
  }
73
68
  function commandModulePath(name) {
74
69
  const base = path.join(root, 'src', 'core', 'commands');
75
- const mjs = path.join(base, name);
70
+ const mjs = path.join(base, name.replace(/\.ts$/, '.mjs'));
76
71
  const ts = path.join(base, name.replace(/\.mjs$/, '.ts'));
77
72
  return fs.existsSync(mjs) ? mjs : ts;
78
73
  }
79
- function commandModuleExists(name) {
80
- return fs.existsSync(commandModulePath(name));
81
- }
82
74
  //# sourceMappingURL=check-route-modularity.js.map
@@ -80,6 +80,28 @@ for (const file of [
80
80
  issues.push(`codex_schema_invalid_json:${file}:${err.message}`);
81
81
  }
82
82
  }
83
+ const searchVisibilitySchemaDir = path.join(root, 'schemas', 'search-visibility');
84
+ for (const file of [
85
+ 'site-inventory.schema.json',
86
+ 'finding-ledger.schema.json',
87
+ 'gate.schema.json',
88
+ 'mutation-plan.schema.json',
89
+ 'verification-report.schema.json'
90
+ ]) {
91
+ const full = path.join(searchVisibilitySchemaDir, file);
92
+ if (!fs.existsSync(full)) {
93
+ issues.push(`search_visibility_schema_missing:${file}`);
94
+ continue;
95
+ }
96
+ try {
97
+ const parsed = JSON.parse(fs.readFileSync(full, 'utf8'));
98
+ if (parsed.type !== 'object' || !parsed.$id)
99
+ issues.push(`search_visibility_schema_shape:${file}`);
100
+ }
101
+ catch (err) {
102
+ issues.push(`search_visibility_schema_invalid_json:${file}:${err.message}`);
103
+ }
104
+ }
83
105
  const result = { schema: 'sks.runtime-schema-check.v1', ok: issues.length === 0, validators_ok: validatorsOk, issues };
84
106
  console.log(JSON.stringify(result, null, 2));
85
107
  if (!result.ok)
@@ -118,13 +118,13 @@ const ALLOWLIST = [
118
118
  file: 'src/core/mad-db/mad-db-runtime-profile.ts',
119
119
  pattern: /codex-mad-db\.config\.toml|writeTextAtomic/,
120
120
  reason: 'MAD-DB runtime profile writes only a mission-local temporary Codex profile and verifies read-only restoration on close',
121
- expires: '4.3.0'
121
+ expires: '4.6.0'
122
122
  },
123
123
  {
124
124
  file: 'src/core/providers/glm/naruto/glm-naruto-trace.ts',
125
125
  pattern: /mission-result\.json|sanitizeArtifact/,
126
126
  reason: 'GLM Naruto trace writer persists sanitized mission-result proof artifacts, not raw env secret files',
127
- expires: '4.3.0'
127
+ expires: '4.6.0'
128
128
  }
129
129
  ];
130
130
  const sources = listSourceFiles().map((file) => ({
@@ -3,11 +3,12 @@ import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { assertGate, emitGate, makeTempRoot, writeText } from './sks-3-1-8-check-lib.js';
5
5
  import { syncCoreSkillsIntegrity } from '../core/codex-native/core-skill-integrity.js';
6
- import { renderCoreSkillTemplate } from '../core/codex-native/core-skill-manifest.js';
6
+ import { buildSksCoreSkillManifest, renderCoreSkillTemplate } from '../core/codex-native/core-skill-manifest.js';
7
7
  const root = await makeTempRoot('sks-core-immutable-');
8
8
  const skillsRoot = path.join(root, '.agents', 'skills');
9
+ const expectedSkillCount = buildSksCoreSkillManifest('1970-01-01T00:00:00.000Z').skills.length;
9
10
  const first = await syncCoreSkillsIntegrity({ root, apply: true, skillsRoot });
10
- assertGate(first.installed.length === 8, 'first immutable sync must install missing managed core skills', first);
11
+ assertGate(first.installed.length === expectedSkillCount, 'first immutable sync must install missing managed core skills', first);
11
12
  const second = await syncCoreSkillsIntegrity({ root, apply: true, skillsRoot });
12
13
  assertGate(second.installed.length === 0 && second.restored.length === 0, 'second immutable sync must be idempotent', second);
13
14
  await writeText(path.join(skillsRoot, 'loop', 'SKILL.md'), `${renderCoreSkillTemplate('loop')}\ncorruption\n`);
@@ -3,10 +3,11 @@ import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { assertGate, emitGate, makeTempRoot, writeText, writeUserSkill } from './sks-3-1-8-check-lib.js';
5
5
  import { syncCoreSkillsIntegrity } from '../core/codex-native/core-skill-integrity.js';
6
- import { renderCoreSkillTemplate } from '../core/codex-native/core-skill-manifest.js';
6
+ import { buildSksCoreSkillManifest, renderCoreSkillTemplate } from '../core/codex-native/core-skill-manifest.js';
7
7
  import { initProject } from '../core/init.js';
8
8
  const root = await makeTempRoot('sks-core-blackbox-');
9
9
  const skillsRoot = path.join(root, '.agents', 'skills');
10
+ const expectedSkillCount = buildSksCoreSkillManifest('1970-01-01T00:00:00.000Z').skills.length;
10
11
  const first = await syncCoreSkillsIntegrity({ root, apply: true, skillsRoot });
11
12
  const second = await syncCoreSkillsIntegrity({ root, apply: true, skillsRoot });
12
13
  await writeText(path.join(skillsRoot, 'research', 'SKILL.md'), `${renderCoreSkillTemplate('research')}\nmutated\n`);
@@ -21,7 +22,7 @@ const setupIntegrity = await syncCoreSkillsIntegrity({ root: setupRoot, apply: f
21
22
  await writeText(path.join(setupRoot, '.agents', 'skills', 'loop', 'SKILL.md'), `${renderCoreSkillTemplate('loop')}\nmutated by setup blackbox\n`);
22
23
  const setupRestore = await initProject(setupRoot, { force: true, installScope: 'project', localOnly: true, globalCommand: 'sks' });
23
24
  const restoredSetupText = await fs.readFile(path.join(setupRoot, '.agents', 'skills', 'loop', 'SKILL.md'), 'utf8');
24
- assertGate(first.installed.length === 8, 'blackbox A: first sync installs missing core skills', first);
25
+ assertGate(first.installed.length === expectedSkillCount, 'blackbox A: first sync installs missing core skills', first);
25
26
  assertGate(second.installed.length === 0 && second.restored.length === 0, 'blackbox B: second sync changes nothing', second);
26
27
  assertGate(third.restored.length === 1 && restoredText === renderCoreSkillTemplate('research'), 'blackbox C: managed drift restored exactly', third);
27
28
  assertGate(user.skipped_user_authored.length === 1, 'blackbox D: user skill is not overwritten', user);
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import { assertGate, emitGate } from './sks-3-1-8-check-lib.js';
3
- import { CORE_SKILL_TEMPLATE_VERSION, buildSksCoreSkillManifest, renderCoreSkillTemplate } from '../core/codex-native/core-skill-manifest.js';
3
+ import { CORE_SKILL_TEMPLATE_VERSION, buildSksCoreSkillManifest, coreSkillDefinitions, renderCoreSkillTemplate } from '../core/codex-native/core-skill-manifest.js';
4
4
  import { sha256 } from '../core/fsx.js';
5
5
  const manifest = buildSksCoreSkillManifest('1970-01-01T00:00:00.000Z');
6
+ const definitions = coreSkillDefinitions();
6
7
  assertGate(manifest.schema === 'sks.core-skill-manifest.v1', 'manifest schema mismatch', manifest);
7
- assertGate(manifest.skills.length === 8, 'manifest must list eight immutable core skills', manifest.skills.map((skill) => skill.canonical_name));
8
+ assertGate(manifest.skills.length === definitions.length, 'manifest must list every immutable core skill definition', {
9
+ manifest: manifest.skills.map((skill) => skill.canonical_name),
10
+ definitions: definitions.map((skill) => skill.canonical_name)
11
+ });
12
+ assertGate(new Set(manifest.skills.map((skill) => skill.canonical_name)).size === manifest.skills.length, 'manifest core skill names must be unique', manifest.skills.map((skill) => skill.canonical_name));
8
13
  assertGate(CORE_SKILL_TEMPLATE_VERSION === 'sks-core-skill-template.v1', 'core skill template version must be content-schema based, not package-version based', { CORE_SKILL_TEMPLATE_VERSION });
9
14
  for (const skill of manifest.skills) {
10
15
  const content = renderCoreSkillTemplate(skill.canonical_name);
@@ -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('geo-claims', {
7
+ title: 'Fixture That Guarantees AI Citation',
8
+ description: 'Fixture that guarantees AI citation and traffic lift.',
9
+ });
10
+ fs.appendFileSync(path.join(fixture, 'README.md'), '\nThis fixture guarantees AI citation and traffic lift.\n');
11
+ const audit = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'geo', '--root', fixture, '--target', 'package', '--offline', '--json'], { allowFailure: true }).json;
12
+ const claims = assertMissionArtifact(audit.mission_id, 'claim-evidence-ledger.json', fixture);
13
+ const findings = assertMissionArtifact(audit.mission_id, 'geo-findings.json', fixture);
14
+ assertGate(claims.claims.some((claim) => claim.safe_to_publish === false && /citation|traffic/i.test(claim.claim)), 'unsupported GEO claim must be unsafe to publish', claims);
15
+ assertGate(findings.findings.some((finding) => finding.category === 'claim-evidence' && finding.severity === 'critical'), 'unsafe claim must become critical claim-evidence finding', findings);
16
+ assertGate(audit.ok === false || findings.findings.some((finding) => finding.blocking), 'critical unsafe claims must block the gate', { audit, findings });
17
+ emitGate('geo:claim-evidence', { mission_id: audit.mission_id, claims: claims.claims.length });
18
+ //# sourceMappingURL=geo-claim-evidence-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', 'geo', '--json']).json;
10
+ assertGate(doctor.route === '$SEO-GEO-OPTIMIZER' && doctor.schema === 'sks.search-visibility.doctor-command.v1', 'optimizer geo doctor must preserve unified route identity', doctor);
11
+ const invalid = runSks(['seo-geo-optimizer', 'nonsense-subcommand', '--mode', 'geo'], { 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('geo:cli-blackbox', { adapter: doctor.adapter, confidence: doctor.confidence });
18
+ //# sourceMappingURL=geo-cli-blackbox-check.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('geo-crawlers');
5
+ const audit = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'geo', '--root', fixture, '--target', 'package', '--offline', '--json']).json;
6
+ const policy = assertMissionArtifact(audit.mission_id, 'ai-crawler-policy.json', fixture);
7
+ const purposes = new Set(policy.entries.map((entry) => entry.purpose));
8
+ const agents = new Set(policy.entries.map((entry) => entry.userAgent));
9
+ assertGate(policy.policy.single_allow_ai_toggle === false && policy.policy.purpose_split_required === true, 'AI crawler policy must split purposes instead of one allow_ai toggle', policy);
10
+ for (const purpose of ['search', 'training', 'user_retrieval'])
11
+ assertGate(purposes.has(purpose), `crawler purpose missing: ${purpose}`, policy);
12
+ for (const agent of ['OAI-SearchBot', 'GPTBot', 'ChatGPT-User', 'Claude-SearchBot', 'ClaudeBot', 'Claude-User'])
13
+ assertGate(agents.has(agent), `crawler user agent missing: ${agent}`, policy);
14
+ assertGate(policy.entries.every((entry) => entry.officialSource && entry.observedAt && entry.expiresAt), 'crawler registry entries must carry dated official sources', policy);
15
+ emitGate('geo:crawler-purpose-policy', { mission_id: audit.mission_id, entries: policy.entries.length });
16
+ //# sourceMappingURL=geo-crawler-policy-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('geo-llms');
7
+ const audit = runSksJson(['seo-geo-optimizer', 'audit', '--mode', 'geo', '--root', fixture, '--target', 'package', '--offline', '--json']).json;
8
+ const plan = runSksJson(['seo-geo-optimizer', 'plan', audit.mission_id, '--mode', 'geo', '--root', fixture, '--json']).json;
9
+ const llmsPlan = assertMissionArtifact(audit.mission_id, 'llms-txt-plan.json', fixture);
10
+ assertGate(audit.ok === true, 'missing llms.txt must not block GEO gate', audit);
11
+ assertGate(plan.operations === 0, 'GEO plan must not create llms.txt unless --include-llms-txt is explicit', plan);
12
+ assertGate(llmsPlan.required_for_gate === false && llmsPlan.experimental_assistive_surface === true, 'llms.txt must be optional and experimental', llmsPlan);
13
+ assertGate(!fs.existsSync(path.join(fixture, 'llms.txt')), 'llms.txt must not be created by default');
14
+ const explicitPlan = runSksJson(['seo-geo-optimizer', 'plan', audit.mission_id, '--mode', 'geo', '--root', fixture, '--include-llms-txt', '--json']).json;
15
+ assertGate(explicitPlan.operations === 1, 'explicit --include-llms-txt should plan managed llms.txt creation', explicitPlan);
16
+ const apply = runSksJson(['seo-geo-optimizer', 'apply', audit.mission_id, '--mode', 'geo', '--root', fixture, '--include-llms-txt', '--apply', '--json']).json;
17
+ assertGate(apply.ok === true && fs.existsSync(path.join(fixture, 'llms.txt')), 'explicit llms.txt apply must create managed file', apply);
18
+ emitGate('geo:llms-txt-optional', { mission_id: audit.mission_id });
19
+ //# sourceMappingURL=geo-llms-txt-optional-check.js.map
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // @ts-nocheck
3
2
  import fs from 'node:fs/promises';
4
3
  import os from 'node:os';
5
4
  import path from 'node:path';
@@ -15,13 +14,17 @@ try {
15
14
  assertGate(approved.ok === true, 'GPT final arbiter fixture must approve safe candidate', approved);
16
15
  assertGate(approved.backend === 'codex-sdk', 'GPT final arbiter backend must be codex-sdk');
17
16
  assertGate(approved.result.status === 'approved', 'safe candidate must return approved');
17
+ assertGate(approved.result.lean_review?.status === 'pass', 'safe candidate must pass lean review');
18
+ assertGate(approved.result.lean_review?.selected_rung === 'minimal-custom', 'lean review must record selected rung');
18
19
  assertGate(approved.final_gate.ok === true, 'approved arbiter result must pass final gate');
19
20
  const artifact = await readJsonFile(path.join(tmp, 'gpt-final-arbiter.json'));
20
21
  assertGate(artifact.result.schema === 'sks.gpt-final-arbiter-result.v1', 'arbiter artifact must contain schema-valid result');
22
+ assertGate(artifact.result.lean_review?.verification_minimum_present === true, 'arbiter artifact must record lean verification minimum');
21
23
  const unsafeTmp = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-gpt-final-arbiter-unsafe-'));
22
24
  const rejected = await mod.runGptFinalArbiter({ ...fixtureInput('unsafe'), candidate_diff: 'unsafe delete all credential patch' }, { cwd: root, mutationLedgerRoot: unsafeTmp });
23
25
  assertGate(rejected.ok === false, 'unsafe candidate must not pass final arbiter');
24
26
  assertGate(rejected.result.status === 'rejected', 'unsafe candidate must be rejected');
27
+ assertGate(rejected.result.lean_review?.status === 'rejected', 'unsafe candidate must reject lean review');
25
28
  assertGate(rejected.blockers.includes('unsafe_candidate_patch'), 'unsafe rejection blocker must be preserved');
26
29
  emitGate('local-collab:gpt-final-arbiter', { approved: approved.result.status, rejected: rejected.result.status });
27
30
  }
@@ -38,6 +38,21 @@ const tasks = [
38
38
  task('source-intelligence:policy', 'npm run source-intelligence:policy --silent', { dependencies: ['build'] }),
39
39
  task('source-intelligence:all-modes', 'npm run source-intelligence:all-modes --silent', { dependencies: ['build', 'source-intelligence:policy', 'ultra-search:provider-interface', 'codex-web:adapter'] }),
40
40
  task('codex-web:adapter', 'npm run codex-web:adapter --silent', { dependencies: ['build'] }),
41
+ task('seo:cli-blackbox', 'npm run seo:cli-blackbox --silent', { dependencies: ['build'] }),
42
+ task('seo:audit-fixture', 'npm run seo:audit-fixture --silent', { dependencies: ['build'] }),
43
+ task('seo:no-mutation-by-default', 'npm run seo:no-mutation-by-default --silent', { dependencies: ['build'] }),
44
+ task('seo:mutation-rollback', 'npm run seo:mutation-rollback --silent', { dependencies: ['build'] }),
45
+ task('seo:canonical-sitemap-locale', 'npm run seo:canonical-sitemap-locale --silent', { dependencies: ['build'] }),
46
+ task('seo:structured-data-visible-content', 'npm run seo:structured-data-visible-content --silent', { dependencies: ['build'] }),
47
+ task('geo:cli-blackbox', 'npm run geo:cli-blackbox --silent', { dependencies: ['build'] }),
48
+ task('geo:claim-evidence', 'npm run geo:claim-evidence --silent', { dependencies: ['build'] }),
49
+ task('geo:crawler-purpose-policy', 'npm run geo:crawler-purpose-policy --silent', { dependencies: ['build'] }),
50
+ task('geo:llms-txt-optional', 'npm run geo:llms-txt-optional --silent', { dependencies: ['build'] }),
51
+ task('seo-geo:route-identity', 'npm run seo-geo:route-identity --silent', { dependencies: ['build'] }),
52
+ task('seo-geo:geo-disambiguation', 'npm run seo-geo:geo-disambiguation --silent', { dependencies: ['build'] }),
53
+ task('seo-geo:skill-rich-content', 'npm run seo-geo:skill-rich-content --silent', { dependencies: ['build'] }),
54
+ task('seo-geo:no-unsupported-ranking-claims', 'npm run seo-geo:no-unsupported-ranking-claims --silent', { dependencies: ['build'] }),
55
+ task('seo-geo:feature-fixture-quality', 'npm run seo-geo:feature-fixture-quality --silent', { dependencies: ['build'] }),
41
56
  task('doctor:codex-doctor-parity', 'npm run doctor:codex-doctor-parity --silent', { dependencies: ['build'] }),
42
57
  task('codex:permission-profiles', 'npm run codex:permission-profiles --silent', { dependencies: ['build'] }),
43
58
  task('codex:legacy-profile-consumers-removed', 'npm run codex:legacy-profile-consumers-removed --silent', { dependencies: ['build'] }),