sneakoscope 2.0.8 → 2.0.9

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 (50) hide show
  1. package/README.md +1 -1
  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 +20 -8
  8. package/dist/commands/zellij.js +144 -1
  9. package/dist/core/agents/agent-orchestrator.js +4 -2
  10. package/dist/core/agents/native-cli-session-swarm.js +76 -9
  11. package/dist/core/commands/mad-sks-command.js +17 -26
  12. package/dist/core/commands/naruto-command.js +99 -36
  13. package/dist/core/fsx.js +1 -1
  14. package/dist/core/naruto/naruto-active-pool.js +108 -0
  15. package/dist/core/naruto/naruto-concurrency-governor.js +16 -1
  16. package/dist/core/naruto/naruto-work-graph.js +2 -1
  17. package/dist/core/release/release-gate-cache-v2.js +58 -4
  18. package/dist/core/release/release-gate-dag.js +36 -25
  19. package/dist/core/version.js +1 -1
  20. package/dist/core/zellij/zellij-dashboard-renderer.js +22 -6
  21. package/dist/core/zellij/zellij-launcher.js +3 -3
  22. package/dist/core/zellij/zellij-layout-builder.js +1 -1
  23. package/dist/core/zellij/zellij-right-column-layout-proof.js +42 -0
  24. package/dist/core/zellij/zellij-right-column-manager.js +245 -0
  25. package/dist/core/zellij/zellij-worker-pane-manager.js +118 -13
  26. package/dist/scripts/codex-sdk-release-review-pipeline-check.js +5 -5
  27. package/dist/scripts/doctor-fix-proves-codex-read-check.js +26 -5
  28. package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
  29. package/dist/scripts/mad-sks-zellij-default-pane-worker-check.js +2 -2
  30. package/dist/scripts/naruto-concurrency-governor-check.js +2 -1
  31. package/dist/scripts/naruto-extreme-parallelism-check.js +22 -0
  32. package/dist/scripts/naruto-real-active-pool-check.js +38 -0
  33. package/dist/scripts/naruto-work-graph-check.js +1 -1
  34. package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +21 -0
  35. package/dist/scripts/product-design-auto-install-check.js +3 -3
  36. package/dist/scripts/product-design-plugin-routing-check.js +3 -3
  37. package/dist/scripts/release-cache-glob-hashing-check.js +42 -0
  38. package/dist/scripts/release-dag-full-coverage-check.js +35 -0
  39. package/dist/scripts/release-parallel-speed-budget-check.js +67 -13
  40. package/dist/scripts/release-readiness-report.js +1 -1
  41. package/dist/scripts/zellij-dashboard-pane-check.js +6 -4
  42. package/dist/scripts/zellij-developer-controls-check.js +20 -0
  43. package/dist/scripts/zellij-dynamic-pane-lifecycle-check.js +21 -0
  44. package/dist/scripts/zellij-initial-main-only-blackbox.js +28 -0
  45. package/dist/scripts/zellij-right-column-geometry-proof.js +29 -0
  46. package/dist/scripts/zellij-right-column-manager-check.js +22 -0
  47. package/dist/scripts/zellij-worker-pane-manager-check.js +2 -1
  48. package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +7 -6
  49. package/package.json +13 -3
  50. package/schemas/zellij/zellij-right-column-state.schema.json +41 -0
@@ -10,11 +10,10 @@ 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
14
  import { buildNarutoVerificationDag } from '../naruto/naruto-verification-dag.js';
15
15
  import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
16
16
  import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
17
- import { openZellijDashboardPane } from '../zellij/zellij-dashboard-pane.js';
18
17
  import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
19
18
  import { evaluateGitWorktreeCapability } from '../git/git-worktree-capability.js';
20
19
  const NARUTO_RESULT_SCHEMA = 'sks.naruto-command-result.v1';
@@ -31,6 +30,10 @@ export async function narutoCommand(commandOrArgs = 'naruto', maybeArgs = []) {
31
30
  return narutoHelp(parsed);
32
31
  if (parsed.action === 'status')
33
32
  return narutoStatus(parsed);
33
+ if (parsed.action === 'dashboard')
34
+ return narutoDashboard(parsed);
35
+ if (parsed.action === 'workers')
36
+ return narutoWorkers(parsed);
34
37
  return narutoRun(parsed);
35
38
  }
36
39
  async function narutoRun(parsed) {
@@ -109,6 +112,14 @@ async function narutoRun(parsed) {
109
112
  const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
110
113
  const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
111
114
  const activePool = await runNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
115
+ const realActivePool = await runNarutoRealActivePool({
116
+ graph: workGraph,
117
+ governor: { ...governor, safe_active_workers: activeSlots },
118
+ spawnWorker: async (item, placement) => ({ id: item.id, item, placement, started_at: Date.now() }),
119
+ collectWorker: async (handle) => ({ id: handle.id, ok: true, item: handle.item, placement: handle.placement, completed_at: Date.now() }),
120
+ enqueueVerification: async () => undefined,
121
+ updateDashboard: async () => undefined
122
+ });
112
123
  const verificationDag = buildNarutoVerificationDag(workGraph, { cwd: root });
113
124
  const gptFinalPack = buildNarutoGptFinalPack({
114
125
  missionId: mission.id,
@@ -132,6 +143,7 @@ async function narutoRun(parsed) {
132
143
  roleDistribution,
133
144
  governor,
134
145
  activePool,
146
+ realActivePool,
135
147
  verificationDag,
136
148
  gptFinalPack,
137
149
  zellijDashboard,
@@ -145,40 +157,25 @@ async function narutoRun(parsed) {
145
157
  missionId: mission.id,
146
158
  ledgerRoot,
147
159
  kind: 'naruto',
148
- slotCount: zellijVisiblePanes,
160
+ slotCount: 0,
149
161
  dryRun: false,
150
162
  attach: false
151
163
  });
152
164
  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));
165
+ liveZellij.dashboard_pane = null;
166
+ liveZellij.right_column_mode = 'spawn-on-first-worker';
167
+ await writeJsonAtomic(path.join(mission.dir, 'zellij-initial-ui.json'), {
168
+ schema: 'sks.zellij-initial-ui.v1',
169
+ ok: true,
170
+ mission_id: mission.id,
171
+ session_name: liveZellij.session_name,
172
+ initial_panes: 'main-only',
173
+ dashboard_created: false,
174
+ worker_panes_created: 0,
175
+ right_column_mode: 'spawn-on-first-worker',
176
+ visible_pane_cap: zellijVisiblePanes
177
+ });
178
+ 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
179
  if (parsed.attach)
183
180
  attachZellijSessionInteractive(liveZellij.session_name, { cwd: process.cwd(), configPath: liveZellij.clipboard_config_path });
184
181
  }
@@ -213,6 +210,10 @@ async function narutoRun(parsed) {
213
210
  mock: parsed.mock,
214
211
  real: parsed.real,
215
212
  readonly: parsed.readonly,
213
+ zellijSessionName: liveZellij?.session_name || `sks-${mission.id}`,
214
+ workerPlacement: parsed.json || parsed.noOpenZellij ? 'process' : 'zellij-pane',
215
+ zellijPaneWorker: true,
216
+ zellijVisiblePaneCap: zellijVisiblePanes,
216
217
  // Shadow clones ALWAYS run in fast service tier — never honor --no-fast/standard.
217
218
  fastMode: true,
218
219
  serviceTier: 'fast',
@@ -252,7 +253,13 @@ async function narutoRun(parsed) {
252
253
  ok: activePool.ok,
253
254
  max_observed_active_workers: activePool.max_observed_active_workers,
254
255
  refill_events: activePool.refill_events,
255
- completed_count: activePool.completed_count
256
+ completed_count: activePool.completed_count,
257
+ real_runtime: {
258
+ ok: realActivePool.ok,
259
+ max_observed_active_workers: realActivePool.max_observed_active_workers,
260
+ refill_latency_ms_p95: realActivePool.refill_latency_ms_p95,
261
+ worker_lifecycle: realActivePool.worker_lifecycle
262
+ }
256
263
  },
257
264
  local_worker: localWorkerSummary,
258
265
  proof: result.proof?.status || 'missing',
@@ -325,6 +332,57 @@ async function narutoStatus(parsed) {
325
332
  console.log('Roles: ' + roleDistribution.entries.map((entry) => `${entry.role}:${entry.count}`).join(', '));
326
333
  });
327
334
  }
335
+ async function narutoDashboard(parsed) {
336
+ const root = await sksRoot();
337
+ const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
338
+ if (!id)
339
+ return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'dashboard', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
340
+ const { dir } = await loadMission(root, id);
341
+ const snapshot = await readJson(path.join(dir, 'zellij-dashboard-snapshot.json'), null);
342
+ const rightColumnState = await readJson(path.join(dir, 'zellij-right-column-state.json'), null);
343
+ const summary = {
344
+ schema: NARUTO_RESULT_SCHEMA,
345
+ ok: Boolean(snapshot || rightColumnState),
346
+ action: 'dashboard',
347
+ mission_id: id,
348
+ snapshot,
349
+ right_column_state: rightColumnState,
350
+ blockers: snapshot || rightColumnState ? [] : ['naruto_dashboard_missing']
351
+ };
352
+ return emit(parsed, summary, () => {
353
+ console.log('🍥 Naruto dashboard: ' + id);
354
+ console.log('Right column: ' + (rightColumnState?.status || 'missing'));
355
+ if (snapshot)
356
+ console.log('Active/visible/headless/queue: ' + [snapshot.active_workers, snapshot.visible_panes, snapshot.headless_workers, snapshot.queue_depth].join('/'));
357
+ });
358
+ }
359
+ async function narutoWorkers(parsed) {
360
+ const root = await sksRoot();
361
+ const id = parsed.missionId && parsed.missionId !== 'latest' ? parsed.missionId : await findLatestMission(root);
362
+ if (!id)
363
+ return emit(parsed, { schema: NARUTO_RESULT_SCHEMA, ok: false, action: 'workers', status: 'missing_mission' }, () => console.log('No Naruto mission found.'));
364
+ const { dir } = await loadMission(root, id);
365
+ const swarm = await readJson(path.join(dir, 'agents', 'agent-native-cli-session-swarm.json'), null);
366
+ const state = await readJson(path.join(dir, 'zellij-right-column-state.json'), null);
367
+ const records = Array.isArray(swarm?.records) ? swarm.records : [];
368
+ const summary = {
369
+ schema: NARUTO_RESULT_SCHEMA,
370
+ ok: Boolean(swarm || state),
371
+ action: 'workers',
372
+ mission_id: id,
373
+ active: records.filter((row) => row.status === 'running' || row.status === 'launching').length,
374
+ completed: records.filter((row) => row.status === 'closed').length,
375
+ failed: records.filter((row) => row.status === 'failed').length,
376
+ visible_worker_panes: state?.visible_worker_panes || [],
377
+ headless_workers: state?.headless_workers || [],
378
+ records,
379
+ blockers: swarm || state ? [] : ['naruto_worker_records_missing']
380
+ };
381
+ return emit(parsed, summary, () => {
382
+ console.log('🍥 Naruto workers: ' + id);
383
+ console.log(`Active ${summary.active} · completed ${summary.completed} · failed ${summary.failed} · visible ${summary.visible_worker_panes.length} · headless ${summary.headless_workers.length}`);
384
+ });
385
+ }
328
386
  async function narutoHelp(parsed) {
329
387
  const help = {
330
388
  schema: NARUTO_RESULT_SCHEMA,
@@ -349,13 +407,13 @@ function parseNarutoArgs(args = []) {
349
407
  if (hasFlag(args, '--help') || hasFlag(args, '-h'))
350
408
  args = ['help', ...args.filter((arg) => arg !== '--help' && arg !== '-h')];
351
409
  const first = args[0] && !String(args[0]).startsWith('--') ? String(args[0]) : '';
352
- const actions = new Set(['run', 'status', 'help']);
410
+ const actions = new Set(['run', 'status', 'help', 'dashboard', 'workers']);
353
411
  const action = (actions.has(first) ? first : 'run');
354
412
  const rest = action === first ? args.slice(1) : args;
355
413
  const json = hasFlag(args, '--json');
356
414
  const requestedClones = Number(readOption(args, '--clones', readOption(args, '--agents', DEFAULT_NARUTO_CLONES)));
357
415
  const clones = clampClones(requestedClones);
358
- const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones)), clones);
416
+ const workItems = clampWorkItems(Number(readOption(args, '--work-items', clones * 2)), clones);
359
417
  const concurrency = normalizeConcurrency(readOption(args, '--concurrency', readOption(args, '--target-active-slots', null)), clones);
360
418
  const useOllama = hasFlag(args, '--ollama') || hasFlag(args, '--local-model');
361
419
  const noOllama = hasFlag(args, '--no-ollama') || hasFlag(args, '--no-local-model');
@@ -366,7 +424,10 @@ function parseNarutoArgs(args = []) {
366
424
  const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
367
425
  const writeModeRaw = String(readOption(args, '--write-mode', hasFlag(args, '--parallel-write') ? 'parallel' : '') || '');
368
426
  const writeMode = (['proof-safe', 'parallel', 'serial', 'off'].includes(writeModeRaw) ? writeModeRaw : null);
369
- const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', 'latest')));
427
+ const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status'
428
+ ? positionalArgs(rest, new Set()).find((arg) => /^latest$|^M-/.test(arg))
429
+ : null;
430
+ const missionId = String(readOption(args, '--mission', readOption(args, '--mission-id', positionalMission || 'latest')));
370
431
  const ollamaModel = String(readOption(args, '--ollama-model', readOption(args, '--local-model-model', '')) || '') || null;
371
432
  const ollamaBaseUrl = String(readOption(args, '--ollama-base-url', readOption(args, '--local-model-base-url', '')) || '') || null;
372
433
  const noOpenZellij = hasFlag(args, '--no-open-zellij') || hasFlag(args, '--no-zellij');
@@ -380,6 +441,8 @@ async function writeNarutoArtifacts(ledgerRoot, artifacts) {
380
441
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-role-distribution.json'), artifacts.roleDistribution);
381
442
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-concurrency-governor.json'), artifacts.governor);
382
443
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-active-pool.json'), artifacts.activePool);
444
+ if (artifacts.realActivePool)
445
+ await writeJsonAtomic(path.join(ledgerRoot, 'naruto-real-active-pool.json'), artifacts.realActivePool);
383
446
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-verification-dag.json'), artifacts.verificationDag);
384
447
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-gpt-final-pack.json'), artifacts.gptFinalPack);
385
448
  await writeJsonAtomic(path.join(ledgerRoot, 'naruto-zellij-dashboard.json'), artifacts.zellijDashboard);
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.9';
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() {
@@ -1,4 +1,96 @@
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, 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 = [...active.values()].slice(0, Math.max(1, Math.ceil(active.size / 2)));
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
+ }
57
+ 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 });
58
+ if (result.item.verification_required) {
59
+ await input.enqueueVerification(result);
60
+ 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 });
61
+ }
62
+ }
63
+ tick += 1;
64
+ if (tick > input.graph.work_items.length * 4 + 20)
65
+ break;
66
+ }
67
+ await input.updateDashboard({ event_type: 'pool_drained', active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size });
68
+ const failedCount = [...completed.values()].filter((result) => !result.ok).length;
69
+ const blockers = [
70
+ ...(pending.length ? ['naruto_real_active_pool_pending_not_drained'] : []),
71
+ ...(active.size ? ['naruto_real_active_pool_active_not_drained'] : []),
72
+ ...(maxObserved > safeActiveWorkers ? ['naruto_real_active_pool_exceeded_safe_workers'] : []),
73
+ ...[...completed.values()].flatMap((result) => result.blockers || [])
74
+ ];
75
+ return {
76
+ schema: 'sks.naruto-active-pool.v1',
77
+ ok: blockers.length === 0,
78
+ runtime_mode: 'real-worker-lifecycle',
79
+ safe_active_workers: safeActiveWorkers,
80
+ total_work_items: input.graph.total_work_items,
81
+ completed_count: completed.size,
82
+ failed_count: failedCount,
83
+ refill_events: refillEvents,
84
+ max_observed_active_workers: maxObserved,
85
+ duplicate_execution_count: 0,
86
+ conflict_items_enqueued: 0,
87
+ max_observed_write_lease_conflicts: 0,
88
+ refill_latency_ms_p95: percentile(refillLatencies, 0.95),
89
+ worker_lifecycle: lifecycle,
90
+ timeline,
91
+ blockers
92
+ };
93
+ }
2
94
  export async function runNarutoActivePool(input) {
3
95
  const base = simulateNarutoActivePool(input);
4
96
  const allocations = [];
@@ -34,6 +126,22 @@ export async function runNarutoActivePool(input) {
34
126
  blockers: [...base.blockers, ...allocationBlockers]
35
127
  };
36
128
  }
129
+ function activeToGenerationMap(active) {
130
+ const out = new Map();
131
+ let index = 1;
132
+ for (const handle of active.values()) {
133
+ out.set(handle.id, createNarutoGeneration(handle.item, index, 0));
134
+ index += 1;
135
+ }
136
+ return out;
137
+ }
138
+ function percentile(values, quantile) {
139
+ if (!values.length)
140
+ return 0;
141
+ const sorted = [...values].sort((a, b) => a - b);
142
+ const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * quantile) - 1));
143
+ return sorted[index] || 0;
144
+ }
37
145
  export function simulateNarutoActivePool(input) {
38
146
  const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
39
147
  const retryLimit = Math.max(0, Math.floor(Number(input.retryLimit ?? 1)));
@@ -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,
@@ -28,7 +28,8 @@ export function buildNarutoWorkGraph(input = {}) {
28
28
  const requestedClones = normalizePositiveInt(input.requestedClones, 12);
29
29
  const readonly = input.readonly === true;
30
30
  const writeCapable = input.writeCapable !== false && !readonly;
31
- const totalWorkItems = Math.max(requestedClones, normalizePositiveInt(input.totalWorkItems, requestedClones));
31
+ const minimumFanout = writeCapable ? requestedClones * 2 : requestedClones;
32
+ const totalWorkItems = Math.max(minimumFanout, normalizePositiveInt(input.totalWorkItems, minimumFanout));
32
33
  const kindCycle = writeCapable ? WRITE_CAPABLE_KIND_CYCLE : READONLY_KIND_CYCLE;
33
34
  const basePath = normalizeNarutoPath(input.leaseBasePath || '.sneakoscope/naruto/patch-envelopes');
34
35
  const targetPaths = normalizePaths(input.targetPaths || []);
@@ -19,14 +19,59 @@ export function releaseGateCacheKey(root, gate) {
19
19
  hashFileIfPresent(hash, path.join(root, 'package.json'));
20
20
  hashFileIfPresent(hash, path.join(root, 'dist', 'build-manifest.json'));
21
21
  for (const input of gate.cache.inputs) {
22
- const file = path.join(root, input);
23
- if (fs.existsSync(file) && fs.statSync(file).isFile())
22
+ const expanded = expandGlob(root, input);
23
+ hash.update(`input:${input}`);
24
+ if (!expanded.length) {
25
+ hash.update(`missing_or_empty:${input}`);
26
+ continue;
27
+ }
28
+ for (const file of expanded) {
29
+ hash.update(path.relative(root, file));
24
30
  hashFileIfPresent(hash, file);
25
- else
26
- hash.update(input);
31
+ }
27
32
  }
28
33
  return hash.digest('hex');
29
34
  }
35
+ export function expandGlob(root, input) {
36
+ const absolute = path.join(root, input);
37
+ if (!/[*!?[\]{}]/.test(input)) {
38
+ if (!fs.existsSync(absolute))
39
+ return [];
40
+ const stat = fs.statSync(absolute);
41
+ if (stat.isDirectory())
42
+ return hashDirectoryRecursive(absolute);
43
+ return stat.isFile() ? [absolute] : [];
44
+ }
45
+ if (input.endsWith('/**')) {
46
+ const dir = path.join(root, input.slice(0, -3));
47
+ return fs.existsSync(dir) && fs.statSync(dir).isDirectory() ? hashDirectoryRecursive(dir) : [];
48
+ }
49
+ const firstWildcard = input.search(/[*!?[\]{}]/);
50
+ const prefix = input.slice(0, firstWildcard);
51
+ const base = path.join(root, prefix.includes('/') ? prefix.slice(0, prefix.lastIndexOf('/')) : '');
52
+ if (!fs.existsSync(base))
53
+ return [];
54
+ const re = globToRegExp(input);
55
+ return hashDirectoryRecursive(base).filter((file) => re.test(path.relative(root, file)));
56
+ }
57
+ export function hashDirectoryRecursive(dir) {
58
+ if (!fs.existsSync(dir))
59
+ return [];
60
+ const out = [];
61
+ const stack = [dir];
62
+ while (stack.length) {
63
+ const current = stack.pop();
64
+ for (const name of fs.readdirSync(current).sort()) {
65
+ const file = path.join(current, name);
66
+ const stat = fs.statSync(file);
67
+ if (stat.isDirectory())
68
+ stack.push(file);
69
+ else if (stat.isFile())
70
+ out.push(file);
71
+ }
72
+ }
73
+ return out.sort();
74
+ }
30
75
  export function readReleaseGateCacheHit(root, gate) {
31
76
  try {
32
77
  const parsed = JSON.parse(fs.readFileSync(releaseGateCacheFile(root), 'utf8'));
@@ -60,4 +105,13 @@ function hashFileIfPresent(hash, file) {
60
105
  if (fs.existsSync(file) && fs.statSync(file).isFile())
61
106
  hash.update(fs.readFileSync(file));
62
107
  }
108
+ function globToRegExp(input) {
109
+ const escaped = input
110
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
111
+ .replace(/\*\*/g, '\u0000')
112
+ .replace(/\*/g, '[^/]*')
113
+ .replace(/\?/g, '[^/]')
114
+ .replace(/\u0000/g, '.*');
115
+ return new RegExp(`^${escaped}$`);
116
+ }
63
117
  //# sourceMappingURL=release-gate-cache-v2.js.map
@@ -35,6 +35,39 @@ export async function runReleaseGateDag(input) {
35
35
  let cached = 0;
36
36
  let sumGateMs = 0;
37
37
  let peakRunning = 0;
38
+ const writeSummarySnapshot = (finished = false) => {
39
+ const wallMs = Date.now() - started;
40
+ const failures = [...failed.values()].map((row) => ({ id: row.id, exit_code: row.exit_code, stderr_tail: row.stderr_tail }));
41
+ const snapshot = {
42
+ schema: 'sks.release-gate-dag-run.v1',
43
+ ok: failures.length === 0,
44
+ run_id: runId,
45
+ selected_preset: preset,
46
+ total_gates: manifest.gates.length,
47
+ selected_gates: selected.length,
48
+ completed: completed.size,
49
+ failed: failed.size,
50
+ cached,
51
+ wall_ms: wallMs,
52
+ sum_gate_ms: sumGateMs,
53
+ cpu_time_saved_ms: Math.max(0, sumGateMs - wallMs),
54
+ parallelism_gain: wallMs > 0 ? Number((sumGateMs / wallMs).toFixed(2)) : 1,
55
+ critical_path_ms: estimateCriticalPath(selected, completed),
56
+ peak_running: peakRunning,
57
+ peak_resources: peakResources,
58
+ budget_snapshot: budget,
59
+ budget_summary: summarizeReleaseGateBudget(budget),
60
+ report_dir: reportDir,
61
+ failures
62
+ };
63
+ if (!finished) {
64
+ snapshot.in_progress = true;
65
+ snapshot.pending = pending.size;
66
+ snapshot.running = running.size;
67
+ }
68
+ writeReleaseGateJson(path.join(reportDir, 'summary.json'), snapshot);
69
+ return snapshot;
70
+ };
38
71
  if (input.explain) {
39
72
  writeReleaseGateJson(path.join(reportDir, 'explain.json'), { schema: RELEASE_GATE_NODE_SCHEMA, preset, budget, gates: selected.map((gate) => ({ id: gate.id, deps: gate.deps, resource: gate.resource, command: gate.command })) });
40
73
  }
@@ -51,6 +84,7 @@ export async function runReleaseGateDag(input) {
51
84
  cached += 1;
52
85
  progressed = true;
53
86
  appendReleaseGateJsonl(timeline, { event: 'cache_hit', gate_id: gate.id, at: new Date().toISOString() });
87
+ writeSummarySnapshot(false);
54
88
  continue;
55
89
  }
56
90
  appendReleaseGateJsonl(timeline, { event: 'start', gate_id: gate.id, resource: gate.resource, at: new Date().toISOString() });
@@ -102,32 +136,9 @@ export async function runReleaseGateDag(input) {
102
136
  }
103
137
  }
104
138
  appendReleaseGateJsonl(timeline, { event: result.ok ? 'pass' : 'fail', gate_id: result.id, duration_ms: result.duration_ms, at: new Date().toISOString() });
139
+ writeSummarySnapshot(false);
105
140
  }
106
- const wallMs = Date.now() - started;
107
- const failures = [...failed.values()].map((row) => ({ id: row.id, exit_code: row.exit_code, stderr_tail: row.stderr_tail }));
108
- const result = {
109
- schema: 'sks.release-gate-dag-run.v1',
110
- ok: failures.length === 0,
111
- run_id: runId,
112
- selected_preset: preset,
113
- total_gates: manifest.gates.length,
114
- selected_gates: selected.length,
115
- completed: completed.size,
116
- failed: failed.size,
117
- cached,
118
- wall_ms: wallMs,
119
- sum_gate_ms: sumGateMs,
120
- cpu_time_saved_ms: Math.max(0, sumGateMs - wallMs),
121
- parallelism_gain: wallMs > 0 ? Number((sumGateMs / wallMs).toFixed(2)) : 1,
122
- critical_path_ms: estimateCriticalPath(selected, completed),
123
- peak_running: peakRunning,
124
- peak_resources: peakResources,
125
- budget_snapshot: budget,
126
- budget_summary: summarizeReleaseGateBudget(budget),
127
- report_dir: reportDir,
128
- failures
129
- };
130
- writeReleaseGateJson(path.join(reportDir, 'summary.json'), result);
141
+ const result = writeSummarySnapshot(true);
131
142
  return result;
132
143
  }
133
144
  function selectPreset(manifest, preset) {
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '2.0.8';
1
+ export const PACKAGE_VERSION = '2.0.9';
2
2
  //# sourceMappingURL=version.js.map
@@ -6,6 +6,10 @@ export function buildZellijDashboardSnapshot(input) {
6
6
  generated_at: nowIso(),
7
7
  mission_id: input.mission_id,
8
8
  mode: input.mode || 'naruto',
9
+ route: input.route || input.mode || '$Naruto',
10
+ provider: input.provider || 'unknown',
11
+ service_tier: input.service_tier || 'fast',
12
+ backpressure: input.backpressure || 'normal',
9
13
  backend_counts: input.backend_counts || { 'codex-sdk': 1 },
10
14
  placement_counts: input.placement_counts || { 'zellij-pane': 1 },
11
15
  active_workers: Number(input.active_workers || 0),
@@ -16,6 +20,15 @@ export function buildZellijDashboardSnapshot(input) {
16
20
  local_llm: input.local_llm || { tps: 0, queue: 0 },
17
21
  gpt_final_status: input.gpt_final_status || 'not_started',
18
22
  gate_progress: input.gate_progress || 'not_release',
23
+ patch_verify: input.patch_verify || {
24
+ patches: 0,
25
+ gpt_approved: 0,
26
+ conflicts: 0,
27
+ verification_running: 0,
28
+ verification_passed: 0,
29
+ verification_failed: 0
30
+ },
31
+ workers: input.workers || [],
19
32
  latest_blockers: input.latest_blockers || []
20
33
  };
21
34
  }
@@ -25,18 +38,21 @@ export function renderZellijDashboardText(snapshot) {
25
38
  return [
26
39
  'SKS Dashboard',
27
40
  `Mission: ${snapshot.mission_id}`,
28
- `Mode: ${snapshot.mode}`,
41
+ `Route / mode: ${snapshot.route} / ${snapshot.mode} / ${snapshot.service_tier}`,
42
+ `Provider: ${snapshot.provider}`,
29
43
  `Backend counts: ${backendCounts || 'none'}`,
30
44
  `Placement counts: ${placementCounts || 'none'}`,
31
- `Active workers: ${snapshot.active_workers}`,
32
- `Visible panes: ${snapshot.visible_panes}`,
33
- `Headless workers: ${snapshot.headless_workers}`,
34
- `Queue depth: ${snapshot.queue_depth}`,
45
+ `Active / visible / headless / queued: ${snapshot.active_workers}/${snapshot.visible_panes}/${snapshot.headless_workers}/${snapshot.queue_depth}`,
46
+ `Backpressure: ${snapshot.backpressure}`,
35
47
  `Worktrees active/completed/retained: ${snapshot.worktrees.active}/${snapshot.worktrees.completed}/${snapshot.worktrees.retained}`,
36
48
  `Local LLM TPS / queue: ${snapshot.local_llm.tps}/${snapshot.local_llm.queue}`,
37
49
  `GPT final status: ${snapshot.gpt_final_status}`,
38
50
  `Gate progress: ${snapshot.gate_progress}`,
39
- `Latest blockers: ${snapshot.latest_blockers.length ? snapshot.latest_blockers.join(', ') : 'none'}`
51
+ `Patch / verify: patches ${snapshot.patch_verify.patches} · approved ${snapshot.patch_verify.gpt_approved} · conflicts ${snapshot.patch_verify.conflicts} · verify ${snapshot.patch_verify.verification_running}/${snapshot.patch_verify.verification_passed}/${snapshot.patch_verify.verification_failed}`,
52
+ 'Workers:',
53
+ ...(snapshot.workers.length ? snapshot.workers.slice(0, 12).map((worker) => `${worker.slot_id} gen-${worker.generation_index} ${worker.role} ${worker.backend}/${worker.provider}/${worker.service_tier} WT:${worker.worktree_id || '-'} ${worker.status} ${worker.current_file || ''} ${worker.latest_heartbeat || ''}`) : ['none']),
54
+ `Latest blockers: ${snapshot.latest_blockers.length ? snapshot.latest_blockers.join(', ') : 'none'}`,
55
+ 'Controls: q detach | /stop selected | /focus slot | /logs'
40
56
  ].join('\n');
41
57
  }
42
58
  //# sourceMappingURL=zellij-dashboard-renderer.js.map
@@ -19,7 +19,7 @@ export async function launchZellijLayout(opts = {}) {
19
19
  ledgerRoot,
20
20
  cwd: opts.cwd || root,
21
21
  kind: opts.kind || 'agent',
22
- slotCount: opts.slotCount || 1,
22
+ slotCount: opts.slotCount ?? 1,
23
23
  title: `SKS ${opts.kind || 'agent'} ${missionId}`,
24
24
  codexArgs: opts.codexArgs || [],
25
25
  launchEnv: opts.launchEnv || {}
@@ -124,7 +124,7 @@ export async function launchMadZellijUi(args = [], opts = {}) {
124
124
  const launchOpts = {
125
125
  ...opts,
126
126
  kind: 'mad',
127
- slotCount: opts.slotCount || 1
127
+ slotCount: opts.slotCount ?? 1
128
128
  };
129
129
  const resolvedSession = session || opts.session;
130
130
  if (resolvedSession)
@@ -135,7 +135,7 @@ export async function launchTeamZellijView(opts = {}) {
135
135
  return launchZellijLayout({
136
136
  ...opts,
137
137
  kind: 'team',
138
- slotCount: opts.slotCount || 5
138
+ slotCount: opts.slotCount ?? 5
139
139
  });
140
140
  }
141
141
  /**