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 +1 -1
- package/package.json +1 -1
- package/src/cli/main.mjs +17 -0
- package/src/core/db-safety.mjs +39 -3
- package/src/core/fsx.mjs +1 -1
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
|
|
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.
|
|
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');
|
package/src/core/db-safety.mjs
CHANGED
|
@@ -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.
|
|
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
|
|