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
@@ -0,0 +1,121 @@
1
+ import fs from 'node:fs';
2
+ import { spawn } from 'node:child_process';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
6
+ import { allocateWorkerWorktree } from '../git/git-worktree-manager.js';
7
+ import { cleanupGitWorktree } from '../git/git-worktree-cleanup.js';
8
+ export async function spawnActualNarutoWorker(input) {
9
+ const workerDir = path.join(input.root, '.sneakoscope', 'missions', input.missionId, 'agents', 'naruto-real-workers', input.item.id);
10
+ await ensureDir(workerDir);
11
+ let worktree = null;
12
+ if (input.worktreePolicy?.mode === 'git-worktree' && input.item.write_allowed === true) {
13
+ const allocation = await allocateWorkerWorktree({
14
+ repoRoot: input.worktreePolicy.main_repo_root || input.root,
15
+ missionId: input.missionId,
16
+ workerId: input.item.id,
17
+ slotId: input.item.id.replace(/[^A-Za-z0-9_-]/g, '-'),
18
+ generationIndex: 1
19
+ }).catch((err) => ({ ok: false, blockers: [`git_worktree_allocate_exception:${err?.message || String(err)}`] }));
20
+ await writeJsonAtomic(path.join(workerDir, 'git-worktree-allocation.json'), allocation);
21
+ if (allocation.ok)
22
+ worktree = allocation;
23
+ }
24
+ const resultPath = path.join(workerDir, 'worker-result.json');
25
+ const heartbeatPath = path.join(workerDir, 'worker-heartbeat.jsonl');
26
+ const intakePath = path.join(workerDir, 'worker-intake.json');
27
+ await writeJsonAtomic(intakePath, {
28
+ schema: 'sks.naruto-actual-worker-intake.v1',
29
+ generated_at: nowIso(),
30
+ mission_id: input.missionId,
31
+ item: input.item,
32
+ placement: input.placement,
33
+ backend: input.backend,
34
+ result_path: resultPath,
35
+ heartbeat_path: heartbeatPath,
36
+ worktree_path: worktree?.worktree_path || null,
37
+ zellij_session_name: input.zellijSessionName || null,
38
+ visible_pane_cap: input.visiblePaneCap
39
+ });
40
+ const child = spawn(process.execPath, [actualWorkerEntrypoint(), intakePath], {
41
+ cwd: worktree?.worktree_path || input.root,
42
+ stdio: ['ignore', 'ignore', 'ignore']
43
+ });
44
+ const exit = waitForExit(child, 30000);
45
+ return {
46
+ id: input.item.id,
47
+ item: input.item,
48
+ placement: input.placement,
49
+ started_at: Date.now(),
50
+ pid: child.pid || null,
51
+ child,
52
+ worker_artifact_dir: workerDir,
53
+ result_path: resultPath,
54
+ heartbeat_path: heartbeatPath,
55
+ worktree,
56
+ exit
57
+ };
58
+ }
59
+ export async function collectActualNarutoWorker(handle) {
60
+ const exit = await handle.exit;
61
+ const result = await readJson(handle.result_path, null).catch(() => null);
62
+ const blockers = [
63
+ ...(exit.code === 0 ? [] : [`naruto_actual_worker_exit_${exit.code ?? exit.signal ?? 'unknown'}`]),
64
+ ...(result?.ok === false ? result.blockers || ['naruto_actual_worker_result_not_ok'] : []),
65
+ ...(result ? [] : ['naruto_actual_worker_result_missing'])
66
+ ];
67
+ if (handle.worktree?.worktree_path) {
68
+ const cleanup = await cleanupGitWorktree({
69
+ repoRoot: handle.worktree.main_repo_root || handle.worktree.repo_root || handle.worktree.repoRoot || '',
70
+ worktreePath: handle.worktree.worktree_path,
71
+ branch: handle.worktree.branch,
72
+ deleteBranch: true
73
+ }).catch((err) => ({ ok: false, blockers: [`git_worktree_cleanup_exception:${err?.message || String(err)}`] }));
74
+ await writeJsonAtomic(path.join(handle.worker_artifact_dir, 'git-worktree-cleanup.json'), cleanup);
75
+ blockers.push(...(cleanup.blockers || []));
76
+ }
77
+ return {
78
+ id: handle.id,
79
+ ok: blockers.length === 0,
80
+ item: handle.item,
81
+ placement: handle.placement,
82
+ completed_at: Date.now(),
83
+ pid: handle.pid,
84
+ worker_artifact_dir: handle.worker_artifact_dir,
85
+ blockers
86
+ };
87
+ }
88
+ function actualWorkerEntrypoint() {
89
+ return fileURLToPath(new URL('./naruto-real-worker-child.js', import.meta.url));
90
+ }
91
+ function waitForExit(child, timeoutMs) {
92
+ return new Promise((resolve) => {
93
+ let settled = false;
94
+ let killTimer = null;
95
+ const finish = (code, signal) => {
96
+ if (settled)
97
+ return;
98
+ settled = true;
99
+ clearTimeout(timer);
100
+ if (killTimer)
101
+ clearTimeout(killTimer);
102
+ resolve({ code, signal });
103
+ };
104
+ const timer = setTimeout(() => {
105
+ if (!child.killed)
106
+ child.kill();
107
+ killTimer = setTimeout(() => {
108
+ if (!settled)
109
+ child.kill('SIGKILL');
110
+ finish(null, 'SIGKILL');
111
+ }, 5000);
112
+ }, Math.max(1000, timeoutMs));
113
+ child.on('close', (code, signal) => {
114
+ finish(code, signal);
115
+ });
116
+ child.on('error', () => {
117
+ finish(1, null);
118
+ });
119
+ });
120
+ }
121
+ //# sourceMappingURL=naruto-real-worker-runtime.js.map
@@ -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.10';
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
  /**
@@ -3,7 +3,7 @@ import { ensureDir, nowIso, packageRoot, writeTextAtomic } from '../fsx.js';
3
3
  import { writeZellijLaneRuntimeManifest } from './zellij-lane-runtime.js';
4
4
  export const ZELLIJ_LAYOUT_SCHEMA = 'sks.zellij-layout.v1';
5
5
  export function buildZellijLayoutKdl(input) {
6
- const slotCount = Math.max(1, Number(input.slotCount || 1));
6
+ const slotCount = Math.max(0, Number(input.slotCount ?? 1));
7
7
  const sessionName = input.sessionName || `sks-${input.missionId}`;
8
8
  const cwd = path.resolve(input.cwd || process.cwd());
9
9
  const ledgerRoot = path.resolve(input.ledgerRoot);
@@ -0,0 +1,42 @@
1
+ export function evaluateZellijRightColumnGeometry(input) {
2
+ const tolerance = Math.max(0, Number(input.tolerance ?? 2));
3
+ const main = input.panes.find((pane) => pane.role === 'main') || null;
4
+ const dashboard = input.panes.find((pane) => pane.role === 'dashboard') || null;
5
+ const workers = input.panes.filter((pane) => pane.role === 'worker');
6
+ const workerGeometries = workers.map((pane) => pane.geometry).filter(Boolean);
7
+ const baseX = workerGeometries[0]?.x;
8
+ const sameRightX = baseX == null ? null : workerGeometries.every((geometry) => geometry.x != null && Math.abs(geometry.x - baseX) <= tolerance);
9
+ const rightOfMain = !main?.geometry ? null : workerGeometries.every((geometry) => {
10
+ if (geometry.x == null || main.geometry?.x == null || main.geometry?.width == null)
11
+ return false;
12
+ return geometry.x >= main.geometry.x + main.geometry.width - tolerance;
13
+ });
14
+ const increasingY = workerGeometries.every((geometry, index) => {
15
+ if (index === 0)
16
+ return true;
17
+ const previous = workerGeometries[index - 1];
18
+ return geometry.y != null && previous?.y != null && geometry.y > previous.y;
19
+ });
20
+ const visibleCapOk = workers.length <= Math.max(0, Math.floor(Number(input.visiblePaneCap || 0)));
21
+ const blockers = [
22
+ ...(!main ? ['right_column_main_pane_missing'] : []),
23
+ ...(!dashboard ? ['right_column_dashboard_missing'] : []),
24
+ ...(sameRightX === false ? ['right_column_worker_x_range_mismatch'] : []),
25
+ ...(rightOfMain === false ? ['right_column_workers_not_right_of_main'] : []),
26
+ ...(increasingY === false ? ['right_column_workers_not_stacked_down'] : []),
27
+ ...(visibleCapOk ? [] : ['right_column_visible_cap_exceeded'])
28
+ ];
29
+ return {
30
+ schema: 'sks.zellij-right-column-layout-proof.v1',
31
+ ok: blockers.length === 0,
32
+ main_pane_id: main?.pane_id || null,
33
+ dashboard_pane_id: dashboard?.pane_id || null,
34
+ worker_pane_ids: workers.map((pane) => pane.pane_id),
35
+ same_right_x: sameRightX,
36
+ right_of_main: rightOfMain,
37
+ increasing_y: increasingY,
38
+ visible_cap_ok: visibleCapOk,
39
+ blockers
40
+ };
41
+ }
42
+ //# sourceMappingURL=zellij-right-column-layout-proof.js.map