sneakoscope 0.7.78 → 0.8.2
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 +28 -3
- package/package.json +1 -1
- package/src/cli/install-helpers.mjs +38 -9
- package/src/cli/main.mjs +65 -23
- package/src/cli/maintenance-commands.mjs +98 -6
- package/src/cli/recallpulse-command.mjs +157 -0
- package/src/core/codex-app.mjs +181 -11
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +95 -1
- package/src/core/init.mjs +43 -8
- package/src/core/pipeline.mjs +3 -3
- package/src/core/recallpulse.mjs +1215 -0
- package/src/core/research.mjs +119 -60
- package/src/core/routes.mjs +3 -2
|
@@ -8,6 +8,7 @@ import { activeRouteContext, evaluateStop, prepareRoute, promptPipelineContext a
|
|
|
8
8
|
import { classifyToolError } from './evaluation.mjs';
|
|
9
9
|
import { REQUIRED_CODEX_MODEL, isForbiddenCodexModel } from './codex-model-guard.mjs';
|
|
10
10
|
import { dollarCommand, stripVisibleDecisionAnswerBlocks } from './routes.mjs';
|
|
11
|
+
import { appendMissionStatus } from './recallpulse.mjs';
|
|
11
12
|
|
|
12
13
|
const TEAM_DIGEST_MAX_EVENTS = 4;
|
|
13
14
|
const TEAM_DIGEST_MESSAGE_CHARS = 180;
|
|
@@ -19,7 +20,7 @@ const CODEX_GIT_ACTION_STOP_ARTIFACT = 'codex-git-action-stop-bypass.json';
|
|
|
19
20
|
const STOP_REPEAT_GUARD_WINDOW_MS = 10 * 60 * 1000;
|
|
20
21
|
const STOP_REPEAT_GUARD_MAX_ENTRIES = 25;
|
|
21
22
|
const DEFAULT_STOP_REPEAT_GUARD_LIMIT = 2;
|
|
22
|
-
const CODEX_GIT_ACTION_STOP_TTL_MS =
|
|
23
|
+
const CODEX_GIT_ACTION_STOP_TTL_MS = 15 * 60 * 1000;
|
|
23
24
|
|
|
24
25
|
async function loadHookPayload() {
|
|
25
26
|
const raw = await readStdin();
|
|
@@ -105,12 +106,39 @@ export async function hookMain(name) {
|
|
|
105
106
|
function blockForbiddenClientModel(payload = {}) {
|
|
106
107
|
const model = forbiddenClientModelFromPayload(payload);
|
|
107
108
|
if (!model || !isForbiddenCodexModel(model)) return null;
|
|
109
|
+
if (looksLikeCodexUiSettingsEvent(payload)) return null;
|
|
108
110
|
return {
|
|
109
111
|
decision: 'block',
|
|
110
112
|
reason: `SKS requires ${REQUIRED_CODEX_MODEL}; client payload requested ${model}. Switch the Codex client/session model to ${REQUIRED_CODEX_MODEL} and retry.`
|
|
111
113
|
};
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
function looksLikeCodexUiSettingsEvent(payload = {}) {
|
|
117
|
+
const prompt = stripVisibleDecisionAnswerBlocks(extractUserPrompt(payload));
|
|
118
|
+
const haystack = [
|
|
119
|
+
payload.action,
|
|
120
|
+
payload.intent,
|
|
121
|
+
payload.operation,
|
|
122
|
+
payload.permission,
|
|
123
|
+
payload.description,
|
|
124
|
+
payload.kind,
|
|
125
|
+
payload.type,
|
|
126
|
+
payload.feature,
|
|
127
|
+
payload.source,
|
|
128
|
+
payload.event,
|
|
129
|
+
payload.hook,
|
|
130
|
+
payload.hook_name,
|
|
131
|
+
payload.metadata?.action,
|
|
132
|
+
payload.metadata?.intent,
|
|
133
|
+
payload.metadata?.operation,
|
|
134
|
+
payload.metadata?.feature,
|
|
135
|
+
payload.metadata?.source,
|
|
136
|
+
payload.context?.surface,
|
|
137
|
+
payload.session?.surface
|
|
138
|
+
].filter(Boolean).join(' ');
|
|
139
|
+
return !prompt && /\b(?:settings|preferences|profile|speed|fast[_\s-]*mode|reasoning|model[_\s-]*select|codex[_\s-]*app)\b/i.test(haystack);
|
|
140
|
+
}
|
|
141
|
+
|
|
114
142
|
function forbiddenClientModelFromPayload(payload = {}) {
|
|
115
143
|
const candidates = [
|
|
116
144
|
payload.model,
|
|
@@ -147,6 +175,12 @@ async function hookUserPrompt(root, state, payload, noQuestion) {
|
|
|
147
175
|
systemMessage: 'SKS: Codex App git action bypassed route gates.'
|
|
148
176
|
};
|
|
149
177
|
}
|
|
178
|
+
if (looksLikeCodexUiSettingsEvent(payload)) {
|
|
179
|
+
return {
|
|
180
|
+
continue: true,
|
|
181
|
+
systemMessage: 'SKS: Codex App settings/profile event ignored; route gates unchanged.'
|
|
182
|
+
};
|
|
183
|
+
}
|
|
150
184
|
if (!noQuestion) {
|
|
151
185
|
const prompt = stripVisibleDecisionAnswerBlocks(extractUserPrompt(payload));
|
|
152
186
|
const madSksConfirmation = await handleMadSksUserConfirmation(root, state, prompt);
|
|
@@ -358,6 +392,12 @@ async function hookStop(root, state, payload, noQuestion) {
|
|
|
358
392
|
systemMessage: 'SKS: Codex App git action accepted without route finalization gates.'
|
|
359
393
|
};
|
|
360
394
|
}
|
|
395
|
+
if (looksLikeCodexGitActionStopCompletion(last, payload)) {
|
|
396
|
+
return {
|
|
397
|
+
continue: true,
|
|
398
|
+
systemMessage: 'SKS: Codex App git action completion accepted without route finalization gates.'
|
|
399
|
+
};
|
|
400
|
+
}
|
|
361
401
|
if (!noQuestion && (hasDfixLightCompletion(last) || await consumeLightRouteStop(root, payload))) {
|
|
362
402
|
return {
|
|
363
403
|
continue: true,
|
|
@@ -468,12 +508,42 @@ function looksLikeCodexGitAction(payload = {}) {
|
|
|
468
508
|
|| /커밋\s*메시지\s*생성/i.test(haystack);
|
|
469
509
|
const promptSignal = /\bgenerate(?:\s+a)?(?:\s+git)?\s+commit\s+message\b/i.test(prompt)
|
|
470
510
|
|| /\bcommit\s+message\b[\s\S]{0,80}\b(?:staged|diff|changes?|git)\b/i.test(prompt)
|
|
511
|
+
|| looksLikeStockCodexGitActionPrompt(prompt)
|
|
471
512
|
|| /커밋\s*메시지\s*생성/i.test(prompt);
|
|
472
513
|
if (!appSignal && !promptSignal) return false;
|
|
514
|
+
if (looksLikeStockCodexGitActionPrompt(prompt)) return true;
|
|
473
515
|
if (appSignal) return true;
|
|
474
516
|
return !looksLikeUserImplementationRequest(prompt);
|
|
475
517
|
}
|
|
476
518
|
|
|
519
|
+
function looksLikeStockCodexGitActionPrompt(prompt = '') {
|
|
520
|
+
const text = String(prompt || '').trim();
|
|
521
|
+
if (!text || text.length > 120) return false;
|
|
522
|
+
return /^(?:generate\s+(?:a\s+)?git\s+commit\s+message(?:\s+for\s+(?:the\s+)?(?:staged\s+)?diff)?|commit\s+changes|commit\s+and\s+push\s+changes|push\s+changes|create\s+(?:a\s+)?commit|create\s+(?:a\s+)?pull\s+request)\.?$/i.test(text);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function looksLikeCodexGitActionStopCompletion(last = '', payload = {}) {
|
|
526
|
+
const text = String(last || '').trim();
|
|
527
|
+
const haystack = [
|
|
528
|
+
payload.action,
|
|
529
|
+
payload.intent,
|
|
530
|
+
payload.operation,
|
|
531
|
+
payload.kind,
|
|
532
|
+
payload.type,
|
|
533
|
+
payload.feature,
|
|
534
|
+
payload.source,
|
|
535
|
+
payload.event,
|
|
536
|
+
payload.metadata?.action,
|
|
537
|
+
payload.metadata?.intent,
|
|
538
|
+
payload.metadata?.operation,
|
|
539
|
+
payload.metadata?.feature,
|
|
540
|
+
payload.metadata?.source
|
|
541
|
+
].filter(Boolean).join(' ');
|
|
542
|
+
if (/\bcodex[_\s-]*app\b[\s\S]{0,80}\bgit\b[\s\S]{0,80}\b(?:action|commit|push|pr)\b/i.test(haystack)) return true;
|
|
543
|
+
if (!text || text.length > 180) return false;
|
|
544
|
+
return /^(?:commit(?:ted)?(?:\s+and\s+pushed)?(?:\s+changes)?(?:\s+complete[.!]?)?|push(?:ed)?(?:\s+changes)?(?:\s+complete[.!]?)?|created\s+(?:a\s+)?pull\s+request[.!]?)$/i.test(text);
|
|
545
|
+
}
|
|
546
|
+
|
|
477
547
|
function looksLikeUserImplementationRequest(text = '') {
|
|
478
548
|
return /(fix|bug|broken|error|issue|implement|change|update|repair|수정|버그|오류|에러|문제|고쳐|고치|해결|변경|수리|패치|안생기|안\s*생기)/i.test(String(text || ''));
|
|
479
549
|
}
|
|
@@ -541,6 +611,18 @@ async function finalizationRepeatDecision(root, state = {}, payload = {}, reason
|
|
|
541
611
|
}
|
|
542
612
|
};
|
|
543
613
|
await writeJsonAtomic(guardPath, record).catch(() => null);
|
|
614
|
+
if (state.mission_id) {
|
|
615
|
+
await appendMissionStatus(root, state.mission_id, {
|
|
616
|
+
category: repeatCount >= limit ? 'warning' : 'blocker',
|
|
617
|
+
audience: ['user', 'route', 'final-summary'],
|
|
618
|
+
stage_id: 'before_final',
|
|
619
|
+
message: repeatCount >= limit
|
|
620
|
+
? `Repeated ${kind} stop prompt was suppressed; route completion is still unclaimed until evidence passes.`
|
|
621
|
+
: reason,
|
|
622
|
+
dedupe_key: key,
|
|
623
|
+
evidence: [STOP_REPEAT_GUARD_ARTIFACT]
|
|
624
|
+
}).catch(() => null);
|
|
625
|
+
}
|
|
544
626
|
if (repeatCount < limit) return null;
|
|
545
627
|
return {
|
|
546
628
|
continue: true,
|
|
@@ -928,6 +1010,18 @@ export async function selftestCodexCommitHooks() {
|
|
|
928
1010
|
const appCommitPushStop = await runHook('stop', { conversation_id: commitPushId, last_assistant_message: 'Commit and push complete.' });
|
|
929
1011
|
if (appCommitPushStop.code !== 0) throw new Error(`selftest failed: app commit-push stop ${appCommitPushStop.code}: ${appCommitPushStop.stderr}`);
|
|
930
1012
|
if (JSON.parse(appCommitPushStop.stdout).decision === 'block') throw new Error('selftest failed: app commit-push stop bypass');
|
|
1013
|
+
const metadataLightId = 'metadata-light-commit-push-selftest';
|
|
1014
|
+
const metadataLightHook = await runHook('user-prompt-submit', { conversation_id: metadataLightId, prompt: 'Commit and push changes.' });
|
|
1015
|
+
if (metadataLightHook.code !== 0) throw new Error(`selftest failed: metadata-light commit-push hook ${metadataLightHook.code}: ${metadataLightHook.stderr}`);
|
|
1016
|
+
const metadataLightJson = JSON.parse(metadataLightHook.stdout);
|
|
1017
|
+
if (metadataLightJson.decision === 'block' || metadataLightJson.hookSpecificOutput?.additionalContext || !String(metadataLightJson.systemMessage || '').includes('git action')) throw new Error('selftest failed: metadata-light app commit-push route bypass');
|
|
1018
|
+
const metadataLightStop = await runHook('stop', { conversation_id: metadataLightId, last_assistant_message: 'Commit and push complete.' });
|
|
1019
|
+
if (metadataLightStop.code !== 0) throw new Error(`selftest failed: metadata-light commit-push stop ${metadataLightStop.code}: ${metadataLightStop.stderr}`);
|
|
1020
|
+
if (JSON.parse(metadataLightStop.stdout).decision === 'block') throw new Error('selftest failed: metadata-light commit-push stop bypass');
|
|
1021
|
+
const settingsHook = await runHook('user-prompt-submit', { model: 'gpt-5.0-forbidden', metadata: { source: 'codex_app_settings', feature: 'speed profile' } });
|
|
1022
|
+
if (settingsHook.code !== 0) throw new Error(`selftest failed: settings hook ${settingsHook.code}: ${settingsHook.stderr}`);
|
|
1023
|
+
const settingsJson = JSON.parse(settingsHook.stdout);
|
|
1024
|
+
if (settingsJson.decision === 'block' || settingsJson.hookSpecificOutput?.additionalContext || !String(settingsJson.systemMessage || '').includes('settings/profile event ignored')) throw new Error('selftest failed: settings/profile event should not route or block');
|
|
931
1025
|
const userHook = await runHook('user-prompt-submit', { prompt: '[커밋 메시지를 생성하지 못했습니다.] 코덱스 앱에서 이 버그 수정해줘' });
|
|
932
1026
|
if (userHook.code !== 0) throw new Error(`selftest failed: user commit hook ${userHook.code}: ${userHook.stderr}`);
|
|
933
1027
|
if (!JSON.parse(userHook.stdout).hookSpecificOutput?.additionalContext?.includes('$Team route prepared')) throw new Error('selftest failed: user prompt route');
|
package/src/core/init.mjs
CHANGED
|
@@ -17,15 +17,32 @@ const GENERATED_PRUNE_POLICY = 'remove_previous_sks_generated_paths_absent_from_
|
|
|
17
17
|
|
|
18
18
|
export const REQUIRED_GENERATED_CODEX_APP_FEATURE_FLAGS = [
|
|
19
19
|
'hooks',
|
|
20
|
+
'remote_control',
|
|
20
21
|
'multi_agent',
|
|
21
22
|
'fast_mode',
|
|
22
23
|
'fast_mode_ui',
|
|
23
24
|
'codex_git_commit',
|
|
24
25
|
'computer_use',
|
|
26
|
+
'browser_use',
|
|
27
|
+
'browser_use_external',
|
|
28
|
+
'image_generation',
|
|
29
|
+
'in_app_browser',
|
|
30
|
+
'guardian_approval',
|
|
31
|
+
'tool_suggest',
|
|
25
32
|
'apps',
|
|
26
33
|
'plugins'
|
|
27
34
|
];
|
|
28
35
|
|
|
36
|
+
const DEFAULT_CODEX_APP_PLUGINS = [
|
|
37
|
+
['browser', 'openai-bundled'],
|
|
38
|
+
['chrome', 'openai-bundled'],
|
|
39
|
+
['computer-use', 'openai-bundled'],
|
|
40
|
+
['latex', 'openai-bundled'],
|
|
41
|
+
['documents', 'openai-primary-runtime'],
|
|
42
|
+
['presentations', 'openai-primary-runtime'],
|
|
43
|
+
['spreadsheets', 'openai-primary-runtime']
|
|
44
|
+
];
|
|
45
|
+
|
|
29
46
|
export function hasTopLevelCodexModeLock(text = '') {
|
|
30
47
|
const lines = String(text || '').split('\n');
|
|
31
48
|
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
@@ -491,11 +508,18 @@ function mergeManagedCodexConfigToml(existingContent = '') {
|
|
|
491
508
|
next = upsertTopLevelTomlString(next, 'service_tier', 'fast');
|
|
492
509
|
next = upsertTopLevelTomlBoolean(next, 'suppress_unstable_features_warning', true);
|
|
493
510
|
next = upsertTomlTableKey(next, 'features', 'hooks = true');
|
|
511
|
+
next = upsertTomlTableKey(next, 'features', 'remote_control = true');
|
|
494
512
|
next = upsertTomlTableKey(next, 'features', 'multi_agent = true');
|
|
495
513
|
next = upsertTomlTableKey(next, 'features', 'fast_mode = true');
|
|
496
514
|
next = upsertTomlTableKey(next, 'features', 'fast_mode_ui = true');
|
|
497
515
|
next = upsertTomlTableKey(next, 'features', 'codex_git_commit = true');
|
|
498
516
|
next = upsertTomlTableKey(next, 'features', 'computer_use = true');
|
|
517
|
+
next = upsertTomlTableKey(next, 'features', 'browser_use = true');
|
|
518
|
+
next = upsertTomlTableKey(next, 'features', 'browser_use_external = true');
|
|
519
|
+
next = upsertTomlTableKey(next, 'features', 'image_generation = true');
|
|
520
|
+
next = upsertTomlTableKey(next, 'features', 'in_app_browser = true');
|
|
521
|
+
next = upsertTomlTableKey(next, 'features', 'guardian_approval = true');
|
|
522
|
+
next = upsertTomlTableKey(next, 'features', 'tool_suggest = true');
|
|
499
523
|
next = upsertTomlTableKey(next, 'features', 'apps = true');
|
|
500
524
|
next = upsertTomlTableKey(next, 'features', 'plugins = true');
|
|
501
525
|
next = upsertTomlTableKey(next, 'user.fast_mode', 'visible = true');
|
|
@@ -506,6 +530,10 @@ function mergeManagedCodexConfigToml(existingContent = '') {
|
|
|
506
530
|
for (const block of managedCodexConfigBlocks()) {
|
|
507
531
|
next = upsertTomlTable(next, block.table, block.text);
|
|
508
532
|
}
|
|
533
|
+
for (const [name, marketplace] of DEFAULT_CODEX_APP_PLUGINS) {
|
|
534
|
+
const table = `plugins."${name}@${marketplace}"`;
|
|
535
|
+
next = upsertTomlTable(next, table, `[${table}]\nenabled = true`);
|
|
536
|
+
}
|
|
509
537
|
return `${next.trim()}\n`;
|
|
510
538
|
}
|
|
511
539
|
|
|
@@ -517,6 +545,7 @@ async function mergeGlobalCodexConfigIfAvailable(configText = '', configPath = '
|
|
|
517
545
|
if (configPath && path.resolve(configPath) === path.resolve(globalConfigPath)) return configText;
|
|
518
546
|
const globalConfig = await readText(globalConfigPath, '');
|
|
519
547
|
let next = mergeGlobalMcpServers(configText, globalConfig);
|
|
548
|
+
next = mergeGlobalCodexAppRuntimeTables(next, globalConfig);
|
|
520
549
|
if (selectedRe.test(next) && /\[model_providers\.codex-lb\]/.test(next)) return `${String(next || '').trim()}\n`;
|
|
521
550
|
const envPath = path.join(home, '.codex', 'sks-codex-lb.env');
|
|
522
551
|
if (!(await exists(envPath))) return next;
|
|
@@ -562,18 +591,24 @@ function mergeGlobalMcpServers(configText = '', globalConfig = '') {
|
|
|
562
591
|
return next;
|
|
563
592
|
}
|
|
564
593
|
|
|
594
|
+
function mergeGlobalCodexAppRuntimeTables(configText = '', globalConfig = '') {
|
|
595
|
+
let next = configText;
|
|
596
|
+
const re = /(?:^|\n)(\[((?:marketplaces|plugins)\.[^\]\r\n]+)\][\s\S]*?)(?=\n\[[^\]]+\]|\s*$)/g;
|
|
597
|
+
for (const match of String(globalConfig || '').matchAll(re)) {
|
|
598
|
+
const block = match[1].trim();
|
|
599
|
+
const table = match[2].trim();
|
|
600
|
+
if (!new RegExp(`(^|\\n)\\[${escapeRegExp(table)}\\]`).test(next)) next = upsertTomlTable(next, table, block);
|
|
601
|
+
}
|
|
602
|
+
return next;
|
|
603
|
+
}
|
|
604
|
+
|
|
565
605
|
function removeLegacyTopLevelCodexModeLocks(text = '') {
|
|
566
|
-
const legacy = {
|
|
567
|
-
model_reasoning_effort: new Set(['high'])
|
|
568
|
-
};
|
|
569
606
|
const lines = String(text || '').split('\n');
|
|
570
607
|
const firstTable = lines.findIndex((x) => /^\s*\[.+\]\s*$/.test(x));
|
|
571
608
|
const end = firstTable === -1 ? lines.length : firstTable;
|
|
572
609
|
return lines.filter((line, index) => {
|
|
573
610
|
if (index >= end) return true;
|
|
574
|
-
|
|
575
|
-
if (!match) return true;
|
|
576
|
-
return !legacy[match[1]]?.has(match[2]);
|
|
611
|
+
return !/^\s*model_reasoning_effort\s*=/.test(line);
|
|
577
612
|
}).join('\n').replace(/^\n+/, '').replace(/\n{3,}/g, '\n\n');
|
|
578
613
|
}
|
|
579
614
|
|
|
@@ -874,7 +909,7 @@ export async function installSkills(root) {
|
|
|
874
909
|
'computer-use-fast': `---\nname: computer-use-fast\ndescription: Alias for the maximum-speed $Computer-Use/$CU Codex Computer Use lane.\n---\n\nUse the same rules as computer-use: skip Team debate, QA-LOOP clarification, upfront TriWiki refresh, Context7, subagents, and reflection unless explicitly requested. Use Codex Computer Use directly; never substitute Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or other browser automation for UI/browser evidence. At the end only, refresh/pack TriWiki, validate it, then provide a concise completion summary plus Honest Mode.\n`,
|
|
875
910
|
'cu': `---\nname: cu\ndescription: Short alias for the maximum-speed $Computer-Use Codex Computer Use lane.\n---\n\nUse the same rules as computer-use. This is a speed lane for focused UI/browser/visual tasks that require Codex Computer Use evidence, with TriWiki refresh/validate and Honest Mode deferred to final closeout.\n`,
|
|
876
911
|
'goal': `---\nname: goal\ndescription: Fast $Goal/$goal bridge overlay for Codex native persisted /goal workflows.\n---\n\nUse when the user invokes $Goal/$goal or asks to persist a workflow with Codex native /goal continuation. Prepare with sks goal create or the $Goal route, write only the lightweight bridge artifacts, then use native Codex /goal create, pause, resume, and clear controls where available. Goal does not replace Team, QA, DB, or other SKS execution routes; continue implementation through the selected route and use Context7 only when external API/library docs are involved. Do not recreate the old no-question loop.\n`,
|
|
877
|
-
'research': `---\nname: research\ndescription: Dollar-command route for $Research or $research frontier discovery workflows.\n---\n\nUse when the user invokes $Research/$research or asks for research, hypotheses, new mechanisms, falsification, or testable predictions. Prefer sks research prepare and sks research run. Run the genius-lens scout council with Einstein
|
|
912
|
+
'research': `---\nname: research\ndescription: Dollar-command route for $Research or $research frontier discovery workflows.\n---\n\nUse when the user invokes $Research/$research or asks for research, hypotheses, new mechanisms, falsification, or testable predictions. Prefer sks research prepare and sks research run. Research is not an implementation route: do not edit repository source, docs, package metadata, generated skills, or harness files; write only route-local mission artifacts under .sneakoscope/missions/<mission-id>/. Run the genius-lens scout council with named persona-inspired cognitive roles: Einstein Scout, Feynman Scout, Turing Scout, von Neumann Scout, and Skeptic Scout. These are lenses only; do not impersonate the historical people. Every Research scout ledger row must include display_name, persona, persona_boundary, effort=xhigh, reasoning_effort=xhigh, service_tier when available, one literal "Eureka!" idea, falsifiers, cheap_probes, and challenge_or_response before synthesis. This is not a fixed three-cycle route: repeat source gathering, Eureka ideas, evidence-bound debate, falsification, and synthesis pressure until every scout records final agreement, or until the explicit max-cycle safety cap pauses with an unpassed gate. Create research-source-skill.md as a route-local Skill Creator artifact, then maximize layered public web/source search across latest papers, official/government or leading-institution data, standards/primary docs, current news, public discourse, developer/practitioner sources, traditional background sources, and counterevidence before synthesis. Record research-source-skill.md, source-ledger.json, scout-ledger.json, debate-ledger.json, novelty-ledger.json, falsification-ledger.json, research-report.md, research-paper.md, genius-opinion-summary.md, and research-gate.json. debate-ledger.json must include consensus_iterations, unanimous_consensus, and per-scout agreements; research-gate.json cannot pass until unanimous_consensus=true with every scout agreement recorded. Context7 is optional and only needed when the research topic depends on external package/API/framework docs; do not use it as the default research evidence layer. Normal Research may take one or two hours when needed; favor real source collection, cross-layer comparison, falsification, and a concise paper manuscript over speed. Do not use --mock except for selftests or dry harness checks; if live source execution is unavailable, record a blocker and keep the gate unpassed. Do not use for ordinary code edits.\n`,
|
|
878
913
|
'autoresearch': `---\nname: autoresearch\ndescription: Dollar-command route for $AutoResearch or $autoresearch iterative experiment loops.\n---\n\nUse for $AutoResearch, iterative improvement, SEO/GEO, ranking, workflow, benchmark, or experiments. Define program, hypothesis, experiment, metric, keep/discard, falsification, next step, and Honest Mode. Load seo-geo-optimizer for README/npm/GitHub/schema/AI-search work.\n`,
|
|
879
914
|
'db': `---\nname: db\ndescription: Dollar-command route for $DB or $db database and Supabase safety checks.\n---\n\nUse when the user invokes $DB/$db or the task touches SQL, Supabase, Postgres, migrations, Prisma, Drizzle, Knex, MCP database tools, or production data. Run or follow sks db policy, sks db scan, sks db classify, and sks db check. Destructive database operations remain forbidden.\n`,
|
|
880
915
|
'mad-sks': `---\nname: mad-sks\ndescription: Explicit high-risk authorization modifier for $MAD-SKS scoped Supabase MCP DB permission widening.\n---\n\nUse only when the user explicitly invokes $MAD-SKS or top-level sks --mad. It can be combined with another route, such as $MAD-SKS $Team or $DB ... $MAD-SKS; in that case the other command remains the primary workflow and MAD-SKS is only the temporary permission grant. The widened permission applies only while the active mission gate is open, must be deactivated when the task ends, and opens live server work, Supabase MCP database writes, column/schema cleanup, direct execute SQL, migration application when required, and normal targeted DB writes. Keep only catastrophic safeguards: whole database/schema/table removal, truncate, all-row delete/update, reset, dangerous project/branch management, credential exfiltration, persistent security weakening, and unrequested fallback implementation remain blocked. Do not carry MAD-SKS permission into later prompts or routes. The permission profile is centralized in src/core/permission-gates.mjs so skill/hook/MCP-style gates share one decision function.\n`,
|
|
@@ -896,7 +931,7 @@ export async function installSkills(root) {
|
|
|
896
931
|
'gx-visual-read': `---\nname: gx-visual-read\ndescription: Read a Sneakoscope Codex deterministic visual sheet and produce context notes.\n---\n\nExtract nodes, edges, invariants, tests, risks, uncertainties, and RGBA anchors from source/render/snapshot. Do not infer hidden nodes.\n`,
|
|
897
932
|
'gx-visual-validate': `---\nname: gx-visual-validate\ndescription: Validate render metadata against vgraph.json and beta.json.\n---\n\nRun sks gx validate and drift; fail stale or incomplete hashes, nodes, edges, invariants, or anchors.\n`,
|
|
898
933
|
'turbo-context-pack': `---\nname: turbo-context-pack\ndescription: Build ultra-low-token context packet with Q4 bits, Q3 tags, top-K claims, and minimal evidence.\n---\n\nDefault to Q4/Q3 plus TriWiki RGBA anchors and attention.use_first. Add Q2/Q1 only when needed or when attention.hydrate_first says source hydration is required. Keep id, hash, path, and coordinate tuple for hydration.\n`,
|
|
899
|
-
'research-discovery': `---\nname: research-discovery\ndescription: Run SKS Research Mode for frontier-style research, hypotheses, novelty ledgers, falsification, and experiments.\n---\n\nFrame criteria, map assumptions, run maximum available web/source search, generate xhigh scout findings through Einstein
|
|
934
|
+
'research-discovery': `---\nname: research-discovery\ndescription: Run SKS Research Mode for frontier-style research, hypotheses, novelty ledgers, falsification, and experiments.\n---\n\nFrame criteria, map assumptions, run maximum available web/source search, generate xhigh scout findings through Einstein Scout, Feynman Scout, Turing Scout, von Neumann Scout, and Skeptic Scout persona-inspired lenses, require each scout to record display_name/persona/persona_boundary plus a literal "Eureka!" idea, run evidence-bound debate, falsify, keep surviving insights, and record source ids, novelty/confidence/falsifiers/next experiments. Do not impersonate historical people and do not overclaim.\n`,
|
|
900
935
|
'performance-evaluator': `---\nname: performance-evaluator\ndescription: Evaluate SKS performance, token-saving, accuracy-proxy, context-compression, or workflow improvements.\n---\n\nUse sks eval run/compare before claims. Report token_savings_pct, accuracy_delta/proxy, required_recall, support, and meaningful_improvement.\n`,
|
|
901
936
|
'image-ux-review': imageUxReviewSkill('image-ux-review'),
|
|
902
937
|
'ux-review': imageUxReviewSkill('ux-review'),
|
package/src/core/pipeline.mjs
CHANGED
|
@@ -921,7 +921,7 @@ async function prepareResearch(root, route, task, required) {
|
|
|
921
921
|
await writeResearchPlan(dir, task, {});
|
|
922
922
|
const pipelinePlan = await writePipelinePlan(dir, { missionId: id, route, task, required, ambiguity: { required: false, status: 'direct_route' } });
|
|
923
923
|
await setCurrent(root, routeState(id, route, 'RESEARCH_PREPARED', required, { prompt: task, pipeline_plan_ready: validatePipelinePlan(pipelinePlan).ok, pipeline_plan_path: PIPELINE_PLAN_ARTIFACT }));
|
|
924
|
-
return routeContext(route, id, task, required, 'Run sks research run latest as a real long-running source-gathering pass, never an automatic mock fallback; create research-source-skill.md, maximize layered public source search, require every scout effort=xhigh plus one Eureka! idea, fill source-ledger.json, scout-ledger.json, debate-ledger.json, novelty-ledger.json, falsification-ledger.json, research-report.md, research-paper.md, genius-opinion-summary.md, and pass research-gate.json.');
|
|
924
|
+
return routeContext(route, id, task, required, 'Run sks research run latest as a real long-running source-gathering pass, never an automatic mock fallback; do not modify repository source code; create research-source-skill.md, maximize layered public source search, require every scout effort=xhigh plus one Eureka! idea, repeat scout/debate/falsification cycles until unanimous_consensus=true for every scout or the explicit safety cap pauses the run, fill source-ledger.json, scout-ledger.json, debate-ledger.json, novelty-ledger.json, falsification-ledger.json, research-report.md, research-paper.md, genius-opinion-summary.md, and pass research-gate.json.');
|
|
925
925
|
}
|
|
926
926
|
|
|
927
927
|
async function prepareAutoResearch(root, route, task, required) {
|
|
@@ -1400,6 +1400,8 @@ function normalizeComplianceReason(reason = '') {
|
|
|
1400
1400
|
async function passedActiveGate(root, state) {
|
|
1401
1401
|
const id = state?.mission_id;
|
|
1402
1402
|
if (!id) return { ok: false, file: null };
|
|
1403
|
+
const hardBlocker = await passedHardBlocker(root, state);
|
|
1404
|
+
if (hardBlocker.ok) return hardBlocker;
|
|
1403
1405
|
const files = gateFilesForState(state);
|
|
1404
1406
|
for (const file of files) {
|
|
1405
1407
|
const p = path.join(missionDir(root, id), file);
|
|
@@ -1414,8 +1416,6 @@ async function passedActiveGate(root, state) {
|
|
|
1414
1416
|
return { ok: false, file };
|
|
1415
1417
|
}
|
|
1416
1418
|
}
|
|
1417
|
-
const hardBlocker = await passedHardBlocker(root, state);
|
|
1418
|
-
if (hardBlocker.ok) return hardBlocker;
|
|
1419
1419
|
return { ok: false, file: files[0] || null };
|
|
1420
1420
|
}
|
|
1421
1421
|
|