sneakoscope 6.0.2 → 6.1.0
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 +5 -3
- package/config/codex-releases/{rust-v0.142.0.json → rust-v0.144.1.json} +6 -6
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/install-helpers.js +255 -440
- package/dist/cli/router.js +53 -4
- package/dist/commands/codex-app.js +8 -3
- package/dist/commands/codex-lb.js +169 -40
- package/dist/commands/codex.js +6 -5
- package/dist/commands/doctor.js +60 -26
- package/dist/commands/versioning.js +7 -0
- package/dist/config/skills-manifest.json +57 -57
- package/dist/core/agents/agent-central-ledger.js +9 -0
- package/dist/core/agents/agent-cleanup-executor.js +334 -41
- package/dist/core/agents/agent-cleanup.js +20 -1
- package/dist/core/agents/agent-command-surface.js +2 -1
- package/dist/core/agents/agent-effort-policy.js +46 -33
- package/dist/core/agents/agent-janitor.js +9 -10
- package/dist/core/agents/agent-namespace-safety.js +56 -0
- package/dist/core/agents/agent-orchestrator.js +26 -4
- package/dist/core/agents/agent-output-validator.js +1 -1
- package/dist/core/agents/agent-proof-evidence.js +16 -2
- package/dist/core/agents/agent-role-config.js +3 -6
- package/dist/core/agents/agent-roster.js +2 -2
- package/dist/core/agents/agent-runner-codex-exec.js +2 -0
- package/dist/core/agents/agent-runner-process.js +1 -0
- package/dist/core/agents/fast-mode-policy.js +3 -5
- package/dist/core/agents/native-cli-session-swarm.js +18 -1
- package/dist/core/agents/native-worker-backend-router.js +180 -108
- package/dist/core/agents/parallel-write-fixture.js +15 -10
- package/dist/core/agents/runtime-proof-summary.js +21 -3
- package/dist/core/agents/work-partition/repo-inventory.js +68 -7
- package/dist/core/code-structure.js +5 -4
- package/dist/core/codex/codex-config-guard.js +31 -9
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +51 -20
- package/dist/core/codex-app/codex-app-restart.js +38 -8
- package/dist/core/codex-app/codex-app-ui-state-snapshot.js +11 -3
- package/dist/core/codex-app/sks-menubar.js +100 -40
- package/dist/core/codex-app.js +60 -17
- package/dist/core/codex-compat/codex-hook-warning-detector.js +10 -1
- package/dist/core/codex-compat/codex-release-manifest.js +6 -6
- package/dist/core/codex-control/codex-0139-doctor-real-probe.js +7 -1
- package/dist/core/codex-control/codex-0139-image-path-real-probe.js +14 -8
- package/dist/core/codex-control/codex-0139-probe-runner.js +103 -28
- package/dist/core/codex-control/codex-0139-real-probe-summary.js +7 -3
- package/dist/core/codex-control/codex-0139-real-probes.js +21 -6
- package/dist/core/codex-control/codex-0139-sandbox-real-probe.js +39 -13
- package/dist/core/codex-control/codex-0139-web-search-probe.js +11 -8
- package/dist/core/codex-control/{codex-0142-capability.js → codex-0144-capability.js} +22 -22
- package/dist/core/codex-control/codex-0144-collab-agent-real-probe.js +63 -0
- package/dist/core/codex-control/codex-model-metadata.js +8 -4
- package/dist/core/codex-control/codex-multi-agent-event-normalizer.js +5 -1
- package/dist/core/codex-control/codex-reliability-shield.js +4 -1
- package/dist/core/codex-control/codex-sdk-adapter.js +53 -6
- package/dist/core/codex-control/codex-sdk-config-policy.js +2 -1
- package/dist/core/codex-control/codex-sdk-sandbox-policy.js +6 -3
- package/dist/core/codex-lb/codex-lb-env.js +107 -0
- package/dist/core/codex-lb/codex-lb-tool-catalog.js +478 -0
- package/dist/core/codex-native/codex-native-feature-broker.js +22 -22
- package/dist/core/codex-native/codex-native-feature-matrix.js +2 -2
- package/dist/core/codex-native/native-capability-postcheck.js +16 -8
- package/dist/core/codex-native/native-capability-repair-matrix.js +76 -27
- package/dist/core/codex-plugins/codex-plugin-json.js +67 -21
- package/dist/core/codex-plugins/codex-plugin-repair.js +118 -0
- package/dist/core/codex-runtime/codex-desktop-config-policy.js +306 -0
- package/dist/core/commands/agent-command.js +19 -0
- package/dist/core/commands/basic-cli.js +45 -24
- package/dist/core/commands/gc-command.js +27 -4
- package/dist/core/commands/image-ux-review-command.js +55 -24
- package/dist/core/commands/mad-sks-command.js +152 -132
- package/dist/core/commands/naruto-command.js +41 -10
- package/dist/core/commands/pipeline-command.js +1 -0
- package/dist/core/commands/wiki-command.js +179 -83
- package/dist/core/computer-use-live-evidence.js +166 -3
- package/dist/core/computer-use-status.js +5 -2
- package/dist/core/daemon/sksd-hook-daemon-entrypoint.js +2 -2
- package/dist/core/daemon/sksd-hook-daemon.js +171 -28
- package/dist/core/daemon/sksd-hook-dispatch.js +2 -2
- package/dist/core/dfix/verification-selector.js +6 -4
- package/dist/core/doctor/browser-use-repair.js +54 -13
- package/dist/core/doctor/codex-0139-doctor.js +3 -2
- package/dist/core/doctor/computer-use-repair.js +28 -39
- package/dist/core/doctor/doctor-codex-startup-repair.js +19 -9
- package/dist/core/doctor/doctor-context7-repair.js +1 -1
- package/dist/core/doctor/doctor-dirty-planner.js +1 -1
- package/dist/core/doctor/doctor-native-capability-repair.js +74 -3
- package/dist/core/doctor/imagegen-repair.js +47 -12
- package/dist/core/doctor/legacy-global-hook-cleanup.js +179 -0
- package/dist/core/feature-registry.js +25 -31
- package/dist/core/fsx.js +54 -6
- package/dist/core/harness-conflicts.js +1 -1
- package/dist/core/hooks-runtime/code-pack-freshness-preflight.js +5 -4
- package/dist/core/hooks-runtime/hook-invocation-dedupe.js +147 -0
- package/dist/core/hooks-runtime/hook-io.js +6 -0
- package/dist/core/hooks-runtime.js +17 -2
- package/dist/core/image/image-artifact-path-contract.js +8 -6
- package/dist/core/image/image-artifact-registry.js +5 -1
- package/dist/core/image-ux-review/imagegen-adapter.js +5 -2
- package/dist/core/imagegen/imagegen-capability.js +10 -13
- package/dist/core/imagegen/require-imagegen.js +37 -11
- package/dist/core/managed-assets/managed-assets-manifest.js +1 -5
- package/dist/core/naruto/naruto-work-graph.js +70 -4
- package/dist/core/naruto/naruto-write-e2e.js +3 -4
- package/dist/core/ops/reporting.js +3 -0
- package/dist/core/pipeline-internals/runtime-core.js +12 -7
- package/dist/core/pipeline-internals/runtime-gates.js +17 -1
- package/dist/core/ppt/style-tokens.js +1 -1
- package/dist/core/ppt-review/index.js +1 -1
- package/dist/core/ppt-review/slide-exporter.js +23 -17
- package/dist/core/ppt-review/slide-imagegen-review.js +1 -1
- package/dist/core/ppt-review/slide-issue-extraction.js +4 -2
- package/dist/core/product-design-plugin.js +2 -2
- package/dist/core/proof/route-finalizer.js +1 -1
- package/dist/core/proof-field.js +3 -3
- package/dist/core/provider/model-router.js +68 -3
- package/dist/core/release/gate-manifest.js +18 -12
- package/dist/core/release/gate-pack-manifest.js +1 -1
- package/dist/core/release/package-size-budget.js +2 -0
- package/dist/core/release/release-gate-affected-selector.js +1 -1
- package/dist/core/release/release-gate-dag.js +43 -3
- package/dist/core/release/release-gate-hermetic-env.js +28 -17
- package/dist/core/release/release-real-contract.js +416 -0
- package/dist/core/retention/retention-budget.js +25 -6
- package/dist/core/retention.js +980 -101
- package/dist/core/routes/constants.js +1 -1
- package/dist/core/routes/evidence.js +4 -4
- package/dist/core/routes/model-mode-router.js +3 -0
- package/dist/core/routes/ppt-policy.js +1 -1
- package/dist/core/routes.js +11 -12
- package/dist/core/secret-redaction.js +4 -0
- package/dist/core/triwiki/code-pack.js +8 -2
- package/dist/core/triwiki/triwiki-cache-key.js +108 -29
- package/dist/core/triwiki-attention.js +13 -6
- package/dist/core/triwiki-provenance.js +274 -0
- package/dist/core/triwiki-runtime.js +21 -7
- package/dist/core/triwiki-wrongness/wrongness-ledger.js +178 -59
- package/dist/core/triwiki-wrongness/wrongness-retrieval.js +11 -2
- package/dist/core/triwiki-wrongness/wrongness-trust-policy.js +2 -0
- package/dist/core/update/update-migration-state.js +1 -1
- package/dist/core/update/update-notice.js +23 -0
- package/dist/core/update-check.js +69 -25
- package/dist/core/version.js +1 -1
- package/dist/core/wiki-coordinate.js +128 -12
- package/dist/core/work-order-ledger.js +168 -9
- package/dist/core/zellij/zellij-command.js +12 -1
- package/dist/core/zellij/zellij-dashboard-pane.js +3 -3
- package/dist/core/zellij/zellij-launcher.js +15 -14
- package/dist/core/zellij/zellij-pane-proof.js +32 -6
- package/dist/core/zellij/zellij-worker-pane-manager.js +5 -3
- package/dist/scripts/agent-fast-mode-default-check.js +48 -29
- package/dist/scripts/agent-role-config-repair-check.js +25 -4
- package/dist/scripts/agent-wiki-context-proof-check.js +16 -2
- package/dist/scripts/all-feature-deep-completion-check.js +14 -4
- package/dist/scripts/build-dist.js +17 -5
- package/dist/scripts/canonical-test-runner.js +188 -0
- package/dist/scripts/check-command-module-budget.js +38 -16
- package/dist/scripts/check-legacy-free.js +1 -1
- package/dist/scripts/codex-0139-interrupt-agent-check.js +5 -1
- package/dist/scripts/{codex-0142-app-server-v2-check.js → codex-0144-app-server-v2-check.js} +11 -10
- package/dist/scripts/{codex-0142-binary-identity-check.js → codex-0144-binary-identity-check.js} +4 -4
- package/dist/scripts/codex-0144-capability-check.js +21 -0
- package/dist/scripts/{codex-0139-real-probes-check.js → codex-0144-core-real-probes-check.js} +19 -5
- package/dist/scripts/{codex-0142-doctor-wiring-check.js → codex-0144-doctor-wiring-check.js} +6 -6
- package/dist/scripts/{codex-0142-manifest-check.js → codex-0144-manifest-check.js} +11 -3
- package/dist/scripts/{codex-0142-policy-check.js → codex-0144-policy-check.js} +2 -2
- package/dist/scripts/{codex-0142-thread-store-check.js → codex-0144-thread-store-check.js} +3 -3
- package/dist/scripts/codex-app-provider-model-ui-check.js +57 -8
- package/dist/scripts/codex-app-ui-preservation-check.js +35 -4
- package/dist/scripts/codex-control-side-effect-scope-check.js +2 -2
- package/dist/scripts/codex-effort-auto-discovery-check.js +1 -1
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +1 -0
- package/dist/scripts/{codex-lb-gpt55-fast-profile-check.js → codex-lb-gpt56-fast-profile-check.js} +56 -5
- package/dist/scripts/codex-model-metadata-check.js +1 -1
- package/dist/scripts/codex-sdk-sandbox-policy-check.js +5 -1
- package/dist/scripts/codex-sdk-version-compat-check.js +2 -1
- package/dist/scripts/dfix-fixture-check.js +1 -1
- package/dist/scripts/docs-truthfulness-check.js +1 -1
- package/dist/scripts/doctor-codex-startup-repair-check.js +35 -10
- package/dist/scripts/doctor-context7-repair-check.js +2 -1
- package/dist/scripts/doctor-fix-proves-codex-read-check.js +1 -1
- package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +34 -7
- package/dist/scripts/doctor-imagegen-repair-check.js +14 -4
- package/dist/scripts/doctor-startup-config-repair-blackbox.js +1 -1
- package/dist/scripts/doctor-startup-config-repair-check.js +1 -1
- package/dist/scripts/gate-policy-audit-check.js +2 -2
- package/dist/scripts/install-update-preserves-config-check.js +1 -1
- package/dist/scripts/legacy-gate-inventory-check.js +7 -5
- package/dist/scripts/legacy-strong-inventory-check.js +7 -5
- package/dist/scripts/legacy-upgrade-matrix-check.js +5 -5
- package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +175 -14
- package/dist/scripts/mad-sks-app-ui-no-mutation-check.js +166 -6
- package/dist/scripts/mutation-callsite-coverage-check.js +0 -11
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +88 -50
- package/dist/scripts/npm-publish-performance-check.js +2 -1
- package/dist/scripts/package-published-contract-check.js +9 -0
- package/dist/scripts/packlist-performance-check.js +5 -2
- package/dist/scripts/postinstall-safe-side-effects-check.js +8 -3
- package/dist/scripts/ppt-no-mock-as-real-check.js +3 -1
- package/dist/scripts/product-design-plugin-routing-check.js +1 -1
- package/dist/scripts/release-check-stamp.js +182 -2
- package/dist/scripts/release-dag-full-coverage-check.js +7 -7
- package/dist/scripts/release-dynamic-presets-check.js +8 -0
- package/dist/scripts/release-gate-dag-runner-check.js +3 -2
- package/dist/scripts/release-gate-dag-runner.js +24 -11
- package/dist/scripts/release-gate-existence-audit.js +8 -9
- package/dist/scripts/release-gate-script-parity-check.js +1 -1
- package/dist/scripts/release-real-check.js +368 -157
- package/dist/scripts/release-runtime-truth-matrix-check.js +22 -0
- package/dist/scripts/route-proof-artifact-structure-check.js +1 -0
- package/dist/scripts/scheduler-batch-dispatch-check.js +1 -1
- package/dist/scripts/search-visibility-gate-lib.js +1 -1
- package/dist/scripts/seo-geo-route-identity-check.js +30 -4
- package/dist/scripts/sizecheck.js +7 -2
- package/dist/scripts/sks-1-11-gate-lib.js +53 -6
- package/dist/scripts/sks-menubar-install-check.js +7 -0
- package/dist/scripts/sks-uninstall-regression-check.js +1 -1
- package/dist/scripts/sksd-daemon-check.js +16 -6
- package/dist/scripts/update-default-command-check.js +4 -2
- package/dist/scripts/write-build-manifest.js +2 -1
- package/dist/scripts/zellij-layout-valid-check.js +31 -6
- package/dist/scripts/zellij-pane-proof-check.js +33 -2
- package/dist/scripts/zellij-real-session-cleanup-check.js +122 -2
- package/dist/scripts/zellij-real-session-launch-check.js +76 -5
- package/dist/scripts/zellij-slot-pane-renderer-check.js +4 -4
- package/package.json +27 -30
- package/schemas/codex/{app-server-0.142 → app-server-0.144}/codex_app_server_protocol.v2.schemas.json +1146 -781
- package/schemas/codex/codex-0139-real-probe-result.schema.json +39 -5
- package/schemas/codex/ppt-slide-issue-ledger.schema.json +2 -1
- package/dist/core/codex-control/codex-0139-multi-agent-real-probe.js +0 -107
- package/dist/scripts/agent-native-release-gate.js +0 -274
- package/dist/scripts/codex-0142-capability-check.js +0 -21
- package/dist/scripts/command-performance-scorecard-check.js +0 -204
- package/dist/scripts/config-managed-merge-callsite-coverage-check.js +0 -212
- package/dist/scripts/dollar-performance-scorecard-check.js +0 -257
- package/dist/scripts/gate-timing-check.js +0 -16
- package/dist/scripts/ops-maturity-scorecard-check.js +0 -12
- package/dist/scripts/performance-baseline-check.js +0 -12
- package/dist/scripts/performance-improvement-report-check.js +0 -58
- package/dist/scripts/release-parallel-check.js +0 -389
- package/dist/scripts/release-runner-efficiency-check.js +0 -15
- package/dist/scripts/retention-long-run-smoke-check.js +0 -299
- package/docs/assets/sneakoscope-architecture-pipeline.jpg +0 -0
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { assertGate, emitGate, makeTempRoot, writeText } from './sks-3-1-8-check-lib.js';
|
|
5
5
|
import { repairCodexStartupConfig } from '../core/doctor/codex-startup-config-repair.js';
|
|
6
6
|
const root = await makeTempRoot('sks-startup-config-blackbox-');
|
|
7
|
-
await writeText(path.join(root, '.codex', 'config.toml'), '[agents.analysis_scout]\nconfig_file = ".codex/agents/stale.toml"\nmessage_role_prefix = "legacy"\n');
|
|
7
|
+
await writeText(path.join(root, '.codex', 'config.toml'), '# SKS managed Codex config fixture\n[agents.analysis_scout]\nconfig_file = ".codex/agents/stale.toml"\nmessage_role_prefix = "legacy"\n');
|
|
8
8
|
const report = await repairCodexStartupConfig({ root, apply: true });
|
|
9
9
|
const text = await fs.readFile(path.join(root, '.codex', 'config.toml'), 'utf8');
|
|
10
10
|
assertGate(report.ok === true, 'startup config blackbox must pass postcheck', report);
|
|
@@ -3,7 +3,7 @@ import { assertGate, emitGate, makeTempRoot, writeText } from './sks-3-1-8-check
|
|
|
3
3
|
import { repairCodexStartupConfig } from '../core/doctor/codex-startup-config-repair.js';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
const root = await makeTempRoot('sks-startup-config-');
|
|
6
|
-
await writeText(path.join(root, '.codex', 'config.toml'), '[agents.analysis_scout]\nconfig_file = ".codex/agents/missing.toml"\nmessage_role_prefix = "legacy"\n');
|
|
6
|
+
await writeText(path.join(root, '.codex', 'config.toml'), '# SKS managed Codex config fixture\n[agents.analysis_scout]\nconfig_file = ".codex/agents/missing.toml"\nmessage_role_prefix = "legacy"\n');
|
|
7
7
|
const report = await repairCodexStartupConfig({ root, apply: true });
|
|
8
8
|
assertGate(report.ok === true, 'startup config repair must repair stale config_file paths and unsupported fields', report);
|
|
9
9
|
emitGate('doctor:startup-config-repair');
|
|
@@ -26,9 +26,9 @@ const directCodexConfigWrites = scanDirectCodexConfigWrites();
|
|
|
26
26
|
const commandGateContract = scanCommandGateContract();
|
|
27
27
|
const directMkdtempCalls = scanDirectMkdtempCalls();
|
|
28
28
|
const rustTempdirCalls = scanRustTempdirCalls();
|
|
29
|
-
if (releaseGates.length >
|
|
29
|
+
if (releaseGates.length > 200)
|
|
30
30
|
blockers.push(`release_preset_gate_budget_exceeded:${releaseGates.length}`);
|
|
31
|
-
if (gates.length >
|
|
31
|
+
if (gates.length > 200)
|
|
32
32
|
blockers.push(`release_manifest_gate_budget_exceeded:${gates.length}`);
|
|
33
33
|
if (Object.keys(pkg.scripts || {}).length > 100)
|
|
34
34
|
blockers.push(`package_script_budget_exceeded:${Object.keys(pkg.scripts || {}).length}`);
|
|
@@ -68,7 +68,7 @@ const results = [];
|
|
|
68
68
|
const codexDir = path.join(dir, '.codex');
|
|
69
69
|
await fs.mkdir(codexDir, { recursive: true });
|
|
70
70
|
const cfg = path.join(codexDir, 'config.toml');
|
|
71
|
-
const broken = 'model = "gpt-5.
|
|
71
|
+
const broken = 'model = "gpt-5.6-terra"\n[features\nhooks = true\n'; // malformed table header
|
|
72
72
|
await fs.writeFile(cfg, broken);
|
|
73
73
|
const res = await helpers.ensureGlobalCodexFastModeDuringInstall({ home: dir, configPath: cfg });
|
|
74
74
|
const after = await fs.readFile(cfg, 'utf8');
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { assertGate, emitGate, readJson, readText } from './sks-1-18-gate-lib.js';
|
|
2
2
|
const gates = (readJson('release-gates.v2.json').gates || []);
|
|
3
3
|
const migration = readText('docs/sks-4-migration.md');
|
|
4
|
-
const
|
|
4
|
+
const currentVersion = String(readJson('package.json').version || '');
|
|
5
|
+
assertGate(/^\d+\.\d+\.\d+(?:[-+].*)?$/.test(currentVersion), 'package version must be valid before evaluating legacy allowlist expiry', { currentVersion });
|
|
6
|
+
const allowlist = parseAllowlist(migration, currentVersion);
|
|
5
7
|
const legacy = gates
|
|
6
8
|
.filter((gate) => gate.preset?.includes('release'))
|
|
7
9
|
.filter((gate) => removableLegacy(gate.id, gate.command))
|
|
@@ -19,7 +21,7 @@ function removableLegacy(id, command) {
|
|
|
19
21
|
|| /codex[:.-]?0139/.test(id)
|
|
20
22
|
|| /codex-0139/.test(command);
|
|
21
23
|
}
|
|
22
|
-
function parseAllowlist(text) {
|
|
24
|
+
function parseAllowlist(text, currentVersion) {
|
|
23
25
|
return [...text.matchAll(/<!--\s*sks-legacy-allowlist\s*([\s\S]*?)-->/g)].map((match) => {
|
|
24
26
|
const body = match[1] || '';
|
|
25
27
|
return {
|
|
@@ -28,7 +30,7 @@ function parseAllowlist(text) {
|
|
|
28
30
|
owner: field(body, 'owner'),
|
|
29
31
|
expires: field(body, 'expires')
|
|
30
32
|
};
|
|
31
|
-
}).filter((row) => row.id && row.reason && row.owner && compareVersion(row.expires,
|
|
33
|
+
}).filter((row) => row.id && row.reason && row.owner && compareVersion(row.expires, currentVersion) > 0);
|
|
32
34
|
}
|
|
33
35
|
function isAllowed(id, allowlist) {
|
|
34
36
|
return allowlist.some((row) => row.id.endsWith('*') ? id.startsWith(row.id.slice(0, -1)) : row.id === id);
|
|
@@ -37,8 +39,8 @@ function field(body, name) {
|
|
|
37
39
|
return body.match(new RegExp(`^${name}:\\s*(.+)$`, 'm'))?.[1]?.trim() || '';
|
|
38
40
|
}
|
|
39
41
|
function compareVersion(a, b) {
|
|
40
|
-
const left = a.split('.').map(Number);
|
|
41
|
-
const right = b.split('.').map(Number);
|
|
42
|
+
const left = (a.split(/[+-]/, 1)[0] || '').split('.').map(Number);
|
|
43
|
+
const right = (b.split(/[+-]/, 1)[0] || '').split('.').map(Number);
|
|
42
44
|
for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
|
|
43
45
|
const delta = (left[i] || 0) - (right[i] || 0);
|
|
44
46
|
if (delta !== 0)
|
|
@@ -2,7 +2,9 @@ import { assertGate, emitGate, importDist, readJson, readText, root } from './sk
|
|
|
2
2
|
const impactMod = await importDist('core/triwiki/triwiki-gate-impact-map.js');
|
|
3
3
|
const map = impactMod.buildTriWikiGateImpactMap(root);
|
|
4
4
|
const migration = readText('docs/sks-4-migration.md');
|
|
5
|
-
const
|
|
5
|
+
const currentVersion = String(readJson('package.json').version || '');
|
|
6
|
+
assertGate(/^\d+\.\d+\.\d+(?:[-+].*)?$/.test(currentVersion), 'package version must be valid before evaluating legacy allowlist expiry', { currentVersion });
|
|
7
|
+
const allowlist = parseAllowlist(migration, currentVersion);
|
|
6
8
|
const gates = (readJson('release-gates.v2.json').gates || []);
|
|
7
9
|
const legacyOrphans = map.impacts.filter((impact) => impact.orphan && removableLegacy(impact.gate_id, ''));
|
|
8
10
|
const legacyPresent = gates.filter((gate) => gate.preset?.includes('release') && removableLegacy(gate.id, gate.command) && !isAllowed(gate.id, allowlist));
|
|
@@ -18,11 +20,11 @@ function removableLegacy(id, command) {
|
|
|
18
20
|
|| /codex[:.-]?0139/.test(id)
|
|
19
21
|
|| /codex-0139/.test(command);
|
|
20
22
|
}
|
|
21
|
-
function parseAllowlist(text) {
|
|
23
|
+
function parseAllowlist(text, currentVersion) {
|
|
22
24
|
return [...text.matchAll(/<!--\s*sks-legacy-allowlist\s*([\s\S]*?)-->/g)].map((match) => {
|
|
23
25
|
const body = match[1] || '';
|
|
24
26
|
return { id: field(body, 'id'), reason: field(body, 'reason'), owner: field(body, 'owner'), expires: field(body, 'expires') };
|
|
25
|
-
}).filter((row) => row.id && row.reason && row.owner && compareVersion(row.expires,
|
|
27
|
+
}).filter((row) => row.id && row.reason && row.owner && compareVersion(row.expires, currentVersion) > 0);
|
|
26
28
|
}
|
|
27
29
|
function isAllowed(id, allowlist) {
|
|
28
30
|
return allowlist.some((row) => row.id.endsWith('*') ? id.startsWith(row.id.slice(0, -1)) : row.id === id);
|
|
@@ -31,8 +33,8 @@ function field(body, name) {
|
|
|
31
33
|
return body.match(new RegExp(`^${name}:\\s*(.+)$`, 'm'))?.[1]?.trim() || '';
|
|
32
34
|
}
|
|
33
35
|
function compareVersion(a, b) {
|
|
34
|
-
const left = a.split('.').map(Number);
|
|
35
|
-
const right = b.split('.').map(Number);
|
|
36
|
+
const left = (a.split(/[+-]/, 1)[0] || '').split('.').map(Number);
|
|
37
|
+
const right = (b.split(/[+-]/, 1)[0] || '').split('.').map(Number);
|
|
36
38
|
for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
|
|
37
39
|
const delta = (left[i] || 0) - (right[i] || 0);
|
|
38
40
|
if (delta !== 0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-nocheck
|
|
3
|
-
// Gate:
|
|
3
|
+
// Gate: migration:upgrade-safety
|
|
4
4
|
// Proves a 1.18.x -> 1.19 upgrade never breaks user Codex config across legacy
|
|
5
5
|
// states. Operates entirely on temp dirs (os.tmpdir + fs.mkdtemp); never touches
|
|
6
6
|
// the real ~/.codex. Always restores process.env.CODEX_HOME / HOME in finally.
|
|
@@ -41,7 +41,7 @@ try {
|
|
|
41
41
|
const dir = mkTmp('sks-legacy-corrupt-');
|
|
42
42
|
tmpDirs.push(dir);
|
|
43
43
|
const configPath = path.join(dir, 'config.toml');
|
|
44
|
-
const corrupted = 'model = "gpt-5.
|
|
44
|
+
const corrupted = 'model = "gpt-5.6-terra"\nservice_tier = "fast"\n\n[mcp_servers.xai-grok.env]\nXAI_API_KEY = "x"\nmodel_provider = "codex-lb"\nnotify = ["a","b"]\n';
|
|
45
45
|
fs.writeFileSync(configPath, corrupted, 'utf8');
|
|
46
46
|
const result = await policy.repairCodexConfigStructure(configPath, { apply: true });
|
|
47
47
|
const okStatuses = ['structure_repaired', 'structure_repair_available', 'structure_ok'];
|
|
@@ -121,7 +121,7 @@ try {
|
|
|
121
121
|
const projectConfigPath = path.join(projectCodexDir, 'config.toml');
|
|
122
122
|
// Machine-local keys/tables (model_provider, [profiles.*]) move out to CODEX_HOME;
|
|
123
123
|
// project-scoped settings (sandbox_mode, [features]) must be PRESERVED in place.
|
|
124
|
-
fs.writeFileSync(projectConfigPath, '
|
|
124
|
+
fs.writeFileSync(projectConfigPath, '# Sneakoscope managed fixture\nmodel_provider = "codex-lb"\nsandbox_mode = "workspace-write"\n\n[features]\nhooks = true\n', 'utf8');
|
|
125
125
|
const result = await policy.splitCodexProjectConfigPolicy(projectRoot, {
|
|
126
126
|
apply: true,
|
|
127
127
|
codexHome: tmpHome2,
|
|
@@ -214,7 +214,7 @@ try {
|
|
|
214
214
|
const dir = mkTmp('sks-legacy-noop-');
|
|
215
215
|
tmpDirs.push(dir);
|
|
216
216
|
const configPath = path.join(dir, 'config.toml');
|
|
217
|
-
const clean = 'model = "gpt-5.
|
|
217
|
+
const clean = 'model = "gpt-5.6-terra"\nservice_tier = "fast"\n\n[features]\nhooks = true\n';
|
|
218
218
|
fs.writeFileSync(configPath, clean, 'utf8');
|
|
219
219
|
const before = fs.readFileSync(configPath, 'utf8');
|
|
220
220
|
const result = await policy.repairCodexConfigStructure(configPath, { apply: true });
|
|
@@ -270,7 +270,7 @@ try {
|
|
|
270
270
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
271
271
|
const reportPath = path.join(reportDir, 'legacy-upgrade-matrix.json');
|
|
272
272
|
fs.writeFileSync(reportPath, `${JSON.stringify({ schema: 'sks.legacy-upgrade-matrix.v1', ok: true, ...summary }, null, 2)}\n`, 'utf8');
|
|
273
|
-
emitGate('
|
|
273
|
+
emitGate('migration:upgrade-safety', {
|
|
274
274
|
report_path: reportPath,
|
|
275
275
|
states_checked: [
|
|
276
276
|
'corrupted_config',
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
5
|
+
import crypto from 'node:crypto';
|
|
4
6
|
import { execFileSync } from 'node:child_process';
|
|
5
7
|
import { assertGate, root } from '../sks-1-18-gate-lib.js';
|
|
8
|
+
const MAX_EMPTY_OUTPUT_ATTEMPTS = 2;
|
|
9
|
+
const CLI_COMPLETION_TIMEOUT_MS = 180_000;
|
|
10
|
+
const CLI_STABILITY_WAIT_MS = 500;
|
|
11
|
+
const CLI_STABILITY_SAMPLE_MS = 25;
|
|
12
|
+
const stabilityWaitCell = new Int32Array(new SharedArrayBuffer(4));
|
|
6
13
|
export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName, backend = 'fake', extraArgs = [], expectedFastMode = null }) {
|
|
7
14
|
const distCli = path.join(root, 'dist', 'bin', 'sks.js');
|
|
8
15
|
assertGate(fs.existsSync(distCli), 'dist CLI missing for native CLI swarm check', { distCli });
|
|
@@ -25,18 +32,37 @@ export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName,
|
|
|
25
32
|
'--json',
|
|
26
33
|
...extraArgs
|
|
27
34
|
];
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const attempts = [];
|
|
36
|
+
let inspected = null;
|
|
37
|
+
while (attempts.length < MAX_EMPTY_OUTPUT_ATTEMPTS) {
|
|
38
|
+
const attempt = runCliAttempt({ distCli, args, backend, attemptNumber: attempts.length + 1 });
|
|
39
|
+
attempts.push(attempt);
|
|
40
|
+
inspected = inspectNativeCliSwarmOutput(attempt.stdout);
|
|
41
|
+
if (inspected.ok || inspected.reason !== 'empty_stdout')
|
|
42
|
+
break;
|
|
43
|
+
if (attempts.length >= MAX_EMPTY_OUTPUT_ATTEMPTS)
|
|
44
|
+
break;
|
|
45
|
+
const stable = waitForStableNonemptyCli(distCli);
|
|
46
|
+
assertGate(stable.ok, 'native CLI session swarm emitted empty JSON and the CLI did not settle for a bounded retry', {
|
|
47
|
+
dist_cli: distCli,
|
|
48
|
+
attempt_count: attempts.length,
|
|
49
|
+
completion_timeout_ms: CLI_COMPLETION_TIMEOUT_MS,
|
|
50
|
+
stability_wait_ms: CLI_STABILITY_WAIT_MS,
|
|
51
|
+
before: attempt.cli_before,
|
|
52
|
+
after: attempt.cli_after,
|
|
53
|
+
stable
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
assertGate(inspected?.ok === true, inspected?.reason === 'empty_stdout'
|
|
57
|
+
? 'native CLI session swarm completed without JSON output after one bounded retry'
|
|
58
|
+
: 'native CLI session swarm emitted incomplete or invalid JSON', {
|
|
59
|
+
dist_cli: distCli,
|
|
60
|
+
attempt_count: attempts.length,
|
|
61
|
+
completion_timeout_ms: CLI_COMPLETION_TIMEOUT_MS,
|
|
62
|
+
attempts: attempts.map(attemptDiagnostic),
|
|
63
|
+
output: inspected
|
|
38
64
|
});
|
|
39
|
-
const result =
|
|
65
|
+
const result = inspected.value;
|
|
40
66
|
const proof = result.native_cli_session_proof || {};
|
|
41
67
|
const noSubagent = result.no_subagent_scaling_policy || {};
|
|
42
68
|
const officialHelper = result.official_subagent_helper_policy || {};
|
|
@@ -55,7 +81,16 @@ export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName,
|
|
|
55
81
|
no_subagent_scaling_policy: noSubagent,
|
|
56
82
|
official_subagent_helper_policy: officialHelper,
|
|
57
83
|
fast_mode_propagation: fast,
|
|
58
|
-
proof_status: result.proof?.status || null
|
|
84
|
+
proof_status: result.proof?.status || null,
|
|
85
|
+
completion_protocol: {
|
|
86
|
+
schema: 'sks.native-cli-check-completion.v1',
|
|
87
|
+
attempt_count: attempts.length,
|
|
88
|
+
max_attempts: MAX_EMPTY_OUTPUT_ATTEMPTS,
|
|
89
|
+
bounded_timeout_ms: CLI_COMPLETION_TIMEOUT_MS,
|
|
90
|
+
atomic_output_read: true,
|
|
91
|
+
transient_empty_output_recovered: attempts.length > 1,
|
|
92
|
+
output_bytes: attempts.at(-1)?.output_bytes || 0
|
|
93
|
+
}
|
|
59
94
|
};
|
|
60
95
|
const out = path.join(root, '.sneakoscope', 'reports', reportName);
|
|
61
96
|
fs.mkdirSync(path.dirname(out), { recursive: true });
|
|
@@ -63,7 +98,8 @@ export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName,
|
|
|
63
98
|
assertGate(result.ok === true, 'native CLI session swarm run must pass', report);
|
|
64
99
|
assertGate(proof.spawned_worker_process_count >= agents, 'spawned native worker process count below requested agents', report);
|
|
65
100
|
assertGate(proof.unique_worker_session_count >= agents, 'unique worker session count below requested agents', report);
|
|
66
|
-
|
|
101
|
+
const expectedSlots = Math.min(agents, Number(proof.target_active_slots || agents));
|
|
102
|
+
assertGate(proof.unique_slot_count >= expectedSlots, 'unique slot count below effective concurrency target', report);
|
|
67
103
|
assertGate(Array.isArray(proof.process_ids) && proof.process_ids.length >= agents, 'process ids missing from native CLI proof', report);
|
|
68
104
|
assertGate(proof.close_report_count >= agents, 'worker close report count below requested agents', report);
|
|
69
105
|
assertGate(noSubagent.ok === true && noSubagent.subagent_events_counted_as_worker_sessions === false, 'no-subagent scaling policy must pass', report);
|
|
@@ -72,10 +108,135 @@ export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName,
|
|
|
72
108
|
assertGate(noSubagent.official_codex_subagent_helper_lane_allowed === true && noSubagent.official_helper_lane_worker_capacity_credit === 0, 'no-subagent policy must allow helper lane with zero capacity credit', report);
|
|
73
109
|
assertGate(fast.ok === true, 'fast mode propagation proof must pass', report);
|
|
74
110
|
assertGate(fast.fast_mode === expectedFast && fast.service_tier === expectedTier, 'worker service tier must match the selected fast-mode policy', { ...report, expected_fast_mode: expectedFast, expected_service_tier: expectedTier });
|
|
75
|
-
if (expectedFast) {
|
|
111
|
+
if (expectedFast && expectedFastMode === true) {
|
|
76
112
|
assertGate(policy.explicit_fast === true || policy.preference_mode === 'fast' || policy.explicit_service_tier === 'fast', 'fast-mode propagation gate must use explicit fast opt-in', report);
|
|
77
113
|
}
|
|
78
114
|
assertGate((proof.worker_command_lines || []).every((line) => line.includes('--agent') && line.includes('worker')), 'worker command lines must use --agent worker', report);
|
|
79
115
|
return report;
|
|
80
116
|
}
|
|
117
|
+
export function inspectNativeCliSwarmOutput(stdout) {
|
|
118
|
+
const text = typeof stdout === 'string' ? stdout : String(stdout || '');
|
|
119
|
+
if (text.trim().length === 0)
|
|
120
|
+
return { ok: false, reason: 'empty_stdout', output_bytes: Buffer.byteLength(text) };
|
|
121
|
+
try {
|
|
122
|
+
const value = JSON.parse(text);
|
|
123
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
124
|
+
return { ok: false, reason: 'non_object_json', output_bytes: Buffer.byteLength(text) };
|
|
125
|
+
}
|
|
126
|
+
return { ok: true, value, output_bytes: Buffer.byteLength(text) };
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
reason: 'invalid_json',
|
|
132
|
+
output_bytes: Buffer.byteLength(text),
|
|
133
|
+
parse_error: error instanceof Error ? error.message : String(error),
|
|
134
|
+
output_sha256: crypto.createHash('sha256').update(text).digest('hex')
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function runCliAttempt({ distCli, args, backend, attemptNumber }) {
|
|
139
|
+
const fixtureRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-native-cli-swarm-fixture-'));
|
|
140
|
+
const fixtureHome = path.join(fixtureRoot, 'home');
|
|
141
|
+
const outputDraft = path.join(fixtureRoot, `.native-cli-result-${attemptNumber}.json.partial`);
|
|
142
|
+
const outputComplete = path.join(fixtureRoot, `.native-cli-result-${attemptNumber}.json`);
|
|
143
|
+
let outputFd = fs.openSync(outputDraft, 'wx', 0o600);
|
|
144
|
+
const cleanupFixture = () => fs.rmSync(fixtureRoot, { recursive: true, force: true });
|
|
145
|
+
process.once('exit', cleanupFixture);
|
|
146
|
+
const cliBefore = cliSnapshot(distCli);
|
|
147
|
+
try {
|
|
148
|
+
execFileSync(process.execPath, args, {
|
|
149
|
+
cwd: fixtureRoot,
|
|
150
|
+
env: {
|
|
151
|
+
...process.env,
|
|
152
|
+
HOME: fixtureHome,
|
|
153
|
+
CODEX_HOME: path.join(fixtureHome, '.codex'),
|
|
154
|
+
SKS_GLOBAL_ROOT: path.join(fixtureHome, '.sneakoscope-global'),
|
|
155
|
+
TMPDIR: fixtureRoot,
|
|
156
|
+
TMP: fixtureRoot,
|
|
157
|
+
TEMP: fixtureRoot,
|
|
158
|
+
PWD: fixtureRoot,
|
|
159
|
+
SKS_TEST_ISOLATION: '1',
|
|
160
|
+
SKS_UPDATE_MIGRATION_GATE_DISABLED: '1',
|
|
161
|
+
NODE_ENV: 'test',
|
|
162
|
+
CI: 'true',
|
|
163
|
+
...(backend === 'codex-sdk' ? { SKS_CODEX_SDK_FAKE: '1' } : {})
|
|
164
|
+
},
|
|
165
|
+
timeout: CLI_COMPLETION_TIMEOUT_MS,
|
|
166
|
+
killSignal: 'SIGTERM',
|
|
167
|
+
stdio: ['ignore', outputFd, 'pipe'],
|
|
168
|
+
maxBuffer: 96 * 1024 * 1024
|
|
169
|
+
});
|
|
170
|
+
fs.fsyncSync(outputFd);
|
|
171
|
+
fs.closeSync(outputFd);
|
|
172
|
+
outputFd = null;
|
|
173
|
+
fs.renameSync(outputDraft, outputComplete);
|
|
174
|
+
const stdout = fs.readFileSync(outputComplete, 'utf8');
|
|
175
|
+
const cliAfter = cliSnapshot(distCli);
|
|
176
|
+
return {
|
|
177
|
+
attempt: attemptNumber,
|
|
178
|
+
stdout,
|
|
179
|
+
output_bytes: Buffer.byteLength(stdout),
|
|
180
|
+
cli_before: cliBefore,
|
|
181
|
+
cli_after: cliAfter
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
if (outputFd !== null) {
|
|
186
|
+
try {
|
|
187
|
+
fs.closeSync(outputFd);
|
|
188
|
+
}
|
|
189
|
+
catch { }
|
|
190
|
+
}
|
|
191
|
+
process.removeListener('exit', cleanupFixture);
|
|
192
|
+
cleanupFixture();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function waitForStableNonemptyCli(distCli) {
|
|
196
|
+
const startedAt = Date.now();
|
|
197
|
+
let previous = cliSnapshot(distCli);
|
|
198
|
+
while (Date.now() - startedAt < CLI_STABILITY_WAIT_MS) {
|
|
199
|
+
Atomics.wait(stabilityWaitCell, 0, 0, CLI_STABILITY_SAMPLE_MS);
|
|
200
|
+
const current = cliSnapshot(distCli);
|
|
201
|
+
if (current.ok && current.size > 0 && sameCliSnapshot(previous, current)) {
|
|
202
|
+
return { ok: true, waited_ms: Date.now() - startedAt, snapshot: current };
|
|
203
|
+
}
|
|
204
|
+
previous = current;
|
|
205
|
+
}
|
|
206
|
+
return { ok: false, waited_ms: Date.now() - startedAt, snapshot: previous };
|
|
207
|
+
}
|
|
208
|
+
function cliSnapshot(file) {
|
|
209
|
+
try {
|
|
210
|
+
const stat = fs.statSync(file);
|
|
211
|
+
const bytes = fs.readFileSync(file);
|
|
212
|
+
return {
|
|
213
|
+
ok: stat.isFile(),
|
|
214
|
+
size: stat.size,
|
|
215
|
+
mtime_ms: stat.mtimeMs,
|
|
216
|
+
dev: String(stat.dev),
|
|
217
|
+
ino: String(stat.ino),
|
|
218
|
+
sha256: crypto.createHash('sha256').update(bytes).digest('hex')
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
return { ok: false, size: 0, error: error instanceof Error ? error.message : String(error) };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function sameCliSnapshot(left, right) {
|
|
226
|
+
return left?.ok === true
|
|
227
|
+
&& right?.ok === true
|
|
228
|
+
&& left.size === right.size
|
|
229
|
+
&& left.mtime_ms === right.mtime_ms
|
|
230
|
+
&& left.dev === right.dev
|
|
231
|
+
&& left.ino === right.ino
|
|
232
|
+
&& left.sha256 === right.sha256;
|
|
233
|
+
}
|
|
234
|
+
function attemptDiagnostic(attempt) {
|
|
235
|
+
return {
|
|
236
|
+
attempt: attempt.attempt,
|
|
237
|
+
output_bytes: attempt.output_bytes,
|
|
238
|
+
cli_before: attempt.cli_before,
|
|
239
|
+
cli_after: attempt.cli_after
|
|
240
|
+
};
|
|
241
|
+
}
|
|
81
242
|
//# sourceMappingURL=native-cli-session-swarm-check-lib.js.map
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
3
4
|
import fs from 'node:fs/promises';
|
|
4
5
|
import os from 'node:os';
|
|
5
6
|
import path from 'node:path';
|
|
6
7
|
import { madHighCommand } from '../core/commands/mad-sks-command.js';
|
|
7
|
-
import { sha256 } from '../core/fsx.js';
|
|
8
|
+
import { runProcess, sha256 } from '../core/fsx.js';
|
|
9
|
+
import { runZellij } from '../core/zellij/zellij-command.js';
|
|
8
10
|
const original = {
|
|
9
11
|
cwd: process.cwd(),
|
|
10
12
|
home: process.env.HOME,
|
|
11
13
|
codexHome: process.env.CODEX_HOME,
|
|
12
14
|
noAttach: process.env.SKS_NO_ZELLIJ_ATTACH,
|
|
13
15
|
requireZellij: process.env.SKS_REQUIRE_ZELLIJ,
|
|
16
|
+
zellijSocketDir: process.env.ZELLIJ_SOCKET_DIR,
|
|
14
17
|
madSwarm: process.env.SKS_MAD_NATIVE_SWARM,
|
|
15
18
|
skipNpm: process.env.SKS_SKIP_NPM_FRESHNESS_CHECK
|
|
16
19
|
};
|
|
@@ -18,6 +21,12 @@ const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-mad-ui-no-mutation-'));
|
|
|
18
21
|
const home = path.join(tmp, 'home');
|
|
19
22
|
const codexHome = path.join(home, '.codex');
|
|
20
23
|
const configPath = path.join(codexHome, 'config.toml');
|
|
24
|
+
const runId = `${process.pid}-${Date.now().toString(36)}-${randomBytes(3).toString('hex')}`;
|
|
25
|
+
const sessionName = `sks-mad-ui-check-${runId}`;
|
|
26
|
+
const socketDir = path.join('/tmp', `sks-zj-madui-${runId}`);
|
|
27
|
+
let report;
|
|
28
|
+
let cleanup = { ok: false, blockers: ['cleanup_not_run'] };
|
|
29
|
+
let fixtureRemoval = { ok: false, blockers: ['fixture_removal_not_run'] };
|
|
21
30
|
try {
|
|
22
31
|
await fs.mkdir(codexHome, { recursive: true });
|
|
23
32
|
await fs.mkdir(path.join(tmp, '.sneakoscope'), { recursive: true });
|
|
@@ -35,10 +44,11 @@ try {
|
|
|
35
44
|
process.env.CODEX_HOME = codexHome;
|
|
36
45
|
process.env.SKS_NO_ZELLIJ_ATTACH = '1';
|
|
37
46
|
process.env.SKS_REQUIRE_ZELLIJ = '0';
|
|
47
|
+
process.env.ZELLIJ_SOCKET_DIR = socketDir;
|
|
38
48
|
process.env.SKS_MAD_NATIVE_SWARM = '0';
|
|
39
49
|
process.env.SKS_SKIP_NPM_FRESHNESS_CHECK = '1';
|
|
40
50
|
process.exitCode = 0;
|
|
41
|
-
await madHighCommand(['--no-attach', '--no-swarm'], {
|
|
51
|
+
const launch = await madHighCommand(['--no-attach', '--no-swarm', '--session', sessionName], {
|
|
42
52
|
maybePromptSksUpdateForLaunch: async () => ({ status: 'skipped' }),
|
|
43
53
|
maybePromptCodexUpdateForLaunch: async () => ({ status: 'skipped' }),
|
|
44
54
|
ensureMadLaunchDependencies: async () => ({ ready: true, actions: [] }),
|
|
@@ -51,33 +61,54 @@ try {
|
|
|
51
61
|
assert.equal(/\[profiles\.sks-mad-high\]/.test(after), false);
|
|
52
62
|
assert.equal(/enabled\s*=\s*false/.test(after), true);
|
|
53
63
|
process.exitCode = 0;
|
|
54
|
-
|
|
64
|
+
report = {
|
|
55
65
|
schema: 'sks.mad-sks-app-ui-no-mutation-check.v1',
|
|
56
66
|
ok: true,
|
|
57
67
|
before_hash: sha256(before),
|
|
58
68
|
after_hash: sha256(after),
|
|
59
69
|
profile_files_written: entries.filter((entry) => /sks-mad-high/.test(entry)),
|
|
60
70
|
plugin_disabled_preserved: true,
|
|
71
|
+
session_name: launch?.session_name || null,
|
|
72
|
+
isolated_socket_dir: socketDir,
|
|
61
73
|
blockers: []
|
|
62
|
-
}
|
|
74
|
+
};
|
|
63
75
|
}
|
|
64
76
|
catch (err) {
|
|
65
|
-
|
|
77
|
+
report = {
|
|
66
78
|
schema: 'sks.mad-sks-app-ui-no-mutation-check.v1',
|
|
67
79
|
ok: false,
|
|
68
80
|
error: err?.message || String(err),
|
|
69
81
|
blockers: ['mad_sks_app_ui_no_mutation_failed']
|
|
70
|
-
}
|
|
82
|
+
};
|
|
71
83
|
}
|
|
72
84
|
finally {
|
|
73
85
|
process.chdir(original.cwd);
|
|
86
|
+
cleanup = await cleanupOwnedSession(sessionName, socketDir, original.cwd);
|
|
87
|
+
if (!cleanup.ok) {
|
|
88
|
+
report.ok = false;
|
|
89
|
+
report.blockers = [...new Set([...(report.blockers || []), ...cleanup.blockers])];
|
|
90
|
+
}
|
|
74
91
|
restoreEnv('HOME', original.home);
|
|
75
92
|
restoreEnv('CODEX_HOME', original.codexHome);
|
|
76
93
|
restoreEnv('SKS_NO_ZELLIJ_ATTACH', original.noAttach);
|
|
77
94
|
restoreEnv('SKS_REQUIRE_ZELLIJ', original.requireZellij);
|
|
95
|
+
restoreEnv('ZELLIJ_SOCKET_DIR', original.zellijSocketDir);
|
|
78
96
|
restoreEnv('SKS_MAD_NATIVE_SWARM', original.madSwarm);
|
|
79
97
|
restoreEnv('SKS_SKIP_NPM_FRESHNESS_CHECK', original.skipNpm);
|
|
98
|
+
if (cleanup.ok) {
|
|
99
|
+
fixtureRemoval = await removeFixtureRootAfterQuiescence(tmp);
|
|
100
|
+
if (!fixtureRemoval.ok) {
|
|
101
|
+
cleanup.ok = false;
|
|
102
|
+
cleanup.blockers = [...new Set([...(cleanup.blockers || []), ...fixtureRemoval.blockers])];
|
|
103
|
+
report.ok = false;
|
|
104
|
+
report.blockers = [...new Set([...(report.blockers || []), ...fixtureRemoval.blockers])];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
80
107
|
}
|
|
108
|
+
report.cleanup = cleanup;
|
|
109
|
+
report.fixture_removal = fixtureRemoval;
|
|
110
|
+
report.temporary_root_removed = fixtureRemoval.ok && !(await exists(tmp));
|
|
111
|
+
emit(report);
|
|
81
112
|
function restoreEnv(key, value) {
|
|
82
113
|
if (value == null)
|
|
83
114
|
delete process.env[key];
|
|
@@ -89,4 +120,133 @@ function emit(report) {
|
|
|
89
120
|
if (!report.ok)
|
|
90
121
|
process.exitCode = 1;
|
|
91
122
|
}
|
|
123
|
+
async function cleanupOwnedSession(sessionName, socketDir, cwd) {
|
|
124
|
+
const ownedProcessTree = await readOwnedZellijProcessTree(sessionName, socketDir, cwd);
|
|
125
|
+
let kill = null;
|
|
126
|
+
let remaining = [];
|
|
127
|
+
for (let attempt = 1; attempt <= 2; attempt += 1) {
|
|
128
|
+
kill = await runZellij(['kill-session', sessionName], {
|
|
129
|
+
cwd,
|
|
130
|
+
env: { ZELLIJ_SOCKET_DIR: socketDir },
|
|
131
|
+
timeoutMs: 5000,
|
|
132
|
+
optional: true
|
|
133
|
+
});
|
|
134
|
+
remaining = await waitForSocketEntriesToClear(socketDir, 1500);
|
|
135
|
+
if (!remaining.includes(sessionName))
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
const processExit = await waitForOwnedProcessesToExit(ownedProcessTree.processes, 5000);
|
|
139
|
+
const sessionRemoved = !remaining.includes(sessionName);
|
|
140
|
+
const socketExclusive = remaining.length === 0;
|
|
141
|
+
if (sessionRemoved && socketExclusive && processExit.ok)
|
|
142
|
+
await fs.rm(socketDir, { recursive: true, force: true });
|
|
143
|
+
const socketDirRemoved = !(await exists(socketDir));
|
|
144
|
+
const blockers = [
|
|
145
|
+
...(!sessionRemoved ? ['mad_ui_check_zellij_session_still_present'] : []),
|
|
146
|
+
...(!socketExclusive ? ['mad_ui_check_zellij_socket_not_exclusive'] : []),
|
|
147
|
+
...(!ownedProcessTree.server_found ? ['mad_ui_check_zellij_server_not_observed'] : []),
|
|
148
|
+
...(!processExit.ok ? ['mad_ui_check_owned_processes_still_alive'] : []),
|
|
149
|
+
...(!socketDirRemoved ? ['mad_ui_check_zellij_socket_dir_not_removed'] : [])
|
|
150
|
+
];
|
|
151
|
+
return {
|
|
152
|
+
ok: blockers.length === 0,
|
|
153
|
+
session_removed: sessionRemoved,
|
|
154
|
+
socket_dir_removed: socketDirRemoved,
|
|
155
|
+
remaining_socket_entries: remaining,
|
|
156
|
+
owned_process_tree: ownedProcessTree,
|
|
157
|
+
owned_process_exit: processExit,
|
|
158
|
+
kill,
|
|
159
|
+
blockers
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function readOwnedZellijProcessTree(sessionName, socketDir, cwd) {
|
|
163
|
+
const serverSocket = path.join(socketDir, 'contract_version_1', sessionName);
|
|
164
|
+
const deadline = Date.now() + 1500;
|
|
165
|
+
let rows = [];
|
|
166
|
+
let server = null;
|
|
167
|
+
do {
|
|
168
|
+
const result = await runProcess('ps', ['-axo', 'pid=,ppid=,command='], {
|
|
169
|
+
cwd,
|
|
170
|
+
timeoutMs: 3000,
|
|
171
|
+
maxOutputBytes: 1024 * 1024
|
|
172
|
+
});
|
|
173
|
+
rows = parseProcessRows(result.stdout);
|
|
174
|
+
server = rows.find((row) => {
|
|
175
|
+
const executable = row.command.trim().split(/\s+/, 1)[0] || '';
|
|
176
|
+
return path.basename(executable) === 'zellij' && row.command.includes(` --server ${serverSocket}`);
|
|
177
|
+
}) || null;
|
|
178
|
+
if (!server && Date.now() < deadline)
|
|
179
|
+
await delay(50);
|
|
180
|
+
} while (!server && Date.now() < deadline);
|
|
181
|
+
if (!server)
|
|
182
|
+
return { server_found: false, server_socket: serverSocket, processes: [] };
|
|
183
|
+
const byParent = new Map();
|
|
184
|
+
for (const row of rows)
|
|
185
|
+
byParent.set(row.ppid, [...(byParent.get(row.ppid) || []), row]);
|
|
186
|
+
const processes = [];
|
|
187
|
+
const visit = (pid) => {
|
|
188
|
+
const row = rows.find((candidate) => candidate.pid === pid);
|
|
189
|
+
if (row)
|
|
190
|
+
processes.push(row);
|
|
191
|
+
for (const child of byParent.get(pid) || [])
|
|
192
|
+
visit(child.pid);
|
|
193
|
+
};
|
|
194
|
+
visit(server.pid);
|
|
195
|
+
return { server_found: true, server_socket: serverSocket, processes };
|
|
196
|
+
}
|
|
197
|
+
function parseProcessRows(text) {
|
|
198
|
+
return text.split(/\r?\n/).map((line) => {
|
|
199
|
+
const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.+)$/);
|
|
200
|
+
return match ? { pid: Number(match[1]), ppid: Number(match[2]), command: match[3] || '' } : null;
|
|
201
|
+
}).filter(Boolean);
|
|
202
|
+
}
|
|
203
|
+
async function waitForOwnedProcessesToExit(processes, timeoutMs) {
|
|
204
|
+
const pids = [...new Set(processes.map((row) => row.pid))];
|
|
205
|
+
const deadline = Date.now() + timeoutMs;
|
|
206
|
+
while (Date.now() < deadline && pids.some(processAlive))
|
|
207
|
+
await delay(50);
|
|
208
|
+
const alive = pids.filter(processAlive);
|
|
209
|
+
return { ok: alive.length === 0, observed_pids: pids, verified_exited_pids: pids.filter((pid) => !alive.includes(pid)), alive_pids: alive };
|
|
210
|
+
}
|
|
211
|
+
function processAlive(pid) {
|
|
212
|
+
try {
|
|
213
|
+
process.kill(pid, 0);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async function removeFixtureRootAfterQuiescence(root) {
|
|
221
|
+
try {
|
|
222
|
+
await fs.rm(root, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 });
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
return { ok: false, removed: false, recreated: false, quiet_period_ms: 300, error: err?.message || String(err), blockers: ['mad_ui_check_fixture_removal_failed'] };
|
|
226
|
+
}
|
|
227
|
+
await delay(300);
|
|
228
|
+
const recreated = await exists(root);
|
|
229
|
+
return {
|
|
230
|
+
ok: !recreated,
|
|
231
|
+
removed: !recreated,
|
|
232
|
+
recreated,
|
|
233
|
+
quiet_period_ms: 300,
|
|
234
|
+
blockers: recreated ? ['mad_ui_check_fixture_recreated_after_return'] : []
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
async function waitForSocketEntriesToClear(socketDir, timeoutMs) {
|
|
238
|
+
const deadline = Date.now() + timeoutMs;
|
|
239
|
+
for (;;) {
|
|
240
|
+
const entries = await fs.readdir(path.join(socketDir, 'contract_version_1')).catch(() => []);
|
|
241
|
+
if (entries.length === 0 || Date.now() >= deadline)
|
|
242
|
+
return entries;
|
|
243
|
+
await delay(50);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
function delay(ms) {
|
|
247
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
248
|
+
}
|
|
249
|
+
async function exists(value) {
|
|
250
|
+
return fs.access(value).then(() => true).catch(() => false);
|
|
251
|
+
}
|
|
92
252
|
//# sourceMappingURL=mad-sks-app-ui-no-mutation-check.js.map
|
|
@@ -117,20 +117,9 @@ function listScanFiles() {
|
|
|
117
117
|
const relative = rel(file);
|
|
118
118
|
if (isTestSource(relative))
|
|
119
119
|
return;
|
|
120
|
-
if (relative.startsWith('src/scripts/'))
|
|
121
|
-
return;
|
|
122
120
|
if (file.endsWith('.ts'))
|
|
123
121
|
files.push(relative);
|
|
124
122
|
});
|
|
125
|
-
walk(path.join(root, 'src', 'scripts'), (file) => {
|
|
126
|
-
if (!file.endsWith('.ts'))
|
|
127
|
-
return;
|
|
128
|
-
if (isTestSource(rel(file)))
|
|
129
|
-
return;
|
|
130
|
-
const base = path.basename(file);
|
|
131
|
-
if (/(install|publish|release|doctor|codex|zellij|migration)/i.test(base))
|
|
132
|
-
files.push(rel(file));
|
|
133
|
-
});
|
|
134
123
|
return files.sort();
|
|
135
124
|
}
|
|
136
125
|
function isTestSource(relative) {
|