sneakoscope 4.8.0 → 4.8.2

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 CHANGED
@@ -49,7 +49,7 @@ sks seo-geo-optimizer apply latest --mode seo --apply --json
49
49
  sks seo-geo-optimizer audit --mode geo --target package --offline --json
50
50
  ```
51
51
 
52
- > 📋 **Current release: `v4.8.0`** — full release history lives in [CHANGELOG.md](CHANGELOG.md). This README documents how Sneakoscope works today, not its version-by-version changes. Release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
52
+ > 📋 **Current release: `v4.8.2`** — full release history lives in [CHANGELOG.md](CHANGELOG.md). This README documents how Sneakoscope works today, not its version-by-version changes. Release readiness is tracked in [docs/release-readiness.md](docs/release-readiness.md).
53
53
 
54
54
  ## 🍥 Parallelism, UX, And Integrations
55
55
 
@@ -363,7 +363,7 @@ sks codex-app set-openrouter-key --api-key-stdin
363
363
  sks codex-app glm-profile install
364
364
  ```
365
365
 
366
- This writes the OpenRouter key to the SKS user secret store, writes redacted metadata only, and installs Codex Desktop-compatible `openrouter` provider plus selectable `sks-glm-52-*` reasoning profiles. The npm package does not patch the Codex Desktop macOS menu binary; a top-level menu between Window and Help requires upstream Codex Desktop UI support. On macOS, `sks doctor --fix` installs or refreshes a right-side SKS menu bar companion that appears as a compact circled `S` status icon with codex-lb, ChatGPT OAuth, OpenRouter/GLM, Fast Check, SKS Version Check, Update SKS Now, settings, and Codex restart actions.
366
+ This writes the OpenRouter key to the SKS user secret store, writes redacted metadata only, and installs Codex Desktop-compatible `openrouter` provider plus selectable `sks-glm-52-*` reasoning profiles. The npm package does not patch the Codex Desktop macOS menu binary; a top-level menu between Window and Help requires upstream Codex Desktop UI support. On macOS, `sks doctor --fix` installs or refreshes a right-side `SKS` menu bar companion with codex-lb, ChatGPT OAuth, OpenRouter/GLM, Fast Check, SKS Version Check, Update SKS Now, settings, and Codex restart actions.
367
367
 
368
368
  ### Switching auth mode: codex-lb ↔ ChatGPT OAuth
369
369
 
@@ -763,7 +763,7 @@ npm run release:check
763
763
  npm run publish:dry
764
764
  ```
765
765
 
766
- `release:check` runs the change-aware affected release gate for ordinary local checks. Publish readiness uses `release:check:full`, which runs the full release DAG and writes a source digest stamp under `.sneakoscope/reports/` so publish commands can verify the same source/dist state. The DAG preserves the 1.18 baseline gates and adds Codex 0.136 compatibility, inherited Codex 0.135/0.134 runner truth, patch swarm runtime truth, transaction journaling, serial conflict rebase, strict strategy-to-patch proof, rollback command proof, Native CLI Session Swarm 5/10/20-process proof, Real Worker Backend Router proof, Codex child overlap proof, model-authored patch-envelope separation, Zellij layout/pane/screen/socket-dir proof, no-subagent-scaling proof, Fast mode default/worker/Codex/MAD propagation proof, Appshots attachment provenance, MCP runtime overlap evidence, task graph expansion, schema-bound follow-up work, actual Agent/Team/Research/QA route blackboxes, scheduler proof hardening, Source Intelligence propagation, Goal mode propagation checks, slot telemetry, update notice, MAD-DB, and Naruto SSOT gates. Broader live gates remain explicit scripts such as `release:real-check`; real Codex patch smoke, real Codex parallel worker proof, and real Zellij proof are optional unless their `SKS_REQUIRE_REAL_*` or `SKS_REQUIRE_ZELLIJ=1` environment variables are set. Generate the human-readable registry with `sks features inventory --write-docs`. Plain `npm publish` uses the `latest` dist-tag. `npm run publish:dry` runs `release:check:full`, verifies the fresh stamp, and then performs provenance/registry and npm dry-run checks. `npm run publish:npm` and `npm run release:publish` run the same prepublish gate and then `npm publish --ignore-scripts`, so the real publish path stays strict even when lifecycle scripts are skipped. npm's `prepublishOnly` uses `prepublish-release-check-or-fast` to accept that current stamp before the real publish; if the stamp is missing or stale, it runs `release:check:full` once before continuing.
766
+ `release:check` runs the change-aware affected release gate for ordinary local checks. Publish readiness uses `release:check:full`, which runs the full release DAG and writes a source digest stamp under `.sneakoscope/reports/` so publish commands can verify the same source/dist state. The DAG preserves the 1.18 baseline gates and adds Codex 0.136 compatibility, inherited Codex 0.135/0.134 runner truth, patch swarm runtime truth, transaction journaling, serial conflict rebase, strict strategy-to-patch proof, rollback command proof, Native CLI Session Swarm 5/10/20-process proof, Real Worker Backend Router proof, Codex child overlap proof, model-authored patch-envelope separation, Zellij layout/pane/screen/socket-dir proof, no-subagent-scaling proof, Fast mode default/worker/Codex/MAD propagation proof, Appshots attachment provenance, MCP runtime overlap evidence, task graph expansion, schema-bound follow-up work, actual Agent/Team/Research/QA route blackboxes, scheduler proof hardening, Source Intelligence propagation, Goal mode propagation checks, slot telemetry, update notice, MAD-DB, and Naruto SSOT gates. Broader live gates remain explicit scripts such as `release:real-check`; real Codex patch smoke, real Codex parallel worker proof, and real Zellij proof are optional unless their `SKS_REQUIRE_REAL_*` or `SKS_REQUIRE_ZELLIJ=1` environment variables are set. Generate the human-readable registry with `sks features inventory --write-docs`. Plain `npm publish` uses the `latest` dist-tag. `npm run publish:dry` runs `release:check:full`, verifies the fresh stamp, and then performs provenance/registry and npm dry-run checks. `npm run publish:npm` and `npm run release:publish` run `npm run publish:prep-ignore-scripts` first, then `npm publish --ignore-scripts`, so the real publish path stays strict even when lifecycle scripts are skipped. Operators who intentionally run `npm publish --ignore-scripts` directly must run `npm run publish:prep-ignore-scripts` immediately before it. npm's `prepublishOnly` uses `prepublish-release-check-or-fast` to accept that current stamp before the real publish; if the stamp is missing or stale, it runs `release:check:full` once before continuing.
767
767
 
768
768
  Version bumps are manual. Run `sks versioning bump` only when preparing release metadata; SKS will not create `.git/hooks/pre-commit` or auto-bump during ordinary commits.
769
769
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "4.8.0"
79
+ version = "4.8.2"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "4.8.0"
3
+ version = "4.8.2"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 4.8.0"),
7
+ Some("--version") => println!("sks-rs 4.8.2"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '4.8.0';
2
+ const FAST_PACKAGE_VERSION = '4.8.2';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -3,6 +3,8 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { ensureDir, exists, runProcess, which, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
5
5
  const LABEL = 'com.sneakoscope.sks-menubar';
6
+ const CONTROL_CENTER_DOMAIN = 'com.apple.controlcenter';
7
+ const CONTROL_CENTER_PREFERRED_POSITION = 360;
6
8
  const MENU_ITEMS = [
7
9
  'Use codex-lb',
8
10
  'Use ChatGPT OAuth',
@@ -105,7 +107,21 @@ export async function installSksMenuBar(opts = {}) {
105
107
  actions.push(`compiled ${executablePath}`);
106
108
  await writeTextAtomic(launchAgentPath, launchAgentSource(executablePath, installDir));
107
109
  actions.push(`wrote ${launchAgentPath}`);
108
- const launchRequested = opts.launch !== false && env.SKS_SKIP_SKS_MENUBAR_LAUNCH !== '1';
110
+ const launchWanted = opts.launch !== false && env.SKS_SKIP_SKS_MENUBAR_LAUNCH !== '1';
111
+ const launchAllowedForHome = path.resolve(home) === realUserHome();
112
+ const installUnderTempDir = isMenuBarInstallPathUnderTempDir(executablePath, env);
113
+ if (launchWanted && !launchAllowedForHome)
114
+ warnings.push('launch_skipped_non_user_home');
115
+ if (launchWanted && installUnderTempDir)
116
+ warnings.push('launch_skipped_temp_install');
117
+ const launchRequested = launchWanted && launchAllowedForHome && !installUnderTempDir;
118
+ if (launchRequested) {
119
+ const preferredPosition = await seedMenuBarPreferredPosition(env);
120
+ if (preferredPosition.ok)
121
+ actions.push('seeded SKS menu bar preferred position');
122
+ else
123
+ warnings.push(preferredPosition.warning);
124
+ }
109
125
  const launch = launchRequested && launchctl
110
126
  ? await launchWithLaunchctl({ launchctl, open, appPath, executablePath, launchAgentPath })
111
127
  : {
@@ -169,6 +185,71 @@ export async function installSksMenuBar(opts = {}) {
169
185
  async function fallbackTool(candidate) {
170
186
  return await exists(candidate).then((ok) => ok ? candidate : null).catch(() => null);
171
187
  }
188
+ async function seedMenuBarPreferredPosition(env) {
189
+ const defaults = env.SKS_MENUBAR_DEFAULTS || await which('defaults').catch(() => null) || await fallbackTool('/usr/bin/defaults');
190
+ if (!defaults)
191
+ return { ok: false, warning: 'defaults_missing_for_menubar_position_seed' };
192
+ // macOS stores status-item ordering hints in Control Center prefs keyed by
193
+ // autosaveName. Seeding this keeps SKS right of the notch-overflow zone.
194
+ const writes = [
195
+ ['write', CONTROL_CENTER_DOMAIN, `NSStatusItem Preferred Position ${LABEL}`, '-int', String(CONTROL_CENTER_PREFERRED_POSITION)],
196
+ ['write', CONTROL_CENTER_DOMAIN, `NSStatusItem Visible ${LABEL}`, '-bool', 'true'],
197
+ ['write', CONTROL_CENTER_DOMAIN, `NSStatusItem VisibleCC ${LABEL}`, '-bool', 'true']
198
+ ];
199
+ for (const args of writes) {
200
+ const result = await runProcess(defaults, args, {
201
+ timeoutMs: 10_000,
202
+ maxOutputBytes: 16 * 1024
203
+ }).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
204
+ if (result.code !== 0)
205
+ return { ok: false, warning: 'menubar_position_seed_failed' };
206
+ }
207
+ return { ok: true };
208
+ }
209
+ /**
210
+ * Refuse to auto-launch a menu bar app whose executable lives under a temp dir.
211
+ * Release gates run in hermetic envs rooted at os.tmpdir()/SKS_TMP_DIR; without
212
+ * this guard a gate could spawn a real GUI status item that leaks into the
213
+ * user's live menu bar (duplicate `com.sneakoscope.sks-menubar` process). This
214
+ * is defense-in-depth behind SKS_SKIP_SKS_MENUBAR_LAUNCH and the home check.
215
+ */
216
+ export function isMenuBarInstallPathUnderTempDir(target, env = process.env) {
217
+ const resolved = path.resolve(target);
218
+ const roots = new Set();
219
+ const addRoot = (value) => {
220
+ if (!value)
221
+ return;
222
+ const abs = path.resolve(value);
223
+ roots.add(abs);
224
+ // macOS resolves TMPDIR / os.tmpdir() (/var/folders/...) through a /private symlink.
225
+ if (abs.startsWith('/var/'))
226
+ roots.add(path.resolve('/private', abs.slice(1)));
227
+ else if (abs.startsWith('/private/var/'))
228
+ roots.add(abs.replace('/private', ''));
229
+ };
230
+ addRoot(os.tmpdir());
231
+ addRoot(env.TMPDIR);
232
+ addRoot(env.SKS_TMP_DIR);
233
+ for (const root of roots) {
234
+ if (resolved === root)
235
+ return true;
236
+ const prefix = root.endsWith(path.sep) ? root : root + path.sep;
237
+ if (resolved.startsWith(prefix))
238
+ return true;
239
+ }
240
+ return false;
241
+ }
242
+ function realUserHome() {
243
+ try {
244
+ const userHome = os.userInfo().homedir;
245
+ if (userHome)
246
+ return path.resolve(userHome);
247
+ }
248
+ catch {
249
+ // Fall back below for platforms where userInfo is unavailable.
250
+ }
251
+ return path.resolve(os.homedir());
252
+ }
172
253
  function resolveSksEntry(explicit) {
173
254
  if (explicit)
174
255
  return path.resolve(explicit);
@@ -222,7 +303,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
222
303
 
223
304
  func applicationDidFinishLaunching(_ notification: Notification) {
224
305
  NSApp.setActivationPolicy(.accessory)
225
- statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
306
+ statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
307
+ statusItem.autosaveName = "com.sneakoscope.sks-menubar"
308
+ statusItem.isVisible = true
226
309
  if let button = statusItem.button {
227
310
  configureStatusButton(button)
228
311
  }
@@ -244,14 +327,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
244
327
  }
245
328
 
246
329
  func configureStatusButton(_ button: NSStatusBarButton) {
247
- if #available(macOS 11.0, *), let image = NSImage(systemSymbolName: "s.circle.fill", accessibilityDescription: "SKS") {
248
- image.isTemplate = true
249
- button.image = image
250
- button.imagePosition = .imageOnly
251
- button.title = ""
252
- } else {
253
- button.title = "S"
254
- }
330
+ button.image = nil
331
+ button.title = "SKS"
332
+ let titleFont = NSFont.systemFont(ofSize: NSFont.systemFontSize, weight: .semibold)
333
+ let titleAttributes: [NSAttributedString.Key: Any] = [
334
+ .font: titleFont,
335
+ .foregroundColor: NSColor.labelColor
336
+ ]
337
+ button.font = titleFont
338
+ button.attributedTitle = NSAttributedString(string: "SKS", attributes: titleAttributes)
255
339
  button.toolTip = "SKS - Sneakoscope Codex settings"
256
340
  button.setAccessibilityLabel("SKS")
257
341
  button.setAccessibilityHelp("Open SKS menu")
@@ -90,9 +90,12 @@ const CORE_SKILL_DEFINITIONS = [
90
90
  canonical_name: 'search-visibility-core',
91
91
  display_name: 'search-visibility-core',
92
92
  route: '$SEO-GEO-OPTIMIZER',
93
- purpose: 'provide the shared search-visibility kernel for SEO and GEO audit, plan, explicit apply, verify, rollback, and Completion Proof without ranking, traffic, or citation guarantees.',
94
- when: 'Use when $SEO-GEO-OPTIMIZER or sks seo-geo-optimizer needs typed mode-specific evidence, gates, artifacts, or safe mutation planning.',
95
- evidence: 'search-visibility artifacts, seo-gate.json or geo-gate.json, mutation-plan.json, rollback-manifest.json, verification-report.json, and completion-proof.json.',
93
+ purpose: 'provide the shared search-visibility kernel for SEO and GEO audit, plan, explicit apply, verify, rollback, and Completion Proof without ranking, traffic, indexing, rich-result, answer inclusion, or AI citation guarantees.',
94
+ when: 'Use when $SEO-GEO-OPTIMIZER or sks seo-geo-optimizer needs typed mode-specific evidence, gates, artifacts, or safe mutation planning for websites, docs, packages, README/npm/GitHub surfaces, or storefront SEO architecture.',
95
+ workflow: 'Read the touched search-visibility flow before planning: SEO constants/source-of-truth, metadata builders, route topology, sitemap/robots, llms.txt, structured-data builders, target route files, internal-link sources, and SEO tests. Inventory canonical URLs, alternates/hreflang, crawlable localized sitemap rows, JSON-LD, crawler hints, claim evidence, and verification commands before compiling mutation-plan.json.',
96
+ safety: 'Treat SEO/GEO as an architecture compiler, not scattered page copy. Prefer existing project helpers and constants; never duplicate public route strings across files. JSON-LD must describe visible or source-of-truth facts only. Do not invent prices, reviews, ratings, availability, shipping terms, rankings, traffic, rich-result eligibility, indexing, or AI-answer outcomes.',
97
+ cli: 'sks seo-geo-optimizer doctor|audit|plan|apply|verify|status|rollback|fixture --mode seo|geo',
98
+ evidence: 'search-visibility/intake.json, adapter-detection.json, site-inventory.json, route-graph.json, robots-policy.json, structured-data-ledger.json, sitemap coverage, llms.txt plan/evidence, mutation-plan.json, rollback-manifest.json, verification-report.json, seo-gate.json or geo-gate.json, and completion-proof.json.',
96
99
  fallback: 'Keep unsupported frameworks plan-only, record unverified production/browser/Search Console/AI citation outcomes, and never invent guarantee evidence.'
97
100
  },
98
101
  {
@@ -101,8 +104,11 @@ const CORE_SKILL_DEFINITIONS = [
101
104
  display_name: 'seo-geo-optimizer',
102
105
  route: '$SEO-GEO-OPTIMIZER',
103
106
  purpose: 'run the unified SEO/GEO optimizer route for Search Engine Optimization and Generative Engine Optimization, not geolocation or GeoIP, with no ranking, traffic, indexing, rich-result, answer inclusion, or AI citation guarantee.',
104
- when: 'Use the CLI entrypoint: sks seo-geo-optimizer doctor|audit|plan|apply|verify|status|rollback|fixture --mode seo|geo for SEO and GEO visibility work.',
105
- evidence: 'site inventory, route graph, seo-findings.json or geo-findings.json, claim-evidence-ledger.json, ai-crawler-policy.json, llms-txt-plan.json, verification report, route gate, and Completion Proof.',
107
+ when: 'Use the single CLI entrypoint: sks seo-geo-optimizer doctor|audit|plan|apply|verify|status|rollback|fixture --mode seo|geo for SEO and GEO visibility work. Legacy seo-geo wording, SEO, GEO, search visibility, AI visibility, sitemap, canonical, JSON-LD, llms.txt, metadata, keyword/intent, or crawler-policy requests should converge here.',
108
+ workflow: 'Follow the architecture-first playbook: identify target intent and market, locate the central SEO source of truth, update/reuse canonical helpers before page code, build metadata through project helpers, emit independently crawlable localized sitemap rows plus alternates when applicable, add factual JSON-LD only from visible/source data, add internal crawl links, update AI/GEO documentation surfaces such as llms.txt when strategic, add focused tests/guards, then verify. For CELIMAX-style storefronts, prefer STOREFRONT_PATH, buildSeoMetadata, buildSeoPath, buildSeoUrl, app/sitemap.ts, proxy.ts, public/llms.txt, and market-specific tests when those files exist.',
109
+ safety: 'Separate implemented, verified, and unverified claims. Competitor/retailer intent pages must be factual, helpful, and non-deceptive; block keyword stuffing, doorway pages, hidden AI-only text, fake ratings/reviews/prices/availability/shipping, competitor defamation, and unsupported "official retailer", "cheapest", "best", "exclusive", first-page, traffic, Search Console, or AI-citation claims.',
110
+ cli: 'sks seo-geo-optimizer doctor|audit|plan|apply|verify|status|rollback|fixture --mode seo|geo',
111
+ evidence: 'SEO intent map, canonical URL map, metadata summary, JSON-LD summary, sitemap coverage summary, internal link plan, unsupported claims ledger, Search Console/analytics follow-up plan, site inventory, route graph, seo-findings.json or geo-findings.json, claim-evidence-ledger.json, ai-crawler-policy.json, llms-txt-plan.json, verification report, route gate, and Completion Proof.',
106
112
  fallback: 'Do not auto-allow training crawlers or fabricate AI answer visibility; mark missing live outcomes unverified and keep recovery on the unified optimizer route.'
107
113
  }
108
114
  ];
@@ -139,9 +145,14 @@ export function renderCoreSkillTemplate(name) {
139
145
  `Command: ${skill.route}`,
140
146
  `Purpose: ${skill.purpose}`,
141
147
  `Use when: ${skill.when}`,
148
+ `Workflow: ${skill.workflow || 'Run the selected route lifecycle, read source evidence before mutation planning, keep changes scoped, verify with the cheapest sufficient check, and record blockers honestly.'}`,
149
+ `CLI entrypoint: ${skill.cli || skill.route}`,
142
150
  `Lean policy: ${lean.policy_id}/${lean.policy_hash}`,
151
+ `Safety: ${skill.safety || 'Preserve user-authored content, keep route state bounded, avoid unsupported guarantees, and stop on hard blockers instead of fabricating fallback behavior.'}`,
152
+ `Evidence/artifacts: ${skill.evidence}`,
143
153
  `Proof paths: ${skill.evidence}`,
144
154
  'Safety rules: preserve user-authored skills, keep route state bounded, and stop on hard blockers instead of fabricating fallback behavior.',
155
+ `Failure/recovery: ${skill.fallback}`,
145
156
  `Failure recovery: ${skill.fallback}`,
146
157
  ''
147
158
  ].join('\n');
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '4.8.0';
8
+ export const PACKAGE_VERSION = '4.8.2';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -1,6 +1,6 @@
1
1
  import { REQUIRED_CODEX_MODEL } from '../codex-model-guard.js';
2
2
  export const MANAGED_ASSET_SCHEMA_VERSION = 1;
3
- export const MANAGED_ASSET_VERSION = '4.8.0';
3
+ export const MANAGED_ASSET_VERSION = '4.8.1';
4
4
  export const MANAGED_ASSET_MARKER = 'SKS-MANAGED-ASSET';
5
5
  export const MANAGED_AGENT_ROLES = Object.freeze([
6
6
  role('sks-explorer', 'analysis-scout.toml', 'analysis_scout', 'SKS analysis scout for bounded read/write slices retained for stale Codex agent-role config repair.', 'workspace-write', ['analysis-scout', 'analysis_scout']),
@@ -25,7 +25,10 @@ export function createReleaseGateHermeticEnv(input) {
25
25
  CODEX_HOME: input.gate.isolation.codex_home === 'temp' ? codexHome : process.env.CODEX_HOME,
26
26
  XDG_CACHE_HOME: cacheHome,
27
27
  SKS_DISABLE_REAL_MODEL_CALLS: input.gate.preset.includes('real-check') ? process.env.SKS_DISABLE_REAL_MODEL_CALLS || '0' : '1',
28
- SKS_DISABLE_GLOBAL_CONFIG_MUTATION: '1'
28
+ SKS_DISABLE_GLOBAL_CONFIG_MUTATION: '1',
29
+ // Gates must never spawn a real GUI menu bar status item into the user's
30
+ // live session. Belt to the temp-path launch guard in installSksMenuBar.
31
+ SKS_SKIP_SKS_MENUBAR_LAUNCH: '1'
29
32
  }
30
33
  };
31
34
  }
@@ -237,7 +237,7 @@ export async function ensureCurrentMigrationBeforeCommand(input) {
237
237
  SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
238
238
  SKS_DISABLE_UPDATE_CHECK: '1'
239
239
  },
240
- timeoutMs: 15_000,
240
+ timeoutMs: 30_000,
241
241
  maxOutputBytes: 32 * 1024
242
242
  });
243
243
  if (!doctor.ok) {
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '4.8.0';
1
+ export const PACKAGE_VERSION = '4.8.2';
2
2
  //# sourceMappingURL=version.js.map
@@ -329,6 +329,9 @@ for (const script of requiredRealScripts) {
329
329
  assertGate(pkg.bin?.sks === 'dist/bin/sks.js', 'package runtime must use dist/bin/sks.js');
330
330
  assertGate(pkg.bin?.sneakoscope === 'dist/bin/sks.js', 'sneakoscope runtime must use dist/bin/sks.js');
331
331
  assertGate(!pkg.files?.includes('src'), 'package files must not include src runtime shadows');
332
+ assertGate(pkg.scripts?.['publish:prep-ignore-scripts'] === 'npm run prepublishOnly', 'publish:prep-ignore-scripts must run the prepublishOnly release gate before lifecycle-disabled publish', { script: pkg.scripts?.['publish:prep-ignore-scripts'] || null });
333
+ assertGate(String(pkg.scripts?.['publish:ignore-scripts'] || '').includes('npm run publish:prep-ignore-scripts'), 'publish:ignore-scripts must run publish:prep-ignore-scripts before npm publish --ignore-scripts', { script: pkg.scripts?.['publish:ignore-scripts'] || null });
334
+ assertGate(String(pkg.scripts?.['publish:ignore-scripts'] || '').includes('--ignore-scripts'), 'publish:ignore-scripts must keep npm lifecycle scripts disabled for the final publish', { script: pkg.scripts?.['publish:ignore-scripts'] || null });
332
335
  for (const file of requiredDocs) {
333
336
  const absolute = path.join(root, file);
334
337
  assertGate(fs.existsSync(absolute), `missing release doc: ${file}`);
@@ -13,7 +13,7 @@ import { root, assertGate, emitGate, importDist, readText, readJson } from './sk
13
13
  assertGate(!/cargo|rustc/.test(readText('src/scripts/build-dist.ts')), 'build-dist must not invoke cargo/rustc');
14
14
  assertGate(!/cargo|rustc/.test(readText('src/scripts/clean-dist.ts')), 'clean-dist must not invoke cargo/rustc');
15
15
  const pkg = readJson('package.json');
16
- for (const s of ['prepack', 'prepublishOnly', 'publish:dry', 'publish:ignore-scripts', 'publish:npm']) {
16
+ for (const s of ['prepack', 'prepublishOnly', 'publish:dry', 'publish:prep-ignore-scripts', 'publish:ignore-scripts', 'publish:npm']) {
17
17
  assertGate(!String(pkg.scripts?.[s] || '').includes('cargo'), `publish_script_compiles_rust:${s}`);
18
18
  }
19
19
  // 2) The published binary is JS; no prebuilt native artifact is shipped or required.
@@ -7,6 +7,7 @@ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
7
  const root = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-seo-geo-skills-'));
8
8
  const init = await importDist('core/init.js');
9
9
  await init.installSkills(root);
10
+ assertGate(!fs.existsSync(path.join(root, '.agents', 'skills', 'seo-geo', 'SKILL.md')), 'generated skills must not recreate legacy seo-geo alias');
10
11
  for (const name of ['search-visibility-core', 'seo-geo-optimizer']) {
11
12
  const file = path.join(root, '.agents', 'skills', name, 'SKILL.md');
12
13
  assertGate(fs.existsSync(file), `generated skill missing: ${name}`);
@@ -18,5 +19,20 @@ for (const name of ['search-visibility-core', 'seo-geo-optimizer']) {
18
19
  assertGate(/CLI entrypoint:/i.test(text), `skill missing CLI entrypoint: ${name}`, text);
19
20
  assertGate(/ranking|citation|traffic|guarantee|보장/i.test(text), `skill must name forbidden guarantee boundary: ${name}`, text);
20
21
  }
22
+ const optimizer = fs.readFileSync(path.join(root, '.agents', 'skills', 'seo-geo-optimizer', 'SKILL.md'), 'utf8');
23
+ for (const token of [
24
+ 'SEO intent map',
25
+ 'canonical URL map',
26
+ 'JSON-LD summary',
27
+ 'sitemap coverage summary',
28
+ 'internal link plan',
29
+ 'unsupported claims ledger',
30
+ 'llms.txt',
31
+ 'Search Console',
32
+ 'STOREFRONT_PATH',
33
+ 'buildSeoMetadata'
34
+ ]) {
35
+ assertGate(optimizer.includes(token), `seo-geo-optimizer skill missing CELIMAX architecture token: ${token}`, optimizer);
36
+ }
21
37
  emitGate('seo-geo:skill-rich-content', { skills_checked: 2 });
22
38
  //# sourceMappingURL=seo-geo-skill-rich-content-check.js.map
@@ -3,21 +3,37 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { installSksMenuBar } from '../core/codex-app/sks-menubar.js';
5
5
  const temp = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-menubar-check-'));
6
+ const envHomeTemp = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-menubar-env-home-check-'));
7
+ const launchGuardEnv = { ...process.env, SKS_SKIP_SKS_MENUBAR_LAUNCH: '0' };
6
8
  const result = await installSksMenuBar({
7
9
  apply: true,
8
- launch: false,
10
+ launch: true,
9
11
  home: temp,
10
12
  root: temp,
11
- sksEntry: path.join(process.cwd(), 'dist', 'bin', 'sks.js')
13
+ sksEntry: path.join(process.cwd(), 'dist', 'bin', 'sks.js'),
14
+ env: launchGuardEnv
15
+ });
16
+ const envHomeResult = await installSksMenuBar({
17
+ apply: true,
18
+ launch: true,
19
+ root: envHomeTemp,
20
+ sksEntry: path.join(process.cwd(), 'dist', 'bin', 'sks.js'),
21
+ env: { ...launchGuardEnv, HOME: envHomeTemp }
12
22
  });
13
23
  const executableExists = result.executable_path ? await exists(result.executable_path) : false;
14
24
  const launchAgentExists = result.launch_agent_path ? await exists(result.launch_agent_path) : false;
15
25
  const actionScriptExists = result.action_script_path ? await exists(result.action_script_path) : false;
16
26
  const generatedSourcePath = result.app_path ? path.join(path.dirname(result.app_path), 'SKSMenuBar.swift') : null;
17
27
  const generatedSource = generatedSourcePath ? await fs.readFile(generatedSourcePath, 'utf8').catch(() => '') : '';
18
- const hasVisibleIconSource = generatedSource.includes('NSStatusItem.squareLength')
19
- && generatedSource.includes('s.circle.fill')
20
- && generatedSource.includes('configureStatusButton');
28
+ const hasVisibleLabelSource = generatedSource.includes('NSStatusItem.variableLength')
29
+ && generatedSource.includes('button.title = "SKS"')
30
+ && generatedSource.includes('button.attributedTitle = NSAttributedString(string: "SKS"')
31
+ && generatedSource.includes('NSColor.labelColor')
32
+ && generatedSource.includes('button.image = nil');
33
+ // The status item must persist its user-arranged position across restarts so a
34
+ // once-dragged item does not jump back behind the notch on the next doctor --fix.
35
+ const hasAutosaveNameSource = generatedSource.includes('statusItem.autosaveName = "com.sneakoscope.sks-menubar"');
36
+ const hasExplicitVisibleSource = generatedSource.includes('statusItem.isVisible = true');
21
37
  const expectedMenuItems = [
22
38
  'Use codex-lb',
23
39
  'Use ChatGPT OAuth',
@@ -28,25 +44,56 @@ const expectedMenuItems = [
28
44
  ];
29
45
  const missingExpectedItems = expectedMenuItems.filter((item) => !result.menu_items.includes(item));
30
46
  const hasExpectedItems = missingExpectedItems.length === 0;
47
+ const launchSkippedForTempHome = result.launch?.requested === false
48
+ && result.launch?.method === 'skipped'
49
+ && result.warnings.includes('launch_skipped_non_user_home');
50
+ const launchSkippedForEnvHome = envHomeResult.launch?.requested === false
51
+ && envHomeResult.launch?.method === 'skipped'
52
+ && envHomeResult.warnings.includes('launch_skipped_non_user_home');
53
+ // Temp-path guard: a temp-rooted install must never request a launch, even if
54
+ // some caller forgets SKS_SKIP_SKS_MENUBAR_LAUNCH. Both fixtures live under
55
+ // os.tmpdir(), so both results must carry the temp-install skip warning.
56
+ const launchSkippedForTempInstall = result.launch?.requested === false
57
+ && result.warnings.includes('launch_skipped_temp_install')
58
+ && envHomeResult.launch?.requested === false
59
+ && envHomeResult.warnings.includes('launch_skipped_temp_install');
60
+ const preferredPositionSkippedForTempInstall = !result.actions.includes('seeded SKS menu bar preferred position')
61
+ && !envHomeResult.actions.includes('seeded SKS menu bar preferred position');
31
62
  const ok = process.platform === 'darwin'
32
63
  ? result.ok === true
33
64
  && result.status === 'installed_launch_skipped'
65
+ && envHomeResult.ok === true
66
+ && envHomeResult.status === 'installed_launch_skipped'
34
67
  && executableExists
35
68
  && launchAgentExists
36
69
  && actionScriptExists
37
70
  && hasExpectedItems
38
- && hasVisibleIconSource
71
+ && hasVisibleLabelSource
72
+ && hasAutosaveNameSource
73
+ && hasExplicitVisibleSource
74
+ && launchSkippedForTempHome
75
+ && launchSkippedForEnvHome
76
+ && launchSkippedForTempInstall
77
+ && preferredPositionSkippedForTempInstall
39
78
  : result.ok === true && result.status === 'unsupported_platform';
40
79
  const report = {
41
80
  schema: 'sks.sks-menubar-install-check.v1',
42
81
  ok,
43
82
  temp,
83
+ env_home_temp: envHomeTemp,
44
84
  result,
85
+ env_home_result: envHomeResult,
45
86
  executable_exists: executableExists,
46
87
  launch_agent_exists: launchAgentExists,
47
88
  action_script_exists: actionScriptExists,
48
89
  generated_source_path: generatedSourcePath,
49
- has_visible_icon_source: hasVisibleIconSource,
90
+ has_visible_label_source: hasVisibleLabelSource,
91
+ has_autosave_name_source: hasAutosaveNameSource,
92
+ has_explicit_visible_source: hasExplicitVisibleSource,
93
+ launch_skipped_for_temp_home: launchSkippedForTempHome,
94
+ launch_skipped_for_env_home: launchSkippedForEnvHome,
95
+ launch_skipped_for_temp_install: launchSkippedForTempInstall,
96
+ preferred_position_skipped_for_temp_install: preferredPositionSkippedForTempInstall,
50
97
  expected_menu_items: expectedMenuItems,
51
98
  missing_expected_items: missingExpectedItems,
52
99
  has_expected_items: hasExpectedItems,
@@ -7,6 +7,7 @@ assertGate(router.includes('ensureCurrentMigrationBeforeCommand'), 'router must
7
7
  assertGate(helper.includes('INSTALLATION_EPOCH_SCHEMA') && helper.includes('installationEpochPath'), 'migration helper must keep a persistent installation epoch');
8
8
  assertGate(helper.includes('projectUpdateMigrationReceiptPath'), 'migration helper must keep a project receipt');
9
9
  assertGate(helper.includes("'--profile', 'migration'") && helper.includes("'--machine-only'") && helper.includes("'--report-file'"), 'first normal command must repair through package-local migration Doctor machine report before continuing');
10
+ assertGate(helper.includes('timeoutMs: 30_000'), 'first normal command migration Doctor timeout must allow slow but successful macOS repair profiles');
10
11
  assertGate(helper.includes('isProjectReceiptCurrentForEpoch'), 'project receipts must be compared against the current installation epoch');
11
12
  assertGate(helper.includes('runUpdateRetentionCleanup') && helper.includes('retention_cleanup'), 'project update migration receipt must run mission retention cleanup and record its receipt');
12
13
  assertGate(helper.includes('clearPendingUpdateMigration') && helper.includes('one project must not consume global migration state'), 'legacy clear helper must preserve the persistent epoch contract');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "4.8.0",
4
+ "version": "4.8.2",
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",
@@ -381,7 +381,8 @@
381
381
  "release:real-check": "node ./dist/scripts/release-real-check.js",
382
382
  "release:publish": "npm run publish:ignore-scripts",
383
383
  "publish:dry": "npm run release:metadata && npm run release:version-truth && npm run publish:packlist-performance && npm run release:check:full && node ./dist/scripts/check-publish-tag.js && node ./dist/scripts/release-check-stamp.js verify && npm run release:provenance -- --publish && npm run release:dist-freshness && node ./dist/scripts/release-registry-check.js --require-unpublished && npm --cache /tmp/sks-npm-cache publish --dry-run --ignore-scripts --registry https://registry.npmjs.org/ --access public",
384
- "publish:ignore-scripts": "npm run prepublishOnly && npm --cache /tmp/sks-npm-cache publish --ignore-scripts --registry https://registry.npmjs.org/ --access public",
384
+ "publish:prep-ignore-scripts": "npm run prepublishOnly",
385
+ "publish:ignore-scripts": "npm run publish:prep-ignore-scripts && npm --cache /tmp/sks-npm-cache publish --ignore-scripts --registry https://registry.npmjs.org/ --access public",
385
386
  "publish:npm": "npm run publish:ignore-scripts",
386
387
  "publish:fast": "node -e \"console.error('publish:fast is quarantined; use publish:dry and publish:npm with the release gates.'); process.exit(1)\"",
387
388
  "prepack": "npm run build",