sneakoscope 3.1.1 → 3.1.2

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 (49) hide show
  1. package/README.md +1 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/cli/install-helpers.js +6 -7
  8. package/dist/core/agents/agent-orchestrator.js +7 -2
  9. package/dist/core/agents/agent-proof-evidence.js +20 -0
  10. package/dist/core/agents/fast-mode-policy.js +7 -5
  11. package/dist/core/agents/intelligent-work-graph.js +93 -14
  12. package/dist/core/agents/native-cli-session-swarm.js +46 -0
  13. package/dist/core/agents/no-subagent-scaling-policy.js +10 -1
  14. package/dist/core/agents/official-subagent-helper-policy.js +62 -0
  15. package/dist/core/codex-app.js +0 -2
  16. package/dist/core/commands/fast-mode-command.js +1 -1
  17. package/dist/core/commands/loop-command.js +35 -3
  18. package/dist/core/commands/naruto-command.js +10 -6
  19. package/dist/core/commands/wiki-command.js +35 -1
  20. package/dist/core/fsx.js +1 -1
  21. package/dist/core/init.js +1 -2
  22. package/dist/core/loops/loop-artifacts.js +21 -0
  23. package/dist/core/loops/loop-concurrency-budget.js +55 -0
  24. package/dist/core/loops/loop-final-arbiter-contract.js +28 -0
  25. package/dist/core/loops/loop-finalizer.js +25 -3
  26. package/dist/core/loops/loop-fixture-policy.js +58 -0
  27. package/dist/core/loops/loop-gate-runner.js +48 -7
  28. package/dist/core/loops/loop-gpt-final-arbiter.js +26 -6
  29. package/dist/core/loops/loop-integration-merge.js +20 -15
  30. package/dist/core/loops/loop-interrupt-registry.js +118 -0
  31. package/dist/core/loops/loop-merge-strategy.js +105 -0
  32. package/dist/core/loops/loop-mutation-ledger.js +103 -0
  33. package/dist/core/loops/loop-runtime-control.js +2 -0
  34. package/dist/core/loops/loop-runtime.js +6 -3
  35. package/dist/core/loops/loop-scheduler.js +2 -2
  36. package/dist/core/loops/loop-side-effect-scanner.js +91 -0
  37. package/dist/core/loops/loop-worker-runtime.js +35 -29
  38. package/dist/core/naruto/naruto-loop-mesh.js +3 -0
  39. package/dist/core/proof/auto-finalize.js +3 -2
  40. package/dist/core/proof/route-finalizer.js +71 -6
  41. package/dist/core/release/release-gate-dag.js +56 -1
  42. package/dist/core/version.js +1 -1
  43. package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +14 -2
  44. package/dist/scripts/loop-directive-check-lib.js +1 -1
  45. package/dist/scripts/loop-hardening-check-lib.js +289 -0
  46. package/dist/scripts/prepublish-release-check-or-fast.js +38 -10
  47. package/dist/scripts/release-check-stamp.js +29 -4
  48. package/dist/scripts/release-gate-existence-audit.js +1 -0
  49. package/package.json +28 -1
@@ -1,9 +1,10 @@
1
1
  import path from 'node:path';
2
- import os from 'node:os';
3
2
  import { fileURLToPath } from 'node:url';
4
3
  import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
5
4
  import { ensureDir, nowIso, readJson, runProcess, writeJsonAtomic } from '../fsx.js';
6
5
  import { loopNodeRoot } from './loop-artifacts.js';
6
+ import { computeLoopConcurrencyBudget, loopWorkerBudgetFor } from './loop-concurrency-budget.js';
7
+ import { decideLoopFixturePolicy, writeLoopFixturePolicyDecision } from './loop-fixture-policy.js';
7
8
  import { buildLoopCheckerPrompt, buildLoopMakerPrompt } from './loop-worker-prompts.js';
8
9
  export async function runLoopMakerWorkers(input) {
9
10
  return runLoopWorkers({ ...input, phase: 'maker' });
@@ -24,9 +25,15 @@ function shouldUseFixture(input) {
24
25
  const requested = input.fixture === true || process.env.SKS_LOOP_RUNTIME_FIXTURE === '1';
25
26
  if (!requested)
26
27
  return false;
27
- const allowed = loopFixtureAllowed(input);
28
- if (!allowed.ok) {
29
- throw new Error(`loop_fixture_runtime_forbidden:${allowed.reason}`);
28
+ const decision = decideLoopFixturePolicy({
29
+ root: input.root,
30
+ missionId: input.plan.mission_id,
31
+ mode: 'worker',
32
+ requested
33
+ });
34
+ void writeLoopFixturePolicyDecision(input.root, input.plan.mission_id, decision).catch(() => undefined);
35
+ if (!decision.allowed) {
36
+ throw new Error(`loop_fixture_runtime_forbidden:${decision.reason}:${decision.blockers.join(',')}`);
30
37
  }
31
38
  return true;
32
39
  }
@@ -34,7 +41,7 @@ async function runLoopWorkerNative(input) {
34
41
  const prompt = input.phase === 'maker'
35
42
  ? buildLoopMakerPrompt({ plan: input.plan, node: input.node, worktreePath: input.worktree?.path || null })
36
43
  : buildLoopCheckerPrompt({ plan: input.plan, node: input.node, makerArtifacts: input.makerArtifacts || [] });
37
- const workerCount = input.phase === 'maker' ? input.node.maker.worker_count : input.node.checker.worker_count;
44
+ const workerCount = effectiveLoopWorkerCount(input);
38
45
  const workGraph = buildLoopNarutoWorkGraph(input, workerCount);
39
46
  // Root-cause-1 fix: keep the ORCHESTRATOR root on the MAIN repo (input.root), not the
40
47
  // loop worktree. All zellij/right-column/slot-telemetry state derives from the orchestrator
@@ -66,6 +73,12 @@ async function runLoopWorkerNative(input) {
66
73
  narutoMode: true,
67
74
  narutoWorkGraph: workGraph,
68
75
  ...zellijPlacementOpts,
76
+ env: {
77
+ SKS_LOOP_ID: input.node.loop_id,
78
+ SKS_LOOP_PHASE: input.phase,
79
+ SKS_LOOP_MAIN_ROOT: input.root,
80
+ SKS_LOOP_WORKER_BUDGET: String(workerCount)
81
+ },
69
82
  ...(input.worktree?.path ? {
70
83
  worktree: {
71
84
  id: input.worktree.id || `loop-${input.node.loop_id}-${input.phase}`,
@@ -98,7 +111,7 @@ async function normalizeNativeResult(input, result) {
98
111
  mission_id: input.plan.mission_id,
99
112
  loop_id: input.node.loop_id,
100
113
  phase: input.phase,
101
- worker_count: input.phase === 'maker' ? input.node.maker.worker_count : input.node.checker.worker_count,
114
+ worker_count: effectiveLoopWorkerCount(input),
102
115
  backend: 'native-agent-orchestrator',
103
116
  artifacts,
104
117
  patch_candidates: input.phase === 'maker' ? artifacts.filter((artifact) => artifact.includes('patch')) : [],
@@ -113,6 +126,12 @@ async function normalizeNativeResult(input, result) {
113
126
  return normalized;
114
127
  }
115
128
  async function runLoopWorkerFixture(input) {
129
+ const fixturePolicy = decideLoopFixturePolicy({
130
+ root: input.root,
131
+ missionId: input.plan.mission_id,
132
+ mode: 'worker',
133
+ requested: true
134
+ });
116
135
  const dir = path.join(loopNodeRoot(input.root, input.plan.mission_id, input.node.loop_id), input.phase);
117
136
  await ensureDir(dir);
118
137
  const resultPath = path.join(dir, 'worker-runtime-result.json');
@@ -150,7 +169,9 @@ async function runLoopWorkerFixture(input) {
150
169
  blockers: [`loop_worker_fixture_child_missing_result:${child.code}`],
151
170
  runtime_proof_path: resultPath,
152
171
  worker_ids: [],
153
- session_ids: []
172
+ session_ids: [],
173
+ fixture_policy: fixturePolicy,
174
+ fixture_allowed_reason: fixturePolicy.allowed ? fixturePolicy.reason : null
154
175
  };
155
176
  }
156
177
  return {
@@ -159,7 +180,9 @@ async function runLoopWorkerFixture(input) {
159
180
  blockers: [
160
181
  ...result.blockers,
161
182
  ...(child.code === 0 ? [] : [`loop_worker_fixture_child_exit:${child.code}`])
162
- ]
183
+ ],
184
+ fixture_policy: fixturePolicy,
185
+ fixture_allowed_reason: fixturePolicy.allowed ? fixturePolicy.reason : null
163
186
  };
164
187
  }
165
188
  function buildLoopNarutoWorkGraph(input, workerCount) {
@@ -250,26 +273,9 @@ function resolveLoopVisiblePaneCap(workerCount) {
250
273
  function fixtureChildEntrypoint() {
251
274
  return fileURLToPath(new URL('../../scripts/loop-worker-fixture-child.js', import.meta.url));
252
275
  }
253
- function loopFixtureAllowed(input) {
254
- const argv = process.argv.join(' ');
255
- const scriptIsCheck = /(?:^|\s)(?:.*[\/\\])?(?:dist|src)[\/\\]scripts[\/\\][^\s]*(?:check|blackbox)\.(?:js|ts)(?:\s|$)/.test(argv);
256
- const explicitTestEnv = process.env.NODE_ENV === 'test'
257
- || process.env.SKS_TEST_RUNTIME_FIXTURE_ALLOWED === '1'
258
- || process.env.VITEST_WORKER_ID !== undefined
259
- || process.env.JEST_WORKER_ID !== undefined
260
- || process.env.NODE_V8_COVERAGE !== undefined;
261
- const tempRoot = isUnderTempRoot(input.root) && /^M-check-/.test(input.plan.mission_id);
262
- if (scriptIsCheck)
263
- return { ok: true, reason: 'release_check_script' };
264
- if (explicitTestEnv)
265
- return { ok: true, reason: 'test_environment' };
266
- if (tempRoot)
267
- return { ok: true, reason: 'hermetic_temp_loop_check' };
268
- return { ok: false, reason: 'fixture_requires_test_context' };
269
- }
270
- function isUnderTempRoot(root) {
271
- const normalizedRoot = path.resolve(root);
272
- const tempRoot = path.resolve(os.tmpdir());
273
- return normalizedRoot === tempRoot || normalizedRoot.startsWith(`${tempRoot}${path.sep}`);
276
+ function effectiveLoopWorkerCount(input) {
277
+ const requested = input.phase === 'maker' ? input.node.maker.worker_count : input.node.checker.worker_count;
278
+ const budget = computeLoopConcurrencyBudget({ plan: input.plan });
279
+ return loopWorkerBudgetFor(budget, input.node.loop_id, input.phase, requested);
274
280
  }
275
281
  //# sourceMappingURL=loop-worker-runtime.js.map
@@ -1,14 +1,17 @@
1
1
  import { writeJsonAtomic } from '../fsx.js';
2
+ import { computeLoopConcurrencyBudget } from '../loops/loop-concurrency-budget.js';
2
3
  import { loopRoot } from '../loops/loop-artifacts.js';
3
4
  import { runLoopPlan } from '../loops/loop-runtime.js';
4
5
  import { routeNarutoLoopWorker } from './naruto-loop-worker-router.js';
5
6
  export async function runNarutoLoopMesh(input) {
6
7
  const routes = input.plan.graph.nodes.flatMap((node) => [routeNarutoLoopWorker(node, 'maker'), routeNarutoLoopWorker(node, 'checker')]);
7
8
  const activeWorkerBudget = splitActiveWorkerBudget(input.plan, input.parallelism);
9
+ const loopConcurrencyBudget = computeLoopConcurrencyBudget({ plan: input.plan, parallelism: input.parallelism });
8
10
  await writeJsonAtomic(`${loopRoot(input.root, input.plan.mission_id)}/naruto-loop-worker-routes.json`, {
9
11
  schema: 'sks.naruto-loop-worker-routes.v1',
10
12
  mission_id: input.plan.mission_id,
11
13
  active_worker_budget: activeWorkerBudget,
14
+ loop_concurrency_budget: loopConcurrencyBudget,
12
15
  routes
13
16
  });
14
17
  return runLoopPlan({
@@ -13,7 +13,7 @@ const AGENT_ARTIFACTS = [
13
13
  'agents/agent-task-board.json',
14
14
  'agents/agent-concurrency-policy.json'
15
15
  ];
16
- export async function maybeFinalizeRoute(root, { missionId, route, gateFile = null, gate = null, artifacts = [], claims = [], visualEvidence = null, visual = false, fixClaim = false, requireRelation = false, mock = false, statusHint = null, reason = null, command = null, dbEvidence = null, testEvidence = null, blockers = [], unverified = [], agents = undefined, allowActiveWrongnessPartial = false } = {}) {
16
+ export async function maybeFinalizeRoute(root, { missionId, route, gateFile = null, gate = null, artifacts = [], claims = [], visualEvidence = null, visual = false, fixClaim = false, requireRelation = false, mock = false, statusHint = null, reason = null, command = null, dbEvidence = null, testEvidence = null, blockers = [], unverified = [], agents = undefined, allowActiveWrongnessPartial = false, failureAnalysis = null } = {}) {
17
17
  if (!missionId || !route) {
18
18
  return { ok: false, skipped: true, reason: 'mission_id_or_route_missing' };
19
19
  }
@@ -61,7 +61,8 @@ export async function maybeFinalizeRoute(root, { missionId, route, gateFile = nu
61
61
  requireRelation,
62
62
  visualClaim: visual,
63
63
  agents,
64
- allowActiveWrongnessPartial
64
+ allowActiveWrongnessPartial,
65
+ failureAnalysis
65
66
  });
66
67
  return { ...proof, auto_finalized: true, gate_passed: passed, status_hint: finalStatus };
67
68
  }
@@ -6,7 +6,7 @@ import { readAgentProofEvidence } from '../agents/agent-proof-evidence.js';
6
6
  import { wrongnessProofEvidence } from '../triwiki-wrongness/wrongness-proof-linker.js';
7
7
  import { computerUseStatusReport } from '../computer-use-status.js';
8
8
  import { readComputerUseLiveEvidence } from '../computer-use-live-evidence.js';
9
- export async function finalizeRouteWithProof(root, { missionId, route, gateFile = null, gate = null, artifacts = [], visualEvidence = null, dbEvidence = null, madSksEvidence = null, testEvidence = null, commandEvidence = null, claims = [], unverified = [], blockers = [], statusHint = 'verified_partial', strict = false, mock = false, fixClaim = false, requireRelation = false, visualClaim = undefined, agents = undefined, allowActiveWrongnessPartial = false } = {}) {
9
+ export async function finalizeRouteWithProof(root, { missionId, route, gateFile = null, gate = null, artifacts = [], visualEvidence = null, dbEvidence = null, madSksEvidence = null, testEvidence = null, commandEvidence = null, claims = [], unverified = [], blockers = [], statusHint = 'verified_partial', strict = false, mock = false, fixClaim = false, requireRelation = false, visualClaim = undefined, agents = undefined, allowActiveWrongnessPartial = false, failureAnalysis = null } = {}) {
10
10
  const policy = routeFinalizerPolicy(route, { strict, fixClaim, requireRelation, visualClaim });
11
11
  const localBlockers = [...blockers];
12
12
  const providedVisualEvidence = visualEvidence;
@@ -66,6 +66,24 @@ export async function finalizeRouteWithProof(root, { missionId, route, gateFile
66
66
  ? (strict ? 'blocked' : statusHint === 'verified' ? 'verified_partial' : statusHint)
67
67
  : visualComputerUseDowngrade ? 'verified_partial'
68
68
  : statusHint;
69
+ const finalUnverified = [
70
+ ...unverified,
71
+ ...(imageEvidence?.mock ? ['Image voxel evidence is mock fixture evidence and does not claim a real visual run.'] : []),
72
+ ...(Number(wrongnessEvidence?.medium_severity_active || 0) > 0 ? ['Active medium-severity wrongness memory remains and prevents full verification claims.'] : [])
73
+ ];
74
+ const resolvedFailureAnalysis = failureAnalysis || inferRouteFailureAnalysis({
75
+ missionId,
76
+ route: policy.route,
77
+ status,
78
+ blockers: localBlockers,
79
+ unverified: finalUnverified,
80
+ wrongnessEvidence,
81
+ imageEvidence,
82
+ agentEvidence,
83
+ computerUse,
84
+ computerUseLive,
85
+ visualComputerUseDowngrade
86
+ });
69
87
  const evidence = {
70
88
  ...collected,
71
89
  ...(dbEvidence ? { db: dbEvidence } : {}),
@@ -109,12 +127,9 @@ export async function finalizeRouteWithProof(root, { missionId, route, gateFile
109
127
  artifacts,
110
128
  evidence,
111
129
  claims,
112
- unverified: [
113
- ...unverified,
114
- ...(imageEvidence?.mock ? ['Image voxel evidence is mock fixture evidence and does not claim a real visual run.'] : []),
115
- ...(Number(wrongnessEvidence?.medium_severity_active || 0) > 0 ? ['Active medium-severity wrongness memory remains and prevents full verification claims.'] : [])
116
- ],
130
+ unverified: finalUnverified,
117
131
  blockers: localBlockers,
132
+ failureAnalysis: resolvedFailureAnalysis,
118
133
  summary: {
119
134
  files_changed: collected.files?.length || 0,
120
135
  commands_run: evidence.commands?.length || 0,
@@ -124,4 +139,54 @@ export async function finalizeRouteWithProof(root, { missionId, route, gateFile
124
139
  }
125
140
  });
126
141
  }
142
+ function inferRouteFailureAnalysis({ missionId, route, status, blockers, unverified, wrongnessEvidence, imageEvidence, agentEvidence, computerUse, computerUseLive, visualComputerUseDowngrade } = {}) {
143
+ if (status === 'verified' && !blockers?.length && !unverified?.length)
144
+ return null;
145
+ const evidence = [
146
+ missionId ? `.sneakoscope/missions/${missionId}/completion-proof.json#unverified` : 'completion-proof.json#unverified',
147
+ ...(wrongnessEvidence?.mission_ledger ? [wrongnessEvidence.mission_ledger] : []),
148
+ ...(agentEvidence ? [missionId ? `.sneakoscope/missions/${missionId}/agents/agent-proof-evidence.json` : 'agents/agent-proof-evidence.json'] : []),
149
+ ...(imageEvidence?.ledger ? ['image_voxels'] : []),
150
+ ...(computerUse ? ['computer_use_status'] : []),
151
+ ...(computerUseLive?.path ? [computerUseLive.path] : [])
152
+ ];
153
+ if (blockers?.length) {
154
+ return {
155
+ status: 'complete',
156
+ root_cause: `Route ${route || 'unknown'} could not be fully verified because finalization recorded blocking conditions: ${blockers.join(', ')}.`,
157
+ corrective_action: 'Preserved the non-verified completion status, recorded blockers in Completion Proof, and linked the available route evidence instead of claiming full completion.',
158
+ evidence
159
+ };
160
+ }
161
+ if (Number(wrongnessEvidence?.medium_severity_active || 0) > 0) {
162
+ return {
163
+ status: 'complete',
164
+ root_cause: `Route ${route || 'unknown'} remains verified_partial because active medium-severity wrongness memory is still present even though no high-severity blocker remains.`,
165
+ corrective_action: 'Kept the Completion Proof at verified_partial, recorded the wrongness caveat in unverified evidence, and avoided a full verified claim until the memory is resolved or explicitly accepted.',
166
+ evidence
167
+ };
168
+ }
169
+ if (visualComputerUseDowngrade) {
170
+ return {
171
+ status: 'complete',
172
+ root_cause: 'Native Computer Use visual confidence was downgraded because live capture or Image Voxel linkage was unavailable or incomplete.',
173
+ corrective_action: 'Kept the Completion Proof at verified_partial and recorded the missing native visual evidence instead of claiming high-confidence visual verification.',
174
+ evidence
175
+ };
176
+ }
177
+ if (imageEvidence?.mock) {
178
+ return {
179
+ status: 'complete',
180
+ root_cause: 'Visual evidence was produced from a mock fixture, which cannot support a real fully verified visual route claim.',
181
+ corrective_action: 'Kept the Completion Proof at verified_partial and recorded the mock-evidence caveat in unverified evidence.',
182
+ evidence
183
+ };
184
+ }
185
+ return {
186
+ status: 'complete',
187
+ root_cause: `Route ${route || 'unknown'} generated a non-verified completion status because unresolved caveats remained in final unverified evidence.`,
188
+ corrective_action: 'Recorded those caveats in Completion Proof and preserved the partial status instead of upgrading the route to verified.',
189
+ evidence
190
+ };
191
+ }
127
192
  //# sourceMappingURL=route-finalizer.js.map
@@ -10,6 +10,7 @@ import { countReleaseGateResources, defaultReleaseGateBudget, summarizeReleaseGa
10
10
  import { selectAffectedReleaseGates } from './release-gate-affected-selector.js';
11
11
  import { guardedProcessKill, guardContextForRoute } from '../safety/mutation-guard.js';
12
12
  import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
13
+ import { rmrf } from '../fsx.js';
13
14
  export function loadReleaseGateManifest(root, file = 'release-gates.v2.json') {
14
15
  const manifestPath = path.join(root, file);
15
16
  const parsed = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
@@ -33,9 +34,11 @@ export async function runReleaseGateDag(input) {
33
34
  ? new Set(selected.flatMap((gate) => gate.deps || []).filter((dep) => !selectedIds.has(dep)))
34
35
  : new Set();
35
36
  const runId = `rg-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`;
37
+ const retentionBefore = await pruneOldReleaseGateRunDirs(root);
36
38
  const reportDir = path.join(root, '.sneakoscope', 'reports', 'release-gates', runId);
37
39
  fs.mkdirSync(reportDir, { recursive: true });
38
40
  const timeline = path.join(reportDir, 'timeline.jsonl');
41
+ appendReleaseGateJsonl(timeline, { event: 'retention', phase: 'before_run', ...retentionBefore, at: new Date().toISOString() });
39
42
  const started = Date.now();
40
43
  const pending = new Map(selected.map((gate) => [gate.id, gate]));
41
44
  const running = new Map();
@@ -166,7 +169,11 @@ export async function runReleaseGateDag(input) {
166
169
  writeSummarySnapshot(false);
167
170
  }
168
171
  const result = writeSummarySnapshot(true);
169
- return result;
172
+ const retentionAfter = await pruneOldReleaseGateRunDirs(root, { preserveRunId: runId });
173
+ const finalResult = { ...result, retention: mergeReleaseGateRetention(retentionBefore, retentionAfter) };
174
+ appendReleaseGateJsonl(timeline, { event: 'retention', phase: 'after_run', ...retentionAfter, at: new Date().toISOString() });
175
+ writeReleaseGateJson(path.join(reportDir, 'summary.json'), finalResult);
176
+ return finalResult;
170
177
  }
171
178
  export function selectReleaseGatePreset(manifest, preset) {
172
179
  const effectivePreset = preset === 'affected' || preset === 'fast' ? 'release' : preset;
@@ -258,4 +265,52 @@ function estimateCriticalPath(gates, completed) {
258
265
  function tail(value, limit = 1200) {
259
266
  return value.length > limit ? value.slice(-limit) : value;
260
267
  }
268
+ export async function pruneOldReleaseGateRunDirs(root, opts = {}) {
269
+ const keep = Math.max(1, Math.floor(Number(opts.keep ?? process.env.SKS_RELEASE_GATE_RUN_RETENTION ?? 20) || 20));
270
+ const preserveRunId = opts.preserveRunId || null;
271
+ const base = path.join(root, '.sneakoscope', 'reports', 'release-gates');
272
+ const report = {
273
+ schema: 'sks.release-gate-run-retention.v1',
274
+ keep,
275
+ scanned: 0,
276
+ kept: 0,
277
+ removed: 0,
278
+ preserve_run_id: preserveRunId,
279
+ removed_run_ids: []
280
+ };
281
+ if (!fs.existsSync(base))
282
+ return report;
283
+ const runs = fs.readdirSync(base, { withFileTypes: true })
284
+ .filter((entry) => entry.isDirectory() && /^rg-\d{4}-/.test(entry.name))
285
+ .map((entry) => {
286
+ const dir = path.join(base, entry.name);
287
+ const summary = path.join(dir, 'summary.json');
288
+ const stat = fs.statSync(fs.existsSync(summary) ? summary : dir);
289
+ return { id: entry.name, dir, mtimeMs: stat.mtimeMs };
290
+ })
291
+ .sort((a, b) => b.mtimeMs - a.mtimeMs);
292
+ report.scanned = runs.length;
293
+ const keepIds = new Set(runs.slice(0, keep).map((run) => run.id));
294
+ if (preserveRunId)
295
+ keepIds.add(preserveRunId);
296
+ for (const run of runs) {
297
+ if (keepIds.has(run.id)) {
298
+ report.kept += 1;
299
+ continue;
300
+ }
301
+ await rmrf(run.dir);
302
+ report.removed += 1;
303
+ report.removed_run_ids.push(run.id);
304
+ }
305
+ return report;
306
+ }
307
+ function mergeReleaseGateRetention(before, after) {
308
+ return {
309
+ ...after,
310
+ scanned: Math.max(before.scanned, after.scanned),
311
+ kept: after.kept,
312
+ removed: before.removed + after.removed,
313
+ removed_run_ids: [...before.removed_run_ids, ...after.removed_run_ids]
314
+ };
315
+ }
261
316
  //# sourceMappingURL=release-gate-dag.js.map
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '3.1.1';
1
+ export const PACKAGE_VERSION = '3.1.2';
2
2
  //# sourceMappingURL=version.js.map
@@ -3,7 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { execFileSync } from 'node:child_process';
5
5
  import { assertGate, root } from '../sks-1-18-gate-lib.js';
6
- export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName, backend = 'fake', extraArgs = [] }) {
6
+ export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName, backend = 'fake', extraArgs = [], expectedFastMode = null }) {
7
7
  const distCli = path.join(root, 'dist', 'bin', 'sks.js');
8
8
  assertGate(fs.existsSync(distCli), 'dist CLI missing for native CLI swarm check', { distCli });
9
9
  const args = [
@@ -37,7 +37,11 @@ export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName,
37
37
  const result = JSON.parse(stdout);
38
38
  const proof = result.native_cli_session_proof || {};
39
39
  const noSubagent = result.no_subagent_scaling_policy || {};
40
+ const officialHelper = result.official_subagent_helper_policy || {};
40
41
  const fast = result.fast_mode_propagation || {};
42
+ const policy = fast.policy || result.fast_mode_policy || {};
43
+ const expectedFast = expectedFastMode === null || expectedFastMode === undefined ? Boolean(policy.fast_mode) : Boolean(expectedFastMode);
44
+ const expectedTier = expectedFast ? 'fast' : 'standard';
41
45
  const report = {
42
46
  schema: 'sks.native-cli-session-swarm-check.v1',
43
47
  ok: result.ok === true,
@@ -47,6 +51,7 @@ export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName,
47
51
  backend: result.backend,
48
52
  native_cli_session_proof: proof,
49
53
  no_subagent_scaling_policy: noSubagent,
54
+ official_subagent_helper_policy: officialHelper,
50
55
  fast_mode_propagation: fast,
51
56
  proof_status: result.proof?.status || null
52
57
  };
@@ -60,7 +65,14 @@ export function runNativeCliSwarmCheck({ agents, workItems = agents, reportName,
60
65
  assertGate(Array.isArray(proof.process_ids) && proof.process_ids.length >= agents, 'process ids missing from native CLI proof', report);
61
66
  assertGate(proof.close_report_count >= agents, 'worker close report count below requested agents', report);
62
67
  assertGate(noSubagent.ok === true && noSubagent.subagent_events_counted_as_worker_sessions === false, 'no-subagent scaling policy must pass', report);
63
- assertGate(fast.ok === true && fast.fast_mode === true && fast.service_tier === 'fast', 'fast mode must propagate by default', report);
68
+ assertGate(officialHelper.ok === true && officialHelper.official_codex_subagent_helper_lane_enabled === true, 'official subagent helper policy must pass', report);
69
+ assertGate(officialHelper.worker_capacity_credit === 0 && officialHelper.subagent_events_counted_as_worker_sessions === false, 'official helper lane must not count toward worker capacity', report);
70
+ 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);
71
+ assertGate(fast.ok === true, 'fast mode propagation proof must pass', report);
72
+ 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 });
73
+ if (expectedFast) {
74
+ 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);
75
+ }
64
76
  assertGate((proof.worker_command_lines || []).every((line) => line.includes('--agent') && line.includes('worker')), 'worker command lines must use --agent worker', report);
65
77
  return report;
66
78
  }
@@ -89,7 +89,7 @@ export async function runLoopDirectiveCheck(id) {
89
89
  const runtimeSource = await fs.readFile(path.join(process.cwd(), 'src/core/loops/loop-runtime.ts'), 'utf8');
90
90
  const workerSource = await fs.readFile(path.join(process.cwd(), 'src/core/loops/loop-worker-runtime.ts'), 'utf8');
91
91
  assert(!/noMutation\s*\?\s*\{\s*fixture:\s*true\s*\}/.test(runtimeSource), 'noMutation must not force fixture mode');
92
- assert(workerSource.includes('loopFixtureAllowed'), 'fixture runtime has an explicit test-context allow guard');
92
+ assert(workerSource.includes('decideLoopFixturePolicy'), 'fixture runtime has an explicit shared test-context policy guard');
93
93
  assert(workerSource.includes('loop_fixture_runtime_forbidden'), 'fixture runtime fails closed outside test context');
94
94
  assert(workerSource.includes("process.env.SKS_LOOP_RUNTIME_FIXTURE === '1'"), 'fixture runtime remains opt-in through SKS_LOOP_RUNTIME_FIXTURE');
95
95
  assert(!workerSource.includes('visualLaneCount: Math.min(4'), 'zellij visual lane count must use the configurable pane cap');