sneakoscope 1.20.3 → 1.20.5

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
@@ -16,7 +16,11 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
16
16
 
17
17
  ## Current Release
18
18
 
19
- SKS **1.20.3** is a targeted patch for `sks --mad` / codex-lb sessions on macOS and project-local Fast mode control. SKS now supplies a short per-user `ZELLIJ_SOCKET_DIR` by default, caps generated session names safely, records `*_command_with_env` attach commands, and classifies `IPC socket path is too long` as `zellij_socket_path_too_long` instead of a generic launch failure. It also adds `sks fast-mode on|off|status|clear`, `$Fast-On`, `$Fast-Off`, and `$Fast-Mode`; saved project preferences are used only when no explicit `--fast`, `--no-fast`, or `--service-tier` flag is present.
19
+ SKS **1.20.5** makes `sks --mad` actually open the Zellij session. Previous releases only *created* a detached background session and printed an `Attach with: ...` hint, so nothing opened in the operator's terminal. SKS now performs the follow-up foreground attach automatically when launched in an interactive TTY (using the same `ZELLIJ_SOCKET_DIR` namespace as the background session), and falls back to printing the manual attach command if attach fails. Auto-attach is skipped for `--json`, non-TTY/piped launches, when already inside a Zellij session, or with `--no-attach` / `SKS_NO_ZELLIJ_ATTACH=1`; `--attach` forces it.
20
+
21
+ SKS **1.20.4** is a targeted `sks --mad` / codex-lb Zellij usability patch: when a background MAD Zellij session launches successfully, SKS now prints the exact `Attach with: ZELLIJ_SOCKET_DIR=... zellij attach ...` command so operators can enter the fresh session without manually reconstructing the socket namespace.
22
+
23
+ SKS **1.20.3** added the macOS Zellij launch fallback and project-local Fast mode control. SKS supplies a short per-user `ZELLIJ_SOCKET_DIR` by default, caps generated session names safely, records `*_command_with_env` attach commands, and classifies `IPC socket path is too long` as `zellij_socket_path_too_long` instead of a generic launch failure. It also adds `sks fast-mode on|off|status|clear`, `$Fast-On`, `$Fast-Off`, and `$Fast-Mode`; saved project preferences are used only when no explicit `--fast`, `--no-fast`, or `--service-tier` flag is present.
20
24
 
21
25
  It carries forward the **1.20.2** stabilization layer: **Mutation Guard** routes genuinely-risky global/config/permission/package mutations through the Requested-Scope Contract + Mutation Ledger (`safety:mutation-callsite-coverage` fails any unguarded, unallowlisted risky call site); `release:check:dynamic:execute` is the real **caching gate runner** (schema v2, real/heavy gates deferred to `release:real-check`, dynamic-only cannot authorize publish); the **Core Skill** deployed snapshot is read by the route runtime and recorded in `agent-proof-evidence.json` (`selected_core_skill`), with promotions written to the mutation ledger; and `sks doctor` exposes an explicit **`zellij_readiness`** block (`zellij:doctor-readiness`). See `docs/dynamic-release-pipeline.md`.
22
26
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "1.20.3"
79
+ version = "1.20.5"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "1.20.3"
3
+ version = "1.20.5"
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 1.20.3"),
7
+ Some("--version") => println!("sks-rs 1.20.5"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": "sks.dist-build-stamp.v1",
3
3
  "package_name": "sneakoscope",
4
- "package_version": "1.20.3",
5
- "source_digest": "c76562a39aaa8ecf01128ce5cda509ad6f8883d1f8c6203c73ddad1d243e54e5",
6
- "source_file_count": 1743,
7
- "built_at_source_time": 1780267464746
4
+ "package_version": "1.20.5",
5
+ "source_digest": "4ba5f14a828460ed0146c7109f59e53e148d6b1ffb42e8c01b00e64086da827e",
6
+ "source_file_count": 1750,
7
+ "built_at_source_time": 1780287464818
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '1.20.3';
2
+ const FAST_PACKAGE_VERSION = '1.20.5';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "1.20.3",
4
- "package_version": "1.20.3",
3
+ "version": "1.20.5",
4
+ "package_version": "1.20.5",
5
5
  "typescript": true,
6
6
  "mjs_runtime_files": 0,
7
- "compiled_file_count": 1018,
8
- "compiled_js_count": 509,
9
- "compiled_dts_count": 509,
10
- "source_digest": "c76562a39aaa8ecf01128ce5cda509ad6f8883d1f8c6203c73ddad1d243e54e5",
11
- "source_file_count": 1743,
12
- "source_files_hash": "b1db64d16ef4e7f980de40d7c8c78b3280d95e78c5583092368dd283aeb367af",
13
- "source_list_hash": "b1db64d16ef4e7f980de40d7c8c78b3280d95e78c5583092368dd283aeb367af",
7
+ "compiled_file_count": 1020,
8
+ "compiled_js_count": 510,
9
+ "compiled_dts_count": 510,
10
+ "source_digest": "4ba5f14a828460ed0146c7109f59e53e148d6b1ffb42e8c01b00e64086da827e",
11
+ "source_file_count": 1750,
12
+ "source_files_hash": "de93b15a72352d00481b958333ed964bf083563809e3beddfe4e64046720df75",
13
+ "source_list_hash": "de93b15a72352d00481b958333ed964bf083563809e3beddfe4e64046720df75",
14
14
  "src_mjs_runtime_files": 0,
15
15
  "dist_stamp_schema": "sks.dist-build-stamp.v1",
16
16
  "files": [
@@ -861,6 +861,8 @@
861
861
  "core/safety/mutation-ledger.js",
862
862
  "core/safety/requested-scope-contract.d.ts",
863
863
  "core/safety/requested-scope-contract.js",
864
+ "core/safety/side-effect-runtime-report.d.ts",
865
+ "core/safety/side-effect-runtime-report.js",
864
866
  "core/secret-redaction.d.ts",
865
867
  "core/secret-redaction.js",
866
868
  "core/session/project-namespace.d.ts",
@@ -4,7 +4,7 @@ import { initProject } from '../init.js';
4
4
  import { createMission, setCurrent } from '../mission.js';
5
5
  import { enableMadHighProfile, madHighProfileName } from '../auto-review.js';
6
6
  import { permissionGateSummary } from '../permission-gates.js';
7
- import { launchMadZellijUi, sanitizeZellijSessionName } from '../zellij/zellij-launcher.js';
7
+ import { attachZellijSessionInteractive, launchMadZellijUi, sanitizeZellijSessionName } from '../zellij/zellij-launcher.js';
8
8
  import { createMadSksAuthorizationManifest, validateMadSksAuthorizationManifest } from '../mad-sks/authorization-manifest.js';
9
9
  import { createMadSksAuditLedger, madSksAuditAction, writeMadSksAuditLedger } from '../mad-sks/audit-ledger.js';
10
10
  import { compareProtectedCoreSnapshots, evaluateMadSksWrite, resolveProtectedCore, snapshotProtectedCore } from '../mad-sks/immutable-harness-guard.js';
@@ -79,10 +79,48 @@ export async function madHighCommand(args = [], deps = {}) {
79
79
  const launchOpts = codexLbImmediateLaunchOpts(cleanArgs, launchLb, { codexArgs: profile.launch_args, conciseBlockers: true, madSksEnv, launchEnv: madSksEnv });
80
80
  const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', launchOpts.session || `sks-mad-${sanitizeZellijSessionName(process.cwd())}`));
81
81
  const launch = await launchMadZellijUi([...cleanArgs, '--workspace', workspace], { ...launchOpts, missionId: madLaunch.mission_id, root: madLaunch.root, cwd: process.cwd(), ledgerRoot: path.join(madLaunch.dir, 'agents'), requireZellij: process.env.SKS_REQUIRE_ZELLIJ === '1' });
82
- if (!launch.ok)
82
+ if (!launch.ok) {
83
83
  console.log(`MAD Zellij action: ${formatMadZellijAction(launch)}`);
84
+ return launch;
85
+ }
86
+ // The launcher only creates a detached background session. In an interactive
87
+ // terminal, immediately attach so the session actually opens for the user
88
+ // instead of leaving them to copy/paste the attach command by hand.
89
+ if (shouldAutoAttachZellij(args)) {
90
+ console.log(`Opening Zellij session: ${launch.session_name} (detach with Ctrl+q, re-attach later with: ${launch.attach_command_with_env})`);
91
+ const attached = attachZellijSessionInteractive(launch.session_name, { cwd: process.cwd() });
92
+ if (!attached.ok) {
93
+ console.log(`Could not open the Zellij session automatically${attached.error ? ` (${attached.error})` : ''}.`);
94
+ if (launch.attach_command_with_env)
95
+ console.log(`Attach with: ${launch.attach_command_with_env}`);
96
+ }
97
+ return launch;
98
+ }
99
+ if (launch.attach_command_with_env)
100
+ console.log(`Attach with: ${launch.attach_command_with_env}`);
84
101
  return launch;
85
102
  }
103
+ // Decide whether to take over the current terminal with a foreground Zellij
104
+ // attach. We only do this for genuinely interactive launches; piped, JSON,
105
+ // non-TTY, or already-inside-Zellij invocations keep the previous behaviour of
106
+ // printing a manual "Attach with:" hint. Use --no-attach (or
107
+ // SKS_NO_ZELLIJ_ATTACH=1) to force the background-only behaviour, and --attach
108
+ // to force attaching even without a detected TTY.
109
+ function shouldAutoAttachZellij(args) {
110
+ const list = (args || []).map((arg) => String(arg));
111
+ if (list.includes('--no-attach'))
112
+ return false;
113
+ if (list.includes('--json'))
114
+ return false;
115
+ if (process.env.SKS_NO_ZELLIJ_ATTACH === '1')
116
+ return false;
117
+ // Nested attach is rejected by Zellij when already inside a session.
118
+ if (process.env.ZELLIJ)
119
+ return false;
120
+ if (list.includes('--attach'))
121
+ return true;
122
+ return Boolean(process.stdout.isTTY && process.stdin.isTTY);
123
+ }
86
124
  function formatMadZellijAction(launch) {
87
125
  const blockers = launch.blockers?.join(', ') || launch.warnings?.join(', ') || 'check Zellij installation';
88
126
  const details = [
@@ -187,6 +225,8 @@ function madLaunchOnlyFlags() {
187
225
  '--MAD',
188
226
  '--mad-sks',
189
227
  '--high',
228
+ '--attach',
229
+ '--no-attach',
190
230
  '--no-auto-install-zellij',
191
231
  '--allow-system',
192
232
  '--allow-db-write',
@@ -1,4 +1,4 @@
1
- export declare const PACKAGE_VERSION = "1.20.3";
1
+ export declare const PACKAGE_VERSION = "1.20.5";
2
2
  export declare const DEFAULT_PROCESS_TAIL_BYTES: number;
3
3
  export declare const DEFAULT_PROCESS_TIMEOUT_MS: number;
4
4
  export interface RunProcessOptions {
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 = '1.20.3';
8
+ export const PACKAGE_VERSION = '1.20.5';
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() {
@@ -7,6 +7,11 @@ export interface GateCacheKeyInput {
7
7
  inputHashes: string[];
8
8
  envMode: string;
9
9
  distHash: string;
10
+ manifestHash?: string;
11
+ packageScriptsHash?: string;
12
+ gateImplementationHash?: string;
13
+ nodeVersion?: string;
14
+ npmVersion?: string;
10
15
  }
11
16
  export declare function gateCacheKey(input: GateCacheKeyInput): string;
12
17
  export interface GateCacheRecord {
@@ -10,7 +10,12 @@ export function gateCacheKey(input) {
10
10
  h: input.gitCommit,
11
11
  i: [...input.inputHashes].sort(),
12
12
  e: input.envMode,
13
- d: input.distHash
13
+ d: input.distHash,
14
+ m: input.manifestHash || '',
15
+ s: input.packageScriptsHash || '',
16
+ gimpl: input.gateImplementationHash || '',
17
+ node: input.nodeVersion || '',
18
+ npm: input.npmVersion || ''
14
19
  });
15
20
  return crypto.createHash('sha256').update(canonical).digest('hex');
16
21
  }
@@ -9,7 +9,9 @@ export interface GateManifestEntry {
9
9
  always_on_release: boolean;
10
10
  required_for_publish: boolean;
11
11
  can_run_incremental: boolean;
12
+ safe_subgate?: string | undefined;
12
13
  }
14
+ export declare const FORBIDDEN_RECURSIVE_GATES: Set<string>;
13
15
  export declare const ALWAYS_ON_GATES: Set<string>;
14
16
  export declare const REQUIRED_FOR_PUBLISH: Set<string>;
15
17
  /** Heuristic mapping from a gate id to the source globs that affect it. */
Binary file
@@ -16,6 +16,7 @@ export interface GuardOptions {
16
16
  confirmed?: boolean;
17
17
  backupPath?: string | null;
18
18
  noOpReason?: string | null;
19
+ pathTargets?: string[];
19
20
  }
20
21
  export declare function guardedWriteFile(ctx: GuardContext, target: string, data: string, opts?: GuardOptions): Promise<void>;
21
22
  export declare function guardedGlobalCodexConfigWrite(ctx: GuardContext, target: string, data: string, opts?: GuardOptions): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import fsp from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { runProcess, writeTextAtomic } from '../fsx.js';
4
- import { isMutationAllowed } from './requested-scope-contract.js';
4
+ import { isMutationAllowed, isPathAllowed } from './requested-scope-contract.js';
5
5
  import { evaluateMutation, recordMutation } from './mutation-ledger.js';
6
6
  // Mutation Guard Adapter (1.20.2 Area 1a).
7
7
  //
@@ -47,6 +47,11 @@ async function guard(ctx, kind, target, opts, apply) {
47
47
  const scope = isMutationAllowed(ctx.contract, scopeForKind(kind), { confirmed: opts.confirmed === true });
48
48
  if (!scope.allowed)
49
49
  throw new MutationGuardViolationError(kind, target, scope.reason);
50
+ for (const pathTarget of pathTargetsFor(kind, target, opts)) {
51
+ const pathDecision = isPathAllowed(ctx.contract, pathTarget);
52
+ if (!pathDecision.allowed)
53
+ throw new MutationGuardViolationError(kind, pathTarget, pathDecision.reason);
54
+ }
50
55
  if (NEEDS_BACKUP.has(kind) && !opts.backupPath && !opts.noOpReason) {
51
56
  throw new MutationGuardViolationError(kind, target, 'backup_or_no_op_reason_required');
52
57
  }
@@ -98,6 +103,24 @@ function buildEvalOpts(target, opts, applied) {
98
103
  out.noOpReason = opts.noOpReason;
99
104
  return out;
100
105
  }
106
+ const PATH_SCOPED_KINDS = new Set([
107
+ 'file_write',
108
+ 'file_delete',
109
+ 'file_rename',
110
+ 'chmod',
111
+ 'xattr',
112
+ 'chflags',
113
+ 'global_config_write'
114
+ ]);
115
+ function pathTargetsFor(kind, target, opts) {
116
+ if (!PATH_SCOPED_KINDS.has(kind))
117
+ return [];
118
+ if (opts.pathTargets?.length)
119
+ return opts.pathTargets;
120
+ if (kind === 'file_rename' && target.includes(' -> '))
121
+ return target.split(' -> ').map((s) => s.trim()).filter(Boolean);
122
+ return [target];
123
+ }
101
124
  // ---- Public guarded wrappers -------------------------------------------------
102
125
  export async function guardedWriteFile(ctx, target, data, opts = {}) {
103
126
  await guard(ctx, 'file_write', target, opts, () => writeTextAtomic(target, data));
@@ -109,7 +132,7 @@ export async function guardedRm(ctx, target, opts = {}) {
109
132
  await guard(ctx, 'file_delete', target, opts, () => fsp.rm(target, { recursive: opts.recursive === true, force: opts.force !== false }));
110
133
  }
111
134
  export async function guardedRename(ctx, from, to, opts = {}) {
112
- await guard(ctx, 'file_rename', `${from} -> ${to}`, opts, () => fsp.rename(from, to));
135
+ await guard(ctx, 'file_rename', `${from} -> ${to}`, { ...opts, pathTargets: [from, to] }, () => fsp.rename(from, to));
113
136
  }
114
137
  export async function guardedChmod(ctx, target, mode, opts = {}) {
115
138
  await guard(ctx, 'chmod', target, opts, () => fsp.chmod(target, mode));
@@ -0,0 +1,24 @@
1
+ import { type MutationLedgerEntry } from './mutation-ledger.js';
2
+ export declare const SIDE_EFFECT_RUNTIME_REPORT_SCHEMA = "sks.side-effect-runtime-report.v1";
3
+ export interface SideEffectRuntimeReport {
4
+ schema: string;
5
+ ok: boolean;
6
+ generated_at: string;
7
+ ledger_paths: string[];
8
+ total_entries: number;
9
+ applied_entries: number;
10
+ unexpected_applied_mutations: number;
11
+ global_mutations_without_confirmation: number;
12
+ config_mutations_without_backup_or_noop: number;
13
+ global_mutations: SideEffectRuntimeGlobalMutation[];
14
+ }
15
+ export interface SideEffectRuntimeGlobalMutation {
16
+ route: string;
17
+ kind: MutationLedgerEntry['kind'];
18
+ target: string;
19
+ backup_path: string | null;
20
+ no_op_reason: string | null;
21
+ reason?: string;
22
+ }
23
+ export declare function buildSideEffectRuntimeReport(root: string): Promise<SideEffectRuntimeReport>;
24
+ //# sourceMappingURL=side-effect-runtime-report.d.ts.map
@@ -0,0 +1,84 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { mutationLedgerPath } from './mutation-ledger.js';
4
+ export const SIDE_EFFECT_RUNTIME_REPORT_SCHEMA = 'sks.side-effect-runtime-report.v1';
5
+ const GLOBAL_KINDS = new Set(['global_config_write', 'codex_app_flag_change', 'codex_lb_auth_change', 'skill_snapshot_promotion']);
6
+ export async function buildSideEffectRuntimeReport(root) {
7
+ const ledgerPaths = await discoverLedgerPaths(root);
8
+ const entries = (await Promise.all(ledgerPaths.map(readLedger))).flat();
9
+ const applied = entries.filter((entry) => entry.applied === true);
10
+ const unexpected = applied.filter((entry) => entry.violation === true || entry.requested_scope_allowed !== true);
11
+ const globalMutations = applied.filter((entry) => GLOBAL_KINDS.has(entry.kind));
12
+ const globalWithoutConfirmation = globalMutations.filter((entry) => entry.requested_scope_allowed !== true);
13
+ const configWithoutBackup = globalMutations.filter((entry) => !entry.backup_path && !entry.no_op_reason);
14
+ return {
15
+ schema: SIDE_EFFECT_RUNTIME_REPORT_SCHEMA,
16
+ ok: unexpected.length === 0 && globalWithoutConfirmation.length === 0 && configWithoutBackup.length === 0,
17
+ generated_at: new Date().toISOString(),
18
+ ledger_paths: ledgerPaths.map((file) => path.relative(root, file)),
19
+ total_entries: entries.length,
20
+ applied_entries: applied.length,
21
+ unexpected_applied_mutations: unexpected.length,
22
+ global_mutations_without_confirmation: globalWithoutConfirmation.length,
23
+ config_mutations_without_backup_or_noop: configWithoutBackup.length,
24
+ global_mutations: globalMutations.map((entry) => ({
25
+ route: entry.route,
26
+ kind: entry.kind,
27
+ target: entry.target,
28
+ backup_path: entry.backup_path ?? null,
29
+ no_op_reason: entry.no_op_reason ?? null,
30
+ ...(entry.reason ? { reason: entry.reason } : {})
31
+ }))
32
+ };
33
+ }
34
+ async function discoverLedgerPaths(root) {
35
+ const found = new Set();
36
+ await addIfExists(found, mutationLedgerPath(root));
37
+ await walkForLedgers(path.join(root, '.sneakoscope', 'missions'), found);
38
+ return [...found].sort();
39
+ }
40
+ async function walkForLedgers(dir, found) {
41
+ let entries;
42
+ try {
43
+ entries = await fsp.readdir(dir, { withFileTypes: true });
44
+ }
45
+ catch {
46
+ return;
47
+ }
48
+ for (const entry of entries) {
49
+ const file = path.join(dir, entry.name);
50
+ if (entry.isDirectory())
51
+ await walkForLedgers(file, found);
52
+ else if (entry.isFile() && entry.name === 'mutation-ledger.jsonl')
53
+ found.add(file);
54
+ }
55
+ }
56
+ async function addIfExists(found, file) {
57
+ try {
58
+ await fsp.access(file);
59
+ found.add(file);
60
+ }
61
+ catch { }
62
+ }
63
+ async function readLedger(file) {
64
+ let text = '';
65
+ try {
66
+ text = await fsp.readFile(file, 'utf8');
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ return text
72
+ .split('\n')
73
+ .map((line) => line.trim())
74
+ .filter(Boolean)
75
+ .flatMap((line) => {
76
+ try {
77
+ return [JSON.parse(line)];
78
+ }
79
+ catch {
80
+ return [];
81
+ }
82
+ });
83
+ }
84
+ //# sourceMappingURL=side-effect-runtime-report.js.map
@@ -5,6 +5,7 @@ export interface PromotionMutationOptions {
5
5
  contract?: RequestedScopeContract;
6
6
  confirmed?: boolean;
7
7
  context?: 'release' | string;
8
+ ledgerRoot?: string;
8
9
  }
9
10
  export declare class SkillDeploymentViolationError extends Error {
10
11
  constructor(fnName: string);
@@ -19,9 +20,24 @@ export declare function isDeploymentContext(): boolean;
19
20
  export declare function assertNotInDeployment(fnName: string): void;
20
21
  export declare const readDeploymentSnapshot: typeof loadDeployedSnapshot;
21
22
  /**
22
- * Promote an accepted candidate to an immutable deployed snapshot. The previous
23
- * snapshot is archived for rollback. Deployed snapshots are never edited in place.
23
+ * Primary release/deployment promotion API. It writes the immutable deployed
24
+ * snapshot and treats the side-effect ledger as part of the transaction: when
25
+ * ledger recording fails, the deployed pointer is rolled back or removed.
24
26
  */
27
+ export declare function promoteToDeployedWithLedger(root: string, accepted: CoreSkillCard, opts: PromotionMutationOptions & {
28
+ contract: RequestedScopeContract;
29
+ }): Promise<{
30
+ ok: boolean;
31
+ blockers: string[];
32
+ snapshot: CoreSkillCard | null;
33
+ archived_path: string | null;
34
+ }>;
35
+ export declare function promoteToDeployedLegacyForCompatibility(root: string, accepted: CoreSkillCard): Promise<{
36
+ ok: boolean;
37
+ blockers: string[];
38
+ snapshot: CoreSkillCard | null;
39
+ archived_path: string | null;
40
+ }>;
25
41
  export declare function promoteToDeployed(root: string, accepted: CoreSkillCard, opts?: PromotionMutationOptions): Promise<{
26
42
  ok: boolean;
27
43
  blockers: string[];
@@ -26,10 +26,22 @@ export function assertNotInDeployment(fnName) {
26
26
  }
27
27
  export const readDeploymentSnapshot = loadDeployedSnapshot;
28
28
  /**
29
- * Promote an accepted candidate to an immutable deployed snapshot. The previous
30
- * snapshot is archived for rollback. Deployed snapshots are never edited in place.
29
+ * Primary release/deployment promotion API. It writes the immutable deployed
30
+ * snapshot and treats the side-effect ledger as part of the transaction: when
31
+ * ledger recording fails, the deployed pointer is rolled back or removed.
31
32
  */
33
+ export async function promoteToDeployedWithLedger(root, accepted, opts) {
34
+ return promoteToDeployedInternal(root, accepted, opts, true);
35
+ }
36
+ export async function promoteToDeployedLegacyForCompatibility(root, accepted) {
37
+ return promoteToDeployedInternal(root, accepted, {}, false);
38
+ }
32
39
  export async function promoteToDeployed(root, accepted, opts = {}) {
40
+ if (opts.contract)
41
+ return promoteToDeployedWithLedger(root, accepted, opts);
42
+ return promoteToDeployedLegacyForCompatibility(root, accepted);
43
+ }
44
+ async function promoteToDeployedInternal(root, accepted, opts, ledgerRequired) {
33
45
  const blockers = [];
34
46
  if (accepted.status !== 'accepted')
35
47
  blockers.push('promote_requires_accepted_status');
@@ -62,11 +74,11 @@ export async function promoteToDeployed(root, accepted, opts = {}) {
62
74
  created_at: nowIso()
63
75
  };
64
76
  await writeJsonAtomic(deployedPath, snapshot);
65
- // Record the promotion as a side-effect-zero ledger entry when a contract is
66
- // provided OR a release/deployment-owned context is active. The archived
67
- // snapshot is the rollback pointer. Best-effort: a ledger write failure never
68
- // breaks promotion semantics (existing 2-arg callers are unaffected).
69
- if (opts.contract && (opts.context === 'release' || opts.confirmed || isDeploymentContext())) {
77
+ if (ledgerRequired) {
78
+ if (!opts.contract) {
79
+ await rollbackPromotionWrite(deployedPath, existing);
80
+ return { ok: false, blockers: ['promotion_ledger_contract_required'], snapshot: null, archived_path: archivedPath };
81
+ }
70
82
  try {
71
83
  const entry = evaluateMutation(opts.contract, 'skill_snapshot_promotion', {
72
84
  target: deployedPath,
@@ -75,14 +87,31 @@ export async function promoteToDeployed(root, accepted, opts = {}) {
75
87
  noOpReason: archivedPath ? null : 'first_deploy_no_previous_snapshot',
76
88
  applied: true
77
89
  });
78
- await recordMutation(root, entry);
90
+ if (entry.violation) {
91
+ await rollbackPromotionWrite(deployedPath, existing);
92
+ return { ok: false, blockers: [`promotion_ledger_violation:${entry.reason || 'unknown'}`], snapshot: null, archived_path: archivedPath };
93
+ }
94
+ await recordMutation(opts.ledgerRoot || root, entry);
79
95
  }
80
- catch {
81
- // best-effort ledger recording; promotion already succeeded.
96
+ catch (err) {
97
+ await rollbackPromotionWrite(deployedPath, existing);
98
+ const message = err instanceof Error ? err.message : String(err);
99
+ return { ok: false, blockers: [`promotion_ledger_write_failed:${message}`], snapshot: null, archived_path: archivedPath };
82
100
  }
83
101
  }
84
102
  return { ok: true, blockers: [], snapshot, archived_path: archivedPath };
85
103
  }
104
+ async function rollbackPromotionWrite(deployedPath, existing) {
105
+ if (existing)
106
+ await writeJsonAtomic(deployedPath, existing);
107
+ else {
108
+ try {
109
+ const fsp = await import('node:fs/promises');
110
+ await fsp.rm(deployedPath, { force: true });
111
+ }
112
+ catch { }
113
+ }
114
+ }
86
115
  export async function rollbackDeployment(root, route, skillId) {
87
116
  const dir = skillDir(root, route, skillId);
88
117
  const historyDir = path.join(dir, 'deployed-history');
@@ -1,2 +1,2 @@
1
- export declare const PACKAGE_VERSION = "1.20.3";
1
+ export declare const PACKAGE_VERSION = "1.20.5";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '1.20.3';
1
+ export const PACKAGE_VERSION = '1.20.5';
2
2
  //# sourceMappingURL=version.js.map
@@ -165,5 +165,25 @@ export declare function launchTeamZellijView(opts?: ZellijLaunchOptions): Promis
165
165
  blockers: any[];
166
166
  warnings: string[];
167
167
  }>;
168
+ export interface ZellijAttachResult {
169
+ ok: boolean;
170
+ status: number | null;
171
+ signal: NodeJS.Signals | null;
172
+ error?: string;
173
+ }
174
+ /**
175
+ * Attach the current terminal to an existing Zellij session in the foreground.
176
+ *
177
+ * `launchZellijLayout` only ever *creates* a detached background session, which
178
+ * is correct for proof/automation but means an interactive launch (e.g.
179
+ * `sks --mad`) never actually opens anything. This helper performs the
180
+ * follow-up foreground attach, inheriting stdio so the session takes over the
181
+ * user's terminal until they detach. It is a no-op-style failure (never throws)
182
+ * when Zellij is missing or attach fails, so callers can fall back to printing a
183
+ * manual attach hint.
184
+ */
185
+ export declare function attachZellijSessionInteractive(sessionName: string, opts?: {
186
+ cwd?: string;
187
+ }): ZellijAttachResult;
168
188
  export declare function sanitizeZellijSessionName(value: unknown): string;
169
189
  //# sourceMappingURL=zellij-launcher.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import { spawnSync } from 'node:child_process';
2
3
  import { appendJsonl, nowIso, sha256, writeJsonAtomic } from '../fsx.js';
3
4
  import { checkZellijCapability } from './zellij-capability.js';
4
5
  import { formatZellijCommand, resolveZellijProcessEnvMeta, runZellij } from './zellij-command.js';
@@ -116,6 +117,38 @@ export async function launchTeamZellijView(opts = {}) {
116
117
  slotCount: opts.slotCount || 5
117
118
  });
118
119
  }
120
+ /**
121
+ * Attach the current terminal to an existing Zellij session in the foreground.
122
+ *
123
+ * `launchZellijLayout` only ever *creates* a detached background session, which
124
+ * is correct for proof/automation but means an interactive launch (e.g.
125
+ * `sks --mad`) never actually opens anything. This helper performs the
126
+ * follow-up foreground attach, inheriting stdio so the session takes over the
127
+ * user's terminal until they detach. It is a no-op-style failure (never throws)
128
+ * when Zellij is missing or attach fails, so callers can fall back to printing a
129
+ * manual attach hint.
130
+ */
131
+ export function attachZellijSessionInteractive(sessionName, opts = {}) {
132
+ if (!sessionName)
133
+ return { ok: false, status: null, signal: null, error: 'missing_session_name' };
134
+ const meta = resolveZellijProcessEnvMeta();
135
+ const env = { ...process.env };
136
+ if (meta.zellij_socket_dir && !env.ZELLIJ_SOCKET_DIR)
137
+ env.ZELLIJ_SOCKET_DIR = meta.zellij_socket_dir;
138
+ try {
139
+ const result = spawnSync('zellij', ['attach', sessionName], {
140
+ cwd: opts.cwd || process.cwd(),
141
+ env,
142
+ stdio: 'inherit'
143
+ });
144
+ if (result.error)
145
+ return { ok: false, status: null, signal: null, error: result.error.message };
146
+ return { ok: result.status === 0, status: result.status ?? null, signal: result.signal ?? null };
147
+ }
148
+ catch (err) {
149
+ return { ok: false, status: null, signal: null, error: err?.message || String(err) };
150
+ }
151
+ }
119
152
  export function sanitizeZellijSessionName(value) {
120
153
  const cleaned = String(value || 'sks-session').replace(/[^A-Za-z0-9_.:-]+/g, '-').replace(/^-+|-+$/g, '');
121
154
  if (!cleaned)
@@ -113,10 +113,16 @@ const tasks = [
113
113
  task('core-skill:no-inference-optimizer', 'npm run core-skill:no-inference-optimizer --silent', { dependencies: ['build'] }),
114
114
  task('core-skill:route-runtime-integration', 'npm run core-skill:route-runtime-integration --silent', { dependencies: ['build'] }),
115
115
  task('core-skill:promotion-side-effect-ledger', 'npm run core-skill:promotion-side-effect-ledger --silent', { dependencies: ['build'] }),
116
+ task('core-skill:legacy-promotion-api-audit', 'npm run core-skill:legacy-promotion-api-audit --silent', { dependencies: ['build'] }),
116
117
  task('safety:side-effect-zero', 'npm run safety:side-effect-zero --silent', { dependencies: ['build'] }),
117
118
  task('safety:mutation-callsite-coverage', 'npm run safety:mutation-callsite-coverage --silent', { dependencies: ['build'] }),
119
+ task('safety:mutation-callsite-coverage:repo-wide', 'npm run safety:mutation-callsite-coverage:repo-wide --silent', { dependencies: ['build'] }),
120
+ task('side-effect:runtime-report', 'npm run side-effect:runtime-report --silent', { dependencies: ['build'] }),
118
121
  task('zellij:doctor-readiness', 'npm run zellij:doctor-readiness --silent', { dependencies: ['build'] }),
122
+ task('release:version-truth', 'npm run release:version-truth --silent', { dependencies: ['build'] }),
119
123
  task('release:gate-planner', 'npm run release:gate-planner --silent', { dependencies: ['build'] }),
124
+ task('release:dynamic-performance', 'npm run release:dynamic-performance --silent', { dependencies: ['release:gate-planner'] }),
125
+ task('release:provenance', 'npm run release:provenance --silent', { dependencies: ['build', 'release:version-truth'] }),
120
126
  task('release:gate-budget', 'npm run release:gate-budget --silent', { dependencies: ['build'] }),
121
127
  task('agent:wiki-context-proof', 'npm run agent:wiki-context-proof --silent', { dependencies: ['build'] }),
122
128
  task('shared-memory:check', 'npm run shared-memory:check --silent', { dependencies: ['build'] }),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "1.20.3",
4
+ "version": "1.20.5",
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",
@@ -81,11 +81,16 @@
81
81
  "core-skill:no-inference-optimizer": "node ./scripts/core-skill-no-inference-optimizer-check.mjs",
82
82
  "core-skill:route-runtime-integration": "node ./scripts/core-skill-route-runtime-integration-check.mjs",
83
83
  "core-skill:promotion-side-effect-ledger": "node ./scripts/core-skill-promotion-side-effect-ledger-check.mjs",
84
+ "core-skill:legacy-promotion-api-audit": "node ./scripts/core-skill-legacy-promotion-api-audit.mjs",
84
85
  "safety:side-effect-zero": "node ./scripts/side-effect-zero-gate-check.mjs",
85
86
  "safety:mutation-callsite-coverage": "node ./scripts/mutation-callsite-coverage-check.mjs",
87
+ "safety:mutation-callsite-coverage:repo-wide": "node ./scripts/mutation-callsite-coverage-check.mjs --repo-wide",
88
+ "side-effect:runtime-report": "node ./scripts/side-effect-runtime-report-check.mjs",
86
89
  "release:gate-planner": "node ./scripts/release-gate-planner.mjs",
87
90
  "release:check:dynamic": "node ./scripts/release-check-dynamic.mjs",
88
91
  "release:check:dynamic:execute": "node ./scripts/release-check-dynamic-execute.mjs",
92
+ "release:dynamic-performance": "node ./scripts/release-dynamic-performance-check.mjs",
93
+ "release:provenance": "node ./scripts/release-provenance-check.mjs",
89
94
  "release:gate-budget": "node ./scripts/release-gate-budget-check.mjs",
90
95
  "agent:wiki-context-proof": "node ./scripts/agent-wiki-context-proof-check.mjs",
91
96
  "prepublish:fast-check": "node ./scripts/prepublish-fast-check.mjs",
@@ -232,18 +237,19 @@
232
237
  "computer-use:live-optional": "node ./scripts/computer-use-live-optional-check.mjs",
233
238
  "computer-use:live-evidence": "node ./scripts/computer-use-live-evidence-check.mjs",
234
239
  "docs:truthfulness": "node ./scripts/docs-truthfulness-check.mjs",
235
- "release:metadata": "node ./scripts/release-metadata-1-19-check.mjs",
240
+ "release:metadata": "node ./scripts/release-metadata-check.mjs",
241
+ "release:version-truth": "node ./scripts/release-version-truth-check.mjs",
236
242
  "release:gate-existence-audit": "node ./scripts/release-gate-existence-audit.mjs",
237
243
  "release:native-agent-backend": "node ./scripts/release-native-agent-fixture-check.mjs",
238
244
  "release:readiness": "node ./scripts/release-readiness-report.mjs",
239
245
  "coverage": "node --experimental-test-coverage --test \"test/**/*.test.mjs\"",
240
- "release:check": "npm run release:check:parallel && npm run codex:0.135-compat && npm run doctor:codex-doctor-parity && npm run codex:permission-profiles && npm run codex:legacy-profile-consumers-removed && npm run terminal:keyboard-enhancement-safety && npm run terminal:tui-output-stability && npm run codex:resume-cwd-truth && npm run mcp:tool-naming-parity && npm run responses:retry-policy-centralized && npm run runtime:no-tmux && npm run zellij:layout-valid && npm run zellij:lane-renderer && npm run mad-sks:zellij-launch && npm run agent:zellij-runtime && npm run codex:config-eperm-fixture && npm run doctor:fix-proves-codex-read && npm run mad:preflight-blocks-unreadable-config && npm run fast:codex-service-tier-proof && npm run codex:project-config-policy-splitter && npm run test:no-orphan-dist-imports && npm run agent:patch-envelope-extraction && npm run agent:patch-queue-runtime && npm run agent:strategy-to-lease-wiring && npm run agent:patch-swarm-runtime && npm run agent:patch-transaction-journal && npm run agent:patch-conflict-rebase && npm run agent:strategy-to-patch-strict && npm run agent:patch-swarm-runtime-truth && npm run agent:rollback-command && npm run agent:patch-verification-dag && npm run agent:patch-rollback-dag && npm run agent:patch-proof-runtime && npm run agent:patch-swarm-route-blackbox && npm run team:patch-swarm-route-blackbox && npm run dfix:patch-swarm-route-blackbox && npm run appshots:thread-attachment-discovery && npm run mcp:readonly-runtime-scheduler && npm run codex:0.134-runner-truth && npm run agent:native-cli-session-swarm && npm run agent:native-cli-session-swarm-10 && npm run agent:native-cli-session-swarm-20 && npm run agent:no-subagent-scaling && npm run agent:native-cli-session-proof && npm run agent:worker-backend-router && npm run agent:codex-child-overlap && npm run agent:model-authored-patch-envelope && npm run agent:fast-mode-default && npm run agent:fast-mode-worker-propagation && npm run codex:fast-mode-profile-propagation && npm run mad-sks:fast-mode-propagation && npm run zellij:launch-command-truth && npm run zellij:real-session-heartbeat && npm run zellij:ui-design && npm run zellij:doctor-readiness && npm run legacy:upgrade-zero-break && npm run publish:packlist-performance && npm run postinstall:safe-side-effects && npm run runtime:ts-rust-boundary && npm run core-skill:card-schema && npm run core-skill:rollout-scoring && npm run core-skill:patch && npm run core-skill:heldout-validation && npm run core-skill:deployment-snapshot && npm run core-skill:no-inference-optimizer && npm run core-skill:route-runtime-integration && npm run core-skill:promotion-side-effect-ledger && npm run safety:side-effect-zero && npm run safety:mutation-callsite-coverage && npm run release:gate-planner && npm run release:gate-budget && npm run agent:wiki-context-proof && npm run shared-memory:check && npm run wrongness:check && npm run wrongness:fixtures && npm run trust:check && npm run git-collaboration:e2e && node ./scripts/release-check-stamp.mjs write && npm run release:readiness --silent && node ./scripts/release-check-stamp.mjs write",
246
+ "release:check": "npm run release:check:parallel && npm run release:version-truth && npm run codex:0.135-compat && npm run doctor:codex-doctor-parity && npm run codex:permission-profiles && npm run codex:legacy-profile-consumers-removed && npm run terminal:keyboard-enhancement-safety && npm run terminal:tui-output-stability && npm run codex:resume-cwd-truth && npm run mcp:tool-naming-parity && npm run responses:retry-policy-centralized && npm run runtime:no-tmux && npm run zellij:layout-valid && npm run zellij:lane-renderer && npm run mad-sks:zellij-launch && npm run agent:zellij-runtime && npm run codex:config-eperm-fixture && npm run doctor:fix-proves-codex-read && npm run mad:preflight-blocks-unreadable-config && npm run fast:codex-service-tier-proof && npm run codex:project-config-policy-splitter && npm run test:no-orphan-dist-imports && npm run agent:patch-envelope-extraction && npm run agent:patch-queue-runtime && npm run agent:strategy-to-lease-wiring && npm run agent:patch-swarm-runtime && npm run agent:patch-transaction-journal && npm run agent:patch-conflict-rebase && npm run agent:strategy-to-patch-strict && npm run agent:patch-swarm-runtime-truth && npm run agent:rollback-command && npm run agent:patch-verification-dag && npm run agent:patch-rollback-dag && npm run agent:patch-proof-runtime && npm run agent:patch-swarm-route-blackbox && npm run team:patch-swarm-route-blackbox && npm run dfix:patch-swarm-route-blackbox && npm run appshots:thread-attachment-discovery && npm run mcp:readonly-runtime-scheduler && npm run codex:0.134-runner-truth && npm run agent:native-cli-session-swarm && npm run agent:native-cli-session-swarm-10 && npm run agent:native-cli-session-swarm-20 && npm run agent:no-subagent-scaling && npm run agent:native-cli-session-proof && npm run agent:worker-backend-router && npm run agent:codex-child-overlap && npm run agent:model-authored-patch-envelope && npm run agent:fast-mode-default && npm run agent:fast-mode-worker-propagation && npm run codex:fast-mode-profile-propagation && npm run mad-sks:fast-mode-propagation && npm run zellij:launch-command-truth && npm run zellij:real-session-heartbeat && npm run zellij:ui-design && npm run zellij:doctor-readiness && npm run legacy:upgrade-zero-break && npm run publish:packlist-performance && npm run postinstall:safe-side-effects && npm run runtime:ts-rust-boundary && npm run core-skill:card-schema && npm run core-skill:rollout-scoring && npm run core-skill:patch && npm run core-skill:heldout-validation && npm run core-skill:deployment-snapshot && npm run core-skill:no-inference-optimizer && npm run core-skill:route-runtime-integration && npm run core-skill:promotion-side-effect-ledger && npm run core-skill:legacy-promotion-api-audit && npm run safety:side-effect-zero && npm run safety:mutation-callsite-coverage && npm run safety:mutation-callsite-coverage:repo-wide && npm run side-effect:runtime-report && npm run release:gate-planner && npm run release:dynamic-performance && npm run release:provenance && npm run release:gate-budget && npm run agent:wiki-context-proof && npm run shared-memory:check && npm run wrongness:check && npm run wrongness:fixtures && npm run trust:check && npm run git-collaboration:e2e && node ./scripts/release-check-stamp.mjs write && npm run release:readiness --silent && node ./scripts/release-check-stamp.mjs write",
241
247
  "release:real-check": "node ./scripts/release-real-check.mjs",
242
248
  "release:publish": "npm run publish:npm",
243
- "publish:dry": "npm run release:metadata && node ./scripts/release-check-stamp.mjs ensure && npm run release:dist-freshness && npm --cache /tmp/sks-npm-cache publish --dry-run --registry https://registry.npmjs.org/ --access public",
249
+ "publish:dry": "npm run release:metadata && npm run release:version-truth && npm run publish:packlist-performance && npm run prepublish:fast-check && node ./scripts/release-check-stamp.mjs verify && npm run release:provenance -- --publish && npm run release:dist-freshness && npm --cache /tmp/sks-npm-cache publish --dry-run --registry https://registry.npmjs.org/ --access public",
244
250
  "publish:npm": "npm --cache /tmp/sks-npm-cache publish --registry https://registry.npmjs.org/ --access public",
245
251
  "prepack": "npm run build",
246
- "prepublishOnly": "npm run release:metadata && npm run release:dist-freshness && npm run publish:packlist-performance && node ./scripts/check-publish-tag.mjs && node ./scripts/release-check-stamp.mjs ensure && node ./scripts/release-registry-check.mjs --require-unpublished",
252
+ "prepublishOnly": "npm run release:metadata && npm run release:version-truth && npm run release:dist-freshness && npm run publish:packlist-performance && npm run prepublish:fast-check && node ./scripts/check-publish-tag.mjs && node ./scripts/release-check-stamp.mjs verify && npm run release:provenance -- --publish && node ./scripts/release-registry-check.mjs --require-unpublished",
247
253
  "dist:check": "node ./scripts/check-dist-runtime.mjs",
248
254
  "ux-review:run-wires-imagegen": "node ./scripts/ux-review-run-wires-imagegen-check.mjs",
249
255
  "ux-review:extract-wires-real-extractor": "node ./scripts/ux-review-extract-wires-real-extractor-check.mjs",