sneakoscope 3.1.0 → 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/commands/zellij-slot-column-anchor.js +3 -1
- package/dist/commands/zellij-slot-pane.js +19 -2
- package/dist/core/agents/agent-janitor.js +10 -1
- package/dist/core/agents/agent-orchestrator.js +8 -2
- package/dist/core/agents/agent-proof-evidence.js +20 -0
- package/dist/core/agents/agent-runner-ollama.js +11 -4
- 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 +115 -9
- 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/codex-control/codex-task-runner.js +9 -0
- package/dist/core/commands/fast-mode-command.js +1 -1
- package/dist/core/commands/loop-command.js +86 -13
- package/dist/core/commands/naruto-command.js +34 -21
- package/dist/core/commands/team-command.js +1 -0
- 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/locks/file-lock.js +88 -0
- package/dist/core/loops/loop-artifacts.js +54 -2
- package/dist/core/loops/loop-checkpoint.js +22 -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 +55 -7
- package/dist/core/loops/loop-fixture-policy.js +58 -0
- package/dist/core/loops/loop-gate-registry.js +96 -0
- package/dist/core/loops/loop-gate-runner.js +206 -17
- package/dist/core/loops/loop-gpt-final-arbiter.js +81 -0
- package/dist/core/loops/loop-integration-merge.js +80 -0
- package/dist/core/loops/loop-interrupt-registry.js +118 -0
- package/dist/core/loops/loop-lease.js +35 -20
- 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-planner.js +36 -5
- package/dist/core/loops/loop-runtime-control.js +27 -0
- package/dist/core/loops/loop-runtime.js +254 -96
- package/dist/core/loops/loop-scheduler.js +14 -5
- package/dist/core/loops/loop-side-effect-scanner.js +91 -0
- package/dist/core/loops/loop-worker-prompts.js +43 -0
- package/dist/core/loops/loop-worker-runtime.js +281 -0
- package/dist/core/loops/loop-worktree-runtime.js +92 -0
- package/dist/core/naruto/naruto-finalizer.js +7 -2
- package/dist/core/naruto/naruto-loop-mesh.js +10 -1
- package/dist/core/proof/auto-finalize.js +3 -2
- package/dist/core/proof/proof-schema.js +6 -0
- package/dist/core/proof/proof-writer.js +5 -2
- package/dist/core/proof/root-cause-policy.js +70 -0
- package/dist/core/proof/route-adapter.js +18 -1
- package/dist/core/proof/route-finalizer.js +71 -6
- package/dist/core/proof/route-proof-gate.js +4 -0
- package/dist/core/release/release-gate-batch-runner.js +56 -10
- package/dist/core/release/release-gate-cache-v2.js +18 -3
- package/dist/core/release/release-gate-dag.js +121 -18
- package/dist/core/release/release-gate-node.js +2 -1
- package/dist/core/release/release-gate-resource-governor.js +27 -6
- package/dist/core/skills/core-skill-meta-update.js +24 -0
- package/dist/core/skills/core-skill-reflection.js +94 -0
- package/dist/core/skills/core-skill-trainer.js +103 -0
- package/dist/core/trust-kernel/completion-contract.js +4 -0
- package/dist/core/trust-kernel/route-contract.js +4 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-right-column-manager.js +13 -2
- package/dist/core/zellij/zellij-slot-column-anchor.js +40 -3
- package/dist/core/zellij/zellij-slot-pane-renderer.js +36 -11
- package/dist/core/zellij/zellij-slot-telemetry.js +96 -44
- package/dist/core/zellij/zellij-worker-pane-manager.js +42 -4
- package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +14 -2
- package/dist/scripts/loop-directive-check-lib.js +225 -2
- package/dist/scripts/loop-hardening-check-lib.js +289 -0
- package/dist/scripts/loop-worker-fixture-child.js +53 -0
- package/dist/scripts/naruto-real-local-gpt-final-smoke.js +10 -1
- 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 +32 -2
|
@@ -120,7 +120,17 @@ export async function wikiCommand(sub, args = []) {
|
|
|
120
120
|
const { id, dir } = await createMission(root, { mode: 'wiki', prompt: 'sks wiki refresh' });
|
|
121
121
|
const gate = { schema_version: 1, passed: validation.result.ok, ok: validation.result.ok, context_pack: '.sneakoscope/wiki/context-pack.json', anchors: wikiAnchorCount(pack.wiki), voxels: wikiVoxelRowCount(pack.wiki) };
|
|
122
122
|
await writeJsonAtomic(path.join(dir, 'wiki-gate.json'), gate);
|
|
123
|
-
await maybeFinalizeRoute(root, {
|
|
123
|
+
await maybeFinalizeRoute(root, {
|
|
124
|
+
missionId: id,
|
|
125
|
+
route: '$Wiki',
|
|
126
|
+
gateFile: 'wiki-gate.json',
|
|
127
|
+
gate,
|
|
128
|
+
artifacts: ['wiki-gate.json', 'completion-proof.json'],
|
|
129
|
+
statusHint: validation.result.ok ? 'verified_partial' : 'blocked',
|
|
130
|
+
blockers: validation.result.ok ? [] : validation.result.issues,
|
|
131
|
+
command: { cmd: 'sks wiki refresh', status: exitCode },
|
|
132
|
+
failureAnalysis: wikiRefreshFailureAnalysis(validation.result, gate)
|
|
133
|
+
});
|
|
124
134
|
}
|
|
125
135
|
if (flag(args, '--json')) {
|
|
126
136
|
process.exitCode = exitCode;
|
|
@@ -197,6 +207,30 @@ export async function wikiCommand(sub, args = []) {
|
|
|
197
207
|
console.error('Usage: sks wiki coords|pack|refresh|publish|rebuild-index|rebuild-summary|validate|validate-shared|wrongness|image-ingest|anchor-add|relation-add|image-validate|image-summary');
|
|
198
208
|
process.exitCode = 1;
|
|
199
209
|
}
|
|
210
|
+
function wikiRefreshFailureAnalysis(validationResult, gate = {}) {
|
|
211
|
+
if (validationResult?.ok) {
|
|
212
|
+
return {
|
|
213
|
+
status: 'complete',
|
|
214
|
+
root_cause: 'Wiki refresh intentionally finalizes as verified_partial because a context-pack refresh can verify TriWiki schema and anchors, but active wrongness memory may still prevent full trust verification.',
|
|
215
|
+
corrective_action: 'The Wiki route records the fresh context-pack validation, wiki gate counts, and completion-proof evidence while keeping the route below full verified status until wrongness memory is separately resolved.',
|
|
216
|
+
evidence: [
|
|
217
|
+
'.sneakoscope/wiki/context-pack.json',
|
|
218
|
+
'wiki-gate.json',
|
|
219
|
+
{ anchors: gate.anchors || 0, voxels: gate.voxels || 0 }
|
|
220
|
+
]
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
status: 'complete',
|
|
225
|
+
root_cause: `Wiki context-pack validation failed during refresh: ${(validationResult?.issues || []).join(', ') || 'unknown validation issue'}.`,
|
|
226
|
+
corrective_action: 'The Wiki route finalized as blocked and preserved validation issues in the completion proof so the context pack can be corrected before any completion claim.',
|
|
227
|
+
evidence: [
|
|
228
|
+
'.sneakoscope/wiki/context-pack.json',
|
|
229
|
+
'wiki-gate.json',
|
|
230
|
+
...(validationResult?.issues || [])
|
|
231
|
+
]
|
|
232
|
+
};
|
|
233
|
+
}
|
|
200
234
|
async function wikiImageIngest(args = []) {
|
|
201
235
|
const root = await sksRoot();
|
|
202
236
|
const imagePath = args.find((arg, i) => i >= 0 && !String(arg).startsWith('--'));
|
package/dist/core/fsx.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '3.1.
|
|
8
|
+
export const PACKAGE_VERSION = '3.1.2';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
package/dist/core/init.js
CHANGED
|
@@ -591,7 +591,6 @@ export async function initProject(root, opts = {}) {
|
|
|
591
591
|
// upgrade never clobbers a user's chosen model/service_tier. model_reasoning_effort
|
|
592
592
|
// is intentionally left untouched at the top level.
|
|
593
593
|
next = upsertTopLevelTomlStringIfAbsent(next, 'model', 'gpt-5.5');
|
|
594
|
-
next = upsertTopLevelTomlStringIfAbsent(next, 'service_tier', 'fast');
|
|
595
594
|
next = upsertTopLevelTomlBooleanIfAbsent(next, 'suppress_unstable_features_warning', true);
|
|
596
595
|
// Codex App feature flags: SET-IF-ABSENT only (see note above).
|
|
597
596
|
for (const flag of MANAGED_CODEX_FEATURE_FLAGS) {
|
|
@@ -599,7 +598,7 @@ export async function initProject(root, opts = {}) {
|
|
|
599
598
|
}
|
|
600
599
|
next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'visible = true');
|
|
601
600
|
next = upsertTomlTableKeyIfAbsent(next, 'user.fast_mode', 'enabled = true');
|
|
602
|
-
next =
|
|
601
|
+
next = removeTomlTableKey(next, 'user.fast_mode', 'default_profile');
|
|
603
602
|
next = upsertTomlTableKeyIfAbsent(next, 'agents', 'max_threads = 6');
|
|
604
603
|
next = upsertTomlTableKeyIfAbsent(next, 'agents', 'max_depth = 1');
|
|
605
604
|
for (const block of managedCodexConfigBlocks()) {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureDir, nowIso, randomId, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { guardedRm, guardContextForRoute } from '../safety/mutation-guard.js';
|
|
5
|
+
import { CONFIRMATION_REQUIRED, REQUESTED_SCOPE_CONTRACT_SCHEMA } from '../safety/requested-scope-contract.js';
|
|
6
|
+
export async function withFileLock(input, fn) {
|
|
7
|
+
const lockPath = path.resolve(input.lockPath);
|
|
8
|
+
const timeoutMs = Math.max(1, input.timeoutMs);
|
|
9
|
+
const staleMs = Math.max(1, input.staleMs);
|
|
10
|
+
const started = Date.now();
|
|
11
|
+
const owner = `${process.pid}-${randomId(8)}`;
|
|
12
|
+
await ensureDir(path.dirname(lockPath));
|
|
13
|
+
while (true) {
|
|
14
|
+
try {
|
|
15
|
+
await fsp.mkdir(lockPath);
|
|
16
|
+
await writeJsonAtomic(path.join(lockPath, 'owner.json'), {
|
|
17
|
+
schema: 'sks.file-lock-owner.v1',
|
|
18
|
+
owner,
|
|
19
|
+
pid: process.pid,
|
|
20
|
+
acquired_at: nowIso(),
|
|
21
|
+
stale_ms: staleMs
|
|
22
|
+
});
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
const code = errorCode(err);
|
|
27
|
+
if (code !== 'EEXIST')
|
|
28
|
+
throw err;
|
|
29
|
+
await recoverStaleLock(lockPath, staleMs);
|
|
30
|
+
if (Date.now() - started > timeoutMs) {
|
|
31
|
+
throw new Error(`file_lock_timeout:${lockPath}`);
|
|
32
|
+
}
|
|
33
|
+
await sleep(jitterDelay());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return await fn();
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await removeLockDir(lockPath).catch(() => undefined);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function recoverStaleLock(lockPath, staleMs) {
|
|
44
|
+
try {
|
|
45
|
+
const stat = await fsp.stat(lockPath);
|
|
46
|
+
if (Date.now() - stat.mtimeMs > staleMs) {
|
|
47
|
+
await removeLockDir(lockPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch { }
|
|
51
|
+
}
|
|
52
|
+
async function removeLockDir(lockPath) {
|
|
53
|
+
await guardedRm(guardContextForRoute(process.cwd(), lockScopeContract(lockPath), 'remove SKS file lock directory'), lockPath, {
|
|
54
|
+
recursive: true,
|
|
55
|
+
force: true
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function lockScopeContract(lockPath) {
|
|
59
|
+
const resolved = path.resolve(lockPath);
|
|
60
|
+
return {
|
|
61
|
+
schema: REQUESTED_SCOPE_CONTRACT_SCHEMA,
|
|
62
|
+
route: 'internal:file-lock',
|
|
63
|
+
user_request: 'Remove only the current SKS file-lock directory.',
|
|
64
|
+
allowed_mutations: {
|
|
65
|
+
project_files: true,
|
|
66
|
+
global_codex_config: false,
|
|
67
|
+
codex_app_process: false,
|
|
68
|
+
codex_lb_auth: false,
|
|
69
|
+
package_install: false,
|
|
70
|
+
zellij_install: false,
|
|
71
|
+
network: false,
|
|
72
|
+
skill_snapshot_promotion: false
|
|
73
|
+
},
|
|
74
|
+
allowed_paths: [resolved, `${resolved}/**`],
|
|
75
|
+
forbidden_paths: ['~/.codex/config.toml', '/Applications/**'],
|
|
76
|
+
requires_explicit_confirmation: [...CONFIRMATION_REQUIRED]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function jitterDelay() {
|
|
80
|
+
return 15 + Math.floor(Math.random() * 45);
|
|
81
|
+
}
|
|
82
|
+
function sleep(ms) {
|
|
83
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
84
|
+
}
|
|
85
|
+
function errorCode(err) {
|
|
86
|
+
return err && typeof err === 'object' && 'code' in err ? String(err.code) : '';
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=file-lock.js.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
export function loopRoot(root, missionId) {
|
|
3
|
-
|
|
3
|
+
const missionsRoot = path.resolve(root, '.sneakoscope', 'missions');
|
|
4
|
+
return containedJoin(missionsRoot, safeArtifactId('mission', missionId), 'loops');
|
|
4
5
|
}
|
|
5
6
|
export function loopNodeRoot(root, missionId, loopId) {
|
|
6
|
-
return
|
|
7
|
+
return containedJoin(loopRoot(root, missionId), safeArtifactId('loop', loopId));
|
|
7
8
|
}
|
|
8
9
|
export function loopPlanPath(root, missionId) {
|
|
9
10
|
return path.join(loopRoot(root, missionId), 'loop-plan.json');
|
|
@@ -20,9 +21,45 @@ export function loopProofPath(root, missionId, loopId) {
|
|
|
20
21
|
export function loopBudgetPath(root, missionId, loopId) {
|
|
21
22
|
return path.join(loopNodeRoot(root, missionId, loopId), 'loop-budget.json');
|
|
22
23
|
}
|
|
24
|
+
export function loopCheckpointPath(root, missionId, loopId, iteration, phase) {
|
|
25
|
+
return path.join(loopNodeRoot(root, missionId, loopId), 'checkpoints', `${String(Math.max(1, Math.floor(iteration))).padStart(4, '0')}-${sanitizeArtifactPart(phase)}.json`);
|
|
26
|
+
}
|
|
27
|
+
export function loopLatestCheckpointPath(root, missionId, loopId) {
|
|
28
|
+
return path.join(loopNodeRoot(root, missionId, loopId), 'checkpoint-latest.json');
|
|
29
|
+
}
|
|
23
30
|
export function loopGraphProofPath(root, missionId) {
|
|
24
31
|
return path.join(loopRoot(root, missionId), 'loop-graph-proof.json');
|
|
25
32
|
}
|
|
33
|
+
export function loopIntegrationMergePath(root, missionId) {
|
|
34
|
+
return path.join(loopRoot(root, missionId), 'integration-merge.json');
|
|
35
|
+
}
|
|
36
|
+
export function loopGptFinalArbiterPath(root, missionId) {
|
|
37
|
+
return path.join(loopRoot(root, missionId), 'loop-gpt-final-arbiter.json');
|
|
38
|
+
}
|
|
39
|
+
export function loopFinalArbiterGateContractPath(root, missionId) {
|
|
40
|
+
return path.join(loopRoot(root, missionId), 'gpt-final-arbiter-gate-contract.json');
|
|
41
|
+
}
|
|
42
|
+
export function loopFixturePolicyPath(root, missionId) {
|
|
43
|
+
return path.join(loopRoot(root, missionId), 'fixture-policy.json');
|
|
44
|
+
}
|
|
45
|
+
export function loopMutationLedgerPath(root, missionId) {
|
|
46
|
+
return path.join(loopRoot(root, missionId), 'mutation-ledger.jsonl');
|
|
47
|
+
}
|
|
48
|
+
export function loopSideEffectReportPath(root, missionId) {
|
|
49
|
+
return path.join(loopRoot(root, missionId), 'loop-side-effect-report.json');
|
|
50
|
+
}
|
|
51
|
+
export function loopActiveWorkerHandlesPath(root, missionId) {
|
|
52
|
+
return path.join(loopRoot(root, missionId), 'active-worker-handles.jsonl');
|
|
53
|
+
}
|
|
54
|
+
export function loopInterruptResultPath(root, missionId) {
|
|
55
|
+
return path.join(loopRoot(root, missionId), 'interrupt-result.json');
|
|
56
|
+
}
|
|
57
|
+
export function loopConcurrencyBudgetPath(root, missionId) {
|
|
58
|
+
return path.join(loopRoot(root, missionId), 'concurrency-budget.json');
|
|
59
|
+
}
|
|
60
|
+
export function loopKillRequestPath(root, missionId) {
|
|
61
|
+
return path.join(loopRoot(root, missionId), 'kill-request.json');
|
|
62
|
+
}
|
|
26
63
|
export function loopGatePath(root, missionId, loopId, gateId) {
|
|
27
64
|
return path.join(loopNodeRoot(root, missionId, loopId), 'gates', `${sanitizeArtifactPart(gateId)}.json`);
|
|
28
65
|
}
|
|
@@ -38,4 +75,19 @@ export function loopOwnerLedgerPath(root, missionId) {
|
|
|
38
75
|
export function sanitizeArtifactPart(value) {
|
|
39
76
|
return String(value || 'artifact').replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 96) || 'artifact';
|
|
40
77
|
}
|
|
78
|
+
function safeArtifactId(kind, value) {
|
|
79
|
+
const text = String(value || '').trim();
|
|
80
|
+
const sanitized = sanitizeArtifactPart(text);
|
|
81
|
+
if (!text || sanitized !== text)
|
|
82
|
+
throw new Error(`invalid_loop_${kind}_id:${text || 'empty'}`);
|
|
83
|
+
return sanitized;
|
|
84
|
+
}
|
|
85
|
+
function containedJoin(base, ...parts) {
|
|
86
|
+
const resolvedBase = path.resolve(base);
|
|
87
|
+
const target = path.resolve(resolvedBase, ...parts);
|
|
88
|
+
if (target !== resolvedBase && !target.startsWith(`${resolvedBase}${path.sep}`)) {
|
|
89
|
+
throw new Error(`loop_artifact_path_escape:${target}`);
|
|
90
|
+
}
|
|
91
|
+
return target;
|
|
92
|
+
}
|
|
41
93
|
//# sourceMappingURL=loop-artifacts.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readJson, writeJsonAtomic } from '../fsx.js';
|
|
2
|
+
import { loopCheckpointPath, loopLatestCheckpointPath } from './loop-artifacts.js';
|
|
3
|
+
export async function writeLoopCheckpoint(input) {
|
|
4
|
+
const checkpoint = {
|
|
5
|
+
schema: 'sks.loop-checkpoint.v1',
|
|
6
|
+
mission_id: input.mission_id,
|
|
7
|
+
loop_id: input.loop_id,
|
|
8
|
+
iteration: input.iteration,
|
|
9
|
+
phase: input.phase,
|
|
10
|
+
state_path: input.state_path,
|
|
11
|
+
proof_path: input.proof_path,
|
|
12
|
+
resumable: input.resumable,
|
|
13
|
+
created_at: new Date().toISOString()
|
|
14
|
+
};
|
|
15
|
+
await writeJsonAtomic(loopCheckpointPath(input.root, input.mission_id, input.loop_id, input.iteration, input.phase), checkpoint);
|
|
16
|
+
await writeJsonAtomic(loopLatestCheckpointPath(input.root, input.mission_id, input.loop_id), checkpoint);
|
|
17
|
+
return checkpoint;
|
|
18
|
+
}
|
|
19
|
+
export async function readLatestLoopCheckpoint(root, missionId, loopId) {
|
|
20
|
+
return readJson(loopLatestCheckpointPath(root, missionId, loopId), null);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=loop-checkpoint.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { loopConcurrencyBudgetPath } from './loop-artifacts.js';
|
|
4
|
+
export function computeLoopConcurrencyBudget(input) {
|
|
5
|
+
const env = input.env || process.env;
|
|
6
|
+
const cores = Math.max(1, os.cpus().length || 1);
|
|
7
|
+
const requestedLoops = input.parallelism === 'safe' ? 2 : input.parallelism === 'extreme' ? Math.min(16, cores) : Math.min(8, cores);
|
|
8
|
+
const envLoops = positiveInt(env.SKS_LOOP_MAX_ACTIVE_LOOPS);
|
|
9
|
+
const envWorkers = positiveInt(env.SKS_LOOP_MAX_ACTIVE_WORKERS);
|
|
10
|
+
const envModelCalls = positiveInt(env.SKS_LOOP_MAX_MODEL_CALLS);
|
|
11
|
+
const maxActiveLoops = envLoops || requestedLoops;
|
|
12
|
+
const maxActiveWorkers = envWorkers || (input.parallelism === 'safe' ? Math.min(8, cores) : input.parallelism === 'extreme' ? Math.min(32, Math.max(4, cores * 2)) : Math.min(16, Math.max(4, cores)));
|
|
13
|
+
const maxModelCalls = envModelCalls || Math.max(1, Math.min(maxActiveWorkers, input.plan.global_budget.max_model_calls || maxActiveWorkers));
|
|
14
|
+
let remainingWorkers = maxActiveWorkers;
|
|
15
|
+
let remainingModelCalls = maxModelCalls;
|
|
16
|
+
const perLoop = input.plan.graph.nodes.map((node) => {
|
|
17
|
+
const requested = Math.max(1, node.maker.worker_count + node.checker.worker_count);
|
|
18
|
+
const fairShare = Math.max(1, Math.floor(maxActiveWorkers / Math.max(1, input.plan.graph.nodes.length)));
|
|
19
|
+
const allocation = Math.min(requested, Math.max(1, fairShare), Math.max(1, remainingWorkers));
|
|
20
|
+
const maker = Math.min(node.maker.worker_count, Math.max(1, Math.ceil(allocation / 2)));
|
|
21
|
+
const checker = Math.min(node.checker.worker_count, Math.max(0, allocation - maker));
|
|
22
|
+
const modelCalls = Math.min(Math.max(1, node.budget.max_model_calls), Math.max(1, remainingModelCalls));
|
|
23
|
+
remainingWorkers = Math.max(0, remainingWorkers - maker - checker);
|
|
24
|
+
remainingModelCalls = Math.max(0, remainingModelCalls - modelCalls);
|
|
25
|
+
return {
|
|
26
|
+
loop_id: node.loop_id,
|
|
27
|
+
maker_workers: maker,
|
|
28
|
+
checker_workers: checker,
|
|
29
|
+
model_call_budget: modelCalls
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
schema: 'sks.loop-concurrency-budget.v1',
|
|
34
|
+
mission_id: input.plan.mission_id,
|
|
35
|
+
max_active_loops: maxActiveLoops,
|
|
36
|
+
max_active_workers: maxActiveWorkers,
|
|
37
|
+
max_model_calls: maxModelCalls,
|
|
38
|
+
per_loop_worker_budget: perLoop,
|
|
39
|
+
headroom_workers: Math.max(0, maxActiveWorkers - perLoop.reduce((sum, row) => sum + row.maker_workers + row.checker_workers, 0)),
|
|
40
|
+
blockers: []
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function writeLoopConcurrencyBudget(root, budget) {
|
|
44
|
+
await writeJsonAtomic(loopConcurrencyBudgetPath(root, budget.mission_id), { ...budget, generated_at: new Date().toISOString() });
|
|
45
|
+
}
|
|
46
|
+
export function loopWorkerBudgetFor(budget, loopId, phase, requested) {
|
|
47
|
+
const row = budget.per_loop_worker_budget.find((item) => item.loop_id === loopId);
|
|
48
|
+
const allowed = phase === 'maker' ? row?.maker_workers : row?.checker_workers;
|
|
49
|
+
return Math.max(1, Math.min(Math.max(1, requested), Math.max(1, allowed || requested)));
|
|
50
|
+
}
|
|
51
|
+
function positiveInt(value) {
|
|
52
|
+
const number = Number(value);
|
|
53
|
+
return Number.isFinite(number) && number >= 1 ? Math.floor(number) : null;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=loop-concurrency-budget.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { writeJsonAtomic } from '../fsx.js';
|
|
2
|
+
import { loopFinalArbiterGateContractPath, loopGptFinalArbiterPath } from './loop-artifacts.js';
|
|
3
|
+
export async function writeLoopFinalArbiterGateContract(root, missionId) {
|
|
4
|
+
const contract = buildLoopFinalArbiterGateContract(root, missionId);
|
|
5
|
+
await writeJsonAtomic(loopFinalArbiterGateContractPath(root, missionId), { ...contract, generated_at: new Date().toISOString() });
|
|
6
|
+
return contract;
|
|
7
|
+
}
|
|
8
|
+
export function buildLoopFinalArbiterGateContract(root, missionId) {
|
|
9
|
+
return {
|
|
10
|
+
schema: 'sks.loop-final-arbiter-gate-contract.v1',
|
|
11
|
+
mission_id: missionId,
|
|
12
|
+
gate_id: 'gpt:final-arbiter',
|
|
13
|
+
handled_by: 'loop-finalizer',
|
|
14
|
+
gate_runner_status: 'deferred',
|
|
15
|
+
finalizer_artifact_path: relativeMissionArtifact(root, loopGptFinalArbiterPath(root, missionId)),
|
|
16
|
+
required_when: ['source_mutation_exists', 'selected_gates_include_gpt_final_arbiter'],
|
|
17
|
+
production_fixture_allowed: false
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function loopFinalArbiterGateContractRelativePath(missionId) {
|
|
21
|
+
return `.sneakoscope/missions/${missionId}/loops/gpt-final-arbiter-gate-contract.json`;
|
|
22
|
+
}
|
|
23
|
+
function relativeMissionArtifact(root, absolute) {
|
|
24
|
+
const normalizedRoot = root.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
25
|
+
const normalized = absolute.replace(/\\/g, '/');
|
|
26
|
+
return normalized.startsWith(`${normalizedRoot}/`) ? normalized.slice(normalizedRoot.length + 1) : normalized;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=loop-final-arbiter-contract.js.map
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { readJson, writeJsonAtomic } from '../fsx.js';
|
|
2
2
|
import { loopGraphProofPath, loopProofPath } from './loop-artifacts.js';
|
|
3
|
+
import { writeLoopFinalArbiterGateContract } from './loop-final-arbiter-contract.js';
|
|
4
|
+
import { runLoopGptFinalArbiter } from './loop-gpt-final-arbiter.js';
|
|
5
|
+
import { mergeLoopWorktrees } from './loop-integration-merge.js';
|
|
3
6
|
import { graphProofFromLoopProofs } from './loop-scheduler.js';
|
|
7
|
+
import { buildLoopSideEffectReport } from './loop-side-effect-scanner.js';
|
|
4
8
|
export async function finalizeLoopGraph(input) {
|
|
5
9
|
const proofs = input.proofs || await Promise.all(input.plan.graph.nodes.map((node) => readJson(loopProofPath(input.root, input.plan.mission_id, node.loop_id), null)));
|
|
6
10
|
const realProofs = proofs.filter((proof) => Boolean(proof));
|
|
@@ -11,16 +15,60 @@ export async function finalizeLoopGraph(input) {
|
|
|
11
15
|
maxActiveWorkers: input.maxActiveWorkers || Math.max(1, realProofs.reduce((sum, proof) => sum + proof.maker_result.worker_count + proof.checker_result.worker_count, 0)),
|
|
12
16
|
wallMs: input.wallMs || 1
|
|
13
17
|
});
|
|
18
|
+
const integrationMerge = await mergeLoopWorktrees({
|
|
19
|
+
root: input.root,
|
|
20
|
+
plan: input.plan,
|
|
21
|
+
proofs: realProofs
|
|
22
|
+
});
|
|
14
23
|
const anyHandoff = realProofs.some((proof) => proof.handoff.required);
|
|
15
|
-
const anySourceMutation = realProofs.some((proof) => proof.changed_files.
|
|
24
|
+
const anySourceMutation = realProofs.some((proof) => proof.changed_files.some((file) => !file.startsWith('.sneakoscope/')));
|
|
25
|
+
const selectedGptFinal = graph.gates.selected.includes('gpt:final-arbiter');
|
|
26
|
+
const contract = anySourceMutation || selectedGptFinal
|
|
27
|
+
? await writeLoopFinalArbiterGateContract(input.root, input.plan.mission_id)
|
|
28
|
+
: null;
|
|
29
|
+
const sideEffectReport = await buildLoopSideEffectReport({
|
|
30
|
+
root: input.root,
|
|
31
|
+
missionId: input.plan.mission_id,
|
|
32
|
+
proofs: realProofs,
|
|
33
|
+
integrationMerge
|
|
34
|
+
});
|
|
35
|
+
const arbiter = anySourceMutation
|
|
36
|
+
? await runLoopGptFinalArbiter({ root: input.root, plan: input.plan, proofs: realProofs, integrationMerge, sideEffectReport })
|
|
37
|
+
: null;
|
|
38
|
+
const blockers = [
|
|
39
|
+
...graph.blockers,
|
|
40
|
+
...(anyHandoff ? ['loop_handoff_required'] : []),
|
|
41
|
+
...(integrationMerge.ok ? [] : integrationMerge.blockers),
|
|
42
|
+
...(sideEffectReport.ok ? [] : sideEffectReport.blockers),
|
|
43
|
+
...(anySourceMutation && !arbiter ? ['gpt_final_arbiter_missing'] : []),
|
|
44
|
+
...(selectedGptFinal && anySourceMutation && (!contract || !arbiter) ? ['gpt_final_arbiter_contract_unfulfilled'] : []),
|
|
45
|
+
...(arbiter && !arbiter.ok ? ['gpt_final_arbiter_not_approved', ...arbiter.blockers] : [])
|
|
46
|
+
];
|
|
16
47
|
const finalGraph = {
|
|
17
48
|
...graph,
|
|
18
|
-
ok: graph.ok &&
|
|
19
|
-
blockers: [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
49
|
+
ok: graph.ok && blockers.length === 0,
|
|
50
|
+
blockers: [...new Set(blockers)],
|
|
51
|
+
integration_merge: {
|
|
52
|
+
ok: integrationMerge.ok,
|
|
53
|
+
artifact_path: `.sneakoscope/missions/${input.plan.mission_id}/loops/integration-merge.json`,
|
|
54
|
+
applied_loops: integrationMerge.applied_loops,
|
|
55
|
+
conflict_loops: integrationMerge.conflict_loops,
|
|
56
|
+
...(integrationMerge.strategy_summary ? { strategy_summary: integrationMerge.strategy_summary } : {})
|
|
57
|
+
},
|
|
58
|
+
side_effect_report: {
|
|
59
|
+
ok: sideEffectReport.ok,
|
|
60
|
+
artifact_path: `.sneakoscope/missions/${input.plan.mission_id}/loops/loop-side-effect-report.json`,
|
|
61
|
+
blockers: sideEffectReport.blockers
|
|
62
|
+
},
|
|
63
|
+
...(arbiter ? {
|
|
64
|
+
gpt_final_arbiter: {
|
|
65
|
+
ok: arbiter.ok,
|
|
66
|
+
artifact_path: arbiter.artifact_path,
|
|
67
|
+
verdict: arbiter.verdict,
|
|
68
|
+
gate_contract_path: `.sneakoscope/missions/${input.plan.mission_id}/loops/gpt-final-arbiter-gate-contract.json`,
|
|
69
|
+
handled_by: 'loop-finalizer'
|
|
70
|
+
}
|
|
71
|
+
} : {})
|
|
24
72
|
};
|
|
25
73
|
await writeJsonAtomic(loopGraphProofPath(input.root, input.plan.mission_id), finalGraph);
|
|
26
74
|
return finalGraph;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { loopFixturePolicyPath } from './loop-artifacts.js';
|
|
5
|
+
export function decideLoopFixturePolicy(input) {
|
|
6
|
+
const env = input.env || process.env;
|
|
7
|
+
const argv = input.argv || process.argv;
|
|
8
|
+
const scriptPath = argv.find((arg) => /(?:^|[\\/])(?:dist|src)[\\/]scripts[\\/]/.test(arg)) || argv[1] || '';
|
|
9
|
+
const scriptName = path.basename(scriptPath);
|
|
10
|
+
const scriptIsCheck = /(?:^|[\\/])(?:dist|src)[\\/]scripts[\\/]/.test(scriptPath)
|
|
11
|
+
&& /(check|blackbox)/.test(scriptName);
|
|
12
|
+
const missionIsCheck = /^M-check-/.test(input.missionId);
|
|
13
|
+
const tempRoot = isUnderTempRoot(input.root);
|
|
14
|
+
const explicitTestEnv = env.NODE_ENV === 'test'
|
|
15
|
+
|| env.SKS_TEST_RUNTIME_FIXTURE_ALLOWED === '1'
|
|
16
|
+
|| env.VITEST_WORKER_ID !== undefined
|
|
17
|
+
|| env.JEST_WORKER_ID !== undefined
|
|
18
|
+
|| env.NODE_V8_COVERAGE !== undefined;
|
|
19
|
+
const commandText = argv.join(' ');
|
|
20
|
+
const productionCommand = /\bsks\s+(?:loop\s+run|goal|naruto)\b/.test(commandText);
|
|
21
|
+
const requestedByEnv = env.SKS_LOOP_GATE_FIXTURE === '1'
|
|
22
|
+
|| env.SKS_LOOP_RUNTIME_FIXTURE === '1'
|
|
23
|
+
|| env.SKS_LOOP_GPT_FINAL_FIXTURE === '1';
|
|
24
|
+
const allowReasons = [
|
|
25
|
+
scriptIsCheck ? 'release_check_script' : null,
|
|
26
|
+
missionIsCheck ? 'check_mission_id' : null,
|
|
27
|
+
tempRoot ? 'temp_project_root' : null,
|
|
28
|
+
explicitTestEnv ? 'test_environment' : null
|
|
29
|
+
].filter((value) => Boolean(value));
|
|
30
|
+
const allowed = input.requested && allowReasons.length > 0 && !productionCommand;
|
|
31
|
+
const productionLike = !scriptIsCheck && !missionIsCheck && !tempRoot && !explicitTestEnv;
|
|
32
|
+
const blockers = input.requested && !allowed
|
|
33
|
+
? [
|
|
34
|
+
'loop_fixture_forbidden_in_production',
|
|
35
|
+
`loop_${input.mode.replace(/-/g, '_')}_fixture_forbidden_in_production`,
|
|
36
|
+
...(productionCommand ? ['loop_fixture_forbidden_for_production_command'] : []),
|
|
37
|
+
...(requestedByEnv && productionLike ? ['loop_fixture_env_without_allowed_reason'] : [])
|
|
38
|
+
]
|
|
39
|
+
: [];
|
|
40
|
+
return {
|
|
41
|
+
schema: 'sks.loop-fixture-policy-decision.v1',
|
|
42
|
+
allowed,
|
|
43
|
+
mode: input.mode,
|
|
44
|
+
requested: input.requested,
|
|
45
|
+
production_like: productionLike || productionCommand,
|
|
46
|
+
reason: allowed ? allowReasons.join('+') : input.requested ? 'fixture_requires_check_or_test_context' : 'fixture_not_requested',
|
|
47
|
+
blockers: [...new Set(blockers)]
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function writeLoopFixturePolicyDecision(root, missionId, decision) {
|
|
51
|
+
await writeJsonAtomic(loopFixturePolicyPath(root, missionId), { ...decision, generated_at: new Date().toISOString() });
|
|
52
|
+
}
|
|
53
|
+
export function isUnderTempRoot(root) {
|
|
54
|
+
const normalizedRoot = path.resolve(root);
|
|
55
|
+
const tempRoot = path.resolve(os.tmpdir());
|
|
56
|
+
return normalizedRoot === tempRoot || normalizedRoot.startsWith(`${tempRoot}${path.sep}`);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=loop-fixture-policy.js.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readJson } from '../fsx.js';
|
|
3
|
+
export async function resolveLoopGate(root, gateId) {
|
|
4
|
+
const builtin = builtinLoopGate(gateId);
|
|
5
|
+
if (builtin)
|
|
6
|
+
return builtin;
|
|
7
|
+
const releaseGate = await resolveReleaseGate(root, gateId);
|
|
8
|
+
if (releaseGate)
|
|
9
|
+
return releaseGate;
|
|
10
|
+
const packageGate = await resolvePackageScriptGate(root, gateId);
|
|
11
|
+
if (packageGate)
|
|
12
|
+
return packageGate;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
export async function listLoopGateDefinitions(root) {
|
|
16
|
+
const packageJson = await readJson(path.join(root, 'package.json'), {});
|
|
17
|
+
const release = await readJson(path.join(root, 'release-gates.v2.json'), {});
|
|
18
|
+
const packageScripts = packageJson && typeof packageJson === 'object' && packageJson.scripts && typeof packageJson.scripts === 'object'
|
|
19
|
+
? Object.keys(packageJson.scripts)
|
|
20
|
+
: [];
|
|
21
|
+
const releaseGates = Array.isArray(release.gates) ? release.gates : [];
|
|
22
|
+
const definitions = [
|
|
23
|
+
...['gpt:final-arbiter', 'human:handoff-required', 'loop:checker-fresh-session', 'loop:state-valid', 'loop:budget-valid'].map((id) => builtinLoopGate(id)).filter((row) => Boolean(row)),
|
|
24
|
+
...releaseGates.map((gate) => normalizeReleaseGate(gate)).filter((row) => Boolean(row)),
|
|
25
|
+
...packageScripts.map((id) => ({
|
|
26
|
+
id,
|
|
27
|
+
command: `npm run ${shellQuote(id)} --silent`,
|
|
28
|
+
source: 'package-json',
|
|
29
|
+
side_effect: 'hermetic',
|
|
30
|
+
timeout_ms: 300000,
|
|
31
|
+
cache_allowed: true
|
|
32
|
+
}))
|
|
33
|
+
];
|
|
34
|
+
const byId = new Map();
|
|
35
|
+
for (const definition of definitions)
|
|
36
|
+
if (!byId.has(definition.id))
|
|
37
|
+
byId.set(definition.id, definition);
|
|
38
|
+
return [...byId.values()];
|
|
39
|
+
}
|
|
40
|
+
function builtinLoopGate(gateId) {
|
|
41
|
+
if (gateId === 'gpt:final-arbiter') {
|
|
42
|
+
return { id: gateId, command: 'builtin:gpt-final-arbiter', source: 'builtin-pseudo', side_effect: 'read-only', timeout_ms: 300000, cache_allowed: false };
|
|
43
|
+
}
|
|
44
|
+
if (gateId === 'human:handoff-required') {
|
|
45
|
+
return { id: gateId, command: 'builtin:human-handoff-required', source: 'builtin-pseudo', side_effect: 'human', timeout_ms: 0, cache_allowed: false };
|
|
46
|
+
}
|
|
47
|
+
if (gateId === 'loop:checker-fresh-session') {
|
|
48
|
+
return { id: gateId, command: 'builtin:loop-checker-fresh-session', source: 'builtin-pseudo', side_effect: 'read-only', timeout_ms: 30000, cache_allowed: false };
|
|
49
|
+
}
|
|
50
|
+
if (gateId === 'loop:state-valid') {
|
|
51
|
+
return { id: gateId, command: 'builtin:loop-state-valid', source: 'builtin-pseudo', side_effect: 'read-only', timeout_ms: 30000, cache_allowed: true };
|
|
52
|
+
}
|
|
53
|
+
if (gateId === 'loop:budget-valid') {
|
|
54
|
+
return { id: gateId, command: 'builtin:loop-budget-valid', source: 'builtin-pseudo', side_effect: 'read-only', timeout_ms: 30000, cache_allowed: true };
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
async function resolveReleaseGate(root, gateId) {
|
|
59
|
+
const release = await readJson(path.join(root, 'release-gates.v2.json'), {});
|
|
60
|
+
const gate = Array.isArray(release.gates) ? release.gates.find((row) => row.id === gateId) : null;
|
|
61
|
+
return gate ? normalizeReleaseGate(gate) : null;
|
|
62
|
+
}
|
|
63
|
+
function normalizeReleaseGate(gate) {
|
|
64
|
+
const id = String(gate.id || '');
|
|
65
|
+
const command = String(gate.command || '');
|
|
66
|
+
if (!id || !command)
|
|
67
|
+
return null;
|
|
68
|
+
return {
|
|
69
|
+
id,
|
|
70
|
+
command,
|
|
71
|
+
source: 'release-gates-v2',
|
|
72
|
+
side_effect: normalizeSideEffect(gate.side_effect),
|
|
73
|
+
timeout_ms: Number.isFinite(Number(gate.timeout_ms)) ? Math.max(1, Number(gate.timeout_ms)) : 300000,
|
|
74
|
+
cache_allowed: gate.cache?.enabled !== false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function resolvePackageScriptGate(root, gateId) {
|
|
78
|
+
const packageJson = await readJson(path.join(root, 'package.json'), {});
|
|
79
|
+
if (!packageJson?.scripts || typeof packageJson.scripts !== 'object' || !(gateId in packageJson.scripts))
|
|
80
|
+
return null;
|
|
81
|
+
return {
|
|
82
|
+
id: gateId,
|
|
83
|
+
command: `npm run ${shellQuote(gateId)} --silent`,
|
|
84
|
+
source: 'package-json',
|
|
85
|
+
side_effect: 'hermetic',
|
|
86
|
+
timeout_ms: 300000,
|
|
87
|
+
cache_allowed: true
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function normalizeSideEffect(value) {
|
|
91
|
+
return value === 'read-only' || value === 'mutation' || value === 'human' ? value : 'hermetic';
|
|
92
|
+
}
|
|
93
|
+
function shellQuote(value) {
|
|
94
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=loop-gate-registry.js.map
|