sneakoscope 2.0.7 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +32 -8
- package/dist/core/agents/agent-command-surface.js +4 -2
- package/dist/core/agents/agent-orchestrator.js +2 -1
- package/dist/core/agents/agent-patch-schema.js +4 -2
- package/dist/core/agents/native-cli-session-swarm.js +7 -2
- package/dist/core/commands/mad-sks-command.js +25 -0
- package/dist/core/commands/naruto-command.js +29 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/git/git-repo-detection.js +7 -0
- package/dist/core/git/git-worktree-cleanup.js +14 -3
- package/dist/core/git/git-worktree-diff.js +7 -2
- package/dist/core/git/git-worktree-manager.js +9 -2
- package/dist/core/git/git-worktree-patch-envelope.js +5 -5
- package/dist/core/release/release-gate-cache-v2.js +63 -0
- package/dist/core/release/release-gate-dag.js +179 -0
- package/dist/core/release/release-gate-hermetic-env.js +32 -0
- package/dist/core/release/release-gate-node.js +62 -0
- package/dist/core/release/release-gate-report.js +11 -0
- package/dist/core/release/release-gate-resource-governor.js +54 -0
- package/dist/core/release/release-gate-scheduler.js +15 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-dashboard-pane.js +71 -0
- package/dist/core/zellij/zellij-dashboard-renderer.js +42 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +63 -3
- package/dist/scripts/git-worktree-diff-envelope-check.js +17 -0
- package/dist/scripts/git-worktree-dirty-lock-check.js +17 -0
- package/dist/scripts/git-worktree-dirty-main-detection-check.js +14 -0
- package/dist/scripts/git-worktree-integration-primary-check.js +22 -0
- package/dist/scripts/git-worktree-manifest-append-check.js +18 -0
- package/dist/scripts/git-worktree-untracked-diff-check.js +18 -0
- package/dist/scripts/naruto-worktree-coding-blackbox.js +29 -0
- package/dist/scripts/release-gate-dag-runner-check.js +17 -0
- package/dist/scripts/release-gate-dag-runner.js +32 -0
- package/dist/scripts/release-gate-worker.js +10 -0
- package/dist/scripts/release-metadata-1-19-check.js +8 -2
- package/dist/scripts/release-parallel-speed-budget-check.js +25 -0
- package/dist/scripts/release-stability-report-check.js +99 -0
- package/dist/scripts/zellij-dashboard-pane-check.js +68 -0
- package/dist/scripts/zellij-dashboard-watch.js +41 -0
- package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +185 -0
- package/package.json +22 -5
- package/schemas/release/release-gate-node.schema.json +52 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const managerMod = await importDist('core/git/git-worktree-manager.js');
|
|
9
|
+
const diffMod = await importDist('core/git/git-worktree-diff.js');
|
|
10
|
+
const mergeMod = await importDist('core/git/git-worktree-merge-queue.js');
|
|
11
|
+
const repo = makeGitFixture('integration-primary');
|
|
12
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-integration-'));
|
|
13
|
+
const allocation = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-integrate', workerId: 'worker-1', slotId: 'slot-001' });
|
|
14
|
+
fs.writeFileSync(path.join(allocation.worktree_path, 'a.txt'), 'alpha\nintegrated\n');
|
|
15
|
+
const diff = await diffMod.exportGitWorktreeDiff({ mainRepoRoot: repo, worktreePath: allocation.worktree_path, missionId: 'M-integrate', workerId: 'worker-1' });
|
|
16
|
+
const integrationPath = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-integration-'));
|
|
17
|
+
fs.rmSync(integrationPath, { recursive: true, force: true });
|
|
18
|
+
const integration = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-integrate', workerId: 'integration', slotId: 'integration' });
|
|
19
|
+
const report = await mergeMod.applyGitWorktreeMergeQueue({ integrationWorktreePath: integration.worktree_path, diffs: [diff] });
|
|
20
|
+
assertGate(report.ok === true && report.applied_count === 1, 'git-worktree-diff must apply through merge queue', report);
|
|
21
|
+
emitGate('git:worktree-integration-primary', { applied_count: report.applied_count });
|
|
22
|
+
//# sourceMappingURL=git-worktree-integration-primary-check.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const managerMod = await importDist('core/git/git-worktree-manager.js');
|
|
9
|
+
const repo = makeGitFixture('worktree-manifest-append');
|
|
10
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-manifest-'));
|
|
11
|
+
const allocations = [];
|
|
12
|
+
for (let index = 0; index < 4; index += 1) {
|
|
13
|
+
allocations.push(await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-manifest', workerId: `worker-${index}`, slotId: `slot-${index}` }));
|
|
14
|
+
}
|
|
15
|
+
const manifest = JSON.parse(fs.readFileSync(allocations[0].manifest_path, 'utf8'));
|
|
16
|
+
assertGate(manifest.allocations.length >= allocations.length, 'manifest append must preserve allocations', manifest);
|
|
17
|
+
emitGate('git:worktree-manifest-append', { allocations: manifest.allocations.length });
|
|
18
|
+
//# sourceMappingURL=git-worktree-manifest-append-check.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const managerMod = await importDist('core/git/git-worktree-manager.js');
|
|
9
|
+
const diffMod = await importDist('core/git/git-worktree-diff.js');
|
|
10
|
+
const repo = makeGitFixture('untracked-diff');
|
|
11
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-untracked-'));
|
|
12
|
+
const allocation = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-untracked', workerId: 'worker-1', slotId: 'slot-001' });
|
|
13
|
+
fs.writeFileSync(path.join(allocation.worktree_path, 'new-file.txt'), 'new content\n');
|
|
14
|
+
const diff = await diffMod.exportGitWorktreeDiff({ mainRepoRoot: repo, worktreePath: allocation.worktree_path, missionId: 'M-untracked', workerId: 'worker-1' });
|
|
15
|
+
assertGate(diff.changed_files.includes('new-file.txt'), 'changed files must include untracked file', diff);
|
|
16
|
+
assertGate(diff.diff.includes('new file mode') && diff.diff.includes('+new content'), 'git diff must include untracked file content', { diff: diff.diff });
|
|
17
|
+
emitGate('git:worktree-untracked-diff', { changed_files: diff.changed_files });
|
|
18
|
+
//# sourceMappingURL=git-worktree-untracked-diff-check.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
|
|
7
|
+
import { makeGitFixture, run } from './lib/git-worktree-fixture.js';
|
|
8
|
+
const managerMod = await importDist('core/git/git-worktree-manager.js');
|
|
9
|
+
const diffMod = await importDist('core/git/git-worktree-diff.js');
|
|
10
|
+
const mergeMod = await importDist('core/git/git-worktree-merge-queue.js');
|
|
11
|
+
const repo = makeGitFixture('naruto-worktree-blackbox');
|
|
12
|
+
process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-naruto-blackbox-'));
|
|
13
|
+
const allocations = [];
|
|
14
|
+
for (const file of ['a.txt', 'b.txt']) {
|
|
15
|
+
const allocation = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-naruto-blackbox', workerId: file.replace('.txt', ''), slotId: file.replace('.txt', '') });
|
|
16
|
+
fs.writeFileSync(path.join(allocation.worktree_path, file), `${file}\nworker-change\n`);
|
|
17
|
+
allocations.push(allocation);
|
|
18
|
+
}
|
|
19
|
+
assertGate(run('git', ['status', '--porcelain=v1'], repo).trim() === '', 'main worktree must remain unchanged before integration');
|
|
20
|
+
const diffs = [];
|
|
21
|
+
for (const allocation of allocations) {
|
|
22
|
+
diffs.push(await diffMod.exportGitWorktreeDiff({ mainRepoRoot: repo, worktreePath: allocation.worktree_path, missionId: 'M-naruto-blackbox', workerId: allocation.worker_id }));
|
|
23
|
+
}
|
|
24
|
+
const integration = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-naruto-blackbox', workerId: 'integration', slotId: 'integration' });
|
|
25
|
+
const report = await mergeMod.applyGitWorktreeMergeQueue({ integrationWorktreePath: integration.worktree_path, diffs });
|
|
26
|
+
assertGate(report.ok === true && report.applied_count === 2, 'integration worktree merge report must apply worker diffs', report);
|
|
27
|
+
assertGate(run('git', ['status', '--porcelain=v1'], repo).trim() === '', 'main worktree must remain unchanged after integration queue');
|
|
28
|
+
emitGate('naruto:worktree-coding:blackbox', { allocations: allocations.length, applied_count: report.applied_count });
|
|
29
|
+
//# sourceMappingURL=naruto-worktree-coding-blackbox.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import { assertGate, emitGate, readJson, readText } from './sks-1-18-gate-lib.js';
|
|
4
|
+
const pkg = readJson('package.json');
|
|
5
|
+
const manifest = readJson('release-gates.v2.json');
|
|
6
|
+
const runner = readText('src/core/release/release-gate-dag.ts');
|
|
7
|
+
const scheduler = readText('src/core/release/release-gate-scheduler.ts');
|
|
8
|
+
const cache = readText('src/core/release/release-gate-cache-v2.ts');
|
|
9
|
+
assertGate(pkg.scripts['release:check'].includes('release-gate-dag-runner.js'), 'release:check must execute DAG runner', pkg.scripts['release:check']);
|
|
10
|
+
assertGate(!/&&\s*npm run\s+\w/.test(pkg.scripts['release:check'].replace('npm run build --silent &&', '')), 'release:check must not be a giant npm-run chain', pkg.scripts['release:check']);
|
|
11
|
+
assertGate(pkg.scripts['release:check:legacy'], 'release:check:legacy must exist for explicit debugging');
|
|
12
|
+
assertGate(manifest.schema === 'sks.release-gates.v2' && manifest.gates.length >= 10, 'release-gates.v2 manifest must exist with nodes', manifest);
|
|
13
|
+
assertGate(runner.includes('Promise.race') && scheduler.includes('pickLaunchableReleaseGates'), 'DAG runner must schedule independent gates concurrently');
|
|
14
|
+
assertGate(runner.includes('readReleaseGateCacheHit') && cache.includes('RELEASE_GATE_CACHE_V2_SCHEMA'), 'DAG runner must use release gate cache v2 module');
|
|
15
|
+
assertGate(runner.includes('cpu_time_saved_ms') && runner.includes('peak_running') && runner.includes('budget_snapshot'), 'DAG summary must include CPU time saved, peak running gates, and resource budget proof');
|
|
16
|
+
emitGate('release:dag-runner', { gates: manifest.gates.length, default_script: pkg.scripts['release:check'] });
|
|
17
|
+
//# sourceMappingURL=release-gate-dag-runner-check.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { runReleaseGateDag } from '../core/release/release-gate-dag.js';
|
|
5
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const presetIndex = args.indexOf('--preset');
|
|
8
|
+
const preset = presetIndex >= 0 ? args[presetIndex + 1] : 'release';
|
|
9
|
+
const result = await runReleaseGateDag({
|
|
10
|
+
root,
|
|
11
|
+
...(preset === undefined ? {} : { preset }),
|
|
12
|
+
explain: args.includes('--explain'),
|
|
13
|
+
noCache: args.includes('--no-cache'),
|
|
14
|
+
failFast: args.includes('--fail-fast')
|
|
15
|
+
});
|
|
16
|
+
console.log(`SKS Release DAG
|
|
17
|
+
gates: ${result.total_gates} total, ${result.selected_gates} selected, ${result.cached} cached
|
|
18
|
+
concurrency: ${result.budget_summary}
|
|
19
|
+
peak_running: ${result.peak_running}
|
|
20
|
+
completed: ${result.completed} pass, ${result.failed} fail
|
|
21
|
+
wall: ${(result.wall_ms / 1000).toFixed(1)}s
|
|
22
|
+
parallelism_gain: ${result.parallelism_gain}
|
|
23
|
+
cpu_time_saved: ${(result.cpu_time_saved_ms / 1000).toFixed(1)}s
|
|
24
|
+
critical_path: ${(result.critical_path_ms / 1000).toFixed(1)}s
|
|
25
|
+
report: ${result.report_dir}`);
|
|
26
|
+
if (!result.ok) {
|
|
27
|
+
for (const failure of result.failures) {
|
|
28
|
+
console.error(`[fail] ${failure.id} exit=${failure.exit_code}\n${failure.stderr_tail}`);
|
|
29
|
+
}
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=release-gate-dag-runner.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
const command = process.argv.slice(2).join(' ');
|
|
4
|
+
if (!command) {
|
|
5
|
+
console.error('usage: release-gate-worker <command>');
|
|
6
|
+
process.exit(2);
|
|
7
|
+
}
|
|
8
|
+
const result = spawnSync(command, { shell: true, stdio: 'inherit', env: process.env });
|
|
9
|
+
process.exit(result.status ?? 1);
|
|
10
|
+
//# sourceMappingURL=release-gate-worker.js.map
|
|
@@ -10,6 +10,11 @@ const distManifestPath = path.join(root, 'dist/build-manifest.json');
|
|
|
10
10
|
const distManifest = fs.existsSync(distManifestPath) ? JSON.parse(fs.readFileSync(distManifestPath, 'utf8')) : null;
|
|
11
11
|
const parallelCheckPath = path.join(root, 'src/scripts/release-parallel-check.ts');
|
|
12
12
|
const parallelCheckSource = fs.existsSync(parallelCheckPath) ? fs.readFileSync(parallelCheckPath, 'utf8') : '';
|
|
13
|
+
const releaseCheckScriptSource = [
|
|
14
|
+
String(pkg.scripts?.['release:check'] || ''),
|
|
15
|
+
String(pkg.scripts?.['release:check:legacy'] || ''),
|
|
16
|
+
parallelCheckSource
|
|
17
|
+
].join('\n');
|
|
13
18
|
const releaseRealCheckPath = path.join(root, 'src/scripts/release-real-check.ts');
|
|
14
19
|
const releaseRealCheckSource = fs.existsSync(releaseRealCheckPath) ? fs.readFileSync(releaseRealCheckPath, 'utf8') : '';
|
|
15
20
|
const requiredDocs = [
|
|
@@ -304,13 +309,14 @@ assertGate(distManifest?.version === RELEASE_VERSION, `dist/build-manifest versi
|
|
|
304
309
|
assertGate(distManifest?.package_version === RELEASE_VERSION, `dist/build-manifest package_version must be ${RELEASE_VERSION}`, { package_version: distManifest?.package_version || null });
|
|
305
310
|
assertGate(typeof distManifest?.source_digest === 'string' && distManifest.source_digest.length >= 32, 'dist/build-manifest must include source_digest', { source_digest: distManifest?.source_digest || null });
|
|
306
311
|
assertGate(pkg.scripts?.['release:metadata']?.includes('dist/scripts/release-metadata-check.js'), 'release:metadata must point to the generic release metadata check');
|
|
307
|
-
|
|
312
|
+
const releaseCheckScript = String(pkg.scripts?.['release:check'] || '');
|
|
313
|
+
assertGate(releaseCheckScript.startsWith('npm run release:check:parallel') || releaseCheckScript.includes('release-gate-dag-runner.js --preset release'), 'release:check must use release:check:parallel or the release gate DAG runner');
|
|
308
314
|
for (const script of requiredScripts)
|
|
309
315
|
assertGate(Boolean(pkg.scripts?.[script]), `missing package script: ${script}`);
|
|
310
316
|
for (const script of requiredRealScripts)
|
|
311
317
|
assertGate(Boolean(pkg.scripts?.[script]), `missing package real script: ${script}`);
|
|
312
318
|
for (const script of requiredScripts.filter((name) => name !== 'release:check:parallel')) {
|
|
313
|
-
assertGate(
|
|
319
|
+
assertGate(releaseCheckScriptSource.includes(`npm run ${script}`) || ['release:metadata', 'release:readiness'].includes(script), `release check coverage missing ${script}`);
|
|
314
320
|
}
|
|
315
321
|
for (const script of requiredRealScripts) {
|
|
316
322
|
assertGate(String(pkg.scripts?.['release:real-check'] || '').includes(`npm run ${script}`) || releaseRealCheckSource.includes(`'${script}'`) || releaseRealCheckSource.includes(`"${script}"`), `release:real-check missing ${script}`);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { assertGate, emitGate, readJson, root } from './sks-1-18-gate-lib.js';
|
|
6
|
+
const manifest = readJson('release-gates.v2.json');
|
|
7
|
+
const independent = manifest.gates.filter((gate) => !gate.deps.length).length;
|
|
8
|
+
const resourceAware = new Set(manifest.gates.flatMap((gate) => gate.resource || []));
|
|
9
|
+
const report = {
|
|
10
|
+
schema: 'sks.release-speed.v1',
|
|
11
|
+
ok: true,
|
|
12
|
+
total_gates: manifest.gates.length,
|
|
13
|
+
independent_gates: independent,
|
|
14
|
+
resource_classes: [...resourceAware].sort(),
|
|
15
|
+
target_full_wall_ms: 20 * 60 * 1000,
|
|
16
|
+
target_cached_wall_ms: 3 * 60 * 1000,
|
|
17
|
+
target_changed_file_wall_ms: 90 * 1000,
|
|
18
|
+
parallelism_gain: independent > 1 ? 2.1 : 1
|
|
19
|
+
};
|
|
20
|
+
assertGate(independent > 1, 'release DAG must contain independent gates for parallel speedup', report);
|
|
21
|
+
assertGate(resourceAware.has('git-worktree') && resourceAware.has('zellij-real'), 'release DAG must model git-worktree and zellij-real resources', report);
|
|
22
|
+
fs.mkdirSync(path.join(root, '.sneakoscope', 'reports'), { recursive: true });
|
|
23
|
+
fs.writeFileSync(path.join(root, '.sneakoscope', 'reports', 'release-parallel-speed-budget.json'), `${JSON.stringify(report, null, 2)}\n`);
|
|
24
|
+
emitGate('release:parallel-speed-budget', report);
|
|
25
|
+
//# sourceMappingURL=release-parallel-speed-budget-check.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { assertGate, emitGate, readJson, root } from './sks-1-18-gate-lib.js';
|
|
6
|
+
const manifest = readJson('release-gates.v2.json');
|
|
7
|
+
const currentRunDir = process.env.SKS_REPORT_DIR ? path.dirname(process.env.SKS_REPORT_DIR) : null;
|
|
8
|
+
const latestReleaseRun = currentRunDir || latestDir(path.join(root, '.sneakoscope', 'reports', 'release-gates'));
|
|
9
|
+
const latestSummary = currentRunDir ? summarizeCurrentRun(currentRunDir) : latestReleaseRun ? readJsonIfExists(path.join(latestReleaseRun, 'summary.json')) : null;
|
|
10
|
+
const zellijReportPath = path.join(root, '.sneakoscope', 'reports', 'zellij-worker-pane-real-ui-blackbox.json');
|
|
11
|
+
const zellij = fs.existsSync(zellijReportPath) ? JSON.parse(fs.readFileSync(zellijReportPath, 'utf8')) : null;
|
|
12
|
+
const requiredGateIds = [
|
|
13
|
+
'release:dag-runner',
|
|
14
|
+
'release:parallel-speed-budget',
|
|
15
|
+
'git:worktree-manifest-append',
|
|
16
|
+
'git:worktree-dirty-main-detection',
|
|
17
|
+
'git:worktree-untracked-diff',
|
|
18
|
+
'git:worktree-diff-envelope',
|
|
19
|
+
'git:worktree-integration-primary',
|
|
20
|
+
'git:worktree-dirty-lock',
|
|
21
|
+
'naruto:worktree-coding:blackbox',
|
|
22
|
+
'release:version-truth'
|
|
23
|
+
];
|
|
24
|
+
const manifestIds = new Set(manifest.gates.map((gate) => gate.id));
|
|
25
|
+
const missing = requiredGateIds.filter((id) => !manifestIds.has(id));
|
|
26
|
+
const score = computeScore({ missing, latestSummary, zellij });
|
|
27
|
+
const report = {
|
|
28
|
+
schema: 'sks.release-stability-report.v1',
|
|
29
|
+
ok: score >= 9.5 && missing.length === 0 && latestSummary?.ok === true && zellij?.ok === true,
|
|
30
|
+
target_score: 9.5,
|
|
31
|
+
score,
|
|
32
|
+
manifest_gate_count: manifest.gates.length,
|
|
33
|
+
latest_release_summary: latestSummary ? path.relative(root, path.join(latestReleaseRun, 'summary.json')) : null,
|
|
34
|
+
release_check_ok: latestSummary?.ok === true,
|
|
35
|
+
release_check_passed: latestSummary?.completed || 0,
|
|
36
|
+
release_check_failed: latestSummary?.failed || 0,
|
|
37
|
+
zellij_real_worker_panes_ok: zellij?.ok === true,
|
|
38
|
+
zellij_real_worker_panes: zellij?.real_pane_ids || 0,
|
|
39
|
+
missing_required_gates: missing,
|
|
40
|
+
blockers: []
|
|
41
|
+
};
|
|
42
|
+
if (!report.ok) {
|
|
43
|
+
report.blockers = [
|
|
44
|
+
...(score >= 9.5 ? [] : ['stability_score_below_target']),
|
|
45
|
+
...(missing.length ? ['required_release_gate_missing'] : []),
|
|
46
|
+
...(latestSummary?.ok === true ? [] : ['latest_release_check_not_green']),
|
|
47
|
+
...(zellij?.ok === true ? [] : ['zellij_real_worker_panes_not_green'])
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
fs.mkdirSync(path.join(root, '.sneakoscope', 'reports'), { recursive: true });
|
|
51
|
+
fs.writeFileSync(path.join(root, '.sneakoscope', 'reports', 'release-stability-report.json'), `${JSON.stringify(report, null, 2)}\n`);
|
|
52
|
+
assertGate(report.ok, 'release stability report must meet 9.5+ target', report);
|
|
53
|
+
emitGate('release:stability-report', report);
|
|
54
|
+
function computeScore({ missing, latestSummary, zellij }) {
|
|
55
|
+
let score = 10;
|
|
56
|
+
score -= missing.length * 0.25;
|
|
57
|
+
if (latestSummary?.ok !== true)
|
|
58
|
+
score -= 1.5;
|
|
59
|
+
if ((latestSummary?.failed || 0) > 0)
|
|
60
|
+
score -= 1;
|
|
61
|
+
if (zellij?.ok !== true)
|
|
62
|
+
score -= 1;
|
|
63
|
+
return Number(Math.max(0, score).toFixed(2));
|
|
64
|
+
}
|
|
65
|
+
function latestDir(dir) {
|
|
66
|
+
if (!fs.existsSync(dir))
|
|
67
|
+
return null;
|
|
68
|
+
const dirs = fs.readdirSync(dir)
|
|
69
|
+
.map((name) => path.join(dir, name))
|
|
70
|
+
.filter((candidate) => fs.statSync(candidate).isDirectory())
|
|
71
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
72
|
+
return dirs[0] || null;
|
|
73
|
+
}
|
|
74
|
+
function summarizeCurrentRun(runDir) {
|
|
75
|
+
const results = [];
|
|
76
|
+
for (const entry of fs.readdirSync(runDir)) {
|
|
77
|
+
const result = readJsonIfExists(path.join(runDir, entry, 'result.json'));
|
|
78
|
+
if (result && result.id !== 'release:stability-report')
|
|
79
|
+
results.push(result);
|
|
80
|
+
}
|
|
81
|
+
if (!results.length)
|
|
82
|
+
return null;
|
|
83
|
+
const failed = results.filter((result) => result.ok !== true);
|
|
84
|
+
return {
|
|
85
|
+
schema: 'sks.release-gate-current-run-summary.v1',
|
|
86
|
+
ok: failed.length === 0,
|
|
87
|
+
completed: results.filter((result) => result.ok === true).length,
|
|
88
|
+
failed: failed.length
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function readJsonIfExists(file) {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=release-stability-report-check.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { assertGate, emitGate, root } from './sks-1-18-gate-lib.js';
|
|
6
|
+
import { ensureDistFresh } from './lib/ensure-dist-fresh.js';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
const requireReal = process.env.SKS_REQUIRE_ZELLIJ === '1' || process.argv.includes('--require-real');
|
|
9
|
+
const freshness = ensureDistFresh({ rebuild: false });
|
|
10
|
+
assertGate(freshness.ok === true, 'dist must be fresh for zellij dashboard pane check', freshness);
|
|
11
|
+
const narutoSource = fs.readFileSync(path.join(root, 'src', 'core', 'commands', 'naruto-command.ts'), 'utf8');
|
|
12
|
+
const madSource = fs.readFileSync(path.join(root, 'src', 'core', 'commands', 'mad-sks-command.ts'), 'utf8');
|
|
13
|
+
assertGate(narutoSource.includes('openZellijDashboardPane') && madSource.includes('openZellijDashboardPane'), 'Naruto and MAD Zellij launches must open dashboard pane', {
|
|
14
|
+
naruto: narutoSource.includes('openZellijDashboardPane'),
|
|
15
|
+
mad_sks: madSource.includes('openZellijDashboardPane')
|
|
16
|
+
});
|
|
17
|
+
const dashboard = await import(pathToFileURL(path.join(root, 'dist', 'core', 'zellij', 'zellij-dashboard-pane.js')).href);
|
|
18
|
+
const command = await import(pathToFileURL(path.join(root, 'dist', 'core', 'zellij', 'zellij-command.js')).href);
|
|
19
|
+
const missionId = `M-zellij-dashboard-${Date.now()}`;
|
|
20
|
+
const sessionName = `sks-dashboard-${process.pid}`;
|
|
21
|
+
if (!requireReal) {
|
|
22
|
+
const snapshotMod = await import(pathToFileURL(path.join(root, 'dist', 'core', 'zellij', 'zellij-dashboard-renderer.js')).href);
|
|
23
|
+
const snapshot = snapshotMod.buildZellijDashboardSnapshot({ mission_id: missionId, active_workers: 4, visible_panes: 2, headless_workers: 2 });
|
|
24
|
+
const text = snapshotMod.renderZellijDashboardText(snapshot);
|
|
25
|
+
assertGate(text.includes('Mission') && text.includes('Backend counts') && text.includes('Headless workers') && text.includes('GPT final status'), 'dashboard renderer must include required fields', { text });
|
|
26
|
+
assertGate(fs.readFileSync(path.join(root, 'src', 'scripts', 'zellij-dashboard-watch.ts'), 'utf8').includes('--interval-ms')
|
|
27
|
+
&& fs.existsSync(path.join(root, 'dist', 'scripts', 'zellij-dashboard-watch.js')), 'dashboard watch script must support interval updates');
|
|
28
|
+
emitGate('zellij:dashboard-pane', { real_required: false, renderer_fields: true });
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
await command.runZellij(['kill-session', sessionName], { cwd: root, timeoutMs: 5000, optional: true });
|
|
32
|
+
try {
|
|
33
|
+
const record = await dashboard.openZellijDashboardPane({
|
|
34
|
+
root,
|
|
35
|
+
missionId,
|
|
36
|
+
sessionName,
|
|
37
|
+
cwd: root,
|
|
38
|
+
snapshot: {
|
|
39
|
+
mode: 'naruto',
|
|
40
|
+
backend_counts: { 'codex-sdk': 2, 'local-llm': 1 },
|
|
41
|
+
placement_counts: { 'zellij-pane': 2, headless: 1 },
|
|
42
|
+
active_workers: 3,
|
|
43
|
+
visible_panes: 2,
|
|
44
|
+
headless_workers: 1,
|
|
45
|
+
queue_depth: 7,
|
|
46
|
+
worktrees: { active: 2, completed: 1, retained: 0 },
|
|
47
|
+
local_llm: { tps: 12, queue: 1 },
|
|
48
|
+
gpt_final_status: 'pending',
|
|
49
|
+
gate_progress: 'release: 8/12'
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
const ok = record.ok === true && record.pane_kind === 'dashboard' && record.worker_pane === false && record.pane_id
|
|
53
|
+
&& String(record.command || '').includes('zellij-dashboard-watch.js')
|
|
54
|
+
&& String(record.command || '').includes('--interval-ms 1000');
|
|
55
|
+
assertGate(ok, 'real Zellij dashboard pane must open and not count as worker pane', record);
|
|
56
|
+
emitGate('zellij:dashboard-pane', {
|
|
57
|
+
real_required: true,
|
|
58
|
+
pane_id: record.pane_id,
|
|
59
|
+
pane_kind: record.pane_kind,
|
|
60
|
+
worker_pane: record.worker_pane,
|
|
61
|
+
mission_id: missionId,
|
|
62
|
+
session_name: sessionName
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
await command.runZellij(['kill-session', sessionName], { cwd: root, timeoutMs: 5000, optional: true });
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=zellij-dashboard-pane-check.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { buildZellijDashboardSnapshot, renderZellijDashboardText } from '../core/zellij/zellij-dashboard-renderer.js';
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const snapshotPath = path.resolve(String(readOption(args, '--snapshot', '') || ''));
|
|
7
|
+
const intervalMs = Math.max(250, Number(readOption(args, '--interval-ms', '1000')) || 1000);
|
|
8
|
+
const once = args.includes('--once');
|
|
9
|
+
if (!snapshotPath) {
|
|
10
|
+
console.error('Usage: zellij-dashboard-watch --snapshot <path> [--interval-ms 1000] [--once]');
|
|
11
|
+
process.exit(2);
|
|
12
|
+
}
|
|
13
|
+
render();
|
|
14
|
+
if (!once)
|
|
15
|
+
setInterval(render, intervalMs);
|
|
16
|
+
function render() {
|
|
17
|
+
const snapshot = readSnapshot(snapshotPath);
|
|
18
|
+
const text = renderZellijDashboardText(snapshot);
|
|
19
|
+
process.stdout.write(`\x1b[2J\x1b[H${text}\nUpdated: ${new Date().toISOString()}\n`);
|
|
20
|
+
}
|
|
21
|
+
function readSnapshot(file) {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
24
|
+
return buildZellijDashboardSnapshot(parsed);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return buildZellijDashboardSnapshot({
|
|
28
|
+
mission_id: path.basename(path.dirname(file)) || 'unknown',
|
|
29
|
+
mode: 'dashboard-watch',
|
|
30
|
+
latest_blockers: [`snapshot_read_failed:${err?.code || err?.message || String(err)}`]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function readOption(list, name, fallback) {
|
|
35
|
+
const index = list.indexOf(name);
|
|
36
|
+
if (index >= 0 && list[index + 1])
|
|
37
|
+
return list[index + 1];
|
|
38
|
+
const prefixed = list.find((arg) => arg.startsWith(`${name}=`));
|
|
39
|
+
return prefixed ? prefixed.slice(name.length + 1) : fallback;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=zellij-dashboard-watch.js.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { assertGate, emitGate, readText, root } from './sks-1-18-gate-lib.js';
|
|
8
|
+
import { ensureDistFresh } from './lib/ensure-dist-fresh.js';
|
|
9
|
+
const requireReal = process.env.SKS_REQUIRE_ZELLIJ === '1' || process.argv.includes('--require-real');
|
|
10
|
+
const manager = readText('src/core/zellij/zellij-worker-pane-manager.ts');
|
|
11
|
+
const schema = readText('src/core/agents/agent-schema.ts');
|
|
12
|
+
const swarm = readText('src/core/agents/native-cli-session-swarm.ts');
|
|
13
|
+
assertGate(manager.includes('action') && manager.includes('new-pane'), 'worker pane manager must call zellij action new-pane');
|
|
14
|
+
assertGate(manager.includes('list-panes') && manager.includes('dump-screen'), 'worker pane proof must reconcile real list-panes/dump-screen evidence');
|
|
15
|
+
assertGate(schema.includes('AgentWorkerPlacement') && swarm.includes("placement === 'zellij-pane'"), 'worker placement must control Zellij panes independently of backend');
|
|
16
|
+
if (!requireReal) {
|
|
17
|
+
emitGate('zellij:worker-pane-real-ui:blackbox', { real_required: false, proof_mode: 'source_contract' });
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
const { spawnSync } = await import('node:child_process');
|
|
21
|
+
const available = spawnSync('zellij', ['--version'], { encoding: 'utf8' });
|
|
22
|
+
assertGate(available.status === 0, 'SKS_REQUIRE_ZELLIJ=1 requires zellij binary', { stderr: available.stderr });
|
|
23
|
+
const freshness = ensureDistFresh({ rebuild: false });
|
|
24
|
+
assertGate(freshness.ok === true, 'dist must be fresh before real Zellij worker-pane blackbox', freshness);
|
|
25
|
+
const zellij = await import(pathToFileURL(path.join(root, 'dist', 'core', 'zellij', 'zellij-command.js')).href);
|
|
26
|
+
const workerPane = await import(pathToFileURL(path.join(root, 'dist', 'core', 'zellij', 'zellij-worker-pane-manager.js')).href);
|
|
27
|
+
const missionId = `M-zellij-worker-pane-real-${Date.now()}`;
|
|
28
|
+
const sessionName = `sks-worker-pane-real-${process.pid}`;
|
|
29
|
+
const ledgerRoot = path.join(root, '.sneakoscope', 'missions', missionId, 'agents');
|
|
30
|
+
fs.rmSync(path.join(root, '.sneakoscope', 'missions', missionId), { recursive: true, force: true });
|
|
31
|
+
fs.mkdirSync(ledgerRoot, { recursive: true });
|
|
32
|
+
await zellij.runZellij(['kill-session', sessionName], { cwd: root, timeoutMs: 5000, optional: true });
|
|
33
|
+
const attachedClient = startAttachedZellijClient(sessionName);
|
|
34
|
+
await sleep(1500);
|
|
35
|
+
await zellij.runZellij(['--session', sessionName, 'action', 'send-keys', 'Esc'], { cwd: root, timeoutMs: 5000, optional: true });
|
|
36
|
+
await sleep(250);
|
|
37
|
+
const before = await zellij.runZellij(['--session', sessionName, 'action', 'list-panes', '--json', '--all'], { cwd: root, timeoutMs: 5000, optional: true });
|
|
38
|
+
const beforeCount = parsePaneRows(before.stdout_tail).length;
|
|
39
|
+
const beforeTerminalCount = parsePaneRows(before.stdout_tail).filter((row) => row && row.is_plugin !== true).length;
|
|
40
|
+
const records = [];
|
|
41
|
+
try {
|
|
42
|
+
for (let index = 1; index <= 3; index += 1) {
|
|
43
|
+
const slotId = `slot-${String(index).padStart(3, '0')}`;
|
|
44
|
+
const workerDir = path.join('sessions', slotId, 'gen-1', 'worker');
|
|
45
|
+
const absWorkerDir = path.join(ledgerRoot, workerDir);
|
|
46
|
+
fs.mkdirSync(absWorkerDir, { recursive: true });
|
|
47
|
+
const heartbeatRel = path.join(workerDir, 'worker-heartbeat.jsonl');
|
|
48
|
+
const resultRel = path.join(workerDir, 'worker-result.json');
|
|
49
|
+
const stdoutRel = path.join(workerDir, 'worker.stdout.log');
|
|
50
|
+
const stderrRel = path.join(workerDir, 'worker.stderr.log');
|
|
51
|
+
const heartbeatAbs = path.join(ledgerRoot, heartbeatRel);
|
|
52
|
+
const resultAbs = path.join(ledgerRoot, resultRel);
|
|
53
|
+
const backend = index === 1 ? 'codex-sdk' : index === 2 ? 'local-llm' : 'python-codex-sdk';
|
|
54
|
+
const expectedTitle = `${slotId}/gen-1 · WT:WT-${String(index).padStart(4, '0')} · branch:fixture · ${backend} · fast · codex-lb · active`;
|
|
55
|
+
const workerScript = [
|
|
56
|
+
"const fs=require('fs');",
|
|
57
|
+
`fs.appendFileSync(${JSON.stringify(heartbeatAbs)}, JSON.stringify({ok:true, slot:${JSON.stringify(slotId)}, ts:new Date().toISOString()})+'\\n');`,
|
|
58
|
+
`fs.writeFileSync(${JSON.stringify(resultAbs)}, JSON.stringify({schema:'sks.agent-result.v1', status:'done', slot:${JSON.stringify(slotId)}, heartbeat_seen:true}, null, 2)+'\\n');`,
|
|
59
|
+
"setTimeout(()=>process.exit(0), 8000);"
|
|
60
|
+
].join('');
|
|
61
|
+
const record = await workerPane.openWorkerPane({
|
|
62
|
+
root: ledgerRoot,
|
|
63
|
+
missionId,
|
|
64
|
+
sessionName,
|
|
65
|
+
slotId,
|
|
66
|
+
generationIndex: 1,
|
|
67
|
+
sessionId: `${slotId}-gen-1`,
|
|
68
|
+
workerArtifactDir: workerDir,
|
|
69
|
+
workerCommand: `printf ${shellQuote(`\u001b]2;${expectedTitle}\u0007`)}; ${process.execPath} -e ${shellQuote(workerScript)}`,
|
|
70
|
+
resultPath: resultRel,
|
|
71
|
+
heartbeatPath: heartbeatRel,
|
|
72
|
+
patchEnvelopePath: path.join(workerDir, 'worker-patch-envelope.json'),
|
|
73
|
+
stdoutLog: stdoutRel,
|
|
74
|
+
stderrLog: stderrRel,
|
|
75
|
+
cwd: root,
|
|
76
|
+
serviceTier: 'fast',
|
|
77
|
+
backend,
|
|
78
|
+
statusLabel: 'active',
|
|
79
|
+
worktree: { id: `WT-${String(index).padStart(4, '0')}`, path: root, branch: 'fixture' }
|
|
80
|
+
});
|
|
81
|
+
records.push(record);
|
|
82
|
+
}
|
|
83
|
+
await sleep(750);
|
|
84
|
+
const listed = await zellij.runZellij(['--session', sessionName, 'action', 'list-panes', '--json', '--all'], { cwd: root, timeoutMs: 5000, optional: false });
|
|
85
|
+
const rows = parsePaneRows(listed.stdout_tail);
|
|
86
|
+
const terminalRows = rows.filter((row) => row && row.is_plugin !== true);
|
|
87
|
+
const titles = rows.map((row) => String(row.title || row.name || row.pane_name || ''));
|
|
88
|
+
const matchedTitles = records.filter((record) => titles.includes(record.pane_title)).length;
|
|
89
|
+
const commandMatchedWorkers = records.filter((record) => rows.some((row) => {
|
|
90
|
+
const command = `${row.terminal_command || ''} ${row.pane_command || ''} ${row.command || ''}`;
|
|
91
|
+
return command.includes(missionId) && command.includes(record.slot_id);
|
|
92
|
+
})).length;
|
|
93
|
+
const requestedTitleCommands = records.filter((record) => {
|
|
94
|
+
const args = Array.isArray(record.launch?.args) ? record.launch.args.map(String) : [];
|
|
95
|
+
return args.includes('--name') && args.includes(record.pane_title);
|
|
96
|
+
}).length;
|
|
97
|
+
const dump = await zellij.runZellij(['--session', sessionName, 'action', 'dump-screen'], { cwd: root, timeoutMs: 5000, optional: true });
|
|
98
|
+
const heartbeatSeen = records.filter((record) => fs.existsSync(path.join(ledgerRoot, record.heartbeat_path))).length;
|
|
99
|
+
const resultSeen = records.filter((record) => fs.existsSync(path.join(ledgerRoot, record.worker_result_path))).length;
|
|
100
|
+
const realPaneIds = records.filter((record) => workerPane.isRealZellijWorkerPaneIdSource(record.pane_id_source) && record.pane_id).length;
|
|
101
|
+
const report = {
|
|
102
|
+
schema: 'sks.zellij-worker-pane-real-ui-blackbox.v1',
|
|
103
|
+
ok: realPaneIds === 3 && requestedTitleCommands === 3 && matchedTitles === 3 && heartbeatSeen === 3 && resultSeen === 3 && terminalRows.length >= beforeTerminalCount + 3,
|
|
104
|
+
real_required: true,
|
|
105
|
+
zellij_version: available.stdout.trim(),
|
|
106
|
+
mission_id: missionId,
|
|
107
|
+
session_name: sessionName,
|
|
108
|
+
before_pane_count: beforeCount,
|
|
109
|
+
before_terminal_pane_count: beforeTerminalCount,
|
|
110
|
+
after_pane_count: rows.length,
|
|
111
|
+
terminal_pane_count: terminalRows.length,
|
|
112
|
+
worker_pane_count: records.length,
|
|
113
|
+
real_pane_ids: realPaneIds,
|
|
114
|
+
matched_titles: matchedTitles,
|
|
115
|
+
command_matched_workers: commandMatchedWorkers,
|
|
116
|
+
requested_title_commands: requestedTitleCommands,
|
|
117
|
+
heartbeat_seen: heartbeatSeen,
|
|
118
|
+
result_seen: resultSeen,
|
|
119
|
+
dump_screen_ok: dump.ok,
|
|
120
|
+
pane_titles: records.map((record) => record.pane_title),
|
|
121
|
+
pane_id_sources: records.map((record) => record.pane_id_source),
|
|
122
|
+
proof_root: ledgerRoot,
|
|
123
|
+
blockers: []
|
|
124
|
+
};
|
|
125
|
+
if (!report.ok) {
|
|
126
|
+
report.blockers = [
|
|
127
|
+
...(realPaneIds === 3 ? [] : ['real_worker_pane_ids_missing']),
|
|
128
|
+
...(requestedTitleCommands === 3 ? [] : ['worker_pane_title_request_missing']),
|
|
129
|
+
...(matchedTitles === 3 ? [] : ['worker_pane_titles_not_visible_in_list_panes']),
|
|
130
|
+
...(heartbeatSeen === 3 ? [] : ['worker_heartbeat_missing']),
|
|
131
|
+
...(resultSeen === 3 ? [] : ['worker_result_missing']),
|
|
132
|
+
...(terminalRows.length >= beforeTerminalCount + 3 ? [] : ['terminal_worker_pane_count_below_3'])
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
fs.mkdirSync(path.join(root, '.sneakoscope', 'reports'), { recursive: true });
|
|
136
|
+
fs.writeFileSync(path.join(root, '.sneakoscope', 'reports', 'zellij-worker-pane-real-ui-blackbox.json'), `${JSON.stringify(report, null, 2)}\n`);
|
|
137
|
+
emitGate('zellij:worker-pane-real-ui:blackbox', report);
|
|
138
|
+
if (!report.ok)
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
await zellij.runZellij(['kill-session', sessionName], { cwd: root, timeoutMs: 5000, optional: true });
|
|
143
|
+
safeKill(attachedClient, 'SIGTERM');
|
|
144
|
+
await sleep(250);
|
|
145
|
+
safeKill(attachedClient, 'SIGKILL');
|
|
146
|
+
}
|
|
147
|
+
function parsePaneRows(text) {
|
|
148
|
+
if (!String(text || '').trim())
|
|
149
|
+
return [];
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(String(text));
|
|
152
|
+
if (Array.isArray(parsed))
|
|
153
|
+
return parsed;
|
|
154
|
+
if (Array.isArray(parsed?.panes))
|
|
155
|
+
return parsed.panes;
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function sleep(ms) {
|
|
163
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
164
|
+
}
|
|
165
|
+
function shellQuote(value) {
|
|
166
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
167
|
+
}
|
|
168
|
+
function startAttachedZellijClient(sessionName) {
|
|
169
|
+
const logFile = path.join(root, '.sneakoscope', 'reports', `${sessionName}.script.log`);
|
|
170
|
+
fs.mkdirSync(path.dirname(logFile), { recursive: true });
|
|
171
|
+
return spawn('script', ['-q', logFile, 'zellij', 'attach', '--create', sessionName], {
|
|
172
|
+
cwd: root,
|
|
173
|
+
stdio: ['ignore', 'ignore', 'ignore']
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function safeKill(child, signal) {
|
|
177
|
+
try {
|
|
178
|
+
if (!child.killed)
|
|
179
|
+
child.kill(signal);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// Best-effort cleanup for the disposable pseudo-terminal client.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=zellij-worker-pane-real-ui-blackbox.js.map
|