sneakoscope 10.3.1 → 10.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.
17
17
  <!-- END SKS SEARCH VISIBILITY MARKETING -->
18
18
 
19
- Current package: **SKS 10.3.1**. Install the latest stable release from npm.
19
+ Current package: **SKS 10.3.3**. Install the latest stable release from npm.
20
20
 
21
21
  [Quick start](#install-in-one-command) · [Commands](#everyday-commands) · [SKS Center](#sks-center-macos) · [Documentation](#documentation) · [Changelog](CHANGELOG.md)
22
22
 
@@ -324,6 +324,7 @@
324
324
  {
325
325
  "canonical_name": "sks-naruto",
326
326
  "trusted_sha256": [
327
+ "65e19a5d16f486b13541048c239e828f8039bfc6e38bafd7949d9c936fe4b5b2",
327
328
  "da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
328
329
  "b981bea4183376c6c0204941ad6c2e78782e2cdea4f8d25cc85630c609faaf95",
329
330
  "5df9c9678eb68fd79ceb998ae7a52dd54dc92336ad5169e8a01acca89f76bd60",
@@ -372,6 +373,7 @@
372
373
  {
373
374
  "canonical_name": "sks-prompt-pipeline",
374
375
  "trusted_sha256": [
376
+ "93fd938dccd65d9b4d1dd140b60e1baedc627e7fcc54f9afd95eb2e44efd1143",
375
377
  "033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
376
378
  "e7c8ef56d38fdc4879c983f7d6706f26fc1cf0a60c508836bd628e815700bcc3",
377
379
  "480c8afffafa9d52c483a963b4b8fafdffc0a47cd6d2efd3fb6d7924c11832c5",
@@ -388,6 +390,7 @@
388
390
  {
389
391
  "canonical_name": "sks-reasoning-router",
390
392
  "trusted_sha256": [
393
+ "41e14e8d5b93d743096664869ee68c41c86e911b0bbc165bdcd10e4a45faa4b6",
391
394
  "03d68436e568b3cee2bffc6ca58819019ca406f7b648ef4704e3465c7c83c114",
392
395
  "647f730520a1c7dc89c2642f5b8502624c65a861ae41aecf1fb137db40fd0611",
393
396
  "da0ddb58adc68ddeebdb9c45bf111c26eaa64d3e9dffe1dd39d7f30b943b8b88",
@@ -259,7 +259,7 @@ dependencies = [
259
259
 
260
260
  [[package]]
261
261
  name = "sks-core"
262
- version = "10.3.1"
262
+ version = "10.3.3"
263
263
  dependencies = [
264
264
  "globset",
265
265
  "grep-matcher",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "10.3.1"
3
+ version = "10.3.3"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema": "sks.skills-manifest.v1",
3
- "package_version": "10.3.1",
3
+ "package_version": "10.3.3",
4
4
  "skills": [
5
5
  {
6
6
  "canonical_name": "sks",
@@ -393,8 +393,9 @@
393
393
  {
394
394
  "canonical_name": "sks-naruto",
395
395
  "type": "core",
396
- "content_sha256": "da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
396
+ "content_sha256": "65e19a5d16f486b13541048c239e828f8039bfc6e38bafd7949d9c936fe4b5b2",
397
397
  "hash_history": [
398
+ "da43debf97f0b4a1583d8202a7306aca97ac97865accf9bca8f41d75c84a1cb9",
398
399
  "b981bea4183376c6c0204941ad6c2e78782e2cdea4f8d25cc85630c609faaf95",
399
400
  "5df9c9678eb68fd79ceb998ae7a52dd54dc92336ad5169e8a01acca89f76bd60",
400
401
  "a1219f6dbad9c8c185953125ef0b8dccf081aa01efb8e8acaa543c6214ac049c",
@@ -451,8 +452,9 @@
451
452
  {
452
453
  "canonical_name": "sks-prompt-pipeline",
453
454
  "type": "official",
454
- "content_sha256": "033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
455
+ "content_sha256": "93fd938dccd65d9b4d1dd140b60e1baedc627e7fcc54f9afd95eb2e44efd1143",
455
456
  "hash_history": [
457
+ "033bb114ec1fac3f8c312f698b1861ed4c2ce6f65c06f4f1a872c4b101b74b92",
456
458
  "e7c8ef56d38fdc4879c983f7d6706f26fc1cf0a60c508836bd628e815700bcc3",
457
459
  "480c8afffafa9d52c483a963b4b8fafdffc0a47cd6d2efd3fb6d7924c11832c5",
458
460
  "5dc8524de9bad66cef133ea5ff2a8052331bd12a7ec85695f727d0d2c8523145"
@@ -471,8 +473,9 @@
471
473
  {
472
474
  "canonical_name": "sks-reasoning-router",
473
475
  "type": "official",
474
- "content_sha256": "03d68436e568b3cee2bffc6ca58819019ca406f7b648ef4704e3465c7c83c114",
476
+ "content_sha256": "41e14e8d5b93d743096664869ee68c41c86e911b0bbc165bdcd10e4a45faa4b6",
475
477
  "hash_history": [
478
+ "03d68436e568b3cee2bffc6ca58819019ca406f7b648ef4704e3465c7c83c114",
476
479
  "647f730520a1c7dc89c2642f5b8502624c65a861ae41aecf1fb137db40fd0611",
477
480
  "da0ddb58adc68ddeebdb9c45bf111c26eaa64d3e9dffe1dd39d7f30b943b8b88",
478
481
  "1595beabf2d31397217311fa1323bd62c801cf6c87db9029ff544c457efec8bb",
@@ -1,5 +1,7 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
1
3
  import path from 'node:path';
2
- import { ensureDir, nowIso, readText, writeTextAtomic } from '../fsx.js';
4
+ import { ensureDir, nowIso, packageRoot, readText, writeTextAtomic } from '../fsx.js';
3
5
  import { CODEX_HOOK_EVENTS } from '../codex-compat/codex-hook-events.js';
4
6
  import { postToolEvidenceEnabled } from '../verification-profile.js';
5
7
  import { buildCodexCommandHookToml, matcherApplies } from './codex-hook-config-writer.js';
@@ -22,7 +24,7 @@ export async function installManagedCodexHooks(root, opts = {}) {
22
24
  const requirementsPath = path.resolve(root, opts.requirementsPath || path.join('.codex', 'requirements.toml'));
23
25
  const scriptPath = path.join(managedDir, 'sks-managed-hook.sh');
24
26
  const tomlPath = path.join(managedDir, 'sks-managed-hooks.toml');
25
- const binCommand = opts.binCommand || 'sks hook';
27
+ const binCommand = opts.binCommand || await defaultManagedHookCommand();
26
28
  const installedEvents = CODEX_HOOK_EVENTS.filter((event) => event !== 'PostToolUse' || postToolEvidenceEnabled(root));
27
29
  const hooksToml = installedEvents.map((event) => buildCodexCommandHookToml({
28
30
  event,
@@ -77,6 +79,32 @@ export async function installManagedCodexHooks(root, opts = {}) {
77
79
  blockers: actual?.blockers || []
78
80
  };
79
81
  }
82
+ export async function defaultManagedHookCommand() {
83
+ const entrypoint = path.join(packageRoot(), 'dist', 'bin', 'sks.js');
84
+ if (!(await isRegularFile(entrypoint)))
85
+ return 'sks hook';
86
+ return `${shellQuote(process.execPath)} ${shellQuote(entrypoint)} hook`;
87
+ }
88
+ export async function retargetLiveManagedHookScript(env = process.env) {
89
+ const home = env.HOME || os.homedir();
90
+ const codexHome = path.resolve(env.CODEX_HOME || path.join(home, '.codex'));
91
+ const requirementsPath = path.join(codexHome, 'requirements.toml');
92
+ const requirements = await readText(requirementsPath, '');
93
+ const configuredDir = managedDirFromRequirements(String(requirements || ''), requirementsPath);
94
+ const managedDir = configuredDir || path.join(codexHome, 'managed-hooks');
95
+ const resolvedDir = path.resolve(managedDir);
96
+ const script = path.join(resolvedDir, 'sks-managed-hook.sh');
97
+ if (!pathIsInside(codexHome, script)) {
98
+ return { ok: false, status: 'absent', script, command: null };
99
+ }
100
+ if (!(await isRegularFile(script))) {
101
+ return { ok: true, status: 'absent', script, command: null };
102
+ }
103
+ const command = await defaultManagedHookCommand();
104
+ await writeTextAtomic(script, managedHookScript(command));
105
+ await chmodExecutable(script);
106
+ return { ok: true, status: 'rewritten', script, command };
107
+ }
80
108
  function managedHookScript(binCommand) {
81
109
  return [
82
110
  '#!/usr/bin/env sh',
@@ -86,6 +114,32 @@ function managedHookScript(binCommand) {
86
114
  `exec ${binCommand} "$subcommand" "$@"`
87
115
  ].join('\n') + '\n';
88
116
  }
117
+ function shellQuote(value) {
118
+ if (/[\0\r\n]/.test(value))
119
+ throw new Error('hook_command_path_invalid');
120
+ return `'${value.replaceAll("'", `'\\''`)}'`;
121
+ }
122
+ function managedDirFromRequirements(text, requirementsPath) {
123
+ const match = text.match(/^\s*managed_dir\s*=\s*(.+)\s*$/m);
124
+ if (!match?.[1])
125
+ return null;
126
+ const raw = match[1].trim().replace(/^['"]|['"]$/g, '');
127
+ if (!raw || raw.includes('\0'))
128
+ return null;
129
+ return path.resolve(path.dirname(requirementsPath), raw);
130
+ }
131
+ function pathIsInside(root, target) {
132
+ const relative = path.relative(path.resolve(root), path.resolve(target));
133
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
134
+ }
135
+ async function isRegularFile(file) {
136
+ try {
137
+ return (await fs.stat(file)).isFile();
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ }
89
143
  function mergeRequirementsToml(existing, opts) {
90
144
  const withoutManagedOnly = String(existing || '')
91
145
  .replace(/^\s*allow_managed_hooks_only\s*=.*$/gm, '')
@@ -16,7 +16,7 @@ const CORE_SKILL_DEFINITIONS = [
16
16
  purpose: 'run a Codex official subagent workflow with official agent threads while parent integration remains owner.',
17
17
  discovery: 'Run $sks-naruto for explicit parallel official subagents.',
18
18
  when: 'Use when the user explicitly invokes $Naruto or the selected route requires bounded parallel delegation.',
19
- workflow: 'Run sks naruto run "<task>" [--agents N] [--max-threads N] [--json] with Codex official subagent threads only. The parent owns decomposition, per-wave capacity, later root-owned waves, integration, and final verification. Automatic targets begin at 4/6/8/16 by task size: bounded, explicit parallel, large-scale, then mass mechanical or exploration fan-out on the Astra Low/Astra Medium lanes. After decomposition both lanes may expand to the SKS-owned 256-child ceiling when independent useful slices and real host capacity remain positive; max_threads defaults to a 256-child frame budget cap, never a target. A measured lower Codex host cap or explicit provider/API budget remains authoritative, and multi-wave scheduling reuses returned capacity. Use gpt-6-astra for every child, preserving parent settings and ignoring child-model overrides. Route tiny mechanical and mass shards to Astra Low, broad search and exploration shards to Astra Medium, instructed ordinary coding execution to Astra Low, and planning, analysis, review, or other judgment to Astra Max. Keep the four task-class profiles and compatibility IDs; explicit Astra effort preferences, including High, remain supported; max_depth=1 blocks nested delegation. Wait for every planned thread before final. In an active Codex App Naruto mission, commit the strict parent evidence with sks naruto parent-summary --mission <id> --stdin, then return localized Markdown without exposing the JSON.',
19
+ workflow: 'Run sks naruto run "<task>" [--agents N] [--max-threads N] [--json] with Codex official subagent threads only. The parent orchestrates only: decompose, assign disjoint slices, spawn, integrate, and verify. Do not implement slice work in the parent thread. The parent owns decomposition, per-wave capacity, later root-owned waves, integration, and final verification. Automatic targets begin at 4/6/8/16 by task size: bounded, explicit parallel, large-scale, then mass mechanical or exploration fan-out. After decomposition both lanes may expand to the SKS-owned 256-child ceiling when independent useful slices and real host capacity remain positive; max_threads defaults to a 256-child frame budget cap, never a target. A measured lower Codex host cap or explicit provider/API budget remains authoritative, and multi-wave scheduling reuses returned capacity. When Jev mode is on, each new child spawn is sealed by Jev to gpt-5.6-luna low, gpt-5.6-sol low, gpt-5.6-terra medium, or gpt-6-astra max; a user role preference stays authoritative, and off mode keeps gpt-6-astra. Do not choose the child model yourself while Jev mode is on. Keep the four task-class profiles and compatibility IDs; explicit Astra effort preferences, including High, remain supported when Jev mode is off or the user asked for that effort; max_depth=1 blocks nested delegation. Wait for every planned thread before final. In an active Codex App Naruto mission, commit the strict parent evidence with sks naruto parent-summary --mission <id> --stdin, then return localized Markdown without exposing the JSON.',
20
20
  safety: 'Preserve user-authored content, inherit the parent permission mode, do not spawn nested subagents, do not inject the full pack or the full TriWiki context into every child, and do not fall back to another model, process runtime, custom scheduler, or worker pool. The historical Naruto process runtime is removed; stop with explicit blocker evidence when the official path is unavailable.',
21
21
  cli: 'sks naruto run "<task>" [--agents N] [--max-threads N] [--json]; sks naruto status|subagents|proof [--mission <id>] [--json]; sks naruto parent-summary --mission <id> --stdin [--json]',
22
22
  evidence: 'subagent-plan.json, subagent-events.jsonl, subagent-parent-summary.json, subagent-evidence.json, naruto-summary.json, and naruto-gate.json.',
@@ -8,7 +8,7 @@ import { applyRecoveryEffect, listProductionRecoveryCandidates } from './recover
8
8
  import { assembleRoutingSelection, buildRoutingCandidates } from './routing.js';
9
9
  import { buildDecisionReceipt } from './receipt.js';
10
10
  import { applyOptionalContextSelection, graphFileDigest, hydrateContextCandidates, sourceSnapshotDigest } from './state.js';
11
- import { DESIGN_DEFAULTS, POLICY_REVISION, UNKNOWN_USAGE } from './types.js';
11
+ import { DESIGN_DEFAULTS, POLICY_REVISION, UNKNOWN_USAGE, sealedRoutingModel } from './types.js';
12
12
  let testOverrides = null;
13
13
  const appliedEffects = new Set();
14
14
  export function setDecisionTestOverrides(overrides) {
@@ -101,6 +101,7 @@ async function decideOfficialSubagentPreparationInner(input, started) {
101
101
  selectedPlan: null,
102
102
  selectedRouting: null,
103
103
  selectedContextIds: null,
104
+ omittedRoutingRoles: [],
104
105
  consumptionEvidence: null
105
106
  };
106
107
  const receipt = buildDecisionReceipt({
@@ -122,6 +123,7 @@ async function decideOfficialSubagentPreparationInner(input, started) {
122
123
  selectedPlan: applied.selectedPlan,
123
124
  selectedRouting: applied.selectedRouting,
124
125
  selectedContextIds: applied.selectedContextIds,
126
+ omittedRoutingRoles: applied.omittedRoutingRoles,
125
127
  llmRejudgeCalls: 0,
126
128
  bundle
127
129
  };
@@ -132,22 +134,24 @@ export async function consultJevTurnModel(input) {
132
134
  const overrides = activeOverrides();
133
135
  const env = input.env || process.env;
134
136
  const config = overrides?.config ?? await readDecisionConfig(env);
137
+ const none = (reason) => ({ called: false, model: null, effort: null, reason });
135
138
  if (!jevEnabled(config))
136
- return { called: false, model: null, reason: 'off' };
139
+ return none('off');
137
140
  const resolved = await resolveOpenRouterApiKey({ env });
138
141
  if (!resolved.key)
139
- return { called: false, model: null, reason: 'missing_key' };
142
+ return none('missing_key');
140
143
  const prompt = String(input.prompt || '').trim();
141
144
  if (!prompt)
142
- return { called: false, model: null, reason: 'empty_prompt' };
145
+ return none('empty_prompt');
146
+ const roleId = input.roleId === 'spawn' ? 'spawn' : 'turn';
143
147
  const bundle = buildDecisionBundle({
144
148
  projectId: sha256(input.root).slice(0, 32),
145
- workflowRunId: 'turn',
146
- workflowRevision: 'turn',
147
- sourceDigest: 'turn',
149
+ workflowRunId: roleId,
150
+ workflowRevision: roleId,
151
+ sourceDigest: roleId,
148
152
  graphDigest: null,
149
153
  goal: prompt,
150
- routingCandidates: [{ id: 'turn', summary: prompt.slice(0, 240) }]
154
+ routingCandidates: [{ id: roleId, summary: prompt.slice(0, 240) }]
151
155
  });
152
156
  const transport = await requestOpenRouterDecision(bundle, {
153
157
  env,
@@ -155,13 +159,14 @@ export async function consultJevTurnModel(input) {
155
159
  ...(overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {})
156
160
  });
157
161
  if (!transport.ok)
158
- return { called: true, model: null, reason: transport.reason };
162
+ return { called: true, model: null, effort: null, reason: transport.reason };
159
163
  const compiled = compileDecision(bundle, transport.response);
160
164
  if (compiled.kind !== 'apply')
161
- return { called: true, model: null, reason: compiled.reason };
165
+ return { called: true, model: null, effort: null, reason: compiled.reason };
162
166
  const selected = assembleRoutingSelection(compiled.effects.flatMap((effect) => (effect.kind === 'select_routing' ? [{ roleId: effect.roleId, model: effect.model }] : [])));
163
- const model = selected?.models.turn || null;
164
- return { called: true, model, reason: model ? 'applied' : 'keep_baseline' };
167
+ const model = selected?.models[roleId] || null;
168
+ const sealed = model ? sealedRoutingModel(model) : null;
169
+ return { called: true, model: sealed?.id || null, effort: sealed?.effort || null, reason: sealed ? 'applied' : 'keep_baseline' };
165
170
  }
166
171
  export function effectAlreadyConsumed(identity) {
167
172
  return appliedEffects.has(identity);
@@ -192,6 +197,7 @@ function applyEffects(attention, contextCandidates, planCandidates, compiled) {
192
197
  let selectedContextIds = null;
193
198
  const evidence = [];
194
199
  const routingEffects = [];
200
+ const omittedRoutingRoles = [];
195
201
  for (const effect of compiled.effects) {
196
202
  if (effect.kind === 'select_optional_context') {
197
203
  nextAttention = applyOptionalContextSelection(attention, contextCandidates, effect.keepIds);
@@ -206,6 +212,10 @@ function applyEffects(attention, contextCandidates, planCandidates, compiled) {
206
212
  if (effect.kind === 'select_routing') {
207
213
  routingEffects.push({ roleId: effect.roleId, model: effect.model });
208
214
  }
215
+ if (effect.kind === 'omit_role') {
216
+ omittedRoutingRoles.push(effect.roleId);
217
+ evidence.push(`omit:${effect.roleId}`);
218
+ }
209
219
  }
210
220
  const selectedRouting = assembleRoutingSelection(routingEffects);
211
221
  if (selectedRouting) {
@@ -216,6 +226,7 @@ function applyEffects(attention, contextCandidates, planCandidates, compiled) {
216
226
  selectedPlan,
217
227
  selectedRouting,
218
228
  selectedContextIds,
229
+ omittedRoutingRoles,
219
230
  consumptionEvidence: evidence.length ? evidence.join('|') : null
220
231
  };
221
232
  }
@@ -291,6 +302,7 @@ function keep(input, reason, started) {
291
302
  selectedPlan: null,
292
303
  selectedRouting: null,
293
304
  selectedContextIds: null,
305
+ omittedRoutingRoles: [],
294
306
  llmRejudgeCalls: 0,
295
307
  bundle: null
296
308
  };
@@ -1,4 +1,4 @@
1
- import { CHOICE_MIN_CONFIDENCE, CHOICE_MIN_PROBABILITY, CONTEXT_DROP_NOUL_MAX, DISTRIBUTION_SUM_TOLERANCE, KEEP_BASELINE_CHOICE, NEEDS_EVIDENCE_CHOICE, POLICY_REVISION, ROUTING_RISK_NOUL_MIN, UNKNOWN_USAGE, sealedRoutingModel } from './types.js';
1
+ import { CHOICE_MIN_CONFIDENCE, CHOICE_MIN_PROBABILITY, CONTEXT_DROP_NOUL_MAX, DISTRIBUTION_SUM_TOLERANCE, KEEP_BASELINE_CHOICE, NEEDS_EVIDENCE_CHOICE, POLICY_REVISION, ROUTING_RISK_NOUL_MIN, ROUTING_ROLE_OMIT_NOUL_MAX, UNKNOWN_USAGE, sealedRoutingModel } from './types.js';
2
2
  export function compileDecision(bundle, response) {
3
3
  const usage = usageFromWire(response.usage);
4
4
  const decoded = decodeWireResponse(bundle, response);
@@ -151,6 +151,16 @@ function compileRouting(bundle, answers) {
151
151
  else if (!reason)
152
152
  reason = compiled.reason;
153
153
  }
154
+ for (const [questionId, binding] of Object.entries(bundle.questionBindings)) {
155
+ if (binding.kind !== 'routing_needed')
156
+ continue;
157
+ const answer = answers[questionId];
158
+ if (!answer || answer.type !== 'noul')
159
+ continue;
160
+ if (answer.noul <= ROUTING_ROLE_OMIT_NOUL_MAX) {
161
+ effects.push({ kind: 'omit_role', roleId: binding.roleId });
162
+ }
163
+ }
154
164
  return { effects, reason };
155
165
  }
156
166
  function compileRoleRouting(bundle, answers, roleId) {
@@ -210,6 +210,20 @@ function appendRoutingSpeculation(roles, questions, bindings, recoverySlot) {
210
210
  }
211
211
  };
212
212
  bindings[riskId] = { kind: 'routing_risk', roleId: role.id };
213
+ if (questionRoom(questions, recoverySlot) < 1)
214
+ break;
215
+ const neededId = `needed_${role.id}`;
216
+ const neededInstructions = redactDecisionText(`Does state.task require routing.roles.${role.id} as its own child agent? Omit the role when another listed role already covers the work.`);
217
+ assertIndependentQuestion(neededInstructions);
218
+ questions[neededId] = {
219
+ type: 'noul',
220
+ instructions: neededInstructions,
221
+ criteria: {
222
+ true: 'This role must be spawned to finish the task.',
223
+ false: 'This role is unnecessary. The parent should not spawn it.'
224
+ }
225
+ };
226
+ bindings[neededId] = { kind: 'routing_needed', roleId: role.id };
213
227
  }
214
228
  }
215
229
  export { QUESTION_REVISION };
@@ -27,6 +27,7 @@ export const ROUTING_DIFFICULTY_RUBRIC = Object.freeze([
27
27
  'Judgment, architecture, ambiguity, security, or other high-stakes work.'
28
28
  ]);
29
29
  export const ROUTING_RISK_NOUL_MIN = 0.70;
30
+ export const ROUTING_ROLE_OMIT_NOUL_MAX = 0.35;
30
31
  export function sealedRoutingModel(model) {
31
32
  const match = SEALED_ROUTING_MODELS.find((row) => row.id === model);
32
33
  return match ? { id: match.id, effort: match.effort } : null;
@@ -1,6 +1,6 @@
1
1
  import { IMAGEGEN_MODEL } from '../imagegen/imagegen-model-policy.js';
2
2
  import { readStdin } from '../fsx.js';
3
- import { buildCompactContinue, buildPermissionRequestAllow, buildPermissionRequestDeny, buildPostToolUseBlock, buildPostToolUseContinue, buildPreToolUseContinue, buildPreToolUseDeny, buildSessionStartContinue, buildStopBlock, buildStopContinue, buildSubagentStartContinue, buildSubagentStopBlock, buildSubagentStopContinue, buildUserPromptSubmitBlock, buildUserPromptSubmitContinue } from '../codex-compat/codex-hook-output-builders.js';
3
+ import { buildCompactContinue, buildPermissionRequestAllow, buildPermissionRequestDeny, buildPostToolUseBlock, buildPostToolUseContinue, buildPreToolUseAllowRewrite, buildPreToolUseContinue, buildPreToolUseDeny, buildSessionStartContinue, buildStopBlock, buildStopContinue, buildSubagentStartContinue, buildSubagentStopBlock, buildSubagentStopContinue, buildUserPromptSubmitBlock, buildUserPromptSubmitContinue } from '../codex-compat/codex-hook-output-builders.js';
4
4
  export async function loadHookPayload() {
5
5
  const raw = await readStdin();
6
6
  try {
@@ -33,6 +33,8 @@ export function normalizeHookResult(name, result = {}) {
33
33
  if (out.decision === 'block' || out.permissionDecision === 'deny' || out.decision === 'deny') {
34
34
  return buildPreToolUseDeny(reason, { systemMessage });
35
35
  }
36
+ if (out.updatedInput)
37
+ return buildPreToolUseAllowRewrite(out.updatedInput, { systemMessage });
36
38
  return buildPreToolUseContinue({ additionalContext: out.additionalContext, systemMessage });
37
39
  }
38
40
  if (eventName === 'PermissionRequest') {
@@ -0,0 +1,50 @@
1
+ import { consultJevTurnModel } from '../decisions/integration.js';
2
+ import { readRoleModelPreferences } from '../subagents/role-model-preferences.js';
3
+ const SPAWN_TOOLS = new Set(['spawn_agent', 'collaboration.spawn_agent', 'functions.spawn_agent']);
4
+ function spawnInput(payload) {
5
+ const name = String(payload?.tool_name || payload?.toolName || payload?.tool?.name || '');
6
+ if (!SPAWN_TOOLS.has(name))
7
+ return null;
8
+ const input = payload?.tool_input || payload?.toolInput || payload?.tool?.input;
9
+ if (!input || typeof input !== 'object' || Array.isArray(input))
10
+ return null;
11
+ return { ...input };
12
+ }
13
+ function narutoParent(state) {
14
+ const mode = String(state?.mode || '').toUpperCase();
15
+ const route = String(state?.route || state?.route_command || '').replace(/^\$/, '').toUpperCase();
16
+ return mode === 'NARUTO' || route === 'NARUTO' || state?.subagents_required === true;
17
+ }
18
+ function spawnTask(input) {
19
+ return [input.message, input.prompt, input.task, input.agent_type, input.agentType]
20
+ .map((value) => String(value || '').trim())
21
+ .filter(Boolean)
22
+ .join('\n')
23
+ .slice(0, 1200);
24
+ }
25
+ export async function jevSpawnModelRewrite(root, state, payload) {
26
+ if (!narutoParent(state))
27
+ return null;
28
+ const input = spawnInput(payload);
29
+ if (!input)
30
+ return null;
31
+ const agent = String(input.agent_type || input.agentType || '').trim();
32
+ if (agent) {
33
+ const preferences = await readRoleModelPreferences().catch(() => null);
34
+ if (preferences?.store.roles[agent])
35
+ return null;
36
+ }
37
+ const task = spawnTask(input);
38
+ if (!task)
39
+ return null;
40
+ const decision = await consultJevTurnModel({ root, prompt: task, roleId: 'spawn' }).catch(() => null);
41
+ if (!decision?.model || !decision.effort)
42
+ return null;
43
+ if (input.model === decision.model && input.reasoning_effort === decision.effort)
44
+ return null;
45
+ return {
46
+ ...input,
47
+ model: decision.model,
48
+ reasoning_effort: decision.effort
49
+ };
50
+ }
@@ -30,6 +30,7 @@ import { activeNarutoParentLaunchMissionId, claimStandaloneParentHostCapabilityR
30
30
  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
+ import { jevSpawnModelRewrite } from './hooks-runtime/jev-spawn-routing.js';
33
34
  import { subagentSpawnPolicyBlockReason } from './hooks-runtime/subagent-spawn-policy.js';
34
35
  import { withFileLock } from './locks/file-lock.js';
35
36
  import { ensureConfinedDirectory, inspectConfinedPath, removeManagedPathVerified } from './managed-path-safety.js';
@@ -596,7 +597,11 @@ async function consumeActiveOfficialWorkflowQueue(root, missionId, workflowRunId
596
597
  }
597
598
  }
598
599
  async function hookPreTool(root, state, payload, noQuestion, sessionKey = null) {
599
- const spawnPolicyBlock = subagentSpawnPolicyBlockReason(payload);
600
+ const jevSpawnInput = await jevSpawnModelRewrite(root, state, payload).catch(() => null);
601
+ const spawnPayload = jevSpawnInput
602
+ ? { ...payload, tool_input: jevSpawnInput, toolInput: jevSpawnInput }
603
+ : payload;
604
+ const spawnPolicyBlock = subagentSpawnPolicyBlockReason(spawnPayload);
600
605
  if (spawnPolicyBlock)
601
606
  return { decision: 'block', permissionDecision: 'deny', reason: spawnPolicyBlock };
602
607
  const artifactDir = officialSubagentArtifactDir(root, state, sessionKey);
@@ -665,15 +670,20 @@ async function hookPreTool(root, state, payload, noQuestion, sessionKey = null)
665
670
  const waveGuidance = await parentWaveGuidanceContext(root, state, sessionKey).catch(() => '');
666
671
  const additionalContext = [skillRefresh.context, waveGuidance].filter(Boolean).join('\n\n');
667
672
  if (additionalContext) {
668
- return {
673
+ return withJevSpawnRewrite({
669
674
  continue: true,
670
675
  additionalContext,
671
676
  ...(waveGuidance
672
677
  ? { systemMessage: visibleHookMessage('pre-tool', 'SKS Naruto wave lifecycle requires root-parent follow-up.') }
673
678
  : { silent: true })
674
- };
679
+ }, jevSpawnInput);
675
680
  }
676
- return { continue: true };
681
+ return withJevSpawnRewrite({ continue: true }, jevSpawnInput);
682
+ }
683
+ function withJevSpawnRewrite(result, updatedInput) {
684
+ if (!updatedInput || result?.decision === 'block' || result?.permissionDecision === 'deny')
685
+ return result;
686
+ return { ...result, permissionDecision: 'allow', updatedInput };
677
687
  }
678
688
  async function parentWaveGuidanceContext(root, state = {}, sessionKey = null) {
679
689
  if (!state?.mission_id && !state?.official_subagent_run_id)
@@ -65,9 +65,9 @@ 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; general work stays parent-owned and uses Naruto only for explicit parallel work or concrete independent slices. 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`,
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. 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. Every child uses gpt-6-astra independently of the parent model; vary only effort by task.\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. 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
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. Use official Codex subagents only when the route requires independent slices, keep parent-owned integration, 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`,
package/dist/core/init.js CHANGED
@@ -274,11 +274,11 @@ const AGENTS_BLOCK = [
274
274
  '## Execution',
275
275
  '',
276
276
  '- Codex native `/goal` is the only persisted goal owner. Goal objectives must state the outcome, scope, constraints, verification, done-when conditions, stop conditions, and non-goals.',
277
- '- General work stays parent-owned. Use `$Naruto` for explicitly requested parallel work or concrete independent slices that benefit from delegation; Answer and tiny DFix work stay lightweight.',
278
- '- The parent owns decomposition, integration, verification, and the final answer. Delegate only independent slices with disjoint write scopes, reuse capacity across root-owned waves, and never nest subagents.',
279
- '- Use gpt-6-astra for every subagent; vary reasoning effort by the slice: Astra Low for tiny mechanical work and instructed ordinary coding execution, Astra Medium for read-heavy context or direct tool operation, and Astra Max for planning, analysis, review, risk, or other focused judgment. Explicit Astra effort preferences, including High, remain supported.',
280
- '- Preserve the user-selected parent model, reasoning effort, and service tier. SKS-launched Naruto defaults to GPT-6 Astra; parent settings and role preferences never override the sealed Astra child profiles.',
281
- '- SKS child spawns must explicitly set model="gpt-6-astra" and the role reasoning effort with `fork_turns="none"` or a positive bounded turn count; carry the complete bounded slice contract in `message`. Never use full-history inheritance for children.',
277
+ '- Answer, Help, Goal, and tiny DFix stay parent-owned. Naruto implementation is parent orchestration only: decompose, assign disjoint slices, spawn, integrate, and verify. Do not implement those slices in the parent thread.',
278
+ '- The parent owns decomposition, integration, verification, and the final answer. Delegate implementation slices with disjoint write scopes, reuse capacity across root-owned waves, and never nest subagents.',
279
+ '- When Jev mode is on, each new Naruto child spawn is sealed by Jev to gpt-5.6-luna low, gpt-5.6-sol low, gpt-5.6-terra medium, or gpt-6-astra max. A user role preference stays authoritative. Off mode keeps gpt-6-astra. Do not pick a child model yourself while Jev mode is on.',
280
+ '- Preserve the user-selected parent model, reasoning effort, and service tier. Parent settings stay on the parent thread. A role preference wins over the Jev seal for that role.',
281
+ '- SKS child spawns must pass the sealed model and reasoning effort with `fork_turns="none"` or a positive bounded turn count; carry the complete bounded slice contract in `message`. Never use full-history inheritance for children. Full-history forks (`fork_turns="all"`, including the omitted default) inherit the parent agent type, model, and reasoning effort and must not be combined with `agent_type`, `model`, or `reasoning_effort`.',
282
282
  '- Route-specific skills own route-specific details. Do not inject unrelated Design, PPT, image, browser, research, DB, or release policy into ordinary work.',
283
283
  '- Do not stop at a plan when implementation was requested. Finish the requested outcome, including verification of the changed path, before stopping for review. Stop early only on a hard blocker or when the stated done-when conditions are met.',
284
284
  '- User instructions outrank skill guidance. Infer routine details, honor authorization already provided, and finish authorized preparation before asking for a decision that changes scope or has irreversible effects.',
@@ -1119,8 +1119,8 @@ export function codexAppQuickReference(scope, commandPrefix) {
1119
1119
  coreEngineeringDirectiveReferenceText(),
1120
1120
  'dollar-commands:',
1121
1121
  ...currentDollarCommands().map((c) => `- \`${sksPrefixedDollarCommand(c.command)}\`: ${c.route}`),
1122
- 'Routing: Answer is read-only, DFix handles tiny edits, and general work stays parent-owned. Use Naruto for explicit parallel work or independent slices that benefit from delegation.',
1123
- 'Subagent context: explicitly set model="gpt-6-astra" and the role reasoning effort with `fork_turns="none"` or a positive bounded turn count. Pass the complete bounded slice contract in `message`; never inherit the parent model or apply a role-model preference.',
1122
+ 'Routing: Answer is read-only, DFix handles tiny edits, and Naruto implementation is parent orchestration with child slices. Answer and tiny DFix stay on the parent.',
1123
+ 'Subagent context: pass the sealed model and reasoning effort with `fork_turns="none"` or a positive bounded turn count. Jev mode seals that model at each new spawn; off mode uses gpt-6-astra. Pass the complete bounded slice contract in `message`. Never use `fork_turns="all"` together with a custom model.',
1124
1124
  'Goal: Codex native /goal is the only persisted goal owner; no SKS Goal mission, bridge, compatibility loop, or fallback state is allowed.',
1125
1125
  'Context: use bounded TriWiki recall when a claim needs project memory; refresh after material changes; validate before handoff/final; use Context7 or official vendor docs when external contracts or versions matter.',
1126
1126
  'Completion: report the result, actual verification, and remaining gaps once. Reflection and Honest Mode are optional unless explicitly requested or required by the strict profile.',
@@ -1211,6 +1211,9 @@ export function narutoDecisionForRoute(route, prompt = '', profile = classifyTas
1211
1211
  return narutoRouteDecision('none', routeId, profile, `task_profile_${profile}_bypass`, true);
1212
1212
  }
1213
1213
  if (profile === 'bounded-work') {
1214
+ if (routeId === 'Naruto') {
1215
+ return narutoRouteDecision('generic_naruto', routeId, profile, 'naruto_parent_orchestrates', false);
1216
+ }
1214
1217
  return narutoRouteDecision('none', routeId, profile, 'task_profile_bounded_work_parent_owned', false);
1215
1218
  }
1216
1219
  if (profile === 'parallel-read' || profile === 'parallel-write' || profile === 'high-risk') {
@@ -461,6 +461,12 @@ function applyOfficialSubagentDecision(derived, decided) {
461
461
  : { ...derived.fanoutPolicy };
462
462
  if (selectedRouting)
463
463
  fanoutPolicy = { ...fanoutPolicy, jev_selected_routing: selectedRouting.id };
464
+ const omitted = new Set(decided.omittedRoutingRoles || []);
465
+ if (omitted.size)
466
+ fanoutPolicy = { ...fanoutPolicy, jev_omitted_roles: [...omitted] };
467
+ const recommendedAgents = omitted.size > 0 && derived.suggestedAgents.some((name) => !omitted.has(name))
468
+ ? derived.suggestedAgents.filter((name) => !omitted.has(name))
469
+ : derived.suggestedAgents;
464
470
  const routingPreferences = selectedRouting
465
471
  ? Object.fromEntries(Object.entries(selectedRouting.efforts)
466
472
  .filter(([name]) => !derived.roleModelPreferences.store.roles[name])
@@ -483,7 +489,7 @@ function applyOfficialSubagentDecision(derived, decided) {
483
489
  waveCount: budget.waveCount,
484
490
  capacity: budget.capacity,
485
491
  triwikiAttention: attention,
486
- recommendedAgents: derived.suggestedAgents,
492
+ recommendedAgents,
487
493
  roleModelPreferences: {
488
494
  ...derived.roleModelPreferences.store.roles,
489
495
  ...routingPreferences
@@ -519,6 +525,7 @@ function applyOfficialSubagentDecision(derived, decided) {
519
525
  }),
520
526
  triwiki_attention: attention,
521
527
  agents,
528
+ suggested_agents: recommendedAgents,
522
529
  fanout_policy: fanoutPolicy,
523
530
  capacity_controller: budget.capacity,
524
531
  jev_decision: decided.receipt,
@@ -53,6 +53,8 @@ export function buildOfficialSubagentPrompt(input) {
53
53
  const spawnModelRouting = renderSpawnModelRouting(narutoChildren);
54
54
  const childModelRules = narutoChildren
55
55
  ? [
56
+ '- the parent orchestrates only: decompose the goal, assign disjoint slices, spawn children, and integrate their results',
57
+ '- do not implement the assigned slice work in the parent thread',
56
58
  '- use the model and reasoning_effort named in each slice spawn contract',
57
59
  '- sealed child models are gpt-5.6-luna, gpt-5.6-sol, gpt-5.6-terra, and gpt-6-astra',
58
60
  '- keep a stored user role-model preference for that role'
@@ -1,3 +1,4 @@
1
+ import { retargetLiveManagedHookScript } from '../../codex-hooks/codex-hook-managed-install.js';
1
2
  import { codexHookTrustDoctor } from '../../codex-hooks/codex-hook-trust-doctor.js';
2
3
  export async function runOtherHarnessCleanupStage(root) {
3
4
  const { cleanupOtherHarnessConflicts, scanHarnessConflicts } = await import('../../harness-conflicts.js');
@@ -38,15 +39,27 @@ export async function runOtherHarnessCleanupStage(root) {
38
39
  }
39
40
  export async function runHookTrustRefreshStage(root) {
40
41
  const result = await codexHookTrustDoctor(root, { fix: true, managed: true, actual: true });
41
- const blockers = result.ok === false
42
- ? (result.blockers || ['hook_trust_refresh_failed'])
43
- : [];
42
+ const liveHook = await retargetLiveManagedHookScript(process.env).catch((err) => ({
43
+ ok: false,
44
+ status: 'absent',
45
+ script: null,
46
+ command: null,
47
+ error: err instanceof Error ? err.message : String(err)
48
+ }));
49
+ const blockers = [
50
+ ...(result.ok === false ? (result.blockers || ['hook_trust_refresh_failed']) : []),
51
+ ...(liveHook.ok === false ? [`live_managed_hook_retarget_failed:${liveHook.error || liveHook.status}`] : [])
52
+ ];
44
53
  return {
45
54
  ok: blockers.length === 0,
46
55
  status: blockers.length ? 'failed' : 'ok',
47
- actions: ['refreshed_hook_trust'],
56
+ actions: ['refreshed_hook_trust', ...(liveHook.status === 'rewritten' ? ['retargeted_live_managed_hook'] : [])],
48
57
  blockers,
49
58
  warnings: result.warnings || [],
50
- detail: { entries: result.current_hash_count ?? null }
59
+ detail: {
60
+ entries: result.current_hash_count ?? null,
61
+ live_hook_status: liveHook.status,
62
+ live_hook_script: liveHook.script
63
+ }
51
64
  };
52
65
  }
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = '10.3.1';
1
+ export const PACKAGE_VERSION = '10.3.3';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "ㅅㅋㅅ",
4
- "version": "10.3.1",
4
+ "version": "10.3.3",
5
5
  "description": "Sneakoscope Codex (`sks`) is an open-source trust layer for Codex CLI and ChatGPT Desktop. It coordinates bounded AI coding agents, records machine-verifiable evidence, preserves project memory, and blocks release claims that are not supported by current tests or artifacts. Search visibility outcomes are measured separately; SKS does not promise rankings or traffic.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",