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.
- 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/cli/install-helpers.js +6 -7
- package/dist/core/agents/agent-orchestrator.js +7 -2
- package/dist/core/agents/agent-proof-evidence.js +20 -0
- package/dist/core/agents/fast-mode-policy.js +7 -5
- package/dist/core/agents/intelligent-work-graph.js +93 -14
- package/dist/core/agents/native-cli-session-swarm.js +46 -0
- package/dist/core/agents/no-subagent-scaling-policy.js +10 -1
- package/dist/core/agents/official-subagent-helper-policy.js +62 -0
- package/dist/core/codex-app.js +0 -2
- package/dist/core/commands/fast-mode-command.js +1 -1
- package/dist/core/commands/loop-command.js +35 -3
- package/dist/core/commands/naruto-command.js +10 -6
- package/dist/core/commands/wiki-command.js +35 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +1 -2
- package/dist/core/loops/loop-artifacts.js +21 -0
- package/dist/core/loops/loop-concurrency-budget.js +55 -0
- package/dist/core/loops/loop-final-arbiter-contract.js +28 -0
- package/dist/core/loops/loop-finalizer.js +25 -3
- package/dist/core/loops/loop-fixture-policy.js +58 -0
- package/dist/core/loops/loop-gate-runner.js +48 -7
- package/dist/core/loops/loop-gpt-final-arbiter.js +26 -6
- package/dist/core/loops/loop-integration-merge.js +20 -15
- package/dist/core/loops/loop-interrupt-registry.js +118 -0
- package/dist/core/loops/loop-merge-strategy.js +105 -0
- package/dist/core/loops/loop-mutation-ledger.js +103 -0
- package/dist/core/loops/loop-runtime-control.js +2 -0
- package/dist/core/loops/loop-runtime.js +6 -3
- package/dist/core/loops/loop-scheduler.js +2 -2
- package/dist/core/loops/loop-side-effect-scanner.js +91 -0
- package/dist/core/loops/loop-worker-runtime.js +35 -29
- package/dist/core/naruto/naruto-loop-mesh.js +3 -0
- package/dist/core/proof/auto-finalize.js +3 -2
- package/dist/core/proof/route-finalizer.js +71 -6
- package/dist/core/release/release-gate-dag.js +56 -1
- package/dist/core/version.js +1 -1
- package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +14 -2
- package/dist/scripts/loop-directive-check-lib.js +1 -1
- package/dist/scripts/loop-hardening-check-lib.js +289 -0
- package/dist/scripts/prepublish-release-check-or-fast.js +38 -10
- package/dist/scripts/release-check-stamp.js +29 -4
- package/dist/scripts/release-gate-existence-audit.js +1 -0
- 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
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
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
|
|
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
|
|
254
|
-
const
|
|
255
|
-
const
|
|
256
|
-
|
|
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
|
-
|
|
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
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '3.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(
|
|
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('
|
|
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');
|