sneakoscope 0.7.53 → 0.7.54

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
@@ -211,7 +211,7 @@ sks --mad
211
211
  sks --mad --yes
212
212
  ```
213
213
 
214
- This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches Codex with `--sandbox danger-full-access --ask-for-approval never` and attaches to the session in an interactive terminal. If codex-lb is configured and no explicit `--workspace`/`--session` was passed, SKS opens a fresh tmux session so the repaired key is loaded by the Codex process immediately. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, and needed migrations are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active. Repeat launches reuse the same named SKS MAD tmux session unless auth repair requires a fresh codex-lb session.
214
+ This syncs existing codex-lb/Codex CLI auth before launch, creates/uses the `sks-mad-high` Codex profile for a one-shot full-access, high-reasoning tmux session with `sandbox_mode = "danger-full-access"` and `approval_policy = "never"`, opens an active MAD-SKS permission gate for that tmux run, then launches Codex with `--sandbox danger-full-access --ask-for-approval never` and attaches to the session in an interactive terminal. If codex-lb is configured and no explicit `--workspace`/`--session` was passed, SKS opens a fresh tmux session so the repaired key is loaded by the Codex process immediately. While the gate is active, live server work, Supabase MCP database writes, direct SQL, targeted DML, schema cleanup, Supabase MCP `apply_migration`, and required Supabase CLI migration application such as `supabase migration up` or `supabase db push` are allowed. Catastrophic database wipe/all-row/project-management safeguards remain active. Repeat launches reuse the same named SKS MAD tmux session unless auth repair requires a fresh codex-lb session.
215
215
 
216
216
  MAD does not disable the pipeline contract: stages, executors, reviewers, and auto-review policy still must not invent unrequested fallback implementation code. If the requested path cannot be implemented, SKS should block with evidence rather than add substitute behavior.
217
217
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "0.7.53",
4
+ "version": "0.7.54",
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",
package/src/cli/main.mjs CHANGED
@@ -3497,6 +3497,12 @@ async function selftest() {
3497
3497
  const supabaseWritePayloadClass = classifyToolPayload({ tool_name: 'mcp__supabase__execute_sql', sql: "update users set name = 'x' where id = '1';" });
3498
3498
  if (supabaseWritePayloadClass.level !== 'write' || !supabaseWritePayloadClass.toolReasons.includes('database_tool')) throw new Error('selftest failed: Supabase execute_sql write classification was weakened');
3499
3499
  if (classifyCommand('supabase db reset').level !== 'destructive') throw new Error('selftest failed: supabase db reset not detected');
3500
+ const supabaseMigrationApplyClass = classifyCommand('supabase migration up --linked');
3501
+ if (supabaseMigrationApplyClass.level !== 'write' || !supabaseMigrationApplyClass.reasons.includes('supabase_migration_apply')) throw new Error('selftest failed: supabase migration apply was not classified as DB write');
3502
+ const supabaseDbPushClass = classifyCommand('supabase db push');
3503
+ if (supabaseDbPushClass.level !== 'write' || !supabaseDbPushClass.reasons.includes('supabase_db_push')) throw new Error('selftest failed: supabase db push was not classified as migration apply work');
3504
+ const supabaseApplyMigrationToolClass = classifyToolPayload({ tool_name: 'mcp__supabase__apply_migration', name: 'add_selftest_table' });
3505
+ if (supabaseApplyMigrationToolClass.level !== 'write' || !supabaseApplyMigrationToolClass.toolReasons.includes('migration_apply_tool')) throw new Error('selftest failed: Supabase apply_migration tool was not classified as DB write');
3500
3506
  const dbDecision = await checkDbOperation(tmp, { mission_id: id }, { tool_name: 'mcp__supabase__execute_sql', sql: 'drop table users;' }, { duringNoQuestion: true });
3501
3507
  if (dbDecision.action !== 'block') throw new Error('selftest failed: destructive MCP SQL allowed');
3502
3508
  const computerUseDecision = await checkDbOperation(tmp, { mission_id: id }, { tool_name: 'mcp__computer_use__open_app', bundle_id: 'com.microsoft.edgemac', action: 'open_app' }, { duringNoQuestion: true });
@@ -3509,6 +3515,17 @@ async function selftest() {
3509
3515
  if (madColumnCleanupDecision.action !== 'allow' || !madColumnCleanupDecision.mad_sks?.permission_profile?.allowed?.includes('direct_execute_sql_writes')) throw new Error('selftest failed: MAD-SKS column cleanup was not allowed through the modular permission gate');
3510
3516
  const madLiveDmlDecision = await checkDbOperation(tmp, madState, { tool_name: 'mcp__supabase__execute_sql', sql: "update users set name = 'fixed' where id = 'selftest';" }, { duringNoQuestion: false });
3511
3517
  if (madLiveDmlDecision.action !== 'allow' || !madLiveDmlDecision.mad_sks?.live_server_writes_allowed) throw new Error('selftest failed: MAD-SKS targeted live DML was not allowed');
3518
+ const madMigrationUpDecision = await checkDbOperation(tmp, madState, { command: 'supabase migration up --linked' }, { duringNoQuestion: true });
3519
+ if (madMigrationUpDecision.action !== 'allow' || !madMigrationUpDecision.mad_sks?.permission_profile?.allowed?.includes('migration_apply_when_required')) throw new Error('selftest failed: MAD-SKS did not allow Supabase migration up during no-question execution');
3520
+ const madDbPushDecision = await checkDbOperation(tmp, madState, { command: 'supabase db push' }, { duringNoQuestion: true });
3521
+ if (madDbPushDecision.action !== 'allow') throw new Error('selftest failed: MAD-SKS did not allow Supabase db push migration application');
3522
+ const madApplyMigrationDecision = await checkDbOperation(tmp, madState, { tool_name: 'mcp__supabase__apply_migration', name: 'add_selftest_table' }, { duringNoQuestion: true });
3523
+ if (madApplyMigrationDecision.action !== 'allow') throw new Error('selftest failed: MAD-SKS did not allow Supabase MCP apply_migration');
3524
+ const madTmuxMission = await createMission(tmp, { mode: 'mad-sks', prompt: 'sks --mad migration selftest' });
3525
+ await writeJsonAtomic(path.join(madTmuxMission.dir, 'mad-sks-gate.json'), { schema_version: 1, passed: false, mad_sks_permission_active: true, migration_apply_allowed: true });
3526
+ const madTmuxState = { mission_id: madTmuxMission.id, mode: 'MADSKS', route_command: '$MAD-SKS', stop_gate: 'mad-sks-gate.json', mad_sks_active: true, mad_sks_modifier: true, mad_sks_gate_file: 'mad-sks-gate.json', migration_apply_allowed: true };
3527
+ const madTmuxMigrationDecision = await checkDbOperation(tmp, madTmuxState, { command: 'supabase migration up --linked' }, { duringNoQuestion: true });
3528
+ if (madTmuxMigrationDecision.action !== 'allow') throw new Error('selftest failed: sks --mad state did not allow Supabase migration application');
3512
3529
  const tableRemovalSql = 'dr' + 'op table users;';
3513
3530
  const madTableRemovalDecision = await checkDbOperation(tmp, madState, { tool_name: 'mcp__supabase__execute_sql', sql: tableRemovalSql }, { duringNoQuestion: false });
3514
3531
  if (madTableRemovalDecision.action !== 'block') throw new Error('selftest failed: MAD-SKS catastrophic table removal was not blocked');
@@ -137,9 +137,14 @@ export function classifyCommand(command = '') {
137
137
  const low = c.toLowerCase();
138
138
  const reasons = [];
139
139
  if (!low.trim()) return { level: 'none', kind: 'none', reasons: [], command: c };
140
+ const supabaseMigrationApply = [];
141
+ if (/\bsupabase\s+migration\s+up\b/.test(low)) supabaseMigrationApply.push('supabase_migration_up', 'supabase_migration_apply');
142
+ if (/\bsupabase\s+db\s+push\b/.test(low)) supabaseMigrationApply.push('supabase_db_push', 'supabase_migration_apply');
143
+ const supabaseMigrationRead = [];
144
+ if (/\bsupabase\s+db\s+(diff|pull)\b/.test(low)) supabaseMigrationRead.push('supabase_migration_schema_read');
145
+ if (/\bsupabase\s+migration\s+(list|new|squash)\b/.test(low)) supabaseMigrationRead.push('supabase_migration_file_work');
140
146
  const hard = [
141
147
  [/\bsupabase\s+db\s+reset\b/, 'supabase_db_reset'],
142
- [/\bsupabase\s+db\s+push\b/, 'supabase_db_push'],
143
148
  [/\bsupabase\s+migration\s+repair\b/, 'supabase_migration_repair'],
144
149
  [/\bprisma\s+migrate\s+reset\b/, 'prisma_migrate_reset'],
145
150
  [/\bprisma\s+db\s+push\b/, 'prisma_db_push'],
@@ -152,6 +157,25 @@ export function classifyCommand(command = '') {
152
157
  const maybeSql = extractSqlLiterals(c).join('\n');
153
158
  const sqlClass = maybeSql ? classifySql(maybeSql) : { level: 'none', reasons: [] };
154
159
  if (reasons.length) return { level: 'destructive', kind: 'db_command', reasons, sql: sqlClass, command: c };
160
+ if (supabaseMigrationApply.length) {
161
+ const level = sqlClass.level === 'destructive' ? 'destructive' : 'write';
162
+ return {
163
+ level,
164
+ kind: 'db_migration',
165
+ reasons: [...new Set([...supabaseMigrationApply, ...(sqlClass.reasons || [])])],
166
+ sql: sqlClass,
167
+ command: c
168
+ };
169
+ }
170
+ if (supabaseMigrationRead.length && !['write', 'destructive'].includes(sqlClass.level)) {
171
+ return {
172
+ level: 'safe',
173
+ kind: 'db_migration',
174
+ reasons: [...new Set([...supabaseMigrationRead, ...(sqlClass.reasons || [])])],
175
+ sql: sqlClass,
176
+ command: c
177
+ };
178
+ }
155
179
  if (/\b(psql|supabase|prisma|drizzle-kit|knex|sequelize)\b/.test(low)) {
156
180
  if (sqlClass.level === 'destructive' || sqlClass.level === 'write') return { level: sqlClass.level, kind: 'db_command', reasons: sqlClass.reasons, sql: sqlClass, command: c };
157
181
  return { level: sqlClass.level === 'safe' ? 'safe' : 'possible_db', kind: 'db_command', reasons: sqlClass.reasons, sql: sqlClass, command: c };
@@ -199,10 +223,15 @@ export function classifyToolPayload(payload = {}) {
199
223
  const sqlClass = classifySql(combined);
200
224
  const commandClass = classifyCommand(strings.find((s) => /\b(supabase|psql|prisma|drizzle|knex|sequelize)\b/i.test(s)) || '');
201
225
  const toolReasons = [];
226
+ const reasons = [];
202
227
  if (/\b(apply_patch|edit|write|create|remove|rename|str_replace|file_write|fs_write)\b/i.test(toolName) && !/supabase|postgres|database|execute_sql|apply_migration|sql_query|db_|_db\b|migration/.test(toolName)) {
203
- return { level: 'none', toolName, toolReasons, sql: sqlClass, command: commandClass, stringsExamined: strings.length };
228
+ return { level: 'none', toolName, toolReasons, reasons, sql: sqlClass, command: commandClass, stringsExamined: strings.length };
204
229
  }
205
230
  if (/supabase|postgres|database|execute_sql|apply_migration|sql_query|db_|_db\b|migration/.test(toolName)) toolReasons.push('database_tool');
231
+ if (/apply_migration|migration_apply/i.test(toolName)) {
232
+ toolReasons.push('migration_apply_tool');
233
+ reasons.push('supabase_migration_apply');
234
+ }
206
235
  if (/delete_project|pause_project|restore_project|delete_branch|reset_branch|merge_branch/.test(toolName)) toolReasons.push('dangerous_supabase_management_tool');
207
236
  let level = 'none';
208
237
  for (const candidate of [sqlClass.level, commandClass.level]) {
@@ -211,8 +240,9 @@ export function classifyToolPayload(payload = {}) {
211
240
  else if ((candidate === 'safe' || candidate === 'possible_db') && level === 'none') level = candidate;
212
241
  }
213
242
  if (toolReasons.includes('dangerous_supabase_management_tool')) level = 'destructive';
243
+ if (toolReasons.includes('migration_apply_tool') && level !== 'destructive') level = 'write';
214
244
  if (toolReasons.includes('database_tool') && level === 'none') level = 'possible_db';
215
- return { level, toolName, toolReasons, sql: sqlClass, command: commandClass, stringsExamined: strings.length };
245
+ return { level, toolName, toolReasons, reasons, sql: sqlClass, command: commandClass, stringsExamined: strings.length };
216
246
  }
217
247
 
218
248
  function contractAllowsDbWrite(contract = {}) {
@@ -290,6 +320,12 @@ export function evaluateDbSafety({ classification, policy = DEFAULT_DB_SAFETY_PO
290
320
  }
291
321
  if (cls.level === 'destructive') reasons.push('destructive_database_operation_blocked_always');
292
322
  if (cls.level === 'write') {
323
+ const reasonSet = new Set([
324
+ ...(cls.reasons || []),
325
+ ...(cls.sql?.reasons || []),
326
+ ...(cls.command?.reasons || [])
327
+ ]);
328
+ if (reasonSet.has('supabase_db_push')) reasons.push('supabase_db_push_requires_active_mad_sks');
293
329
  if (effective.mode === 'read_only_only') reasons.push('database_write_mode_is_read_only_only');
294
330
  if (effective.env === 'production' || effective.env === 'production_read_only') reasons.push('production_database_writes_forbidden');
295
331
  if (!['local_dev', 'preview_branch', 'supabase_branch'].includes(effective.env)) reasons.push('database_write_target_not_local_or_branch');
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.7.53';
8
+ export const PACKAGE_VERSION = '0.7.54';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11