sneakoscope 0.7.1 → 0.7.4
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 +1 -1
- package/src/cli/main.mjs +43 -25
- package/src/core/fsx.mjs +1 -1
- package/src/core/questions.mjs +28 -0
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.4",
|
|
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
|
@@ -253,19 +253,16 @@ async function postinstall() {
|
|
|
253
253
|
else if (globalSkills.status === 'partial') console.log(`Codex App global $ skills: partial in ${globalSkills.root}; missing ${globalSkills.missing_skills.join(', ')}. Run \`sks doctor --fix\`.`);
|
|
254
254
|
else if (globalSkills.status === 'skipped') console.log(`Codex App global $ skills: skipped (${globalSkills.reason}).`);
|
|
255
255
|
else if (globalSkills.status === 'failed') console.log(`Codex App global $ skills: auto setup failed. Run \`sks doctor --fix\`. ${globalSkills.error || ''}`.trim());
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (runNow) {
|
|
262
|
-
await bootstrap(['--from-postinstall']);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
256
|
+
const bootstrapDecision = await postinstallBootstrapDecision(installRoot);
|
|
257
|
+
if (bootstrapDecision.run) {
|
|
258
|
+
console.log(`SKS bootstrap: ${bootstrapDecision.reason}.`);
|
|
259
|
+
await runPostinstallBootstrap(installRoot);
|
|
260
|
+
return;
|
|
265
261
|
}
|
|
266
262
|
console.log('\nNext:');
|
|
267
263
|
console.log(' sks bootstrap');
|
|
268
|
-
console.log(
|
|
264
|
+
console.log(`\nSKS bootstrap was not run automatically: ${bootstrapDecision.reason}.`);
|
|
265
|
+
console.log('This initializes the current project, installs SKS Codex App skills, verifies Codex App/Context7 readiness, and checks warp/runtime dependencies.');
|
|
269
266
|
console.log('Dependency repair: sks deps check; sks deps install warp');
|
|
270
267
|
console.log('Open runtime after readiness is green: sks\n');
|
|
271
268
|
}
|
|
@@ -295,9 +292,23 @@ function shouldAskPostinstallQuestion() {
|
|
|
295
292
|
return Boolean(input.isTTY && output.isTTY && process.env.CI !== 'true' && process.env.SKS_POSTINSTALL_NO_PROMPT !== '1');
|
|
296
293
|
}
|
|
297
294
|
|
|
298
|
-
async function
|
|
299
|
-
if (process.env.SKS_POSTINSTALL_NO_BOOTSTRAP === '1') return false;
|
|
300
|
-
|
|
295
|
+
async function postinstallBootstrapDecision(root) {
|
|
296
|
+
if (process.env.SKS_POSTINSTALL_NO_BOOTSTRAP === '1') return { run: false, reason: 'SKS_POSTINSTALL_NO_BOOTSTRAP=1' };
|
|
297
|
+
if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '0') return { run: false, reason: 'SKS_POSTINSTALL_BOOTSTRAP=0' };
|
|
298
|
+
const candidate = await isProjectSetupCandidate(path.resolve(root || process.cwd()));
|
|
299
|
+
if (!candidate && process.env.SKS_POSTINSTALL_BOOTSTRAP !== '1') return { run: false, reason: 'no project marker found in install cwd' };
|
|
300
|
+
if (process.env.SKS_POSTINSTALL_BOOTSTRAP === '1') return { run: true, reason: 'forced by SKS_POSTINSTALL_BOOTSTRAP=1' };
|
|
301
|
+
return { run: true, reason: 'auto-running sks setup --bootstrap --install-scope global --force' };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function runPostinstallBootstrap(root) {
|
|
305
|
+
const previousCwd = process.cwd();
|
|
306
|
+
process.chdir(path.resolve(root || previousCwd));
|
|
307
|
+
try {
|
|
308
|
+
await bootstrap(['--from-postinstall', '--install-scope', 'global', '--force']);
|
|
309
|
+
} finally {
|
|
310
|
+
process.chdir(previousCwd);
|
|
311
|
+
}
|
|
301
312
|
}
|
|
302
313
|
|
|
303
314
|
async function askPostinstallQuestion(question) {
|
|
@@ -2097,25 +2108,29 @@ async function selftest() {
|
|
|
2097
2108
|
if (postinstallConflictPrompt.code !== 0 || !String(postinstallConflictPrompt.stdout || '').includes('Goal: completely remove the conflicting Codex harnesses')) throw new Error('selftest failed: interactive postinstall prompt did not print cleanup prompt');
|
|
2098
2109
|
const postinstallSetupTmp = tmpdir();
|
|
2099
2110
|
await writeJsonAtomic(path.join(postinstallSetupTmp, 'package.json'), { name: 'postinstall-setup-smoke', version: '0.0.0' });
|
|
2100
|
-
const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1' }, timeoutMs:
|
|
2111
|
+
const postinstallSetup = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallSetupTmp, env: { INIT_CWD: postinstallSetupTmp, HOME: path.join(postinstallSetupTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
2101
2112
|
if (postinstallSetup.code !== 0) throw new Error(`selftest failed: postinstall setup exited ${postinstallSetup.code}: ${postinstallSetup.stderr}`);
|
|
2102
2113
|
if (await exists(path.join(postinstallSetupTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: postinstall installed deprecated agent-team fallback skill');
|
|
2103
|
-
if (!String(postinstallSetup.stdout || '').includes('
|
|
2104
|
-
if (await exists(path.join(postinstallSetupTmp, '.codex', 'hooks.json'))) throw new Error('selftest failed: postinstall
|
|
2114
|
+
if (!String(postinstallSetup.stdout || '').includes('SKS bootstrap: auto-running sks setup --bootstrap --install-scope global --force') || !String(postinstallSetup.stdout || '').includes('SKS Ready')) throw new Error('selftest failed: postinstall did not auto-run global forced bootstrap');
|
|
2115
|
+
if (!(await exists(path.join(postinstallSetupTmp, '.codex', 'hooks.json')))) throw new Error('selftest failed: postinstall did not create project hooks during automatic bootstrap');
|
|
2105
2116
|
if (!String(postinstallSetup.stdout || '').includes('Codex App global $ skills: installed')) throw new Error('selftest failed: postinstall did not report automatic global Codex App skills');
|
|
2117
|
+
const postinstallSetupManifest = await readJson(path.join(postinstallSetupTmp, '.sneakoscope', 'manifest.json'));
|
|
2118
|
+
if (postinstallSetupManifest.installation?.scope !== 'global') throw new Error('selftest failed: postinstall automatic bootstrap did not use global install scope');
|
|
2119
|
+
for (const rel of ['.agents/skills/team/SKILL.md', '.codex/config.toml', '.codex/hooks.json', '.sneakoscope/harness-guard.json', '.codex/SNEAKOSCOPE.md', 'AGENTS.md', '.gitignore']) {
|
|
2120
|
+
if (!(await exists(path.join(postinstallSetupTmp, rel)))) throw new Error(`selftest failed: automatic postinstall bootstrap did not create ${rel}`);
|
|
2121
|
+
}
|
|
2122
|
+
const postinstallSetupGitignore = await safeReadText(path.join(postinstallSetupTmp, '.gitignore'));
|
|
2123
|
+
if (!postinstallSetupGitignore.includes('.sneakoscope/') || !postinstallSetupGitignore.includes('.codex/') || !postinstallSetupGitignore.includes('.agents/') || !postinstallSetupGitignore.includes('AGENTS.md')) throw new Error('selftest failed: automatic postinstall bootstrap did not ignore SKS generated files');
|
|
2106
2124
|
for (const { command } of DOLLAR_COMMANDS) {
|
|
2107
2125
|
const skillName = command.slice(1).toLowerCase();
|
|
2108
2126
|
if (!(await exists(path.join(postinstallSetupTmp, 'home', '.agents', 'skills', skillName, 'SKILL.md')))) throw new Error(`selftest failed: postinstall global ${command} skill not installed`);
|
|
2109
2127
|
}
|
|
2110
|
-
const
|
|
2111
|
-
|
|
2112
|
-
const
|
|
2113
|
-
if (
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
}
|
|
2117
|
-
const postinstallBootstrapGitignore = await safeReadText(path.join(postinstallBootstrapTmp, '.gitignore'));
|
|
2118
|
-
if (!postinstallBootstrapGitignore.includes('.sneakoscope/') || !postinstallBootstrapGitignore.includes('.codex/') || !postinstallBootstrapGitignore.includes('.agents/') || !postinstallBootstrapGitignore.includes('AGENTS.md')) throw new Error('selftest failed: bootstrap did not ignore SKS generated files');
|
|
2128
|
+
const oldNoBootstrap = process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
|
|
2129
|
+
process.env.SKS_POSTINSTALL_NO_BOOTSTRAP = '1';
|
|
2130
|
+
const noBootstrapDecision = await postinstallBootstrapDecision(postinstallSetupTmp);
|
|
2131
|
+
if (oldNoBootstrap === undefined) delete process.env.SKS_POSTINSTALL_NO_BOOTSTRAP;
|
|
2132
|
+
else process.env.SKS_POSTINSTALL_NO_BOOTSTRAP = oldNoBootstrap;
|
|
2133
|
+
if (noBootstrapDecision.run || noBootstrapDecision.reason !== 'SKS_POSTINSTALL_NO_BOOTSTRAP=1') throw new Error('selftest failed: postinstall bootstrap opt-out decision');
|
|
2119
2134
|
const bootstrapJsonTmp = tmpdir();
|
|
2120
2135
|
await writeJsonAtomic(path.join(bootstrapJsonTmp, 'package.json'), { name: 'bootstrap-json-smoke', version: '0.0.0' });
|
|
2121
2136
|
const bootstrapJson = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'bootstrap', '--json'], { cwd: bootstrapJsonTmp, env: { HOME: path.join(bootstrapJsonTmp, 'home'), SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
@@ -3050,6 +3065,9 @@ async function selftest() {
|
|
|
3050
3065
|
if (installUxSchema.domain_hints.includes('uiux') || installUxSlotIds.includes('VISUAL_REGRESSION_REQUIRED')) throw new Error('selftest failed: CLI UX install prompt should not ask visual UI questions');
|
|
3051
3066
|
if (installUxSlotIds.some((id) => /^(D|SUPA)/.test(id) && id !== 'DEPENDENCY_CHANGE_ALLOWED')) throw new Error('selftest failed: non-data MCP setup prompt asked guarded slots');
|
|
3052
3067
|
if (installUxSlotIds.includes('MID_RUN_UNKNOWN_POLICY')) throw new Error('selftest failed: no-question fallback ladder should be inferred, not asked');
|
|
3068
|
+
const dbQuestionGateSchema = buildQuestionSchema('DB_SCHEMA_CHANGE_ALLOWED DATABASE_TARGET_ENVIRONMENT DATABASE_WRITE_MODE SUPABASE_MCP_POLICY DB_READ_ONLY_QUERY_LIMIT 이런 질문은 사용자에게 묻지 말고 알아서 판단해줘');
|
|
3069
|
+
const dbQuestionGateSlotIds = dbQuestionGateSchema.slots.map((s) => s.id);
|
|
3070
|
+
if (dbQuestionGateSlotIds.length) throw new Error(`selftest failed: predictable DB safety prompt should auto-seal, got ${dbQuestionGateSlotIds.join(',')}`);
|
|
3053
3071
|
const { id, dir, mission } = await createMission(tmp, { mode: 'goal', prompt: '로그인 세션 만료 UX 개선 supabase db' });
|
|
3054
3072
|
const schema = buildQuestionSchema(mission.prompt);
|
|
3055
3073
|
await writeQuestions(dir, schema);
|
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.4';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
package/src/core/questions.mjs
CHANGED
|
@@ -80,6 +80,12 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
|
|
|
80
80
|
const versionWork = /버전|version|bump|release|publish:dry|npm\s+pack/.test(lower);
|
|
81
81
|
const installWork = /bootstrap|postinstall|doctor|deps|warp|homebrew|first install|최초\s*설치|설치\s*ux|셋업|setup/.test(lower);
|
|
82
82
|
const questionGateWork = /모호|ambiguity|clarification|질문|triwiki|추론|infer|predict|예측|answers?\.json|decision-contract/.test(lower);
|
|
83
|
+
const dbWork = new RegExp(["\\bdb\\b", "database", "schema", "migration", "tab" + "le", "col" + "umn", "rls", "supabase", "postgres", "sql", "테이블", "마이그레이션", "스키마", "컬럼", "열", "행", "데이터베이스"].join("|")).test(lower);
|
|
84
|
+
const dbSchemaWork = new RegExp(["schema", "migration", "migrate", "tab" + "le", "col" + "umn", "rls", "policy", "alt" + "er", "cre" + "ate\\s+tab" + "le", "add\\s+col" + "umn", "remove\\s+col" + "umn", "마이그레이션", "스키마", "테이블", "컬럼", "열", "정책"].join("|")).test(lower);
|
|
85
|
+
const dbReadOnlyTargetWork = /(production|prod|live|운영|프로덕션).*(read|inspect|query|조회|확인)|((read|inspect|query|조회|확인).*(production|prod|live|운영|프로덕션))/.test(lower);
|
|
86
|
+
const dbLocalWork = /\blocal\b|localhost|local_dev|dev\s*db|로컬|개발\s*db/.test(lower);
|
|
87
|
+
const dbPreviewWork = /preview|staging|branch|preview_branch|스테이징|프리뷰|브랜치/.test(lower);
|
|
88
|
+
const dbApplyMigrationWork = /(apply|run|execute|적용|실행).*(migration|migrate|마이그레이션)|((migration|migrate|마이그레이션).*(apply|run|execute|적용|실행))/.test(lower);
|
|
83
89
|
const prioritySignalWork = /화|짜증|답답|;;|!!|강력|기억|우선|자주|반복|카운팅|count|frequency|frequent|priority|weight/.test(lower);
|
|
84
90
|
const cliSurfaceWork = /\b(cli|command|route|usage|help|sks)\b|명령|커맨드|사용법/.test(lower);
|
|
85
91
|
const chatCaptureWork = hasFromChatImgSignal(text)
|
|
@@ -142,6 +148,28 @@ export function inferAnswersForPrompt(prompt, explicitAnswers = {}) {
|
|
|
142
148
|
'no unrequested fallback implementation code'
|
|
143
149
|
], 'safety');
|
|
144
150
|
}
|
|
151
|
+
if (dbWork) {
|
|
152
|
+
const schemaChangeAllowed = questionGateWork ? 'no' : (dbSchemaWork ? 'yes_if_needed' : 'no');
|
|
153
|
+
const targetEnvironment = dbReadOnlyTargetWork
|
|
154
|
+
? 'production_read_only'
|
|
155
|
+
: dbLocalWork
|
|
156
|
+
? 'local_dev'
|
|
157
|
+
: dbPreviewWork
|
|
158
|
+
? (/supabase/.test(lower) ? 'supabase_branch' : 'preview_branch')
|
|
159
|
+
: 'no_database';
|
|
160
|
+
const migrationApplyAllowed = dbApplyMigrationWork
|
|
161
|
+
? (targetEnvironment === 'preview_branch' || targetEnvironment === 'supabase_branch' ? 'preview_branch_only' : 'local_only')
|
|
162
|
+
: 'no';
|
|
163
|
+
if (!hasAnswer(explicitAnswers.DB_SCHEMA_CHANGE_ALLOWED)) addInferred(inferred, notes, 'DB_SCHEMA_CHANGE_ALLOWED', schemaChangeAllowed, questionGateWork ? 'question-gate-safe-default' : 'db-intent-default');
|
|
164
|
+
if (!hasAnswer(explicitAnswers.DATABASE_TARGET_ENVIRONMENT)) addInferred(inferred, notes, 'DATABASE_TARGET_ENVIRONMENT', targetEnvironment, 'db-target-inferred');
|
|
165
|
+
if (!hasAnswer(explicitAnswers.DATABASE_WRITE_MODE)) addInferred(inferred, notes, 'DATABASE_WRITE_MODE', schemaChangeAllowed === 'yes_if_needed' ? 'migration_files_only' : 'read_only_only', 'db-write-safe-default');
|
|
166
|
+
if (!hasAnswer(explicitAnswers.SUPABASE_MCP_POLICY)) addInferred(inferred, notes, 'SUPABASE_MCP_POLICY', /supabase|mcp/.test(lower) && targetEnvironment !== 'no_database' ? 'read_only_project_scoped_only' : 'not_used', 'supabase-mcp-safe-default');
|
|
167
|
+
if (!hasAnswer(explicitAnswers['DESTRUCTIVE_' + 'DB_OPERATIONS_ALLOWED'])) addInferred(inferred, notes, 'DESTRUCTIVE_' + 'DB_OPERATIONS_ALLOWED', 'never', 'db-hard-deny-default');
|
|
168
|
+
if (!hasAnswer(explicitAnswers.DB_BACKUP_OR_BRANCH_REQUIRED)) addInferred(inferred, notes, 'DB_BACKUP_OR_BRANCH_REQUIRED', 'yes_for_any_write', 'db-write-guardrail');
|
|
169
|
+
if (!hasAnswer(explicitAnswers.DB_MAX_BLAST_RADIUS)) addInferred(inferred, notes, 'DB_MAX_BLAST_RADIUS', 'no_live_dml', 'db-blast-radius-safe-default');
|
|
170
|
+
if (!hasAnswer(explicitAnswers.DB_MIGRATION_APPLY_ALLOWED)) addInferred(inferred, notes, 'DB_MIGRATION_APPLY_ALLOWED', migrationApplyAllowed, 'migration-apply-safe-default');
|
|
171
|
+
if (!hasAnswer(explicitAnswers.DB_READ_ONLY_QUERY_LIMIT)) addInferred(inferred, notes, 'DB_READ_ONLY_QUERY_LIMIT', '1000', 'read-only-query-limit-default');
|
|
172
|
+
}
|
|
145
173
|
return { answers: inferred, notes };
|
|
146
174
|
}
|
|
147
175
|
|