sneakoscope 0.9.10 → 0.9.11

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.9.10",
4
+ "version": "0.9.11",
5
5
  "description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
@@ -3,7 +3,7 @@ import os from 'node:os';
3
3
  import fsp from 'node:fs/promises';
4
4
  import readline from 'node:readline/promises';
5
5
  import { stdin as input, stdout as output } from 'node:process';
6
- import { ensureDir, exists, globalSksRoot, packageRoot, readText, runProcess, tmpdir, which, writeTextAtomic } from '../core/fsx.mjs';
6
+ import { ensureDir, exists, globalSksRoot, packageRoot, PACKAGE_VERSION, readText, runProcess, tmpdir, which, writeTextAtomic } from '../core/fsx.mjs';
7
7
  import { getCodexInfo } from '../core/codex-adapter.mjs';
8
8
  import { formatHarnessConflictReport, llmHarnessCleanupPrompt, scanHarnessConflicts } from '../core/harness-conflicts.mjs';
9
9
  import { initProject, installSkills } from '../core/init.mjs';
@@ -32,6 +32,7 @@ export async function postinstall({ bootstrap }) {
32
32
  console.log('\nSKS installed.');
33
33
  const shim = await ensureSksCommandDuringInstall();
34
34
  if (shim.status === 'present') console.log(`SKS command: available (${shim.command}).`);
35
+ else if (shim.status === 'repaired') console.log(`SKS command: stale PATH shim repaired (${shim.command}).`);
35
36
  else if (shim.status === 'created') console.log(`SKS command: shim created at ${shim.command}.`);
36
37
  else if (shim.status === 'created_not_on_path') console.log(`SKS command: shim created at ${shim.command}. Add ${path.dirname(shim.command)} to PATH, or run npx -y -p sneakoscope sks.`);
37
38
  else if (shim.status === 'skipped') console.log(`SKS command: skipped (${shim.reason}).`);
@@ -1367,10 +1368,13 @@ function escapeRegExp(value) {
1367
1368
  export async function ensureSksCommandDuringInstall(opts = {}) {
1368
1369
  if (process.env.SKS_SKIP_POSTINSTALL_SHIM === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_SHIM=1' };
1369
1370
  const pathEnv = opts.pathEnv ?? process.env.PATH ?? '';
1370
- const existing = await findCommandOnPath('sks', pathEnv);
1371
- if (isStableSksBin(existing)) return { status: 'present', command: existing };
1372
1371
  const nodeBin = opts.nodeBin || process.execPath;
1373
1372
  const target = opts.target || path.join(packageRoot(), 'bin', 'sks.mjs');
1373
+ const repair = await reconcileSksPathShimsDuringInstall({ ...opts, pathEnv, nodeBin, target });
1374
+ if (repair.status === 'repaired') return { ...repair, command: repair.command || repair.repaired?.[0]?.path || target };
1375
+ if (repair.status === 'failed') return repair;
1376
+ const existing = await findCommandOnPath('sks', pathEnv);
1377
+ if (isStableSksBin(existing)) return { status: 'present', command: existing };
1374
1378
  const dirs = candidateShimDirs(pathEnv, opts.home || process.env.HOME);
1375
1379
  const script = process.platform === 'win32'
1376
1380
  ? `@echo off\r\n"${nodeBin}" "${target}" %*\r\n`
@@ -1394,6 +1398,80 @@ export async function ensureSksCommandDuringInstall(opts = {}) {
1394
1398
  return { status: 'failed', error: lastError };
1395
1399
  }
1396
1400
 
1401
+ export async function selftestSksShimRepair() {
1402
+ const staleShimTmp = tmpdir();
1403
+ const staleBin = path.join(staleShimTmp, 'old-prefix', 'bin');
1404
+ const stalePkg = path.join(staleShimTmp, 'old-prefix', 'lib', 'node_modules', 'sneakoscope');
1405
+ await ensureDir(path.join(stalePkg, 'bin'));
1406
+ await ensureDir(staleBin);
1407
+ await writeTextAtomic(path.join(stalePkg, 'package.json'), JSON.stringify({ name: 'sneakoscope', version: '0.0.1' }, null, 2));
1408
+ await writeTextAtomic(path.join(stalePkg, 'bin', 'sks.mjs'), '#!/usr/bin/env node\nconsole.log("sneakoscope 0.0.1");\n');
1409
+ await fsp.chmod(path.join(stalePkg, 'bin', 'sks.mjs'), 0o755).catch(() => {});
1410
+ await fsp.symlink(path.join(stalePkg, 'bin', 'sks.mjs'), path.join(staleBin, 'sks'));
1411
+ const repair = await ensureSksCommandDuringInstall({ force: true, pathEnv: staleBin, home: path.join(staleShimTmp, 'home') });
1412
+ if (repair.status !== 'repaired') throw new Error(`selftest: stale global sks shim was not repaired (${repair.status})`);
1413
+ const run = await runProcess(path.join(staleBin, 'sks'), ['--version'], { timeoutMs: 10000, maxOutputBytes: 16 * 1024 });
1414
+ if (run.code !== 0 || !String(run.stdout || '').includes(PACKAGE_VERSION)) throw new Error('selftest: repaired stale sks shim does not run current package version');
1415
+ return { ok: true, repaired: repair.repaired || [] };
1416
+ }
1417
+
1418
+ async function reconcileSksPathShimsDuringInstall(opts = {}) {
1419
+ if (process.env.SKS_SKIP_POSTINSTALL_SHIM_REPAIR === '1' && !opts.force) return { status: 'skipped', reason: 'SKS_SKIP_POSTINSTALL_SHIM_REPAIR=1' };
1420
+ const target = opts.target || path.join(packageRoot(), 'bin', 'sks.mjs');
1421
+ const nodeBin = opts.nodeBin || process.execPath;
1422
+ const currentVersion = await installedPackageVersion(packageRoot());
1423
+ const commands = await findCommandsOnPath(['sks', 'sneakoscope'], opts.pathEnv ?? process.env.PATH ?? '');
1424
+ const repaired = [];
1425
+ const failed = [];
1426
+ for (const command of commands) {
1427
+ const info = await inspectSksPathShim(command.path, { target, currentVersion });
1428
+ if (!info.repairable) continue;
1429
+ const script = process.platform === 'win32'
1430
+ ? `@echo off\r\n"${nodeBin}" "${target}" %*\r\n`
1431
+ : `#!/bin/sh\nexec "${nodeBin}" "${target}" "$@"\n`;
1432
+ try {
1433
+ await writeTextAtomic(command.path, script);
1434
+ if (process.platform !== 'win32') await fsp.chmod(command.path, 0o755).catch(() => {});
1435
+ repaired.push({ path: command.path, name: command.name, previous_version: info.version || null, target });
1436
+ } catch (err) {
1437
+ failed.push({ path: command.path, name: command.name, previous_version: info.version || null, error: err.message });
1438
+ }
1439
+ }
1440
+ if (repaired.length) return { status: 'repaired', command: repaired[0].path, repaired, failed };
1441
+ if (failed.length) return { status: 'failed', error: failed.map((entry) => `${entry.path}: ${entry.error}`).join('; '), failed };
1442
+ return { status: 'present' };
1443
+ }
1444
+
1445
+ async function inspectSksPathShim(candidate, opts = {}) {
1446
+ if (!candidate || isTransientNpmBinPath(candidate)) return { repairable: false, reason: 'transient_or_missing' };
1447
+ const target = path.resolve(opts.target || path.join(packageRoot(), 'bin', 'sks.mjs'));
1448
+ const resolved = await fsp.realpath(candidate).catch(() => candidate);
1449
+ if (path.resolve(resolved) === target) return { repairable: false, reason: 'current_target' };
1450
+ const packageDir = sksPackageRootForBin(resolved) || sksPackageRootForBin(candidate);
1451
+ if (!packageDir) return { repairable: false, reason: 'not_sneakoscope_bin' };
1452
+ const version = await installedPackageVersion(packageDir);
1453
+ const currentVersion = opts.currentVersion || await installedPackageVersion(packageRoot());
1454
+ if (!version || !currentVersion || compareVersions(version, currentVersion) >= 0) return { repairable: false, reason: 'not_older', version, current_version: currentVersion };
1455
+ return { repairable: true, version, current_version: currentVersion, package_dir: packageDir, resolved };
1456
+ }
1457
+
1458
+ function sksPackageRootForBin(file) {
1459
+ const normalized = String(file || '').split(path.sep).join('/');
1460
+ const marker = '/node_modules/sneakoscope/bin/';
1461
+ const idx = normalized.lastIndexOf(marker);
1462
+ if (idx < 0) return null;
1463
+ return normalized.slice(0, idx + '/node_modules/sneakoscope'.length).split('/').join(path.sep);
1464
+ }
1465
+
1466
+ async function installedPackageVersion(root) {
1467
+ const pkg = await readJsonMaybe(path.join(root, 'package.json'));
1468
+ return pkg?.version || (root === packageRoot() ? PACKAGE_VERSION : null);
1469
+ }
1470
+
1471
+ async function readJsonMaybe(file) {
1472
+ try { return JSON.parse(await fsp.readFile(file, 'utf8')); } catch { return null; }
1473
+ }
1474
+
1397
1475
  function candidateShimDirs(pathEnv, home) {
1398
1476
  const seen = new Set();
1399
1477
  const out = [];
@@ -1413,14 +1491,26 @@ function candidateShimDirs(pathEnv, home) {
1413
1491
  }
1414
1492
 
1415
1493
  async function findCommandOnPath(name, pathEnv) {
1494
+ const found = await findCommandsOnPath([name], pathEnv);
1495
+ return found[0]?.path || null;
1496
+ }
1497
+
1498
+ async function findCommandsOnPath(names, pathEnv) {
1416
1499
  const suffixes = process.platform === 'win32' ? ['.cmd', '.exe', ''] : [''];
1500
+ const out = [];
1501
+ const seen = new Set();
1417
1502
  for (const dir of String(pathEnv || '').split(path.delimiter).filter(Boolean)) {
1418
- for (const suffix of suffixes) {
1419
- const candidate = path.join(dir, `${name}${suffix}`);
1420
- if (await exists(candidate)) return candidate;
1503
+ for (const name of names) {
1504
+ for (const suffix of suffixes) {
1505
+ const candidate = path.join(dir, `${name}${suffix}`);
1506
+ const key = path.resolve(candidate);
1507
+ if (seen.has(key) || !await exists(candidate)) continue;
1508
+ seen.add(key);
1509
+ out.push({ name, path: candidate });
1510
+ }
1421
1511
  }
1422
1512
  }
1423
- return null;
1513
+ return out;
1424
1514
  }
1425
1515
 
1426
1516
  async function ensureGlobalContext7DuringInstall() {
package/src/cli/main.mjs CHANGED
@@ -81,7 +81,7 @@ import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs'
81
81
  import { buildTmuxLaunchPlan, buildTmuxOpenArgs, codexLaunchCommand, createTmuxSession, defaultCodexLaunchArgs, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, shouldAutoAttachTmux, sksAsciiLogo, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchMadTmuxUi, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, reconcileTmuxTeamCockpit, runTmuxStatus, sanitizeTmuxSessionName, sweepCodexLbTmuxSessions, sweepTmuxTeamSurfaces, teamLaneStyle } from '../core/tmux-ui.mjs';
82
82
  import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
83
83
  import { context7Command } from './context7-command.mjs';
84
- import { askPostinstallQuestion, checkCodexLbResponseChain, checkContext7, checkRequiredSkills, codexLbChatgptBackupPath, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexFastModeDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, formatCodexLbRepairResultText, formatCodexLbStatusText, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, releaseCodexLbAuthHold, repairCodexLbAuth, selftestCodexLb, shouldAutoApproveInstall, unselectCodexLbProvider } from './install-helpers.mjs';
84
+ import { askPostinstallQuestion, checkCodexLbResponseChain, checkContext7, checkRequiredSkills, codexLbChatgptBackupPath, codexLbStatus, configureCodexLb, ensureCodexCliTool, ensureGlobalCodexFastModeDuringInstall, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, ensureTmuxCliTool, formatCodexLbRepairResultText, formatCodexLbStatusText, globalCodexSkillsRoot, maybePromptCodexLbSetupForLaunch, maybePromptCodexUpdateForLaunch, postinstall, postinstallBootstrapDecision, releaseCodexLbAuthHold, repairCodexLbAuth, selftestCodexLb, selftestSksShimRepair, shouldAutoApproveInstall, unselectCodexLbProvider } from './install-helpers.mjs';
85
85
  import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, madHighCommand as runMadHighCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
86
86
  import { openClawCommand } from './openclaw-command.mjs';
87
87
  import { recallPulseCommand } from './recallpulse-command.mjs';
@@ -2330,6 +2330,7 @@ async function selftest() {
2330
2330
  if (await exists(path.join(postinstallSetupHome, '.agents', 'skills', name, 'SKILL.md'))) throw new Error(`selftest: postinstall global skills shadow the first-party ${name} plugin`);
2331
2331
  }
2332
2332
  if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', 'getdesign-reference', 'SKILL.md')))) throw new Error('selftest: postinstall global getdesign-reference skill not installed');
2333
+ await selftestSksShimRepair();
2333
2334
  const oldNoBootstrap = process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
2334
2335
  process.env.SKS_POSTINSTALL_NO_BOOTSTRAP = '1';
2335
2336
  const noBootstrapDecision = await postinstallBootstrapDecision(postinstallSetupTmp);
package/src/core/fsx.mjs 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
 
8
- export const PACKAGE_VERSION = '0.9.10';
8
+ export const PACKAGE_VERSION = '0.9.11';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11