sneakoscope 10.3.2 → 10.3.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/README.md +22 -12
- package/config/skills-hash-ledger.v1.json +7 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/config/skills-manifest.json +13 -7
- package/dist/core/agents/agent-effort-policy.js +15 -13
- package/dist/core/agents/native-worker-backend-router.js +13 -8
- package/dist/core/codex-hooks/codex-hook-managed-install.js +56 -2
- package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +1 -0
- package/dist/core/codex-native/core-skill-manifest.js +4 -4
- package/dist/core/decisions/cli.js +9 -2
- package/dist/core/decisions/config.js +7 -0
- package/dist/core/decisions/integration.js +54 -12
- package/dist/core/decisions/policy.js +27 -4
- package/dist/core/decisions/questions.js +39 -9
- package/dist/core/decisions/routing.js +15 -10
- package/dist/core/decisions/types.js +11 -25
- package/dist/core/doctor/current-project-guidance.js +38 -1
- package/dist/core/doctor/skill-legacy-surface.js +2 -1
- package/dist/core/hooks-runtime/hook-context.js +1 -1
- package/dist/core/hooks-runtime/jev-spawn-routing.js +21 -2
- package/dist/core/hooks-runtime/managed-guidance-preflight.js +30 -0
- package/dist/core/hooks-runtime/official-subagent-lifecycle.js +3 -3
- package/dist/core/hooks-runtime/parent-orchestration-gate.js +372 -0
- package/dist/core/hooks-runtime/subagent-context.js +13 -3
- package/dist/core/hooks-runtime/subagent-spawn-policy.js +5 -10
- package/dist/core/hooks-runtime.js +46 -8
- package/dist/core/init/skills/inventory.js +5 -1
- package/dist/core/init/skills.js +4 -4
- package/dist/core/init.js +39 -7
- package/dist/core/managed-assets/managed-assets-manifest.js +17 -17
- package/dist/core/pipeline-internals/runtime-core.js +2 -2
- package/dist/core/provider/model-router.js +8 -5
- package/dist/core/recallpulse/policy.js +2 -2
- package/dist/core/recallpulse.js +3 -3
- package/dist/core/release/gate-affected-globs.js +0 -1
- package/dist/core/research/mock-result.js +2 -2
- package/dist/core/research/research-adversarial-review.js +7 -7
- package/dist/core/research/research-claim-synthesizer.js +3 -3
- package/dist/core/research/research-falsification-runner.js +3 -3
- package/dist/core/research/research-super-search.js +5 -2
- package/dist/core/research/research-synthesis-writer.js +2 -2
- package/dist/core/research.js +5 -5
- package/dist/core/routes/dollar-manifest-lite.js +1 -1
- package/dist/core/routes.js +10 -10
- package/dist/core/runtime/task-profile.js +9 -5
- package/dist/core/subagents/model-policy.js +27 -45
- package/dist/core/subagents/model-tiers.js +153 -0
- package/dist/core/subagents/naruto-command-args.js +3 -3
- package/dist/core/subagents/naruto-help-contract.js +18 -13
- package/dist/core/subagents/naruto-host-credentials.js +8 -13
- package/dist/core/subagents/official-subagent-config.js +11 -7
- package/dist/core/subagents/official-subagent-preparation.js +12 -8
- package/dist/core/subagents/official-subagent-prompt.js +33 -33
- package/dist/core/subagents/official-subagent-runner.js +3 -3
- package/dist/core/subagents/role-model-preferences.js +26 -21
- package/dist/core/update/managed-permission-repair.js +462 -0
- package/dist/core/update/update-migration-state/retired-local-decision.js +32 -2
- package/dist/core/update/update-migration-state/simple-stages.js +42 -5
- package/dist/core/update/update-migration-state.js +63 -1
- package/dist/core/update-check.js +35 -8
- package/dist/core/version.js +1 -1
- package/dist/scripts/codex-native-agent-role-content-check.js +10 -5
- package/dist/scripts/codex-native-gate-lib.js +3 -1
- package/dist/scripts/current-surface-update-e2e-check.js +1 -0
- package/dist/scripts/mutation-callsite-coverage-check.js +2 -2
- package/dist/scripts/release-affected-selector-check.js +1 -2
- package/dist/scripts/typed-routing-gate-lib.js +3 -1
- package/package.json +1 -1
- package/release-gates.v2.json +2 -6
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { agentWorkerHookContext } from '../agents/agent-recursion-guard.js';
|
|
3
|
+
import { consultJevToolDelegation } from '../decisions/integration.js';
|
|
4
|
+
import { nowIso, readJson, sha256, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { ensureConfinedDirectory } from '../managed-path-safety.js';
|
|
6
|
+
import { missionDir } from '../mission.js';
|
|
7
|
+
import { readSubagentEvents } from '../subagents/subagent-evidence.js';
|
|
8
|
+
export const PARENT_ORCHESTRATION_GATE_FILENAME = 'parent-orchestration-gate.json';
|
|
9
|
+
export const PARENT_ORCHESTRATION_GATE_SCHEMA = 'sks.parent-orchestration-gate.v1';
|
|
10
|
+
export const PARENT_ORCHESTRATION_MAX_BLOCKS = 2;
|
|
11
|
+
const MAX_TARGETS = 16;
|
|
12
|
+
const SPAWN_TOOL_RE = /^(?:functions\.|collaboration\.)?spawn_agent$/;
|
|
13
|
+
const FILE_EDIT_TOOL_RE = /^(?:functions\.)?(?:apply_patch|edit|write|multiedit|notebookedit|str_replace_editor|str_replace_based_edit_tool|file_write|fs_write|write_file|create_file|edit_file|update_file|delete_file)$/i;
|
|
14
|
+
const SHELL_TOOL_RE = /^(?:functions\.)?(?:shell|shell_command|exec_command|local_shell|bash|container\.exec)$/i;
|
|
15
|
+
const SHELL_WRITE_RE = new RegExp([
|
|
16
|
+
String.raw `(?:^|[\s;&|(])(?:rm|mv|cp|touch|mkdir|tee|ln|install)\b`,
|
|
17
|
+
String.raw `\bsed\s+(?:-[a-zA-Z]*i|--in-place)`,
|
|
18
|
+
String.raw `\bperl\s+-p?i\b`,
|
|
19
|
+
String.raw `\bpython\d?\s+-c\b`,
|
|
20
|
+
String.raw `\bnode\s+-e\b`,
|
|
21
|
+
String.raw `(?<![<>\d])>{1,2}(?!&)\s*(?!\/dev\/null)\S`,
|
|
22
|
+
String.raw `\*\*\*\s+(?:Update|Add|Delete|Move to)\s+File:`,
|
|
23
|
+
String.raw `\bgit\s+(?:apply|checkout|restore|stash|reset|commit|rebase|merge|cherry-pick|clean)\b`,
|
|
24
|
+
String.raw `\b(?:npm|pnpm|yarn|bun)\s+(?:install|add|remove|uninstall|i)\b`,
|
|
25
|
+
String.raw `\bcargo\s+(?:add|remove)\b`,
|
|
26
|
+
String.raw `\bpip\d?\s+install\b`
|
|
27
|
+
].join('|'), 'i');
|
|
28
|
+
const PATCH_FILE_RE = /\*\*\*\s+(?:Update|Add|Delete|Move to)\s+File:\s*([^\n\r]+)/g;
|
|
29
|
+
const PATCH_HEADER_RE = /\*\*\*\s+(?:Update|Add|Delete|Move to)\s+File:/;
|
|
30
|
+
const SHELL_PATH_TOKEN_RE = /(?:^|\s)((?:\.{1,2}\/|\/|~\/)?[\w@.+-]+(?:\/[\w@.+-]+)+|[\w@+-]+\.[a-z0-9]{1,8})(?=\s|$)/gi;
|
|
31
|
+
export function isSpawnToolPayload(payload = {}) {
|
|
32
|
+
return SPAWN_TOOL_RE.test(toolNameOf(payload));
|
|
33
|
+
}
|
|
34
|
+
function toolNameOf(payload = {}) {
|
|
35
|
+
return String(payload?.tool_name || payload?.toolName || payload?.tool?.name || payload?.name || '').trim();
|
|
36
|
+
}
|
|
37
|
+
function toolInputOf(payload = {}) {
|
|
38
|
+
const input = payload?.tool_input || payload?.toolInput || payload?.tool?.input || payload?.input;
|
|
39
|
+
return input && typeof input === 'object' && !Array.isArray(input) ? input : {};
|
|
40
|
+
}
|
|
41
|
+
function commandText(input, payload = {}) {
|
|
42
|
+
const raw = input.command ?? input.cmd ?? input.args ?? payload?.command ?? '';
|
|
43
|
+
if (Array.isArray(raw))
|
|
44
|
+
return raw.map((entry) => String(entry ?? '')).join(' ');
|
|
45
|
+
return String(raw ?? '');
|
|
46
|
+
}
|
|
47
|
+
function narutoState(state = {}) {
|
|
48
|
+
const mode = String(state?.mode || '').toUpperCase();
|
|
49
|
+
const route = String(state?.route || state?.route_command || '').replace(/^\$/, '').toUpperCase();
|
|
50
|
+
return mode === 'NARUTO' || route === 'NARUTO' || state?.subagents_required === true;
|
|
51
|
+
}
|
|
52
|
+
export function childThreadHookPayload(payload = {}) {
|
|
53
|
+
const agentId = String(payload?.agent_id || payload?.agentId || '').trim();
|
|
54
|
+
const agentType = String(payload?.agent_type || payload?.agentType || '').trim();
|
|
55
|
+
if (agentId || agentType)
|
|
56
|
+
return true;
|
|
57
|
+
const thread = String(payload?.thread_id || payload?.threadId || '').trim();
|
|
58
|
+
const session = String(payload?.session_id || payload?.sessionId || '').trim();
|
|
59
|
+
return Boolean(thread && session && thread !== session);
|
|
60
|
+
}
|
|
61
|
+
export function narutoRootParentHook(state = {}, payload = {}, sessionKey = null) {
|
|
62
|
+
if (!narutoState(state))
|
|
63
|
+
return false;
|
|
64
|
+
if (!String(state?.mission_id || '').trim())
|
|
65
|
+
return false;
|
|
66
|
+
if (state?.route_closed === true)
|
|
67
|
+
return false;
|
|
68
|
+
if (agentWorkerHookContext(state, payload))
|
|
69
|
+
return false;
|
|
70
|
+
if (childThreadHookPayload(payload))
|
|
71
|
+
return false;
|
|
72
|
+
const scope = String(state?.session_scope || '').trim();
|
|
73
|
+
const current = String(sessionKey || '').trim();
|
|
74
|
+
if (scope && current && scope !== current)
|
|
75
|
+
return false;
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
export function isSneakoscopeArtifactPath(target) {
|
|
79
|
+
const normalized = String(target || '').trim().replace(/\\/g, '/').replace(/^['"]|['"]$/g, '');
|
|
80
|
+
if (!normalized)
|
|
81
|
+
return false;
|
|
82
|
+
return /^(?:\.\/)?\.sneakoscope(?:\/|$)/.test(normalized) || /\/\.sneakoscope(?:\/|$)/.test(normalized);
|
|
83
|
+
}
|
|
84
|
+
function pushTarget(targets, value) {
|
|
85
|
+
const text = String(value ?? '').trim();
|
|
86
|
+
if (!text || targets.length >= MAX_TARGETS || targets.includes(text))
|
|
87
|
+
return;
|
|
88
|
+
targets.push(text);
|
|
89
|
+
}
|
|
90
|
+
function collectInputTargets(input, targets) {
|
|
91
|
+
for (const key of ['path', 'file_path', 'filePath', 'file', 'filename', 'target', 'target_path', 'targetPath', 'destination']) {
|
|
92
|
+
if (typeof input[key] === 'string')
|
|
93
|
+
pushTarget(targets, input[key]);
|
|
94
|
+
}
|
|
95
|
+
for (const key of ['paths', 'files', 'targets']) {
|
|
96
|
+
const rows = input[key];
|
|
97
|
+
if (Array.isArray(rows))
|
|
98
|
+
for (const row of rows)
|
|
99
|
+
pushTarget(targets, row);
|
|
100
|
+
}
|
|
101
|
+
const edits = input.edits;
|
|
102
|
+
if (Array.isArray(edits)) {
|
|
103
|
+
for (const edit of edits) {
|
|
104
|
+
if (edit && typeof edit === 'object') {
|
|
105
|
+
const row = edit;
|
|
106
|
+
pushTarget(targets, row.path ?? row.file_path ?? row.filePath ?? row.file);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function patchHeaderTargets(texts) {
|
|
112
|
+
const targets = [];
|
|
113
|
+
for (const text of texts) {
|
|
114
|
+
if (typeof text !== 'string' || !PATCH_HEADER_RE.test(text))
|
|
115
|
+
continue;
|
|
116
|
+
for (const match of text.matchAll(PATCH_FILE_RE))
|
|
117
|
+
pushTarget(targets, match[1]);
|
|
118
|
+
}
|
|
119
|
+
return targets;
|
|
120
|
+
}
|
|
121
|
+
function collectShellTargets(command, targets) {
|
|
122
|
+
for (const match of command.matchAll(SHELL_PATH_TOKEN_RE)) {
|
|
123
|
+
const token = String(match[1] || '').trim();
|
|
124
|
+
if (!token || token.startsWith('-') || /^https?:\/\//i.test(token))
|
|
125
|
+
continue;
|
|
126
|
+
if (/^\d+(?:\.\d+)*$/.test(token))
|
|
127
|
+
continue;
|
|
128
|
+
pushTarget(targets, token);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export function parentMutationIntent(payload = {}) {
|
|
132
|
+
const toolName = toolNameOf(payload);
|
|
133
|
+
if (!toolName || SPAWN_TOOL_RE.test(toolName))
|
|
134
|
+
return null;
|
|
135
|
+
const input = toolInputOf(payload);
|
|
136
|
+
const command = commandText(input, payload);
|
|
137
|
+
const patchTargets = patchHeaderTargets([command, input.patch, input.input, input.content, input.text]);
|
|
138
|
+
if (FILE_EDIT_TOOL_RE.test(toolName)) {
|
|
139
|
+
const targets = [];
|
|
140
|
+
collectInputTargets(input, targets);
|
|
141
|
+
for (const target of patchTargets)
|
|
142
|
+
pushTarget(targets, target);
|
|
143
|
+
if (!patchTargets.length && command)
|
|
144
|
+
collectShellTargets(command, targets);
|
|
145
|
+
return {
|
|
146
|
+
toolName,
|
|
147
|
+
kind: 'file_edit',
|
|
148
|
+
targets,
|
|
149
|
+
exempt: targets.length > 0 && targets.every(isSneakoscopeArtifactPath)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (SHELL_TOOL_RE.test(toolName)) {
|
|
153
|
+
if (!command || !SHELL_WRITE_RE.test(command))
|
|
154
|
+
return null;
|
|
155
|
+
const targets = patchTargets.length ? [...patchTargets] : [];
|
|
156
|
+
if (!patchTargets.length)
|
|
157
|
+
collectShellTargets(command, targets);
|
|
158
|
+
return {
|
|
159
|
+
toolName,
|
|
160
|
+
kind: 'shell_write',
|
|
161
|
+
targets,
|
|
162
|
+
exempt: targets.length > 0 && targets.every(isSneakoscopeArtifactPath)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
export function parentOrchestrationLedgerPath(root, missionId) {
|
|
168
|
+
return path.join(missionDir(root, missionId), PARENT_ORCHESTRATION_GATE_FILENAME);
|
|
169
|
+
}
|
|
170
|
+
function emptyLedger(missionId, workflowRunId) {
|
|
171
|
+
return {
|
|
172
|
+
schema: PARENT_ORCHESTRATION_GATE_SCHEMA,
|
|
173
|
+
mission_id: missionId,
|
|
174
|
+
workflow_run_id: workflowRunId,
|
|
175
|
+
spawns: 0,
|
|
176
|
+
blocks: 0,
|
|
177
|
+
escapes: 0,
|
|
178
|
+
parent_owned_allows: 0,
|
|
179
|
+
wait_blocks: 0,
|
|
180
|
+
wait_escapes: 0,
|
|
181
|
+
wait_fingerprint: null,
|
|
182
|
+
last_action: null,
|
|
183
|
+
last_tool: null,
|
|
184
|
+
last_targets: [],
|
|
185
|
+
last_jev_reason: null,
|
|
186
|
+
updated_at: null
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
export async function readParentOrchestrationLedger(root, missionId, workflowRunId = null) {
|
|
190
|
+
const raw = await readJson(parentOrchestrationLedgerPath(root, missionId), null).catch(() => null);
|
|
191
|
+
const fresh = emptyLedger(missionId, workflowRunId);
|
|
192
|
+
if (!raw || raw.schema !== PARENT_ORCHESTRATION_GATE_SCHEMA || String(raw.mission_id || '') !== missionId)
|
|
193
|
+
return fresh;
|
|
194
|
+
if (workflowRunId && raw.workflow_run_id && String(raw.workflow_run_id) !== workflowRunId)
|
|
195
|
+
return fresh;
|
|
196
|
+
return {
|
|
197
|
+
...fresh,
|
|
198
|
+
workflow_run_id: workflowRunId || (typeof raw.workflow_run_id === 'string' ? raw.workflow_run_id : null),
|
|
199
|
+
spawns: countOf(raw.spawns),
|
|
200
|
+
blocks: countOf(raw.blocks),
|
|
201
|
+
escapes: countOf(raw.escapes),
|
|
202
|
+
parent_owned_allows: countOf(raw.parent_owned_allows),
|
|
203
|
+
wait_blocks: countOf(raw.wait_blocks),
|
|
204
|
+
wait_escapes: countOf(raw.wait_escapes),
|
|
205
|
+
wait_fingerprint: typeof raw.wait_fingerprint === 'string' ? raw.wait_fingerprint : null,
|
|
206
|
+
last_action: ['allow', 'block', 'escape', 'spawn', 'wait'].includes(raw.last_action) ? raw.last_action : null,
|
|
207
|
+
last_tool: typeof raw.last_tool === 'string' ? raw.last_tool : null,
|
|
208
|
+
last_targets: Array.isArray(raw.last_targets) ? raw.last_targets.map((row) => String(row)).slice(0, MAX_TARGETS) : [],
|
|
209
|
+
last_jev_reason: typeof raw.last_jev_reason === 'string' ? raw.last_jev_reason : null,
|
|
210
|
+
updated_at: typeof raw.updated_at === 'string' ? raw.updated_at : null
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function countOf(value) {
|
|
214
|
+
const parsed = Number(value);
|
|
215
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
216
|
+
}
|
|
217
|
+
async function writeLedger(root, ledger) {
|
|
218
|
+
const dir = missionDir(root, ledger.mission_id);
|
|
219
|
+
await ensureConfinedDirectory(path.resolve(root), dir);
|
|
220
|
+
await writeJsonAtomic(path.join(dir, PARENT_ORCHESTRATION_GATE_FILENAME), { ...ledger, updated_at: nowIso() });
|
|
221
|
+
}
|
|
222
|
+
export async function recordParentOrchestrationSpawn(root, state = {}, opts = {}) {
|
|
223
|
+
const missionId = String(state?.mission_id || '').trim();
|
|
224
|
+
if (!missionId || !narutoState(state))
|
|
225
|
+
return null;
|
|
226
|
+
const workflowRunId = String(state?.official_subagent_run_id || '').trim() || null;
|
|
227
|
+
const ledger = await readParentOrchestrationLedger(root, missionId, workflowRunId);
|
|
228
|
+
if (opts.atLeastOne && ledger.spawns > 0)
|
|
229
|
+
return ledger;
|
|
230
|
+
const next = { ...ledger, spawns: ledger.spawns + 1, last_action: 'spawn' };
|
|
231
|
+
await writeLedger(root, next);
|
|
232
|
+
return next;
|
|
233
|
+
}
|
|
234
|
+
async function childThreadActivity(root, missionId, workflowRunId) {
|
|
235
|
+
const events = await readSubagentEvents(missionDir(root, missionId)).catch(() => []);
|
|
236
|
+
const started = new Set();
|
|
237
|
+
const running = new Set();
|
|
238
|
+
for (const event of events) {
|
|
239
|
+
if (workflowRunId && event.run_id && event.run_id !== workflowRunId)
|
|
240
|
+
continue;
|
|
241
|
+
const thread = event.thread_id || event.agent_id;
|
|
242
|
+
if (!thread)
|
|
243
|
+
continue;
|
|
244
|
+
if (event.event_name === 'SubagentStart') {
|
|
245
|
+
started.add(thread);
|
|
246
|
+
running.add(thread);
|
|
247
|
+
}
|
|
248
|
+
else if (event.event_name === 'SubagentStop') {
|
|
249
|
+
running.delete(thread);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { started: started.size, running: [...running].sort() };
|
|
253
|
+
}
|
|
254
|
+
function missionGoal(state = {}) {
|
|
255
|
+
return String(state?.prompt || state?.task || state?.goal || '').trim();
|
|
256
|
+
}
|
|
257
|
+
function renderTargets(intent) {
|
|
258
|
+
return intent.targets.length ? intent.targets.slice(0, 6).join(', ') : 'source files';
|
|
259
|
+
}
|
|
260
|
+
function blockMessage(missionId, intent, jev) {
|
|
261
|
+
const jevNote = jev?.called
|
|
262
|
+
? jev.choice === 'delegate_child'
|
|
263
|
+
? ' Jev classified this edit as slice work for a child.'
|
|
264
|
+
: ` Jev did not classify this edit as parent-owned (${jev.reason}).`
|
|
265
|
+
: '';
|
|
266
|
+
return [
|
|
267
|
+
`SKS parent orchestration gate denied ${intent.toolName} on ${renderTargets(intent)}: mission ${missionId} has no child thread yet.`,
|
|
268
|
+
'The Naruto parent orchestrates only. Decompose the task into disjoint slices and spawn each child with spawn_agent first',
|
|
269
|
+
'(sealed model and reasoning_effort; Jev seals them when Jev mode is on; fork_turns="none"; the complete slice contract in message),',
|
|
270
|
+
'wait for the children, then integrate and verify.',
|
|
271
|
+
'Until the first child starts, the parent may write only .sneakoscope artifacts.' + jevNote
|
|
272
|
+
].join(' ');
|
|
273
|
+
}
|
|
274
|
+
function escapeMessage(missionId, intent, ledger) {
|
|
275
|
+
return [
|
|
276
|
+
`SKS parent orchestration gate released ${intent.toolName} on ${renderTargets(intent)} after ${ledger.blocks} denied attempts without a child spawn.`,
|
|
277
|
+
`This release is recorded as an escape in .sneakoscope/missions/${missionId}/${PARENT_ORCHESTRATION_GATE_FILENAME}.`,
|
|
278
|
+
'Spawn children for the remaining slices before the next source edit.'
|
|
279
|
+
].join(' ');
|
|
280
|
+
}
|
|
281
|
+
function waitMessage(missionId, intent, running) {
|
|
282
|
+
return [
|
|
283
|
+
`SKS parent orchestration gate denied ${intent.toolName} on ${renderTargets(intent)}: ${running.length} child thread(s) of mission ${missionId} are still running.`,
|
|
284
|
+
'The Naruto parent does not implement slice work beside its children.',
|
|
285
|
+
'Wait for the running children, collect their results, then integrate and verify.',
|
|
286
|
+
'If more work remains, spawn another child for it instead of editing it in the parent.'
|
|
287
|
+
].join(' ');
|
|
288
|
+
}
|
|
289
|
+
function waitEscapeMessage(missionId, intent, ledger) {
|
|
290
|
+
return [
|
|
291
|
+
`SKS parent orchestration gate released ${intent.toolName} on ${renderTargets(intent)} after ${ledger.wait_blocks} denied attempts while children were still recorded as running.`,
|
|
292
|
+
`The release is recorded in .sneakoscope/missions/${missionId}/${PARENT_ORCHESTRATION_GATE_FILENAME}; edit only integration work, not an assigned slice.`
|
|
293
|
+
].join(' ');
|
|
294
|
+
}
|
|
295
|
+
function runningFingerprint(running) {
|
|
296
|
+
return sha256(running.join('\n')).slice(0, 16);
|
|
297
|
+
}
|
|
298
|
+
export async function evaluateParentOrchestrationGate(input) {
|
|
299
|
+
const { root, state, payload } = input;
|
|
300
|
+
if (!narutoRootParentHook(state, payload, input.sessionKey))
|
|
301
|
+
return { action: 'allow', reason: 'not_root_parent', jev: null };
|
|
302
|
+
const intent = parentMutationIntent(payload);
|
|
303
|
+
if (!intent)
|
|
304
|
+
return { action: 'allow', reason: 'no_mutation_intent', jev: null };
|
|
305
|
+
if (intent.exempt)
|
|
306
|
+
return { action: 'allow', reason: 'sneakoscope_artifact', jev: null };
|
|
307
|
+
const missionId = String(state.mission_id).trim();
|
|
308
|
+
const workflowRunId = String(state?.official_subagent_run_id || '').trim() || null;
|
|
309
|
+
const ledger = await readParentOrchestrationLedger(root, missionId, workflowRunId);
|
|
310
|
+
const activity = await childThreadActivity(root, missionId, workflowRunId);
|
|
311
|
+
if (ledger.spawns > 0 || activity.started > 0) {
|
|
312
|
+
return evaluateWhileChildrenExist(root, missionId, intent, ledger, activity);
|
|
313
|
+
}
|
|
314
|
+
if (ledger.blocks >= PARENT_ORCHESTRATION_MAX_BLOCKS) {
|
|
315
|
+
const next = { ...ledger, last_tool: intent.toolName, last_targets: intent.targets, escapes: ledger.escapes + 1, last_action: 'escape' };
|
|
316
|
+
await writeLedger(root, next).catch(() => null);
|
|
317
|
+
const message = ledger.escapes === 0 ? escapeMessage(missionId, intent, next) : null;
|
|
318
|
+
return { action: 'escape', reason: 'max_blocks_reached', message, jev: null };
|
|
319
|
+
}
|
|
320
|
+
const jev = await consultJevToolDelegation({
|
|
321
|
+
root,
|
|
322
|
+
missionGoal: missionGoal(state),
|
|
323
|
+
toolName: intent.toolName,
|
|
324
|
+
targets: intent.targets
|
|
325
|
+
}).catch(() => ({ called: false, choice: null, reason: 'consult_failed' }));
|
|
326
|
+
const base = {
|
|
327
|
+
...ledger,
|
|
328
|
+
last_tool: intent.toolName,
|
|
329
|
+
last_targets: intent.targets,
|
|
330
|
+
last_jev_reason: jev.called ? `${jev.reason}${jev.choice ? `:${jev.choice}` : ''}` : jev.reason
|
|
331
|
+
};
|
|
332
|
+
if (jev.choice === 'parent_owned') {
|
|
333
|
+
await writeLedger(root, { ...base, parent_owned_allows: ledger.parent_owned_allows + 1, last_action: 'allow' }).catch(() => null);
|
|
334
|
+
return { action: 'allow', reason: 'jev_parent_owned', jev };
|
|
335
|
+
}
|
|
336
|
+
const next = { ...base, blocks: ledger.blocks + 1, last_action: 'block' };
|
|
337
|
+
await writeLedger(root, next).catch(() => null);
|
|
338
|
+
return {
|
|
339
|
+
action: 'block',
|
|
340
|
+
reason: jev.choice === 'delegate_child' ? 'jev_delegate_child' : 'no_child_thread',
|
|
341
|
+
message: blockMessage(missionId, intent, jev),
|
|
342
|
+
jev
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
async function evaluateWhileChildrenExist(root, missionId, intent, ledger, activity) {
|
|
346
|
+
const spawns = Math.max(ledger.spawns, activity.started);
|
|
347
|
+
if (activity.running.length === 0) {
|
|
348
|
+
if (spawns !== ledger.spawns)
|
|
349
|
+
await writeLedger(root, { ...ledger, spawns, last_action: 'spawn' }).catch(() => null);
|
|
350
|
+
return { action: 'allow', reason: 'children_settled', jev: null };
|
|
351
|
+
}
|
|
352
|
+
const fingerprint = runningFingerprint(activity.running);
|
|
353
|
+
const sameSet = ledger.wait_fingerprint === fingerprint;
|
|
354
|
+
const waitBlocks = sameSet ? ledger.wait_blocks : 0;
|
|
355
|
+
const waitEscapes = sameSet ? ledger.wait_escapes : 0;
|
|
356
|
+
const base = {
|
|
357
|
+
...ledger,
|
|
358
|
+
spawns,
|
|
359
|
+
wait_fingerprint: fingerprint,
|
|
360
|
+
last_tool: intent.toolName,
|
|
361
|
+
last_targets: intent.targets
|
|
362
|
+
};
|
|
363
|
+
if (waitBlocks >= PARENT_ORCHESTRATION_MAX_BLOCKS) {
|
|
364
|
+
const next = { ...base, wait_blocks: waitBlocks, wait_escapes: waitEscapes + 1, last_action: 'escape' };
|
|
365
|
+
await writeLedger(root, next).catch(() => null);
|
|
366
|
+
const message = waitEscapes === 0 ? waitEscapeMessage(missionId, intent, next) : null;
|
|
367
|
+
return { action: 'escape', reason: 'wait_max_blocks_reached', message, jev: null };
|
|
368
|
+
}
|
|
369
|
+
const next = { ...base, wait_blocks: waitBlocks + 1, wait_escapes: waitEscapes, last_action: 'wait' };
|
|
370
|
+
await writeLedger(root, next).catch(() => null);
|
|
371
|
+
return { action: 'block', reason: 'children_running', message: waitMessage(missionId, intent, activity.running), jev: null };
|
|
372
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { readJson } from '../fsx.js';
|
|
3
3
|
import { managedOfficialSubagentRoleByName } from '../managed-assets/managed-assets-manifest.js';
|
|
4
|
-
import {
|
|
4
|
+
import { latestModelForTier, latestTierModelSet } from '../subagents/model-tiers.js';
|
|
5
|
+
import { jevEnabled, readDecisionConfig } from '../decisions/config.js';
|
|
5
6
|
export async function sealedSubagentRoutingContext(artifactDir, payload = {}) {
|
|
6
7
|
const plan = await readJson(path.join(artifactDir, 'subagent-plan.json'), null).catch(() => null);
|
|
7
8
|
if (!plan || plan.workflow !== 'official_codex_subagent')
|
|
@@ -13,9 +14,18 @@ export async function sealedSubagentRoutingContext(artifactDir, payload = {}) {
|
|
|
13
14
|
if (!agentName)
|
|
14
15
|
return '';
|
|
15
16
|
const narutoPlan = plan.mode === 'naruto' || plan.route === '$Naruto';
|
|
17
|
+
const jevSealed = await readDecisionConfig().then(jevEnabled).catch(() => false);
|
|
18
|
+
if (jevSealed) {
|
|
19
|
+
return [
|
|
20
|
+
'SKS sealed child routing:',
|
|
21
|
+
`- custom agent: ${agentName}`,
|
|
22
|
+
'- model and model_reasoning_effort: sealed on the spawn call by Jev routing',
|
|
23
|
+
'- keep this sealed profile; do not retarget model/effort or spawn nested agents'
|
|
24
|
+
].join('\n');
|
|
25
|
+
}
|
|
16
26
|
const plannedModel = String(planned?.routed_model || planned?.model || '').trim();
|
|
17
|
-
const sealedModels =
|
|
18
|
-
const model = narutoPlan && sealedModels.has(plannedModel) ? plannedModel :
|
|
27
|
+
const sealedModels = latestTierModelSet();
|
|
28
|
+
const model = narutoPlan && sealedModels.has(plannedModel) ? plannedModel : (role?.model || latestModelForTier('deep'));
|
|
19
29
|
const effort = String((model === plannedModel
|
|
20
30
|
? planned?.routed_model_reasoning_effort || planned?.model_reasoning_effort
|
|
21
31
|
: null) || role?.model_reasoning_effort || 'medium').trim();
|
|
@@ -1,21 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { latestTierModelSet } from '../subagents/model-tiers.js';
|
|
2
2
|
const SPAWN_TOOLS = new Set(['spawn_agent', 'collaboration.spawn_agent', 'functions.spawn_agent']);
|
|
3
|
-
const SEALED_CHILD_MODELS = new Set([
|
|
4
|
-
ASTRA_SUBAGENT_MODEL,
|
|
5
|
-
NARUTO_LUNA_MODEL,
|
|
6
|
-
NARUTO_SOL_MODEL,
|
|
7
|
-
NARUTO_TERRA_MODEL
|
|
8
|
-
]);
|
|
9
3
|
export function subagentSpawnPolicyBlockReason(payload = {}) {
|
|
10
4
|
const name = String(payload.tool_name || payload.toolName || payload.tool?.name || '');
|
|
11
5
|
if (!SPAWN_TOOLS.has(name))
|
|
12
6
|
return null;
|
|
13
7
|
const input = payload.tool_input || payload.toolInput || payload.tool?.input || {};
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
const allowed = latestTierModelSet();
|
|
9
|
+
if (!allowed.has(String(input.model || ''))) {
|
|
10
|
+
return `SKS children must name a current model: ${[...allowed].join(', ')} (the latest fast, balanced, context, or deep tier). Retry spawn_agent with the slice contract model, which the SKS hook re-seals when Jev mode is on, and fork_turns="none" or a positive bounded turn count. Include the complete slice contract in message; do not inherit the parent model.`;
|
|
16
11
|
}
|
|
17
12
|
if (input.fork_turns !== 'none' && !/^[1-9]\d*$/.test(String(input.fork_turns || ''))) {
|
|
18
|
-
return 'SKS
|
|
13
|
+
return 'SKS child spawns require fork_turns="none" or a positive bounded turn count. Retry with the complete slice contract in message; full-history/default forks cannot carry an explicit child model.';
|
|
19
14
|
}
|
|
20
15
|
return null;
|
|
21
16
|
}
|
|
@@ -31,6 +31,8 @@ import { classifyTaskProfile } from './runtime/task-profile.js';
|
|
|
31
31
|
import { resolveSubagentThreadBudget } from './subagents/thread-budget.js';
|
|
32
32
|
import { readOfficialSubagentConfig } from './subagents/official-subagent-config.js';
|
|
33
33
|
import { jevSpawnModelRewrite } from './hooks-runtime/jev-spawn-routing.js';
|
|
34
|
+
import { maybeReconcileManagedGuidancePreflight } from './hooks-runtime/managed-guidance-preflight.js';
|
|
35
|
+
import { evaluateParentOrchestrationGate, isSpawnToolPayload, recordParentOrchestrationSpawn } from './hooks-runtime/parent-orchestration-gate.js';
|
|
34
36
|
import { subagentSpawnPolicyBlockReason } from './hooks-runtime/subagent-spawn-policy.js';
|
|
35
37
|
import { withFileLock } from './locks/file-lock.js';
|
|
36
38
|
import { ensureConfinedDirectory, inspectConfinedPath, removeManagedPathVerified } from './managed-path-safety.js';
|
|
@@ -120,9 +122,15 @@ export async function evaluateHookPayload(name, payload = {}, opts = {}) {
|
|
|
120
122
|
parentLaunchMissionId: activeNarutoParentLaunchMissionId()
|
|
121
123
|
});
|
|
122
124
|
const withNarutoDecision = (result) => ({ ...result, sksNarutoDecision });
|
|
125
|
+
if (name === 'user-prompt-submit' || name === 'session-start') {
|
|
126
|
+
await maybeReconcileManagedGuidancePreflight(root).catch(() => null);
|
|
127
|
+
}
|
|
123
128
|
if (name === 'user-prompt-submit') {
|
|
124
129
|
const result = await hookUserPrompt(root, state, payload, noQuestion, sessionKey);
|
|
125
|
-
const
|
|
130
|
+
const orchestrationRequired = sksNarutoDecision.required === true
|
|
131
|
+
&& (sksNarutoDecision.action === 'prepare_naruto' || sksNarutoDecision.action === 'observe_required');
|
|
132
|
+
const withOrchestration = attachParentOrchestrationDirective(result, orchestrationRequired);
|
|
133
|
+
const withJev = await attachJevTurnRouting(root, payload, withOrchestration, orchestrationRequired);
|
|
126
134
|
const withSkillContext = await attachAuthoritativeSksSkillContext(root, state, payload, withJev);
|
|
127
135
|
return withNarutoDecision(attachOfficialSubagentSpawnCompatibilityContext(state, payload, withSkillContext));
|
|
128
136
|
}
|
|
@@ -169,7 +177,7 @@ async function hookSubagentStart(root, state, payload = {}, sessionKey = null) {
|
|
|
169
177
|
: '';
|
|
170
178
|
const resourceGuard = skillGuardBinding ? [
|
|
171
179
|
`SKS Naruto policy: max_threads frame budget is ${budget.maxThreads} (cap, not a spawn target).`,
|
|
172
|
-
'
|
|
180
|
+
'Keep the model and reasoning effort your spawn call sealed; do not retarget them.',
|
|
173
181
|
'Use max_depth=1. Naruto children must not spawn children.',
|
|
174
182
|
'Do not duplicate an already assigned slice.',
|
|
175
183
|
'Parallel writes require disjoint paths; serialize overlapping paths.',
|
|
@@ -208,6 +216,7 @@ async function hookSubagentStart(root, state, payload = {}, sessionKey = null) {
|
|
|
208
216
|
}
|
|
209
217
|
}
|
|
210
218
|
if (skillGuardBinding && artifactDirSafe) {
|
|
219
|
+
await recordParentOrchestrationSpawn(root, state, { atLeastOne: true }).catch(() => null);
|
|
211
220
|
try {
|
|
212
221
|
await recordAndRefreshSubagentEvidence(root, state, payload, 'SubagentStart', sessionKey);
|
|
213
222
|
}
|
|
@@ -236,7 +245,24 @@ async function hookSubagentStart(root, state, payload = {}, sessionKey = null) {
|
|
|
236
245
|
const additionalContext = [coreEngineeringDirectiveReferenceText(), resourceGuard, routingContext, active, skillContext].filter(Boolean).join('\n\n');
|
|
237
246
|
return { continue: true, additionalContext, ...(skillBlockers.length ? { silent: true } : {}) };
|
|
238
247
|
}
|
|
239
|
-
|
|
248
|
+
function attachParentOrchestrationDirective(result, orchestrationRequired) {
|
|
249
|
+
if (!orchestrationRequired || !result || result.decision === 'block')
|
|
250
|
+
return result;
|
|
251
|
+
const line = [
|
|
252
|
+
'SKS parent orchestration gate is armed for this turn.',
|
|
253
|
+
'The parent thread orchestrates only: decompose the task into disjoint slices, spawn each child with spawn_agent',
|
|
254
|
+
'(sealed model and reasoning_effort, fork_turns="none", complete slice contract in message), wait, then integrate and verify.',
|
|
255
|
+
'The PreToolUse hook denies parent-thread source edits until the first child thread starts and while children are still running; .sneakoscope artifacts stay parent-writable.'
|
|
256
|
+
].join(' ');
|
|
257
|
+
const additionalContext = [result.additionalContext, line].filter(Boolean).join('\n\n');
|
|
258
|
+
return {
|
|
259
|
+
...result,
|
|
260
|
+
additionalContext,
|
|
261
|
+
parent_orchestration_gate: 'armed',
|
|
262
|
+
...(result.systemMessage ? { systemMessage: visibleHookMessage('user-prompt-submit', additionalContext) } : {})
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
async function attachJevTurnRouting(root, payload, result, orchestrationRequired = false) {
|
|
240
266
|
if (!result || result.decision === 'block')
|
|
241
267
|
return result;
|
|
242
268
|
const prompt = stripVisibleDecisionAnswerBlocks(extractUserPrompt(payload));
|
|
@@ -246,7 +272,9 @@ async function attachJevTurnRouting(root, payload, result) {
|
|
|
246
272
|
if (!decision?.called)
|
|
247
273
|
return result;
|
|
248
274
|
const line = decision.model
|
|
249
|
-
?
|
|
275
|
+
? orchestrationRequired
|
|
276
|
+
? `Jev rated this task as ${decision.model} (${decision.effort}). Use that as the default child seal; Jev re-seals every spawn_agent call and the parent model stays as the user set it.`
|
|
277
|
+
: `Jev rated this turn as ${decision.effort}-effort work (${decision.model} tier). Treat it as this turn's reasoning hint; the parent model, effort, and service tier stay as the user set them.`
|
|
250
278
|
: '';
|
|
251
279
|
if (!line)
|
|
252
280
|
return { ...result, jev_turn: decision };
|
|
@@ -664,18 +692,28 @@ async function hookPreTool(root, state, payload, noQuestion, sessionKey = null)
|
|
|
664
692
|
return agentRecursionDecision;
|
|
665
693
|
if (noQuestion && looksInteractiveCommand(command))
|
|
666
694
|
return { decision: 'block', reason: interactiveCommandReason(command) };
|
|
695
|
+
const orchestration = await evaluateParentOrchestrationGate({ root, state, payload, sessionKey })
|
|
696
|
+
.catch(() => null);
|
|
697
|
+
if (orchestration?.action === 'block') {
|
|
698
|
+
return { decision: 'block', permissionDecision: 'deny', reason: orchestration.message };
|
|
699
|
+
}
|
|
667
700
|
const hostCapabilityDecision = await enforceHostCapabilityPreTool(root, state, payload, sessionKey);
|
|
668
701
|
if (hostCapabilityDecision && hostCapabilityDecision.continue !== true)
|
|
669
702
|
return hostCapabilityDecision;
|
|
703
|
+
if (isSpawnToolPayload(spawnPayload))
|
|
704
|
+
await recordParentOrchestrationSpawn(root, state).catch(() => null);
|
|
670
705
|
const waveGuidance = await parentWaveGuidanceContext(root, state, sessionKey).catch(() => '');
|
|
671
|
-
const
|
|
706
|
+
const escapeNote = orchestration?.action === 'escape' ? orchestration.message || '' : '';
|
|
707
|
+
const additionalContext = [skillRefresh.context, waveGuidance, escapeNote].filter(Boolean).join('\n\n');
|
|
672
708
|
if (additionalContext) {
|
|
673
709
|
return withJevSpawnRewrite({
|
|
674
710
|
continue: true,
|
|
675
711
|
additionalContext,
|
|
676
|
-
...(
|
|
677
|
-
? { systemMessage: visibleHookMessage('pre-tool',
|
|
678
|
-
:
|
|
712
|
+
...(escapeNote
|
|
713
|
+
? { systemMessage: visibleHookMessage('pre-tool', escapeNote), parent_orchestration_gate: 'escaped' }
|
|
714
|
+
: waveGuidance
|
|
715
|
+
? { systemMessage: visibleHookMessage('pre-tool', 'SKS Naruto wave lifecycle requires root-parent follow-up.') }
|
|
716
|
+
: { silent: true })
|
|
679
717
|
}, jevSpawnInput);
|
|
680
718
|
}
|
|
681
719
|
return withJevSpawnRewrite({ continue: true }, jevSpawnInput);
|
|
@@ -33,7 +33,11 @@ export const REMOVED_SKS_SKILL_NAMES = [
|
|
|
33
33
|
'ralph-supervisor',
|
|
34
34
|
'ralph-resolver',
|
|
35
35
|
'loop',
|
|
36
|
-
'sks-loop'
|
|
36
|
+
'sks-loop',
|
|
37
|
+
'with-local-llm-on',
|
|
38
|
+
'with-local-llm-off',
|
|
39
|
+
'sks-with-local-llm-on',
|
|
40
|
+
'sks-with-local-llm-off'
|
|
37
41
|
];
|
|
38
42
|
export const LEGACY_SKS_SUPPORT_SKILL_NAMES = [
|
|
39
43
|
'autoresearch-loop',
|
package/dist/core/init/skills.js
CHANGED
|
@@ -40,7 +40,7 @@ async function installOfficialSkills(root) {
|
|
|
40
40
|
const skills = {
|
|
41
41
|
'dfix': `---\nname: dfix\ndescription: Apply $sks-dfix only to tiny copy, config, or mechanical edits.\n---\n\nUse for tiny copy/config/docs/labels/spacing/translation/simple mechanical edits. List exact micro-edits, inspect only needed files, apply only those edits, and run cheap verification. Keep broad implementation out of DFix; for UI/UX micro-edits read \`design.md\` when present and use imagegen for image/logo/raster assets. Bypass broad SKS routing, mission state, TriWiki/TriFix/reflection/state recording, Goal, Research, eval, redesign, and repeated full-route Honest Mode loops. Report the change, actual verification, and remaining issues once. ${CODEX_IMAGEGEN_REQUIRED_POLICY}\n`,
|
|
42
42
|
'answer': `---\nname: answer\ndescription: Answer questions with research or docs; never implement.\n---\n\nUse for explanations, comparisons, status, facts, source-backed research, or docs guidance. Use repo/TriWiki first for project-local facts; hydrate low-trust claims from source. Browse or use Context7 for current external package/API/framework/MCP docs. End with a concise answer that separates what was verified from what was inferred; do not create missions, subagents, or file edits.\n`,
|
|
43
|
-
'sks': `---\nname: sks\ndescription: Use $sks for setup, status, conflicts, or SKS commands.\n---\n\nUse local SKS commands: bootstrap, deps, commands, quickstart, codex-app, context7, guard, conflicts, reasoning, wiki, pipeline status, pipeline plan, skill-dream.
|
|
43
|
+
'sks': `---\nname: sks\ndescription: Use $sks for setup, status, conflicts, or SKS commands.\n---\n\nUse local SKS commands: bootstrap, deps, commands, quickstart, codex-app, context7, guard, conflicts, reasoning, wiki, pipeline status, pipeline plan, skill-dream. Route code-changing work through Naruto, where the parent orchestrates child slices instead of implementing them itself; keep answers, SKS commands, and tiny DFix edits in the current task. Surface route/guard/scope, use TriWiki, do not edit installed harness files outside this engine repo, and clear conflicting third-party Codex harness markers via \`sks conflicts cleanup --yes\`. ${skillDreamPolicyText()}\n`,
|
|
44
44
|
'plan': `---\nname: plan\ndescription: Write a plan artifact only; never edit product or source files.\n---\n\nUse when the user invokes $Plan or asks for a plan-only frontdoor. Produce a concrete plan artifact under .sneakoscope/plans/<slug>.md with goal, scope, files to inspect, implementation steps, acceptance checks, and rollback notes. Do not edit product/source files, generated harness files, package metadata, or docs beyond the plan artifact. Keep implementation_allowed=false and hand off execution to $Work only after the user or route explicitly moves from planning to work. Finish with what is planned, what remains unimplemented, and Honest Mode.\n`,
|
|
45
45
|
'work': `---\nname: work\ndescription: Execute the latest .sneakoscope plan with evidence-gated close.\n---\n\nUse when the user invokes $Work or asks to execute the latest SKS plan. Resolve the newest .sneakoscope/plans/*.md, route execution through Naruto evidence gates, keep leases and verification artifacts current, and do not claim completion without machine evidence or explicit blocker evidence. If no plan exists, block with a clear next action: run $Plan first or provide a task.\n`,
|
|
46
46
|
'review': `---\nname: review\ndescription: Review a selected diff with machine evidence first.\n---\n\nUse when the user asks for $Review or sks review. Review the selected diff read-only unless --fix is explicitly supplied. Machine evidence such as TypeScript, lint, tests, conflict markers, or secret scans outranks LLM findings and must be tagged evidence: machine; judgment-only findings must be tagged evidence: llm. --fix may attempt at most one machine-evidence fix pass and must re-run verification once. Do not mutate code for LLM-only opinions.\n`,
|
|
@@ -65,10 +65,10 @@ async function installOfficialSkills(root) {
|
|
|
65
65
|
'mad-sks': `---\nname: mad-sks\ndescription: Use $sks-mad-sks only for explicit high-risk SQL or grants.\n---\n\nUse only when the user explicitly invokes $MAD-SKS, top-level sks --mad, or \`sks mad-sks plan|run|apply|sql|apply-migration|status|close|rollback-apply\`. MAD-SKS is the single high-risk route for scoped permission widening and bound SQL-plane execution. It can be combined with another route, such as $MAD-SKS $Naruto or $DB ... $MAD-SKS; in that case the other command remains the primary workflow and MAD-SKS is the temporary permission grant or SQL-plane executor. The widened permission applies only while the active mission gate is open, must be deactivated when the task ends, and can open approved scopes such as target-project file writes, shell commands, package installs, local service control, network operations, browser/Computer Use workflows, generated assets, file permissions, migrations, Supabase MCP database writes, column/schema cleanup, direct execute SQL, and normal targeted DB writes.\n\nSQL-plane policy:\n${canonicalMadSksSqlPlanePolicy}\n\nCatastrophic SQL boundary: TRUNCATE, all-row UPDATE/DELETE, table/schema/database DROP, and equivalent reset operations are allowed only through the SQL-plane executor and only when the user's prompt or CLI SQL statement literally names that operation. Other MAD-SKS executors, including db-write, keep those catastrophic categories blocked. Whole database/schema/table removal outside SQL-plane, dangerous project/branch management, credential exfiltration, persistent security weakening, destructive delete without explicit confirmation, and unrequested fallback implementation remain blocked. Do not carry MAD-SKS permission into later prompts or routes. The permission profile source is centralized in src/core/permission-gates.ts and emitted as dist/core/permission-gates.js so skill/hook/MCP-style gates share one decision function.\n`,
|
|
66
66
|
'gx': `---\nname: gx\ndescription: Run $sks-gx to render or validate a GX visual cartridge.\n---\n\nUse when the user invokes $GX/$gx or asks for architecture/context visualization through SKS. Prefer sks gx init, render, validate, drift, and snapshot. vgraph.json remains the source of truth.\n`,
|
|
67
67
|
'help': `---\nname: help\ndescription: Explain installed SKS commands via $sks-help.\n---\n\nUse when the user invokes $Help/$help or asks what commands exist. Prefer concise output from sks commands, sks usage <topic>, sks quickstart, sks aliases, and sks codex-app.\n`,
|
|
68
|
-
'prompt-pipeline': `---\nname: prompt-pipeline\ndescription: Route an execution request; Answer and DFix stay light.\n---\n\nClassify only the route and the constraints that change execution. Answer handles real information requests; DFix handles tiny copy/config/docs/labels/spacing/translation/mechanical edits
|
|
68
|
+
'prompt-pipeline': `---\nname: prompt-pipeline\ndescription: Route an execution request; Answer and DFix stay light.\n---\n\nClassify only the route and the constraints that change execution. Answer handles real information requests; DFix handles tiny copy/config/docs/labels/spacing/translation/mechanical edits. Answer and tiny DFix stay parent-owned. Naruto implementation is parent orchestration: spawn disjoint slices and do not implement them in the parent thread; the PreToolUse gate denies parent source edits until the first child starts and while children are running. Preserve the literal user request; TriWiki may enrich it but never replace an explicit requirement. ${coreEngineeringDirectiveReferenceText()} For code work, identify the requested outcome, actual code/data flow, exact write scope, acceptance criteria, and the smallest meaningful verification. Load route-specific Design, PPT, image, browser, research, DB, release, or Context7 rules only when that route or external contract requires them. Materialize pipeline-plan.json with route, scopes, verification, and blockers, then finish the requested work instead of stopping at the plan.\n`,
|
|
69
69
|
[SOLUTION_SCOUT_SKILL_NAME]: `---\nname: ${SOLUTION_SCOUT_SKILL_NAME}\ndescription: Scout external precedent only when the local path needs it.\n---\n\nUse only when the user asks for similar cases or when a package, API, platform, or externally documented behavior makes outside precedent materially useful. Search with the concrete symptom and current stack, prefer primary sources, and keep the local code path authoritative. Do not add this step to ordinary local bugfixes. If external evidence is unavailable, record that gap instead of inventing a workaround.\n`,
|
|
70
|
-
'reasoning-router': `---\nname: reasoning-router\ndescription: Inspect reasoning hints; keep the user's Codex settings.\n---\n\nUse the active Codex model catalog for supported reasoning efforts. GPT-6 Astra does not support none/minimal; use low when migrating those efforts, and preserve other supported choices. Task hints are advisory: do not override the selected model, effort, or service tier or persist profile changes without a request. Inspect with sks reasoning and sks pipeline status.
|
|
71
|
-
'pipeline-runner': `---\nname: pipeline-runner\ndescription: Execute the selected SKS route with bounded evidence.\n---\n\nEvery $ command selects a route. Use current.json, mission artifacts, and pipeline-plan.json as bounded execution state; load only the selected route skill. ${coreEngineeringDirectiveReferenceText()} Codex native /goal is the only persisted goal owner.
|
|
70
|
+
'reasoning-router': `---\nname: reasoning-router\ndescription: Inspect reasoning hints; keep the user's Codex settings.\n---\n\nUse the active Codex model catalog for supported reasoning efforts. GPT-6 Astra does not support none/minimal; use low when migrating those efforts, and preserve other supported choices. Task hints are advisory: do not override the selected model, effort, or service tier or persist profile changes without a request. Inspect with sks reasoning and sks pipeline status. When Jev mode is on, each new child spawn is sealed by Jev and must not be replaced with a parent-chosen model. Off mode uses gpt-6-astra. A user role preference stays authoritative.\n`,
|
|
71
|
+
'pipeline-runner': `---\nname: pipeline-runner\ndescription: Execute the selected SKS route with bounded evidence.\n---\n\nEvery $ command selects a route. Use current.json, mission artifacts, and pipeline-plan.json as bounded execution state; load only the selected route skill. ${coreEngineeringDirectiveReferenceText()} Codex native /goal is the only persisted goal owner. When the route requires official Codex subagents, the parent decomposes, spawns children, waits, and owns integration and verification without implementing slice work itself; use bounded TriWiki context, fetch current external docs only when relevant, record real blockers, and finish with a completion summary that states what was verified and what remains. Reflection and Honest Mode sections are strict-profile requirements only.\n`,
|
|
72
72
|
'context7-docs': `---\nname: context7-docs\ndescription: Fetch Context7 docs when an external library contract matters.\n---\n\nWhen an external library, framework, API, MCP, or generated-docs contract matters, resolve-library-id, then query-docs for the resolved id. Legacy get-library-docs evidence is accepted. Prefer sks context7 tools/resolve/docs/evidence and finish only after both evidence stages exist. Check setup with sks context7 check.\n`,
|
|
73
73
|
'super-search': `---\nname: super-search\ndescription: Run $sks-super-search for provider-independent source proof.\n---\n\nUse when the user invokes $Super-Search or asks for provider-independent source proof, X-search collection, or URL acquisition. Prefer \`sks super-search doctor --json\` for readiness and \`sks super-search run "<query>" --mode balanced --json\` for provider-independent source proof; use \`sks super-search x "<query>" --json\` for X-search intent and \`sks super-search fetch "<url>" --json\` for URL acquisition. Context7 is required only when the query depends on current package/API/framework/MCP/generated documentation behavior. Provider-specific credentials are optional and must not be required for route readiness. Evidence/artifacts remain under \`.sneakoscope/missions/<super-search-* or route mission>/super-search/\`: intent.json, axes.json, query-variants.json, provider-plan.json, source-ledger.json, lead-ledger.json, claim-ledger.json, attempt-ledger.json, synthesis.md, super-search-proof.json, super-search-gate.json, and super-search-result.json. Do not turn weak discovery into supported claims; finish with an Honest Mode summary of verified sources, blockers, and unverified external coverage.\n`,
|
|
74
74
|
'search-visibility-core': `---\nname: search-visibility-core\ndescription: Shared SEO/GEO kernel for $sks-seo-geo-optimizer evidence.\n---\n\nPurpose: keep Search Engine Optimization and Generative Engine Optimization on one typed search-visibility kernel instead of duplicate implementations. Use when $SEO-GEO-OPTIMIZER or \`sks seo-geo-optimizer\` is selected. Workflow: doctor detects package/static/Next evidence; audit writes source-backed inventory and findings; plan compiles safe mutation operations; apply requires explicit \`--apply\`; verify separates source, build, HTTP, browser, production, and measured outcome; rollback only reverses mission-owned operations. Safety: default read-only, never overwrite unmanaged robots.txt, sitemap, llms.txt, metadata, or structured data; do not hard-code customer routes; do not invent prices, reviews, availability, rankings, traffic, or AI citation outcomes. Evidence/artifacts: search-visibility/intake.json, adapter-detection.json, site-inventory.json, route-graph.json, robots-policy.json, structured-data-ledger.json, mutation-plan.json, mutation-journal.jsonl, rollback-manifest.json, verification-report.json, route gate, and completion-proof.json. Failure/recovery: unsupported frameworks stay audit/plan-only; missing production/browser/Search Console evidence remains unverified, not fabricated. CLI entrypoint: \`sks seo-geo-optimizer ... --mode seo|geo\`.\n`,
|