sneakoscope 2.0.8 → 2.0.10

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.
Files changed (76) hide show
  1. package/README.md +8 -4
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +33 -8
  8. package/dist/cli/command-registry.js +1 -0
  9. package/dist/commands/doctor.js +18 -1
  10. package/dist/commands/zellij-slot-pane.js +26 -0
  11. package/dist/commands/zellij.js +144 -1
  12. package/dist/core/agents/agent-orchestrator.js +202 -9
  13. package/dist/core/agents/agent-role-config.js +92 -0
  14. package/dist/core/agents/native-cli-session-swarm.js +230 -48
  15. package/dist/core/commands/mad-sks-command.js +17 -26
  16. package/dist/core/commands/naruto-command.js +155 -37
  17. package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
  18. package/dist/core/fsx.js +1 -1
  19. package/dist/core/hooks-runtime.js +4 -0
  20. package/dist/core/init.js +1 -0
  21. package/dist/core/naruto/naruto-active-pool.js +141 -0
  22. package/dist/core/naruto/naruto-concurrency-governor.js +17 -2
  23. package/dist/core/naruto/naruto-real-worker-child.js +35 -0
  24. package/dist/core/naruto/naruto-real-worker-runtime.js +121 -0
  25. package/dist/core/naruto/naruto-work-graph.js +2 -1
  26. package/dist/core/release/release-gate-cache-v2.js +58 -4
  27. package/dist/core/release/release-gate-dag.js +36 -25
  28. package/dist/core/version.js +1 -1
  29. package/dist/core/zellij/zellij-dashboard-renderer.js +22 -6
  30. package/dist/core/zellij/zellij-launcher.js +3 -3
  31. package/dist/core/zellij/zellij-layout-builder.js +1 -1
  32. package/dist/core/zellij/zellij-right-column-layout-proof.js +42 -0
  33. package/dist/core/zellij/zellij-right-column-manager.js +304 -0
  34. package/dist/core/zellij/zellij-slot-pane-renderer.js +82 -0
  35. package/dist/core/zellij/zellij-ui-mode.js +16 -0
  36. package/dist/core/zellij/zellij-worker-pane-manager.js +152 -17
  37. package/dist/scripts/agent-role-config-repair-check.js +33 -0
  38. package/dist/scripts/codex-sdk-release-review-pipeline-check.js +5 -5
  39. package/dist/scripts/doctor-fix-proves-codex-read-check.js +26 -5
  40. package/dist/scripts/git-worktree-integration-primary-check.js +4 -2
  41. package/dist/scripts/git-worktree-integration-primary-runtime-check.js +20 -0
  42. package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
  43. package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +2 -2
  44. package/dist/scripts/mutation-callsite-coverage-check.js +2 -1
  45. package/dist/scripts/naruto-concurrency-governor-check.js +2 -1
  46. package/dist/scripts/naruto-extreme-parallelism-check.js +22 -0
  47. package/dist/scripts/naruto-extreme-parallelism-real-check.js +42 -0
  48. package/dist/scripts/naruto-real-active-pool-check.js +39 -0
  49. package/dist/scripts/naruto-real-active-pool-runtime-check.js +53 -0
  50. package/dist/scripts/naruto-work-graph-check.js +1 -1
  51. package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +48 -0
  52. package/dist/scripts/product-design-auto-install-check.js +3 -3
  53. package/dist/scripts/product-design-plugin-routing-check.js +3 -3
  54. package/dist/scripts/readme-architecture-imagegen-official-check.js +4 -3
  55. package/dist/scripts/release-cache-glob-hashing-check.js +42 -0
  56. package/dist/scripts/release-check-dynamic-execute.js +27 -1
  57. package/dist/scripts/release-check-dynamic.js +38 -11
  58. package/dist/scripts/release-check-stamp.js +7 -2
  59. package/dist/scripts/release-dag-full-coverage-check.js +35 -0
  60. package/dist/scripts/release-dynamic-performance-check.js +31 -1
  61. package/dist/scripts/release-gate-existence-audit.js +29 -33
  62. package/dist/scripts/release-parallel-speed-budget-check.js +67 -13
  63. package/dist/scripts/release-readiness-report.js +14 -3
  64. package/dist/scripts/zellij-dashboard-pane-check.js +6 -4
  65. package/dist/scripts/zellij-developer-controls-check.js +20 -0
  66. package/dist/scripts/zellij-dynamic-pane-lifecycle-check.js +21 -0
  67. package/dist/scripts/zellij-initial-main-only-blackbox.js +28 -0
  68. package/dist/scripts/zellij-right-column-geometry-proof.js +162 -0
  69. package/dist/scripts/zellij-right-column-headless-overflow-check.js +22 -0
  70. package/dist/scripts/zellij-right-column-manager-check.js +22 -0
  71. package/dist/scripts/zellij-slot-only-ui-check.js +22 -0
  72. package/dist/scripts/zellij-slot-pane-renderer-check.js +38 -0
  73. package/dist/scripts/zellij-worker-pane-manager-check.js +2 -1
  74. package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +7 -6
  75. package/package.json +23 -5
  76. package/schemas/zellij/zellij-right-column-state.schema.json +41 -0
@@ -10,11 +10,11 @@ import { attachZellijSessionInteractive, launchZellijLayout } from '../zellij/ze
10
10
  import { buildNarutoWorkGraph } from '../naruto/naruto-work-graph.js';
11
11
  import { buildNarutoRoleDistribution } from '../naruto/naruto-role-policy.js';
12
12
  import { decideNarutoConcurrency } from '../naruto/naruto-concurrency-governor.js';
13
- import { runNarutoActivePool } from '../naruto/naruto-active-pool.js';
13
+ import { runNarutoActivePool, runNarutoRealActivePool } from '../naruto/naruto-active-pool.js';
14
+ import { collectActualNarutoWorker, spawnActualNarutoWorker } from '../naruto/naruto-real-worker-runtime.js';
14
15
  import { buildNarutoVerificationDag } from '../naruto/naruto-verification-dag.js';
15
16
  import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
16
17
  import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
17
- import { openZellijDashboardPane } from '../zellij/zellij-dashboard-pane.js';
18
18
  import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
19
19
  import { evaluateGitWorktreeCapability } from '../git/git-worktree-capability.js';
20
20
  const NARUTO_RESULT_SCHEMA = 'sks.naruto-command-result.v1';
@@ -31,6 +31,10 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
31
31
  return narutoHelp(parsed);
32
32
  if (parsed.action === 'status')
33
33
  return narutoStatus(parsed);
34
+ if (parsed.action === 'dashboard')
35
+ return narutoDashboard(parsed);
36
+ if (parsed.action === 'workers')
37
+ return narutoWorkers(parsed);
34
38
  return narutoRun(parsed);
35
39
  }
36
40
  async function narutoRun(parsed) {
@@ -109,6 +113,32 @@ async function narutoRun(parsed) {
109
113
  const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
110
114
  const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
111
115
  const activePool = await runNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
116
+ const realRuntimeWorktreePolicy = schedulerBackend === 'fake'
117
+ ? {
118
+ mode: 'patch-envelope-only',
119
+ required: false,
120
+ main_repo_root: worktreePolicy.main_repo_root,
121
+ worktree_root: null,
122
+ fallback_reason: 'fake_backend_fixture_skips_real_worktree_cleanup'
123
+ }
124
+ : worktreePolicy;
125
+ const realActivePool = await runNarutoRealActivePool({
126
+ graph: workGraph,
127
+ governor: { ...governor, safe_active_workers: activeSlots },
128
+ spawnWorker: async (item, placement) => spawnActualNarutoWorker({
129
+ root,
130
+ missionId: mission.id,
131
+ item,
132
+ placement,
133
+ backend: schedulerBackend,
134
+ worktreePolicy: realRuntimeWorktreePolicy,
135
+ zellijSessionName: `sks-${mission.id}`,
136
+ visiblePaneCap: zellijVisiblePanes
137
+ }),
138
+ collectWorker: async (handle) => collectActualNarutoWorker(handle),
139
+ enqueueVerification: async () => undefined,
140
+ updateDashboard: async () => undefined
141
+ });
112
142
  const verificationDag = buildNarutoVerificationDag(workGraph, { cwd: root });
113
143
  const gptFinalPack = buildNarutoGptFinalPack({
114
144
  missionId: mission.id,
@@ -132,6 +162,7 @@ async function narutoRun(parsed) {
132
162
  roleDistribution,
133
163
  governor,
134
164
  activePool,
165
+ realActivePool,
135
166
  verificationDag,
136
167
  gptFinalPack,
137
168
  zellijDashboard,
@@ -145,40 +176,25 @@ async function narutoRun(parsed) {
145
176
  missionId: mission.id,
146
177
  ledgerRoot,
147
178
  kind: 'naruto',
148
- slotCount: zellijVisiblePanes,
179
+ slotCount: 0,
149
180
  dryRun: false,
150
181
  attach: false
151
182
  });
152
183
  if (liveZellij?.ok && liveZellij.capability?.status === 'ok') {
153
- liveZellij.dashboard_pane = await openZellijDashboardPane({
154
- root,
155
- missionId: mission.id,
156
- sessionName: liveZellij.session_name,
157
- cwd: root,
158
- snapshot: {
159
- mode: 'naruto',
160
- backend_counts: { [schedulerBackend]: activeSlots },
161
- placement_counts: { 'zellij-pane': zellijVisiblePanes, headless: Math.max(0, activeSlots - zellijVisiblePanes) },
162
- active_workers: activeSlots,
163
- visible_panes: zellijVisiblePanes,
164
- headless_workers: Math.max(0, activeSlots - zellijVisiblePanes),
165
- queue_depth: Math.max(0, workGraph.total_work_items - activeSlots),
166
- worktrees: {
167
- active: worktreePolicy.mode === 'git-worktree' ? activeSlots : 0,
168
- completed: 0,
169
- retained: 0
170
- },
171
- local_llm: { tps: 0, queue: localWorker.auto_select_eligible ? Math.max(0, activeSlots - zellijVisiblePanes) : 0 },
172
- gpt_final_status: 'pending',
173
- gate_progress: 'naruto:pre-orchestrator'
174
- }
175
- }).catch((err) => ({
176
- ok: false,
177
- pane_kind: 'dashboard',
178
- worker_pane: false,
179
- blockers: [`zellij_dashboard_exception:${err?.message || String(err)}`]
180
- }));
181
- console.log('Zellij: prepared ' + zellijVisiblePanes + ' visible active clone lane(s) in ' + liveZellij.session_name + ' with ' + Math.max(0, activeSlots - zellijVisiblePanes) + ' headless active worker(s). Attach with: ' + (liveZellij.attach_command_with_env || liveZellij.attach_command));
184
+ liveZellij.dashboard_pane = null;
185
+ liveZellij.right_column_mode = 'spawn-on-first-worker';
186
+ await writeJsonAtomic(path.join(mission.dir, 'zellij-initial-ui.json'), {
187
+ schema: 'sks.zellij-initial-ui.v1',
188
+ ok: true,
189
+ mission_id: mission.id,
190
+ session_name: liveZellij.session_name,
191
+ initial_panes: 'main-only',
192
+ dashboard_created: false,
193
+ worker_panes_created: 0,
194
+ right_column_mode: 'spawn-on-first-worker',
195
+ visible_pane_cap: zellijVisiblePanes
196
+ });
197
+ console.log('Zellij: started main-only session ' + liveZellij.session_name + '; right column opens on first visible worker spawn. Attach with: ' + (liveZellij.attach_command_with_env || liveZellij.attach_command));
182
198
  if (parsed.attach)
183
199
  attachZellijSessionInteractive(liveZellij.session_name, { cwd: process.cwd(), configPath: liveZellij.clipboard_config_path });
184
200
  }
@@ -213,6 +229,10 @@ async function narutoRun(parsed) {
213
229
  mock: parsed.mock,
214
230
  real: parsed.real,
215
231
  readonly: parsed.readonly,
232
+ zellijSessionName: liveZellij?.session_name || `sks-${mission.id}`,
233
+ workerPlacement: parsed.json || parsed.noOpenZellij ? 'process' : 'zellij-pane',
234
+ zellijPaneWorker: true,
235
+ zellijVisiblePaneCap: zellijVisiblePanes,
216
236
  // Shadow clones ALWAYS run in fast service tier — never honor --no-fast/standard.
217
237
  fastMode: true,
218
238
  serviceTier: 'fast',
@@ -252,11 +272,23 @@ async function narutoRun(parsed) {
252
272
  ok: activePool.ok,
253
273
  max_observed_active_workers: activePool.max_observed_active_workers,
254
274
  refill_events: activePool.refill_events,
255
- completed_count: activePool.completed_count
275
+ completed_count: activePool.completed_count,
276
+ real_runtime: {
277
+ ok: realActivePool.ok,
278
+ active_cap: realActivePool.active_cap,
279
+ max_observed_active_workers: realActivePool.max_observed_active_workers,
280
+ average_active_workers: realActivePool.average_active_workers,
281
+ active_pool_utilization: realActivePool.active_pool_utilization,
282
+ refill_latency_ms_p95: realActivePool.refill_latency_ms_p95,
283
+ visible_workers: realActivePool.visible_workers,
284
+ headless_workers: realActivePool.headless_workers,
285
+ worker_lifecycle_count: realActivePool.worker_lifecycle.length,
286
+ worker_lifecycle_sample: realActivePool.worker_lifecycle.slice(0, 5)
287
+ }
256
288
  },
257
289
  local_worker: localWorkerSummary,
258
290
  proof: result.proof?.status || 'missing',
259
- run: result,
291
+ run: compactNarutoRunResult(result),
260
292
  zellij: null
261
293
  };
262
294
  summary.zellij = liveZellij;
@@ -273,6 +305,36 @@ async function narutoRun(parsed) {
273
305
  console.log('Zellij: optional live panes unavailable (' + ((summary.zellij.warnings || []).join('; ') || summary.zellij.capability?.status || 'unknown') + ')');
274
306
  });
275
307
  }
308
+ function compactNarutoRunResult(result) {
309
+ return {
310
+ schema: result?.schema || 'sks.agent-run.v1',
311
+ ok: result?.ok === true,
312
+ mission_id: result?.mission_id || null,
313
+ route: result?.route || NARUTO_ROUTE,
314
+ backend: result?.backend || null,
315
+ target_active_slots: result?.target_active_slots ?? null,
316
+ proof: result?.proof ? {
317
+ ok: result.proof.ok === true,
318
+ status: result.proof.status || null,
319
+ blockers: result.proof.blockers || []
320
+ } : null,
321
+ scheduler: result?.scheduler ? {
322
+ state: {
323
+ completed_count: result.scheduler.state?.completed_count ?? result.scheduler.completed_count ?? null,
324
+ failed_count: result.scheduler.state?.failed_count ?? result.scheduler.failed_count ?? null,
325
+ blocked_count: result.scheduler.state?.blocked_count ?? result.scheduler.blocked_count ?? null,
326
+ max_observed_active_slots: result.scheduler.state?.max_observed_active_slots ?? result.scheduler.max_observed_active_slots ?? null
327
+ }
328
+ } : null,
329
+ artifacts: {
330
+ ledger_root: result?.ledger_root || null,
331
+ proof: 'agent-proof-evidence.json',
332
+ scheduler: 'agent-scheduler-state.json',
333
+ native_cli_session_swarm: 'agent-native-cli-session-swarm.json',
334
+ naruto_real_active_pool: 'naruto-real-active-pool.json'
335
+ }
336
+ };
337
+ }
276
338
  function summarizeNarutoLocalWorkerResult(localWorker, result) {
277
339
  const backendCounts = {};
278
340
  const rows = Array.isArray(result?.results) ? result.results : [];
@@ -325,6 +387,57 @@ async function narutoStatus(parsed) {
325
387
  console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
326
388
  });
327
389
  }
390
+ async function narutoDashboard(parsed) {
391
+ const root = await sksRoot();
392
+ const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
393
+ if (!id)
394
+ return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'dashboard', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
395
+ const { dir } = await loadMission(root, id);
396
+ const snapshot = await readJson(path.join(dir, 'zellij-dashboard-snapshot.json'), null);
397
+ const rightColumnState = await readJson(path.join(dir, 'zellij-right-column-state.json'), null);
398
+ const summary = {
399
+ schema: NARUTO_RESULT_SCHEMA,
400
+ ok: Boolean(snapshot || rightColumnState),
401
+ action: 'dashboard',
402
+ mission_id: id,
403
+ snapshot,
404
+ right_column_state: rightColumnState,
405
+ blockers: snapshot || rightColumnState ? [] : ['naruto_dashboard_missing']
406
+ };
407
+ return emit(parsed, summary, () => {
408
+ console.log('🍥 Naruto dashboard: ' + id);
409
+ console.log('Right column: ' + (rightColumnState?.status || 'missing'));
410
+ if (snapshot)
411
+ console.log('Active/visible/headless/queue: ' + [snapshot.active_workers, snapshot.visible_panes, snapshot.headless_workers, snapshot.queue_depth].join('/'));
412
+ });
413
+ }
414
+ async function narutoWorkers(parsed) {
415
+ const root = await sksRoot();
416
+ const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
417
+ if (!id)
418
+ return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'workers', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
419
+ const { dir } = await loadMission(root, id);
420
+ const swarm = await readJson(path.join(dir, 'agents', 'agent-native-cli-session-swarm.json'), null);
421
+ const state = await readJson(path.join(dir, 'zellij-right-column-state.json'), null);
422
+ const records = Array.isArray(swarm?.records) ? swarm.records : [];
423
+ const summary = {
424
+ schema: NARUTO_RESULT_SCHEMA,
425
+ ok: Boolean(swarm || state),
426
+ action: 'workers',
427
+ mission_id: id,
428
+ active: records.filter((row) => row.status === 'running' || row.status === 'launching').length,
429
+ completed: records.filter((row) => row.status === 'closed').length,
430
+ failed: records.filter((row) => row.status === 'failed').length,
431
+ visible_worker_panes: state?.visible_worker_panes || [],
432
+ headless_workers: state?.headless_workers || [],
433
+ records,
434
+ blockers: swarm || state ? [] : ['naruto_worker_records_missing']
435
+ };
436
+ return emit(parsed, summary, () => {
437
+ console.log('🍥 Naruto workers: ' + id);
438
+ console.log(`Active ${summary.active} · completed ${summary.completed} · failed ${summary.failed} · visible ${summary.visible_worker_panes.length} · headless ${summary.headless_workers.length}`);
439
+ });
440
+ }
328
441
  async function narutoHelp(parsed) {
329
442
  const help = {
330
443
  schema: NARUTO_RESULT_SCHEMA,
@@ -349,13 +462,13 @@ function parseNarutoArgs(args = []) {
349
462
  if (hasFlag(args, '--help') || hasFlag(args, '-h'))
350
463
  args = ['help', ...args.filter((arg) => arg !== '--help' && arg !== '-h')];
351
464
  const first = args[0] && !String(args[0]).startsWith('--') ? String(args[0]) : '';
352
- const actions = new Set(['run', 'status', 'help']);
465
+ const actions = new Set(['run', 'status', 'help', 'dashboard', 'workers']);
353
466
  const action = (actions.has(first) ? first : 'run');
354
467
  const rest = action === first ? args.slice(1) : args;
355
468
  const json = hasFlag(args, '--json');
356
469
  const requestedClones = Number(readOption(args, '--clones', readOption(args, '--agents', DEFAULT_NARUTO_CLONES)));
357
470
  const clones = clampClones(requestedClones);
358
- const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones)), clones);
471
+ const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones * 2)), clones);
359
472
  const concurrency = normalizeConcurrency(readOption(args, '--concurrency', readOption(args, '--target-active-slots', null)), clones);
360
473
  const useOllama = hasFlag(args, '--ollama') || hasFlag(args, '--local-model');
361
474
  const noOllama = hasFlag(args, '--no-ollama') || hasFlag(args, '--no-local-model');
@@ -366,7 +479,10 @@ function parseNarutoArgs(args = []) {
366
479
  const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
367
480
  const writeModeRaw = String(readOption(args, '--write-mode', hasFlag(args, '--parallel-write') ? 'parallel' : '') || '');
368
481
  const writeMode = (['proof-safe', 'parallel', 'serial', 'off'].includes(writeModeRaw) ? writeModeRaw : null);
369
- const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', 'latest')));
482
+ const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status'
483
+ ? positionalArgs(rest, new Set()).find((arg) => /^latest$|^M-/.test(arg))
484
+ : null;
485
+ const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', positionalMission || 'latest')));
370
486
  const ollamaModel = String(readOption(args, '--ollama-model', readOption(args, '--local-model-model', '')) || '') || null;
371
487
  const ollamaBaseUrl = String(readOption(args, '--ollama-base-url', readOption(args, '--local-model-base-url', '')) || '') || null;
372
488
  const noOpenZellij = hasFlag(args, '--no-open-zellij') || hasFlag(args, '--no-zellij');
@@ -380,6 +496,8 @@ async function writeNarutoArtifacts(ledgerRoot, artifacts) {
380
496
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-role-distribution.json'), artifacts.roleDistribution);
381
497
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-concurrency-governor.json'), artifacts.governor);
382
498
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-active-pool.json'), artifacts.activePool);
499
+ if (artifacts.realActivePool)
500
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-real-active-pool.json'), artifacts.realActivePool);
383
501
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-verification-dag.json'), artifacts.verificationDag);
384
502
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-gpt-final-pack.json'), artifacts.gptFinalPack);
385
503
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-zellij-dashboard.json'), artifacts.zellijDashboard);
@@ -62,6 +62,11 @@ export function buildDoctorReadinessMatrix(input = {}) {
62
62
  warnings.add('local_llm_degraded');
63
63
  if (localModel.enabled === true && localStatus === 'blocked')
64
64
  warnings.add('local_llm_blocked_worker_tier_disabled');
65
+ const agentRoleConfig = input.agent_role_config || {};
66
+ if (agentRoleConfig.ok === false)
67
+ blockers.add('agent_role_config_repair_failed');
68
+ if (Array.isArray(agentRoleConfig.missing) && agentRoleConfig.missing.length && agentRoleConfig.apply !== true)
69
+ warnings.add('agent_role_config_missing_repair_available');
65
70
  const localCollaborationPolicy = resolveLocalCollaborationPolicy({ mode: input.local_collaboration?.mode || null });
66
71
  const gptFinalAvailable = input.local_collaboration?.gpt_final_arbiter_available === undefined
67
72
  ? codexBinOk
@@ -126,6 +131,7 @@ export function buildDoctorReadinessMatrix(input = {}) {
126
131
  worker_tier_enabled: localModel.enabled === true && localStatus === 'verified',
127
132
  blockers: normalizeList(localModel.blockers)
128
133
  },
134
+ agent_role_config: agentRoleConfig,
129
135
  ready: blockers.size === 0 && cliReady,
130
136
  primary_blocker: [...blockers][0] || null,
131
137
  blockers: [...blockers],
package/dist/core/fsx.js CHANGED
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
  import crypto from 'node:crypto';
6
6
  import { spawn } from 'node:child_process';
7
7
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '2.0.8';
8
+ export const PACKAGE_VERSION = '2.0.10';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
@@ -25,6 +25,10 @@ const STOP_REPEAT_GUARD_WINDOW_MS = 10 * 60 * 1000;
25
25
  const STOP_REPEAT_GUARD_MAX_ENTRIES = 25;
26
26
  const DEFAULT_STOP_REPEAT_GUARD_LIMIT = 2;
27
27
  const CODEX_GIT_ACTION_STOP_TTL_MS = 15 * 60 * 1000;
28
+ const UPDATE_CHECK_HOOK_INVOCATION_POLICY = 'function-only:no-runSksUpdateCheck-call-in-hooks';
29
+ // Update checks stay function-only in hooks: the policy marker above is checked
30
+ // by release readiness so ordinary Codex hook flow cannot grow a hidden update
31
+ // prompt path.
28
32
  async function loadHookPayload() {
29
33
  const raw = await readStdin();
30
34
  try {
package/dist/core/init.js CHANGED
@@ -1257,6 +1257,7 @@ async function removeDirIfEmpty(dir) {
1257
1257
  }
1258
1258
  async function installCodexAgents(root) {
1259
1259
  const agents = {
1260
+ 'analysis-scout.toml': `name = "analysis_scout"\ndescription = "Read-only SKS analysis scout retained for stale Codex agent-role config repair."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "medium"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are an SKS read-only analysis scout.\nDo not edit files.\nInspect only the assigned source paths and return concise source-backed findings.\n"""\n`,
1260
1261
  'native-agent-intake.toml': `name = "native_agent"\ndescription = "Read-only Team native agent. Maps one independent repo/docs/tests/API/risk/user-friction slice and returns TriWiki-ready source-backed findings before debate starts."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "medium"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are an SKS Team native agent.\nDo not edit files.\nOwn exactly one investigation slice assigned by the parent orchestrator.\nUse the mission roster or worker inbox reasoning_effort when the host exposes it; simple bounded work can use low, tool-heavy runtime work medium, and knowledge/research/safety/release work high or xhigh. Default to medium only when no assignment is visible.\nMap relevant source files, docs, tests, APIs, DB or safety risks, UX friction, and likely implementation boundaries.\nReturn concise source-backed claims suitable for team-analysis.md and TriWiki ingestion: claim, source path, evidence hash or quoted anchor, risk, confidence, and recommended implementation slice.\nDo not debate the final plan and do not implement code.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
1261
1262
  'team-consensus.toml': `name = "team_consensus"\ndescription = "Planning and debate specialist for SKS Team mode. Maps options, constraints, role-persona risks, and proposes the agreed objective before implementation starts."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are the SKS Team consensus specialist.\nDo not edit files.\nUse the mission roster reasoning_effort when the host exposes it; planning has a medium minimum, with high or xhigh for knowledge, safety, release, research, or recovery work.\nMap the affected code paths, viable approaches, constraints, risks, and acceptance criteria.\nRun the debate as role-persona synthesis: final users are low-context, self-interested, stubborn, and inconvenience-averse; executors are capable developers; reviewers are strict.\nArgue for the smallest coherent objective that can be handed to a fresh executor_N development team.\nPlan for at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes before integration or final.\nReturn: recommended objective, rejected alternatives, implementation slices, required reviewers, user-friction risks, and unresolved risks.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
1262
1263
  'implementation-worker.toml': `name = "implementation_worker"\ndescription = "Implementation specialist for SKS Team mode. Owns one bounded write set and coordinates with other executor_N workers."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "medium"\nsandbox_mode = "workspace-write"\ndeveloper_instructions = """\nYou are an SKS Team executor/developer in the fresh development bundle.\nYou are not alone in the codebase. Other executor_N workers may be editing disjoint files.\nUse the mission roster or worker inbox reasoning_effort when the host exposes it; simple bounded changes can use low, tool-heavy implementation medium, and safety/release/DB work high.\nOnly edit the files or module slice assigned to you.\nDo not revert or overwrite edits made by others.\nRead local patterns first, make the smallest correct change, avoid adding user friction, run focused verification for your slice, and report changed paths plus evidence.\nDo not create fallback implementation code, substitute behavior, mock behavior, or compatibility shims unless the user or sealed decision contract explicitly requested them.\nRespect all SKS hooks, DB safety rules, no-question run rules, and H-Proof completion gates.\nAlso return concise LIVE_EVENT lines for started, blocked, changed files, verification, and final result so the parent can record them.\n"""\n`,
@@ -1,4 +1,129 @@
1
1
  import { createNarutoGeneration, completeNarutoGeneration } from './naruto-generation-scheduler.js';
2
+ export async function runNarutoRealActivePool(input) {
3
+ const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
4
+ const visibleCap = Math.max(0, input.governor.safe_zellij_visible_panes);
5
+ const pending = [...input.graph.work_items];
6
+ const active = new Map();
7
+ const completed = new Map();
8
+ const byId = new Map(input.graph.work_items.map((item) => [item.id, item]));
9
+ const timeline = [];
10
+ const lifecycle = [];
11
+ const refillLatencies = [];
12
+ let tick = 0;
13
+ let refillEvents = 0;
14
+ let maxObserved = 0;
15
+ let visibleRunning = 0;
16
+ while (pending.length || active.size) {
17
+ const beforeLaunch = Date.now();
18
+ let launched = 0;
19
+ for (;;) {
20
+ if (active.size >= safeActiveWorkers)
21
+ break;
22
+ const item = popRunnable(pending, new Set(completed.keys()), activeToGenerationMap(active), byId);
23
+ if (!item)
24
+ break;
25
+ const placement = visibleRunning < visibleCap
26
+ ? { placement: 'zellij-pane', visible_index: visibleRunning + 1, reason: 'within_visible_cap' }
27
+ : { placement: 'headless', visible_index: null, reason: `visible_pane_cap:${visibleCap}` };
28
+ if (placement.placement === 'zellij-pane')
29
+ visibleRunning += 1;
30
+ const handle = await input.spawnWorker(item, placement);
31
+ active.set(handle.id, handle);
32
+ lifecycle.push({ work_item_id: item.id, placement: placement.placement, pid: handle.pid || null, worker_artifact_dir: handle.worker_artifact_dir || null, started_at: handle.started_at, completed_at: null, ok: null });
33
+ launched += 1;
34
+ await input.updateDashboard({ event_type: 'worker_spawned', work_item_id: item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement });
35
+ }
36
+ if (launched) {
37
+ refillEvents += launched;
38
+ refillLatencies.push(Date.now() - beforeLaunch);
39
+ await input.updateDashboard({ event_type: 'refill', active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size });
40
+ }
41
+ maxObserved = Math.max(maxObserved, active.size);
42
+ timeline.push({ tick, active: active.size, pending: pending.length, completed: completed.size, event: launched ? 'refill' : 'wait' });
43
+ const done = await nextCollectableWorkers(active);
44
+ if (!done.length)
45
+ break;
46
+ for (const handle of done) {
47
+ active.delete(handle.id);
48
+ if (handle.placement.placement === 'zellij-pane')
49
+ visibleRunning = Math.max(0, visibleRunning - 1);
50
+ const result = await input.collectWorker(handle);
51
+ completed.set(result.item.id, result);
52
+ const row = lifecycle.find((entry) => entry.work_item_id === result.item.id && entry.completed_at == null);
53
+ if (row) {
54
+ row.completed_at = result.completed_at;
55
+ row.ok = result.ok;
56
+ row.pid = result.pid || row.pid || null;
57
+ row.worker_artifact_dir = result.worker_artifact_dir || row.worker_artifact_dir || null;
58
+ }
59
+ await input.updateDashboard({ event_type: 'worker_completed', work_item_id: result.item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement: result.placement });
60
+ if (result.item.verification_required) {
61
+ await input.enqueueVerification(result);
62
+ await input.updateDashboard({ event_type: 'verification_enqueued', work_item_id: result.item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement: result.placement });
63
+ }
64
+ }
65
+ tick += 1;
66
+ if (tick > input.graph.work_items.length * 4 + 20)
67
+ break;
68
+ }
69
+ await input.updateDashboard({ event_type: 'pool_drained', active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size });
70
+ const failedCount = [...completed.values()].filter((result) => !result.ok).length;
71
+ const activeSamples = timeline.map((row) => row.active);
72
+ const averageActiveWorkers = activeSamples.length
73
+ ? activeSamples.reduce((sum, value) => sum + value, 0) / activeSamples.length
74
+ : 0;
75
+ const saturatedSamples = timeline
76
+ .filter((row) => row.pending + row.active >= safeActiveWorkers)
77
+ .map((row) => row.active);
78
+ const averageSaturatedActiveWorkers = saturatedSamples.length
79
+ ? saturatedSamples.reduce((sum, value) => sum + value, 0) / saturatedSamples.length
80
+ : averageActiveWorkers;
81
+ const utilizationDenominator = Math.max(1, safeActiveWorkers);
82
+ const activePoolUtilization = Math.min(1, averageSaturatedActiveWorkers / utilizationDenominator);
83
+ const enoughWorkForUtilization = input.graph.total_work_items >= safeActiveWorkers * 2;
84
+ const blockers = [
85
+ ...(pending.length ? ['naruto_real_active_pool_pending_not_drained'] : []),
86
+ ...(active.size ? ['naruto_real_active_pool_active_not_drained'] : []),
87
+ ...(maxObserved > safeActiveWorkers ? ['naruto_real_active_pool_exceeded_safe_workers'] : []),
88
+ ...(enoughWorkForUtilization && maxObserved < Math.ceil(safeActiveWorkers * 0.8) ? ['naruto_real_active_pool_underutilized'] : []),
89
+ ...(enoughWorkForUtilization && activePoolUtilization < 0.8 ? ['naruto_real_active_pool_low_sustained_utilization'] : []),
90
+ ...[...completed.values()].flatMap((result) => result.blockers || [])
91
+ ];
92
+ return {
93
+ schema: 'sks.naruto-active-pool.v1',
94
+ ok: blockers.length === 0,
95
+ runtime_mode: 'real-worker-lifecycle',
96
+ active_cap: safeActiveWorkers,
97
+ safe_active_workers: safeActiveWorkers,
98
+ total_work_items: input.graph.total_work_items,
99
+ completed_count: completed.size,
100
+ failed_count: failedCount,
101
+ refill_events: refillEvents,
102
+ max_observed_active_workers: maxObserved,
103
+ duplicate_execution_count: 0,
104
+ conflict_items_enqueued: 0,
105
+ max_observed_write_lease_conflicts: 0,
106
+ average_active_workers: Number(averageSaturatedActiveWorkers.toFixed(4)),
107
+ active_pool_utilization: Number(activePoolUtilization.toFixed(4)),
108
+ visible_workers: lifecycle.filter((row) => row.placement === 'zellij-pane').length,
109
+ headless_workers: lifecycle.filter((row) => row.placement === 'headless').length,
110
+ refill_latency_ms_p95: percentile(refillLatencies, 0.95),
111
+ worker_lifecycle: lifecycle,
112
+ timeline,
113
+ blockers
114
+ };
115
+ }
116
+ async function nextCollectableWorkers(active) {
117
+ const handles = [...active.values()];
118
+ const handlesWithExit = handles.filter((handle) => typeof handle.exit?.then === 'function');
119
+ if (!handlesWithExit.length)
120
+ return handles.slice(0, Math.max(1, Math.ceil(active.size / 2)));
121
+ const completed = await Promise.race(handlesWithExit.map(async (handle) => {
122
+ await handle.exit.catch(() => undefined);
123
+ return handle;
124
+ }));
125
+ return completed ? [completed] : [];
126
+ }
2
127
  export async function runNarutoActivePool(input) {
3
128
  const base = simulateNarutoActivePool(input);
4
129
  const allocations = [];
@@ -34,6 +159,22 @@ export async function runNarutoActivePool(input) {
34
159
  blockers: [...base.blockers, ...allocationBlockers]
35
160
  };
36
161
  }
162
+ function activeToGenerationMap(active) {
163
+ const out = new Map();
164
+ let index = 1;
165
+ for (const handle of active.values()) {
166
+ out.set(handle.id, createNarutoGeneration(handle.item, index, 0));
167
+ index += 1;
168
+ }
169
+ return out;
170
+ }
171
+ function percentile(values, quantile) {
172
+ if (!values.length)
173
+ return 0;
174
+ const sorted = [...values].sort((a, b) => a - b);
175
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * quantile) - 1));
176
+ return sorted[index] || 0;
177
+ }
37
178
  export function simulateNarutoActivePool(input) {
38
179
  const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
39
180
  const retryLimit = Math.max(0, Math.floor(Number(input.retryLimit ?? 1)));
@@ -7,7 +7,7 @@ export function decideNarutoConcurrency(input = {}) {
7
7
  const pending = normalizeNonNegativeInt(input.pendingWorkQueueSize, totalWorkItems);
8
8
  const leaseConflicts = normalizeNonNegativeInt(input.activeLeaseConflicts, 0);
9
9
  const hardware = probeHardwareCapacity(input.hardware || {});
10
- const zellijVisiblePaneCap = normalizePositiveInt(input.zellijVisiblePaneCap, Math.min(12, Math.max(4, Math.floor(hardware.terminal_rows / 3))));
10
+ const zellijVisiblePaneCap = normalizePositiveInt(input.zellijVisiblePaneCap, Math.min(8, Math.max(4, Math.floor(hardware.terminal_rows / 5))));
11
11
  const backend = String(input.backend || 'codex-sdk');
12
12
  const freeGb = hardware.free_memory_bytes / (1024 * 1024 * 1024);
13
13
  const totalGb = hardware.total_memory_bytes / (1024 * 1024 * 1024);
@@ -17,11 +17,20 @@ export function decideNarutoConcurrency(input = {}) {
17
17
  const gbPerWorker = heavy ? Number(process.env.SKS_NARUTO_GB_PER_WORKER || 0.25) : Number(process.env.SKS_NARUTO_LIGHT_GB_PER_WORKER || 0.1);
18
18
  const memoryCap = Math.max(1, Math.floor(memoryBudgetGb / Math.max(0.05, gbPerWorker)));
19
19
  const fdCap = Math.max(1, Math.floor((hardware.file_descriptor_limit - hardware.process_count) / 6));
20
+ const cpuCap = Math.max(1, hardware.cpu_core_count * (heavy ? 2 : 4));
21
+ const ioCap = Math.max(2, Math.floor(hardware.cpu_core_count / 2));
22
+ const processCap = Math.max(1, Number(process.env.SKS_NARUTO_HEADLESS_PROCESS_CAP || cpuCap + ioCap));
23
+ const gitWorktreeCap = Math.max(1, Number(process.env.SKS_NARUTO_GIT_WORKTREE_CAP || Math.min(requestedClones, processCap)));
20
24
  const localLlmParallel = Math.max(1, Math.min(4, hardware.local_llm_max_parallel_requests));
21
25
  const remoteCodexParallel = Math.max(1, Math.min(hardware.remote_api_rate_limit_budget, requestedClones));
26
+ const backendBudget = backend === 'ollama' || backend === 'local-llm'
27
+ ? localLlmParallel
28
+ : backend === 'codex-sdk' || backend === 'zellij'
29
+ ? Math.max(remoteCodexParallel, Math.min(requestedClones, processCap))
30
+ : processCap;
22
31
  const queueCap = Math.max(1, Math.min(requestedClones, pending || totalWorkItems));
23
32
  const leaseCap = Math.max(1, requestedClones - leaseConflicts);
24
- const rawSafe = Math.max(1, Math.min(requestedClones, totalWorkItems, memoryCap, fdCap, remoteCodexParallel, queueCap, leaseCap, 100));
33
+ const rawSafe = Math.max(1, Math.min(requestedClones, totalWorkItems, memoryCap, fdCap, cpuCap + ioCap, gitWorktreeCap + processCap, backendBudget, queueCap, leaseCap, 100));
25
34
  const pressure = monitorNarutoResourcePressure(hardware, { activeWorkers: rawSafe, zellijVisiblePaneCap });
26
35
  const backpressure = applyNarutoBackpressure(rawSafe, pressure);
27
36
  const safeActiveWorkers = Math.max(1, Math.min(rawSafe, backpressure.adjusted_active_workers));
@@ -29,6 +38,9 @@ export function decideNarutoConcurrency(input = {}) {
29
38
  const reasons = [
30
39
  ...(memoryCap < requestedClones ? ['memory_cap'] : []),
31
40
  ...(fdCap < requestedClones ? ['file_descriptor_budget'] : []),
41
+ ...(cpuCap + ioCap < requestedClones ? ['cpu_io_budget'] : []),
42
+ ...(gitWorktreeCap + processCap < requestedClones ? ['git_worktree_process_budget'] : []),
43
+ ...(backendBudget < requestedClones ? ['backend_parallel_budget'] : []),
32
44
  ...(remoteCodexParallel < requestedClones ? ['remote_api_rate_limit_budget'] : []),
33
45
  ...(localLlmParallel <= 4 ? ['local_llm_max_parallel_requests'] : []),
34
46
  ...(safeVisible < safeActiveWorkers ? ['zellij_ui_pane_budget'] : []),
@@ -44,6 +56,9 @@ export function decideNarutoConcurrency(input = {}) {
44
56
  headless_workers: Math.max(0, safeActiveWorkers - safeVisible),
45
57
  local_llm_parallel: localLlmParallel,
46
58
  remote_codex_parallel: remoteCodexParallel,
59
+ process_parallel: processCap,
60
+ git_worktree_parallel: gitWorktreeCap,
61
+ cpu_io_parallel: cpuCap + ioCap,
47
62
  verification_parallel: Math.max(1, Math.min(hardware.cpu_core_count * 2, safeActiveWorkers, 16)),
48
63
  reasons: [...new Set(reasons)],
49
64
  backpressure: backpressure.backpressure,
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs/promises';
3
+ import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
4
+ async function main() {
5
+ const intakePath = process.argv[2];
6
+ if (!intakePath)
7
+ throw new Error('naruto worker intake path is required');
8
+ const intake = await readJson(intakePath, null);
9
+ if (!intake?.result_path || !intake?.heartbeat_path || !intake?.item?.id) {
10
+ throw new Error('naruto worker intake is invalid');
11
+ }
12
+ await fs.appendFile(intake.heartbeat_path, `${JSON.stringify({
13
+ schema: 'sks.naruto-actual-worker-heartbeat.v1',
14
+ ts: nowIso(),
15
+ item_id: intake.item.id,
16
+ status: 'running'
17
+ })}\n`);
18
+ await new Promise((resolve) => setTimeout(resolve, 25));
19
+ await writeJsonAtomic(intake.result_path, {
20
+ schema: 'sks.naruto-actual-worker-result.v1',
21
+ ok: true,
22
+ generated_at: nowIso(),
23
+ item_id: intake.item.id,
24
+ placement: intake.placement,
25
+ backend: intake.backend,
26
+ worktree_path: intake.worktree_path
27
+ });
28
+ }
29
+ main().then(() => {
30
+ process.exit(0);
31
+ }).catch((err) => {
32
+ console.error(err?.message || String(err));
33
+ process.exit(1);
34
+ });
35
+ //# sourceMappingURL=naruto-real-worker-child.js.map