sneakoscope 5.6.0 → 5.7.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.
@@ -8,6 +8,7 @@ import { COMMANDS } from '../../cli/command-registry.js';
8
8
  import { reconcileSkills } from '../init/skills.js';
9
9
  import { codexHookTrustDoctor } from '../codex-hooks/codex-hook-trust-doctor.js';
10
10
  import { writeCodexConfigGuarded } from '../codex/codex-config-guard.js';
11
+ import { REQUIRED_CODEX_MODEL } from '../codex-model-guard.js';
11
12
  export const UPDATE_MIGRATION_SCHEMA = 'sks.project-migration-receipt.v2';
12
13
  export const INSTALLATION_EPOCH_SCHEMA = 'sks.installation-epoch.v1';
13
14
  export function installationEpochPath() {
@@ -343,53 +344,71 @@ async function runMenubarRetargetStage(root) {
343
344
  return { ok: true, status: 'ok', actions: ['menubar_action_script_absent'], blockers: [], warnings: [] };
344
345
  const desired = path.join(packageRoot(), 'dist', 'bin', 'sks.js');
345
346
  const line = `SKS_ENTRY=${JSON.stringify(desired)}`;
347
+ const actions = [];
346
348
  const next = /^\s*SKS_ENTRY\s*=.*$/m.test(text)
347
349
  ? text.replace(/^\s*SKS_ENTRY\s*=.*$/m, line)
348
350
  : `${line}\n${text}`;
349
351
  if (next !== text) {
350
352
  await writeTextAtomic(actionScript, next);
351
- return { ok: true, status: 'ok', actions: ['retargeted_menubar_action_script'], blockers: [], warnings: [], detail: { action_script: actionScript } };
353
+ actions.push('retargeted_menubar_action_script');
354
+ }
355
+ const stat = await fsp.stat(actionScript).catch(() => null);
356
+ if (!stat || (stat.mode & 0o111) === 0) {
357
+ await fsp.chmod(actionScript, 0o755);
358
+ actions.push('restored_menubar_action_executable_bit');
352
359
  }
353
- return { ok: true, status: 'ok', actions: ['menubar_action_script_current'], blockers: [], warnings: [], detail: { action_script: actionScript } };
360
+ return {
361
+ ok: true,
362
+ status: 'ok',
363
+ actions: actions.length ? actions : ['menubar_action_script_current'],
364
+ blockers: [],
365
+ warnings: [],
366
+ detail: { action_script: actionScript }
367
+ };
354
368
  }
355
369
  async function runConfigFastModeNormalizeStage() {
356
370
  const configPath = path.join(os.homedir(), '.codex', 'config.toml');
357
371
  const text = await readText(configPath, null);
358
372
  if (typeof text !== 'string')
359
373
  return { ok: true, status: 'ok', actions: ['codex_config_absent'], blockers: [], warnings: [] };
360
- const table = text.match(/(^|\n)\[user\.fast_mode\][\s\S]*?(?=\n\[|$)/);
361
- const misplaced = table?.[0]?.match(/^\s*default_profile\s*=\s*"([^"]+)"\s*$/m)?.[1] || null;
362
- const hasTopLevel = /^\s*default_profile\s*=\s*"[^"]+"\s*$/m.test(text.slice(0, Math.max(0, text.search(/^\s*\[/m))));
363
- if (!misplaced)
364
- return { ok: true, status: 'ok', actions: ['fastmode_default_profile_current'], blockers: [], warnings: [] };
365
- let next = text.replace(/(^|\n)(\[user\.fast_mode\][\s\S]*?)(?=\n\[|$)/, (_match, prefix, block) => {
366
- const cleaned = String(block).split(/\r?\n/).filter((line) => !/^\s*default_profile\s*=/.test(line)).join('\n').trimEnd();
367
- return `${prefix}${cleaned}`;
368
- });
369
- if (!hasTopLevel)
370
- next = insertTopLevelTomlKey(next, `default_profile = ${JSON.stringify(misplaced)}`);
374
+ const normalized = normalizeLegacyFastModeConfigForUpdate(text);
375
+ if (normalized.text === ensureTrailingNewline(text)) {
376
+ return {
377
+ ok: true,
378
+ status: 'ok',
379
+ actions: ['fastmode_config_current'],
380
+ blockers: [],
381
+ warnings: [],
382
+ detail: { config_path: configPath, default_profile: normalized.defaultProfile }
383
+ };
384
+ }
371
385
  let guardResult = null;
372
- if (next !== text) {
373
- guardResult = await writeCodexConfigGuarded({
374
- configPath,
375
- before: text,
376
- mutate: () => `${next.trim()}\n`,
377
- cause: 'project-update-fastmode-default-profile',
378
- backupTag: 'project-update-fastmode-default-profile',
379
- preserveFastUiKeys: true
380
- });
381
- if (!guardResult.ok) {
382
- return {
383
- ok: false,
384
- status: 'failed',
385
- actions: ['normalize_fastmode_default_profile_blocked'],
386
- blockers: [`codex_config_guard:${guardResult.status}`],
387
- warnings: [],
388
- detail: { config_path: configPath, default_profile: misplaced, guard: guardResult }
389
- };
390
- }
386
+ guardResult = await writeCodexConfigGuarded({
387
+ configPath,
388
+ before: text,
389
+ mutate: () => normalized.text,
390
+ cause: 'project-update-fastmode-normalize',
391
+ backupTag: 'project-update-fastmode-normalize',
392
+ preserveFastUiKeys: true
393
+ });
394
+ if (!guardResult.ok) {
395
+ return {
396
+ ok: false,
397
+ status: 'failed',
398
+ actions: ['normalize_fastmode_config_blocked'],
399
+ blockers: [`codex_config_guard:${guardResult.status}`],
400
+ warnings: [],
401
+ detail: { config_path: configPath, default_profile: normalized.defaultProfile, guard: guardResult }
402
+ };
391
403
  }
392
- return { ok: true, status: 'ok', actions: ['normalized_fastmode_default_profile'], blockers: [], warnings: [], detail: { config_path: configPath, default_profile: misplaced, guard: guardResult } };
404
+ return {
405
+ ok: true,
406
+ status: 'ok',
407
+ actions: normalized.actions.length ? normalized.actions : ['fastmode_config_current'],
408
+ blockers: [],
409
+ warnings: [],
410
+ detail: { config_path: configPath, default_profile: normalized.defaultProfile, guard: guardResult }
411
+ };
393
412
  }
394
413
  async function runHookTrustRefreshStage(root) {
395
414
  const result = await codexHookTrustDoctor(root, { fix: true, managed: true, actual: true });
@@ -456,6 +475,132 @@ function insertTopLevelTomlKey(text, line) {
456
475
  return `${line}\n${raw}`.trim() + '\n';
457
476
  return `${raw.slice(0, firstTable).trimEnd()}\n${line}\n\n${raw.slice(firstTable).trimStart()}`.trim() + '\n';
458
477
  }
478
+ function normalizeLegacyFastModeConfigForUpdate(text) {
479
+ let next = String(text || '');
480
+ const actions = [];
481
+ const misplaced = tomlTableString(next, 'user.fast_mode', 'default_profile');
482
+ const topLevel = topLevelTomlString(next, 'default_profile');
483
+ if (misplaced) {
484
+ next = removeTomlTableKeyLocal(next, 'user.fast_mode', 'default_profile');
485
+ actions.push('normalized_fastmode_default_profile_location');
486
+ }
487
+ if (topLevel && topLevel !== 'sks-fast-high' && !tomlTableBlock(next, `profiles.${topLevel}`)) {
488
+ next = upsertTopLevelTomlStringLocal(next, 'default_profile', 'sks-fast-high');
489
+ actions.push('retargeted_missing_fastmode_default_profile_target');
490
+ }
491
+ else if (!topLevel && misplaced === 'sks-fast-high') {
492
+ next = insertTopLevelTomlKey(next, `default_profile = ${JSON.stringify(misplaced)}`);
493
+ actions.push('restored_fastmode_top_level_default_profile');
494
+ }
495
+ const beforeUi = next;
496
+ next = upsertTomlTableKeyIfAbsentLocal(next, 'user.fast_mode', 'visible = true');
497
+ next = upsertTomlTableKeyIfAbsentLocal(next, 'user.fast_mode', 'enabled = true');
498
+ if (next !== beforeUi)
499
+ actions.push('ensured_fastmode_ui_visibility');
500
+ const beforeProfile = next;
501
+ next = upsertTomlTableKeyLocal(next, 'profiles.sks-fast-high', `model = "${REQUIRED_CODEX_MODEL}"`);
502
+ next = upsertTomlTableKeyLocal(next, 'profiles.sks-fast-high', 'service_tier = "fast"');
503
+ if (next !== beforeProfile)
504
+ actions.push('normalized_sks_fast_high_profile');
505
+ return { text: ensureTrailingNewline(next), actions, defaultProfile: misplaced || topLevel };
506
+ }
507
+ function tomlTableString(text, table, key) {
508
+ const block = tomlTableBlock(text, table);
509
+ const match = block?.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, 'm'));
510
+ return match?.[1] || null;
511
+ }
512
+ function topLevelTomlString(text, key) {
513
+ const top = String(text || '').slice(0, Math.max(0, String(text || '').search(/^\s*\[/m)));
514
+ const match = top.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, 'm'));
515
+ return match?.[1] || null;
516
+ }
517
+ function tomlTableBlock(text, table) {
518
+ const lines = String(text || '').split(/\r?\n/);
519
+ const start = lines.findIndex((line) => tableHeaderMatches(line, table));
520
+ if (start < 0)
521
+ return null;
522
+ let end = lines.length;
523
+ for (let index = start + 1; index < lines.length; index += 1) {
524
+ if (/^\s*\[[^\]]+\]\s*$/.test(lines[index] || '')) {
525
+ end = index;
526
+ break;
527
+ }
528
+ }
529
+ return lines.slice(start, end).join('\n');
530
+ }
531
+ function removeTomlTableKeyLocal(text, table, key) {
532
+ const lines = String(text || '').split(/\r?\n/);
533
+ let inTable = false;
534
+ const out = [];
535
+ for (const line of lines) {
536
+ if (/^\s*\[[^\]]+\]\s*$/.test(line))
537
+ inTable = tableHeaderMatches(line, table);
538
+ if (inTable && new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(line))
539
+ continue;
540
+ out.push(line);
541
+ }
542
+ return out.join('\n');
543
+ }
544
+ function upsertTomlTableKeyIfAbsentLocal(text, table, line) {
545
+ const key = tomlLineKey(line);
546
+ return tomlTableHasKey(text, table, key) ? text : upsertTomlTableKeyLocal(text, table, line);
547
+ }
548
+ function upsertTomlTableKeyLocal(text, table, line) {
549
+ const raw = String(text || '').trimEnd();
550
+ const key = tomlLineKey(line);
551
+ const lines = raw ? raw.split(/\r?\n/) : [];
552
+ const start = lines.findIndex((candidate) => tableHeaderMatches(candidate, table));
553
+ if (start < 0) {
554
+ return `${raw}${raw ? '\n\n' : ''}[${table}]\n${line}\n`;
555
+ }
556
+ let end = lines.length;
557
+ for (let index = start + 1; index < lines.length; index += 1) {
558
+ if (/^\s*\[[^\]]+\]\s*$/.test(lines[index] || '')) {
559
+ end = index;
560
+ break;
561
+ }
562
+ }
563
+ for (let index = start + 1; index < end; index += 1) {
564
+ if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(lines[index] || '')) {
565
+ if (lines[index] === line)
566
+ return `${lines.join('\n')}\n`;
567
+ lines[index] = line;
568
+ return `${lines.join('\n')}\n`;
569
+ }
570
+ }
571
+ lines.splice(end, 0, line);
572
+ return `${lines.join('\n')}\n`;
573
+ }
574
+ function upsertTopLevelTomlStringLocal(text, key, value) {
575
+ const raw = String(text || '').trimEnd();
576
+ const line = `${key} = ${JSON.stringify(value)}`;
577
+ const lines = raw ? raw.split(/\r?\n/) : [];
578
+ const firstTable = lines.findIndex((candidate) => /^\s*\[[^\]]+\]\s*$/.test(candidate || ''));
579
+ const end = firstTable < 0 ? lines.length : firstTable;
580
+ for (let index = 0; index < end; index += 1) {
581
+ if (new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(lines[index] || '')) {
582
+ lines[index] = line;
583
+ return `${lines.join('\n')}\n`;
584
+ }
585
+ }
586
+ return insertTopLevelTomlKey(raw, line);
587
+ }
588
+ function tomlTableHasKey(text, table, key) {
589
+ const block = tomlTableBlock(text, table);
590
+ return Boolean(block && new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`, 'm').test(block));
591
+ }
592
+ function tableHeaderMatches(line, table) {
593
+ return new RegExp(`^\\s*\\[${escapeRegExp(table)}\\]\\s*$`).test(line || '');
594
+ }
595
+ function tomlLineKey(line) {
596
+ return String(line || '').split('=')[0]?.trim() || '';
597
+ }
598
+ function ensureTrailingNewline(text) {
599
+ return `${String(text || '').trim()}\n`;
600
+ }
601
+ function escapeRegExp(value) {
602
+ return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
603
+ }
459
604
  export async function ensureCurrentMigrationBeforeCommand(input) {
460
605
  const env = input.env || process.env;
461
606
  const command = input.command;
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '5.6.0';
1
+ export const PACKAGE_VERSION = '5.7.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -5,6 +5,7 @@ import path from 'node:path';
5
5
  import { codexFastModeDesktopStatus, codexLbConfigPath, configureCodexLb, ensureGlobalCodexFastModeDuringInstall, releaseCodexLbAuthHold, repairCodexLbAuth } from '../cli/install-helpers.js';
6
6
  import { repairCodexConfigStructure, splitCodexProjectConfigPolicy } from '../core/codex/codex-project-config-policy.js';
7
7
  import { parseCodexConfigToml, validateCodexConfigRoundTrip } from '../core/codex/codex-config-toml.js';
8
+ import { REQUIRED_CODEX_MODEL } from '../core/codex-model-guard.js';
8
9
  const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-codex-lb-gpt55-fast-'));
9
10
  const home = path.join(tmp, 'home');
10
11
  const root = path.join(tmp, 'project');
@@ -44,7 +45,7 @@ await fs.writeFile(projectConfig, [
44
45
  'enabled = true',
45
46
  '',
46
47
  '[profiles.sks-fast-high]',
47
- 'model = "gpt-5.5"',
48
+ `model = "${REQUIRED_CODEX_MODEL}"`,
48
49
  'service_tier = "fast"',
49
50
  ''
50
51
  ].join('\n'));
@@ -85,7 +86,7 @@ function assertFastProfile(text, label) {
85
86
  ...validation.blockers,
86
87
  ...(parsed.default_profile === 'sks-fast-high' ? [] : ['default_profile_not_sks_fast_high']),
87
88
  ...(parsed.user?.fast_mode?.default_profile === undefined ? [] : ['default_profile_inside_user_fast_mode']),
88
- ...(profile.model === 'gpt-5.5' ? [] : ['sks_fast_high_model_not_gpt55']),
89
+ ...(profile.model === REQUIRED_CODEX_MODEL ? [] : ['sks_fast_high_model_not_required_codex_model']),
89
90
  ...(profile.service_tier === 'fast' ? [] : ['sks_fast_high_service_tier_not_fast']),
90
91
  ...(provider.requires_openai_auth === true ? [] : ['codex_lb_requires_openai_auth_not_true']),
91
92
  ...(provider.wire_api === 'responses' ? [] : ['codex_lb_wire_api_not_responses']),
@@ -12,8 +12,10 @@ const MAX_UNPACKED = Number(process.env.SKS_MAX_UNPACKED_BYTES || 10 * 1024 * 10
12
12
  // (route-success-helpers.ts, seo-command.ts, ppt-command.ts, qa-loop-command.ts,
13
13
  // research-command.ts, image-ux-review-command.ts, mad-sks-command.ts,
14
14
  // sks-menubar.ts) with genuine new production logic, pushing the packed size
15
- // to ~2306 KiB. Kept modest headroom rather than a large jump.
16
- const MAX_PACKED = Number(process.env.SKS_MAX_PACK_BYTES || 2340 * 1024);
15
+ // to ~2306 KiB. 5.7.0 adds doctor/update migration repair coverage and publish
16
+ // contract checks, pushing the packed tarball to ~2345 KiB. Keep a narrow cap
17
+ // rather than giving the package a broad size budget.
18
+ const MAX_PACKED = Number(process.env.SKS_MAX_PACK_BYTES || 2350 * 1024);
17
19
  function runNpmPack() {
18
20
  const npmCli = process.env.npm_execpath; // set when invoked via `npm run`
19
21
  const npmCache = process.env.SKS_RELEASE_NPM_CACHE || path.join(os.tmpdir(), 'sneakoscope-npm-cache');
@@ -393,7 +393,18 @@ for (const gate of allManifestGates)
393
393
  assertGate(pkg.bin?.sks === 'dist/bin/sks.js', 'package runtime must use dist/bin/sks.js');
394
394
  assertGate(pkg.bin?.sneakoscope === 'dist/bin/sks.js', 'sneakoscope runtime must use dist/bin/sks.js');
395
395
  assertGate(!pkg.files?.includes('src'), 'package files must not include src runtime shadows');
396
- 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 });
396
+ const publishPrepIgnoreScripts = String(pkg.scripts?.['publish:prep-ignore-scripts'] || '');
397
+ assertGate(!/\bprepublishOnly\b/.test(publishPrepIgnoreScripts), 'publish:prep-ignore-scripts must not depend on npm lifecycle hooks that --ignore-scripts disables', { script: publishPrepIgnoreScripts || null });
398
+ for (const required of [
399
+ 'build:incremental',
400
+ 'release:version-truth',
401
+ 'publish:packlist-performance',
402
+ 'package-published-contract-check.js',
403
+ 'release-registry-check.js --require-unpublished --require-publish-auth',
404
+ 'publish-tag:check'
405
+ ]) {
406
+ assertGate(publishPrepIgnoreScripts.includes(required), `publish:prep-ignore-scripts missing lifecycle-disabled publish preflight: ${required}`, { script: publishPrepIgnoreScripts || null });
407
+ }
397
408
  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 });
398
409
  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 });
399
410
  for (const file of requiredDocs) {
@@ -27,6 +27,46 @@ const secondResult = await installSksMenuBar({
27
27
  sksEntry: localEntry,
28
28
  env: launchGuardEnv
29
29
  });
30
+ if (secondResult.action_script_path)
31
+ await fs.rm(secondResult.action_script_path, { force: true });
32
+ const restoredScriptResult = await installSksMenuBar({
33
+ apply: true,
34
+ launch: true,
35
+ home: temp,
36
+ root: fakeRoot,
37
+ sksEntry: localEntry,
38
+ env: launchGuardEnv
39
+ });
40
+ if (restoredScriptResult.action_script_path) {
41
+ await fs.writeFile(restoredScriptResult.action_script_path, '#!/bin/zsh\necho stale-sks-menubar-action\n', 'utf8');
42
+ }
43
+ const staleScriptMissingEntryResult = await installSksMenuBar({
44
+ apply: true,
45
+ launch: true,
46
+ home: temp,
47
+ root: fakeRoot,
48
+ sksEntry: path.join(fakeRoot, 'dist', 'bin', 'missing-sks.js'),
49
+ env: launchGuardEnv
50
+ });
51
+ const recoveredStaleScriptResult = await installSksMenuBar({
52
+ apply: true,
53
+ launch: true,
54
+ home: temp,
55
+ root: fakeRoot,
56
+ sksEntry: localEntry,
57
+ env: launchGuardEnv
58
+ });
59
+ if (recoveredStaleScriptResult.action_script_path) {
60
+ await fs.chmod(recoveredStaleScriptResult.action_script_path, 0o644);
61
+ }
62
+ const restoredExecutableBitResult = await installSksMenuBar({
63
+ apply: true,
64
+ launch: true,
65
+ home: temp,
66
+ root: fakeRoot,
67
+ sksEntry: localEntry,
68
+ env: launchGuardEnv
69
+ });
30
70
  const envHomeResult = await installSksMenuBar({
31
71
  apply: true,
32
72
  launch: true,
@@ -55,6 +95,9 @@ const hasVisibleStatusSource = generatedSource.includes('NSStatusItem.variableLe
55
95
  && generatedSource.includes('SKS ⋯')
56
96
  && generatedSource.includes('Timer.scheduledTimer(withTimeInterval: 30.0');
57
97
  const hasBackgroundReadonlyActions = generatedSource.includes('runSksBackground(["codex-lb", "fast-check"]')
98
+ && generatedSource.includes('runSksBackground(["fast-mode", "on", "--json"]')
99
+ && generatedSource.includes('runSksBackground(["fast-mode", "off", "--json"]')
100
+ && generatedSource.includes('runSksSilent(["fast-mode", "status", "--json"]')
58
101
  && generatedSource.includes('runSksBackground(["update", "check"]')
59
102
  && generatedSource.includes('display notification')
60
103
  && !generatedSource.includes('runSksInTerminal')
@@ -89,6 +132,21 @@ const hasBuildStamp = buildStampExists
89
132
  && result.build_stamp?.codesign_identifier === 'com.sneakoscope.sks-menubar';
90
133
  const isIdempotent = secondResult.actions.includes('menubar_up_to_date')
91
134
  && secondResult.build_stamp?.action_script_sha256 === result.build_stamp?.action_script_sha256;
135
+ const restoresMissingActionScript = restoredScriptResult.actions.includes(`wrote ${secondResult.action_script_path}`)
136
+ && actionScriptExists
137
+ && restoredScriptResult.build_stamp?.action_script_sha256 === result.build_stamp?.action_script_sha256;
138
+ const blocksStaleActionScriptReuse = staleScriptMissingEntryResult.ok === false
139
+ && staleScriptMissingEntryResult.blockers.includes('sks_entry_unresolved')
140
+ && staleScriptMissingEntryResult.warnings.includes('sks_entry_unresolved_stale_action_script_not_reused')
141
+ && staleScriptMissingEntryResult.target_check?.used_previous_script === false;
142
+ const recoversStaleActionScriptWhenEntryExists = recoveredStaleScriptResult.ok === true
143
+ && recoveredStaleScriptResult.actions.includes(`wrote ${restoredScriptResult.action_script_path}`)
144
+ && recoveredStaleScriptResult.build_stamp?.action_script_sha256 === result.build_stamp?.action_script_sha256;
145
+ const restoresExecutableBit = restoredExecutableBitResult.ok === true
146
+ && restoredExecutableBitResult.actions.includes('menubar_up_to_date')
147
+ && restoredExecutableBitResult.actions.includes('restored action script executable bit')
148
+ && actionScriptExists
149
+ && await isExecutable(result.action_script_path);
92
150
  const hasCommandRegistry = commandRegistry.includes('menubar:')
93
151
  && commandRegistry.includes('menubarCommand')
94
152
  && commandRegistry.includes('dist/core/commands/menubar-command.js');
@@ -102,6 +160,8 @@ const expectedMenuItems = [
102
160
  'Use ChatGPT OAuth',
103
161
  'Set codex-lb Domain and Key',
104
162
  'Set OpenRouter Key and GLM Profiles',
163
+ 'Fast Mode On',
164
+ 'Fast Mode Off',
105
165
  'Fast Check',
106
166
  'SKS Version Check',
107
167
  'Update SKS Now',
@@ -147,6 +207,10 @@ const darwinOk = cltMissing
147
207
  && hasBuildStamp
148
208
  && swiftParse.ok === true
149
209
  && isIdempotent
210
+ && restoresMissingActionScript
211
+ && blocksStaleActionScriptReuse
212
+ && recoversStaleActionScriptWhenEntryExists
213
+ && restoresExecutableBit
150
214
  && hasCommandRegistry
151
215
  && noLaunchctlSecretSetenv
152
216
  && hasLaunchctlUnsetenv
@@ -164,6 +228,10 @@ const report = {
164
228
  env_home_temp: envHomeTemp,
165
229
  result,
166
230
  second_result: secondResult,
231
+ restored_script_result: restoredScriptResult,
232
+ stale_script_missing_entry_result: staleScriptMissingEntryResult,
233
+ recovered_stale_script_result: recoveredStaleScriptResult,
234
+ restored_executable_bit_result: restoredExecutableBitResult,
167
235
  env_home_result: envHomeResult,
168
236
  executable_exists: executableExists,
169
237
  launch_agent_exists: launchAgentExists,
@@ -185,6 +253,10 @@ const report = {
185
253
  has_build_stamp: hasBuildStamp,
186
254
  swift_parse: swiftParse,
187
255
  is_idempotent: isIdempotent,
256
+ restores_missing_action_script: restoresMissingActionScript,
257
+ blocks_stale_action_script_reuse: blocksStaleActionScriptReuse,
258
+ recovers_stale_action_script_when_entry_exists: recoversStaleActionScriptWhenEntryExists,
259
+ restores_executable_bit: restoresExecutableBit,
188
260
  has_command_registry: hasCommandRegistry,
189
261
  no_launchctl_secret_setenv: noLaunchctlSecretSetenv,
190
262
  has_launchctl_unsetenv: hasLaunchctlUnsetenv,
@@ -209,6 +281,17 @@ async function exists(file) {
209
281
  return false;
210
282
  }
211
283
  }
284
+ async function isExecutable(file) {
285
+ if (!file)
286
+ return false;
287
+ try {
288
+ await fs.access(file, fs.constants.X_OK);
289
+ return true;
290
+ }
291
+ catch {
292
+ return false;
293
+ }
294
+ }
212
295
  async function swiftParseSmoke(sourcePath) {
213
296
  if (process.platform !== 'darwin')
214
297
  return { ok: true, status: 'skipped', reason: 'not_macos' };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "5.6.0",
4
+ "version": "5.7.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",
@@ -61,19 +61,23 @@
61
61
  },
62
62
  "scripts": {
63
63
  "build": "npm run build:clean",
64
+ "prepack": "npm run build",
64
65
  "build:clean": "node -e \"const fs=require('fs'); fs.rmSync('dist',{recursive:true,force:true}); fs.rmSync('.sneakoscope/cache/tsbuildinfo',{recursive:true,force:true})\" && tsc -p tsconfig.json && node ./dist/scripts/ensure-bin-executable.js && node ./dist/scripts/build-dist.js",
65
66
  "build:incremental": "tsc -p tsconfig.json && node ./dist/scripts/ensure-bin-executable.js && node ./dist/scripts/build-dist.js",
66
67
  "test": "node --test --test-concurrency=1 dist/core/__tests__/*.test.js dist/core/proof/__tests__/*.test.js dist/core/stop-gate/__tests__/*.test.js dist/core/commands/__tests__/*.test.js dist/core/dfix/__tests__/*.test.js dist/core/ppt/__tests__/*.test.js dist/core/mad-sks/__tests__/*.test.js dist/core/codex-app/__tests__/*.test.js dist/core/doctor/__tests__/*.test.js dist/cli/__tests__/*.test.js dist/core/triwiki/__tests__/*.test.js dist/core/naruto/__tests__/*.test.js dist/core/agent-bridge/__tests__/*.test.js",
67
68
  "selftest:real": "node ./dist/bin/sks.js selftest --real --json",
68
- "check": "npm run release:check:affected",
69
+ "check": "npm run release:check:confidence",
69
70
  "dev:sks": "npm run build:incremental --silent && node ./dist/bin/sks.js",
70
- "prepublishOnly": "npm run release:check:affected",
71
+ "prepublishOnly": "node ./dist/scripts/prepublish-release-check-or-fast.js && node ./dist/scripts/release-check-stamp.js verify && node ./dist/scripts/package-published-contract-check.js && node ./dist/scripts/release-registry-check.js --require-unpublished --require-publish-auth",
71
72
  "runtime:no-src-mjs": "node ./dist/scripts/runtime-no-src-mjs-check.js",
72
73
  "runtime:ts-source-of-truth": "node ./dist/scripts/runtime-ts-source-of-truth-check.js",
73
74
  "architecture:guard": "node ./dist/scripts/architecture-guard-check.js",
74
75
  "runtime:dist-parity": "node ./dist/scripts/runtime-dist-parity-check.js",
75
- "release:check": "npm run release:check:affected --silent",
76
+ "release:check": "npm run release:check:affected",
76
77
  "release:metadata": "node ./dist/scripts/release-metadata-check.js",
78
+ "release:version-truth": "node ./dist/scripts/release-version-truth-check.js",
79
+ "release:dist-freshness": "node ./dist/scripts/release-dist-freshness-check.js",
80
+ "release:provenance": "node ./dist/scripts/release-provenance-check.js",
77
81
  "release:gate-script-parity": "node ./dist/scripts/release-gate-script-parity-check.js",
78
82
  "ppt:real-imagegen-wiring": "node ./dist/scripts/ppt-real-imagegen-wiring-check.js",
79
83
  "ux-review:run-wires-imagegen": "node ./dist/scripts/ux-review-run-wires-imagegen-check.js",
@@ -111,6 +115,7 @@
111
115
  "zellij:layout-valid": "node ./dist/scripts/zellij-layout-valid-check.js",
112
116
  "zellij:doctor-readiness": "node ./dist/scripts/zellij-doctor-readiness-check.js",
113
117
  "codex:project-config-policy-splitter": "node ./dist/scripts/codex-project-config-policy-splitter-check.js",
118
+ "legacy:upgrade-zero-break": "node ./dist/scripts/legacy-upgrade-matrix-check.js",
114
119
  "packcheck": "for d in bin test; do [ -d \"$d\" ] && find \"$d\" -name '*.mjs' -print0; done | xargs -0 -n1 node --check && npm run runtime:no-src-mjs",
115
120
  "changelog:check": "node ./dist/scripts/changelog-check.js",
116
121
  "cli-entrypoint:check": "node ./dist/scripts/check-cli-entrypoint.js",
@@ -133,8 +138,12 @@
133
138
  "release:check:fast": "npm run build:incremental --silent && node ./dist/scripts/release-gate-dag-runner.js --preset fast --changed-since auto --sla 5m && node ./dist/scripts/release-check-stamp.js write",
134
139
  "release:check:confidence": "npm run build:incremental --silent && node ./dist/scripts/release-gate-dag-runner.js --preset confidence --changed-since auto --sla 5m && node ./dist/scripts/release-check-stamp.js write",
135
140
  "release:check:full": "npm run build:clean --silent && node ./dist/scripts/release-gate-dag-runner.js --preset release --full && node ./dist/scripts/release-gate-dag-runner.js --preset bench --full && node ./dist/scripts/release-check-stamp.js write",
136
- "publish:prep-ignore-scripts": "npm run prepublishOnly",
141
+ "publish:packlist-performance": "node ./dist/scripts/packlist-performance-check.js",
142
+ "publish:dry": "npm run publish:prep-ignore-scripts && npm publish --dry-run --ignore-scripts --json",
143
+ "publish:prep-ignore-scripts": "npm run build:incremental --silent && npm run release:version-truth --silent && npm run publish:packlist-performance --silent && node ./dist/scripts/package-published-contract-check.js && node ./dist/scripts/release-registry-check.js --require-unpublished --require-publish-auth && npm run publish-tag:check --silent",
137
144
  "publish:ignore-scripts": "npm run publish:prep-ignore-scripts && npm publish --ignore-scripts",
145
+ "publish:npm": "npm run publish:ignore-scripts",
146
+ "release:publish": "npm run publish:ignore-scripts",
138
147
  "gates:run": "node ./dist/scripts/release-gate-dag-runner.js",
139
148
  "policy:gate-audit": "node ./dist/scripts/gate-policy-audit-check.js"
140
149
  },