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
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { exists, runProcess } from '../fsx.js';
|
|
3
|
+
import { gitBlocker, runGitCommand } from '../git/git-worktree-runner.js';
|
|
4
|
+
export async function mergeSingleLoopWorktree(input) {
|
|
5
|
+
const attempts = [];
|
|
6
|
+
const changedFiles = [...new Set(input.proof.changed_files)];
|
|
7
|
+
const diff = await runGitCommand(input.worktreePath, ['diff', '--binary', '--full-index', 'HEAD'], { timeoutMs: 60000 }).catch(() => null);
|
|
8
|
+
if (!diff?.ok) {
|
|
9
|
+
return result(input.proof.loop_id, false, null, attempts, changedFiles, [`loop_merge_diff_failed:${input.proof.loop_id}`]);
|
|
10
|
+
}
|
|
11
|
+
if (!diff.stdout.trim())
|
|
12
|
+
return result(input.proof.loop_id, true, 'already_applied', attempts, changedFiles, []);
|
|
13
|
+
const applyCheck = await gitAttempt('apply-check', input.root, ['apply', '--check', '--whitespace=nowarn', '-'], diff.stdout);
|
|
14
|
+
attempts.push(applyCheck);
|
|
15
|
+
if (applyCheck.ok) {
|
|
16
|
+
const apply = await gitAttempt('apply', input.root, ['apply', '--whitespace=nowarn', '-'], diff.stdout);
|
|
17
|
+
attempts.push(apply);
|
|
18
|
+
if (apply.ok)
|
|
19
|
+
return result(input.proof.loop_id, true, 'apply', attempts, changedFiles, []);
|
|
20
|
+
await rollbackApply(input.root, diff.stdout);
|
|
21
|
+
}
|
|
22
|
+
const alreadyApplied = await gitAttempt('apply-check', input.root, ['apply', '--reverse', '--check', '--whitespace=nowarn', '-'], diff.stdout);
|
|
23
|
+
if (alreadyApplied.ok) {
|
|
24
|
+
attempts.push({ ...alreadyApplied, strategy: 'apply-check', blockers: [] });
|
|
25
|
+
return result(input.proof.loop_id, true, 'already_applied', attempts, changedFiles, []);
|
|
26
|
+
}
|
|
27
|
+
const apply3Check = await gitAttempt('apply-3way', input.root, ['apply', '--3way', '--check', '--whitespace=nowarn', '-'], diff.stdout);
|
|
28
|
+
attempts.push(apply3Check);
|
|
29
|
+
if (apply3Check.ok) {
|
|
30
|
+
const apply3 = await gitAttempt('apply-3way', input.root, ['apply', '--3way', '--whitespace=nowarn', '-'], diff.stdout);
|
|
31
|
+
attempts.push(apply3);
|
|
32
|
+
if (apply3.ok)
|
|
33
|
+
return result(input.proof.loop_id, true, 'apply-3way', attempts, changedFiles, []);
|
|
34
|
+
await abortMergeLikeState(input.root);
|
|
35
|
+
}
|
|
36
|
+
const head = await runGitCommand(input.worktreePath, ['rev-parse', '--verify', 'HEAD'], { timeoutMs: 10000 }).catch(() => null);
|
|
37
|
+
const commit = head?.ok ? head.stdout.trim() : '';
|
|
38
|
+
if (commit) {
|
|
39
|
+
const cherry = await gitAttempt('cherry-pick', input.root, ['cherry-pick', '--no-commit', commit], undefined);
|
|
40
|
+
attempts.push(cherry);
|
|
41
|
+
if (cherry.ok)
|
|
42
|
+
return result(input.proof.loop_id, true, 'cherry-pick', attempts, changedFiles, []);
|
|
43
|
+
await runGitCommand(input.root, ['cherry-pick', '--abort'], { timeoutMs: 30000 }).catch(() => null);
|
|
44
|
+
await abortMergeLikeState(input.root);
|
|
45
|
+
}
|
|
46
|
+
if (input.allowBranchMerge && input.proof.worktree.branch) {
|
|
47
|
+
const branch = input.proof.worktree.branch;
|
|
48
|
+
const merge = await gitAttempt('merge-no-commit', input.root, ['merge', '--no-ff', '--no-commit', branch], undefined);
|
|
49
|
+
attempts.push(merge);
|
|
50
|
+
if (merge.ok)
|
|
51
|
+
return result(input.proof.loop_id, true, 'merge-no-commit', attempts, changedFiles, []);
|
|
52
|
+
await runGitCommand(input.root, ['merge', '--abort'], { timeoutMs: 30000 }).catch(() => null);
|
|
53
|
+
await abortMergeLikeState(input.root);
|
|
54
|
+
}
|
|
55
|
+
const handoff = {
|
|
56
|
+
strategy: 'handoff',
|
|
57
|
+
ok: false,
|
|
58
|
+
exit_code: null,
|
|
59
|
+
stdout_tail: '',
|
|
60
|
+
stderr_tail: 'all merge strategies failed',
|
|
61
|
+
duration_ms: 1,
|
|
62
|
+
blockers: [`loop_merge_conflict_handoff:${input.proof.loop_id}`]
|
|
63
|
+
};
|
|
64
|
+
attempts.push(handoff);
|
|
65
|
+
return result(input.proof.loop_id, false, 'handoff', attempts, changedFiles, handoff.blockers);
|
|
66
|
+
}
|
|
67
|
+
async function gitAttempt(strategy, cwd, args, input) {
|
|
68
|
+
const started = Date.now();
|
|
69
|
+
const res = await runGitCommand(cwd, args, { timeoutMs: 60000, ...(input === undefined ? {} : { input }) }).catch((err) => null);
|
|
70
|
+
if (!res) {
|
|
71
|
+
return { strategy, ok: false, exit_code: null, stdout_tail: '', stderr_tail: '', duration_ms: Math.max(1, Date.now() - started), blockers: [`loop_merge_${strategy}_exception`] };
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
strategy,
|
|
75
|
+
ok: res.ok,
|
|
76
|
+
exit_code: res.code,
|
|
77
|
+
stdout_tail: res.stdout_tail,
|
|
78
|
+
stderr_tail: res.stderr_tail,
|
|
79
|
+
duration_ms: Math.max(1, Date.now() - started),
|
|
80
|
+
blockers: res.ok ? [] : [gitBlocker(`loop_merge_${strategy}_failed`, res)]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function rollbackApply(root, diff) {
|
|
84
|
+
await runGitCommand(root, ['apply', '--reverse', '--whitespace=nowarn', '-'], { input: diff, timeoutMs: 60000 }).catch(() => null);
|
|
85
|
+
await abortMergeLikeState(root);
|
|
86
|
+
}
|
|
87
|
+
async function abortMergeLikeState(root) {
|
|
88
|
+
if (await exists(path.join(root, '.git', 'MERGE_HEAD')))
|
|
89
|
+
await runGitCommand(root, ['merge', '--abort'], { timeoutMs: 30000 }).catch(() => null);
|
|
90
|
+
if (await exists(path.join(root, '.git', 'CHERRY_PICK_HEAD')))
|
|
91
|
+
await runGitCommand(root, ['cherry-pick', '--abort'], { timeoutMs: 30000 }).catch(() => null);
|
|
92
|
+
await runProcess('git', ['reset', '--merge'], { cwd: root, timeoutMs: 30000, maxOutputBytes: 64 * 1024 }).catch(() => null);
|
|
93
|
+
}
|
|
94
|
+
function result(loopId, ok, selectedStrategy, attempts, changedFiles, blockers) {
|
|
95
|
+
return {
|
|
96
|
+
schema: 'sks.loop-merge-strategy-result.v1',
|
|
97
|
+
loop_id: loopId,
|
|
98
|
+
ok,
|
|
99
|
+
selected_strategy: selectedStrategy,
|
|
100
|
+
attempts,
|
|
101
|
+
changed_files: changedFiles,
|
|
102
|
+
blockers: [...new Set(blockers)]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=loop-merge-strategy.js.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { appendJsonl, readText } from '../fsx.js';
|
|
2
|
+
import { loopMutationLedgerPath } from './loop-artifacts.js';
|
|
3
|
+
import { enforceLoopOwnerScope } from './loop-worktree-runtime.js';
|
|
4
|
+
export async function appendLoopMutationEvent(root, missionId, event) {
|
|
5
|
+
await appendJsonl(loopMutationLedgerPath(root, missionId), {
|
|
6
|
+
schema: 'sks.loop-mutation-ledger-event.v1',
|
|
7
|
+
ts: event.ts || new Date().toISOString(),
|
|
8
|
+
mission_id: missionId,
|
|
9
|
+
loop_id: event.loop_id,
|
|
10
|
+
event_type: event.event_type,
|
|
11
|
+
file_path: event.file_path,
|
|
12
|
+
source: event.source,
|
|
13
|
+
allowed_by_owner_scope: event.allowed_by_owner_scope,
|
|
14
|
+
details: event.details
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export async function readLoopMutationLedger(root, missionId) {
|
|
18
|
+
const text = await readText(loopMutationLedgerPath(root, missionId), '');
|
|
19
|
+
return String(text).split(/\r?\n/)
|
|
20
|
+
.map((line) => line.trim())
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.map((line) => {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(line);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
.filter((row) => Boolean(row));
|
|
31
|
+
}
|
|
32
|
+
export async function mutationLedgerFromLoopProofs(input) {
|
|
33
|
+
const events = [];
|
|
34
|
+
for (const proof of input.proofs) {
|
|
35
|
+
const workerChanged = [...new Set([...(proof.maker_result.changed_files || []), ...proof.changed_files])];
|
|
36
|
+
for (const file of workerChanged) {
|
|
37
|
+
const violations = enforceLoopOwnerScope([file], proof.owner_scope);
|
|
38
|
+
const eventType = violations.length ? 'owner_scope_violation' : 'file_changed';
|
|
39
|
+
const event = {
|
|
40
|
+
schema: 'sks.loop-mutation-ledger-event.v1',
|
|
41
|
+
ts: new Date().toISOString(),
|
|
42
|
+
mission_id: input.missionId,
|
|
43
|
+
loop_id: proof.loop_id,
|
|
44
|
+
event_type: eventType,
|
|
45
|
+
file_path: file,
|
|
46
|
+
source: 'git-diff',
|
|
47
|
+
allowed_by_owner_scope: violations.length === 0,
|
|
48
|
+
details: { status: proof.status, blockers: violations }
|
|
49
|
+
};
|
|
50
|
+
events.push(event);
|
|
51
|
+
await appendJsonl(loopMutationLedgerPath(input.root, input.missionId), event);
|
|
52
|
+
}
|
|
53
|
+
if (proof.gate_result.blockers?.some((blocker) => blocker.includes('side_effect') || blocker.includes('mutation'))) {
|
|
54
|
+
const event = {
|
|
55
|
+
schema: 'sks.loop-mutation-ledger-event.v1',
|
|
56
|
+
ts: new Date().toISOString(),
|
|
57
|
+
mission_id: input.missionId,
|
|
58
|
+
loop_id: proof.loop_id,
|
|
59
|
+
event_type: 'gate_side_effect',
|
|
60
|
+
file_path: null,
|
|
61
|
+
source: 'gate-result',
|
|
62
|
+
allowed_by_owner_scope: false,
|
|
63
|
+
details: { blockers: proof.gate_result.blockers || [] }
|
|
64
|
+
};
|
|
65
|
+
events.push(event);
|
|
66
|
+
await appendJsonl(loopMutationLedgerPath(input.root, input.missionId), event);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (input.integrationMerge) {
|
|
70
|
+
for (const loopId of input.integrationMerge.applied_loops) {
|
|
71
|
+
const event = {
|
|
72
|
+
schema: 'sks.loop-mutation-ledger-event.v1',
|
|
73
|
+
ts: new Date().toISOString(),
|
|
74
|
+
mission_id: input.missionId,
|
|
75
|
+
loop_id: loopId,
|
|
76
|
+
event_type: 'merge_applied',
|
|
77
|
+
file_path: null,
|
|
78
|
+
source: 'integration-merge',
|
|
79
|
+
allowed_by_owner_scope: true,
|
|
80
|
+
details: { changed_files: input.integrationMerge.changed_files }
|
|
81
|
+
};
|
|
82
|
+
events.push(event);
|
|
83
|
+
await appendJsonl(loopMutationLedgerPath(input.root, input.missionId), event);
|
|
84
|
+
}
|
|
85
|
+
for (const loopId of input.integrationMerge.conflict_loops) {
|
|
86
|
+
const event = {
|
|
87
|
+
schema: 'sks.loop-mutation-ledger-event.v1',
|
|
88
|
+
ts: new Date().toISOString(),
|
|
89
|
+
mission_id: input.missionId,
|
|
90
|
+
loop_id: loopId,
|
|
91
|
+
event_type: 'merge_conflict',
|
|
92
|
+
file_path: null,
|
|
93
|
+
source: 'integration-merge',
|
|
94
|
+
allowed_by_owner_scope: false,
|
|
95
|
+
details: { blockers: input.integrationMerge.blockers }
|
|
96
|
+
};
|
|
97
|
+
events.push(event);
|
|
98
|
+
await appendJsonl(loopMutationLedgerPath(input.root, input.missionId), event);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return events;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=loop-mutation-ledger.js.map
|
|
@@ -6,6 +6,7 @@ import { inferLoopOwnerScope } from './loop-owner-inference.js';
|
|
|
6
6
|
import { classifyLoopRisk } from './loop-risk-classifier.js';
|
|
7
7
|
import { defaultLoopBudget, validateLoopPlan } from './loop-schema.js';
|
|
8
8
|
export async function planLoopsFromRequest(input) {
|
|
9
|
+
const parallelism = input.parallelism || 'balanced';
|
|
9
10
|
const maxLoops = Math.max(1, Math.min(32, input.maxLoops || 8));
|
|
10
11
|
const domains = decomposeRequestIntoLoopDomains(input.request).slice(0, maxLoops);
|
|
11
12
|
const actionNodes = domains.map((domain) => {
|
|
@@ -21,7 +22,8 @@ export async function planLoopsFromRequest(input) {
|
|
|
21
22
|
dependencies: [],
|
|
22
23
|
route: domain.id === 'docs' ? '$Loop' : '$Naruto',
|
|
23
24
|
level: domain.id === 'docs' ? 'L1-assisted' : 'L2-action',
|
|
24
|
-
risk
|
|
25
|
+
risk,
|
|
26
|
+
parallelism
|
|
25
27
|
});
|
|
26
28
|
return { ...nodeBase, gates: selectLoopGates({ node: nodeBase, changedFiles: [...ownerScope.files, ...ownerScope.directories], risk }) };
|
|
27
29
|
});
|
|
@@ -36,7 +38,8 @@ export async function planLoopsFromRequest(input) {
|
|
|
36
38
|
dependencies: actionNodes.map((node) => node.loop_id),
|
|
37
39
|
route: '$Integration',
|
|
38
40
|
level: 'L1-assisted',
|
|
39
|
-
risk: integrationRisk
|
|
41
|
+
risk: integrationRisk,
|
|
42
|
+
parallelism
|
|
40
43
|
});
|
|
41
44
|
const integrationNode = {
|
|
42
45
|
...integrationBase,
|
|
@@ -86,8 +89,10 @@ export async function planLoopsFromRequest(input) {
|
|
|
86
89
|
return plan;
|
|
87
90
|
}
|
|
88
91
|
function makeNode(input) {
|
|
92
|
+
const makerWorkerCount = dynamicMakerWorkerCount(input);
|
|
93
|
+
const checkerWorkerCount = dynamicCheckerWorkerCount(input);
|
|
89
94
|
const budget = defaultLoopBudget({
|
|
90
|
-
max_subagents: input.route === '$Integration' ? 2 : 4,
|
|
95
|
+
max_subagents: input.route === '$Integration' ? 2 : Math.max(4, makerWorkerCount + checkerWorkerCount + 1),
|
|
91
96
|
max_changed_files: input.ownerScope.files.length ? Math.max(4, input.ownerScope.files.length + 2) : 12
|
|
92
97
|
});
|
|
93
98
|
return {
|
|
@@ -105,14 +110,14 @@ function makeNode(input) {
|
|
|
105
110
|
maker: {
|
|
106
111
|
route: '$Naruto',
|
|
107
112
|
role: input.route === '$Integration' ? 'planner' : input.loopId.includes('docs') ? 'writer' : 'implementer',
|
|
108
|
-
worker_count:
|
|
113
|
+
worker_count: makerWorkerCount,
|
|
109
114
|
backend_preference: ['codex-sdk', 'python-codex-sdk', 'local-llm'],
|
|
110
115
|
local_draft_allowed: input.risk.level !== 'critical',
|
|
111
116
|
gpt_final_required: input.risk.requires_gpt_final
|
|
112
117
|
},
|
|
113
118
|
checker: {
|
|
114
119
|
route: input.loopId.includes('research') ? '$Research' : input.loopId.includes('docs') ? '$DFix' : '$QA-LOOP',
|
|
115
|
-
worker_count:
|
|
120
|
+
worker_count: checkerWorkerCount,
|
|
116
121
|
fresh_session_required: true,
|
|
117
122
|
stronger_model_required: input.risk.level === 'high' || input.risk.level === 'critical',
|
|
118
123
|
required_before_next_iteration: input.level === 'L2-action'
|
|
@@ -133,6 +138,32 @@ function makeNode(input) {
|
|
|
133
138
|
risk: input.risk
|
|
134
139
|
};
|
|
135
140
|
}
|
|
141
|
+
// Maker parallelism scales with the loop's owned scope instead of a flat 2:
|
|
142
|
+
// Naruto can fan out far wider, and a fixed count starved wide scopes while
|
|
143
|
+
// over-provisioning single-file loops. Risk still clamps the ceiling so
|
|
144
|
+
// critical work cannot stampede, and 'safe' mode keeps the old behavior.
|
|
145
|
+
function dynamicMakerWorkerCount(input) {
|
|
146
|
+
if (input.route === '$Integration')
|
|
147
|
+
return 1;
|
|
148
|
+
const scopeSize = input.ownerScope.files.length + input.ownerScope.directories.length * 3;
|
|
149
|
+
const modeCap = input.parallelism === 'safe' ? 2 : input.parallelism === 'extreme' ? 8 : 6;
|
|
150
|
+
const riskCap = input.risk.level === 'critical' ? 2 : modeCap;
|
|
151
|
+
const riskFloor = input.risk.level === 'high' ? 3 : 2;
|
|
152
|
+
const scopeScaled = Math.max(riskFloor, Math.ceil(scopeSize / 3));
|
|
153
|
+
return Math.max(1, Math.min(modeCap, riskCap, scopeScaled));
|
|
154
|
+
}
|
|
155
|
+
// Checker workers are read-only GPT review lanes. They scale more conservatively
|
|
156
|
+
// than makers, but wide/high-risk owner scopes get more than one fresh reviewer.
|
|
157
|
+
function dynamicCheckerWorkerCount(input) {
|
|
158
|
+
if (input.route === '$Integration')
|
|
159
|
+
return 1;
|
|
160
|
+
const scopeSize = input.ownerScope.files.length + input.ownerScope.directories.length * 3;
|
|
161
|
+
const modeCap = input.parallelism === 'safe' ? 1 : input.parallelism === 'extreme' ? 4 : 3;
|
|
162
|
+
const riskFloor = input.risk.level === 'high' || input.risk.level === 'critical' ? 2 : 1;
|
|
163
|
+
const riskCap = input.risk.level === 'critical' ? Math.min(2, modeCap) : modeCap;
|
|
164
|
+
const scopeScaled = Math.max(riskFloor, Math.ceil(scopeSize / 6));
|
|
165
|
+
return Math.max(1, Math.min(modeCap, riskCap, scopeScaled));
|
|
166
|
+
}
|
|
136
167
|
function titleFromDomain(domainId) {
|
|
137
168
|
return domainId === 'loop-general-coding' ? 'General coding loop' : `${domainId} loop`;
|
|
138
169
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readJson, writeJsonAtomic } from '../fsx.js';
|
|
2
|
+
import { loopKillRequestPath, loopProofPath, loopStatePath } from './loop-artifacts.js';
|
|
3
|
+
import { writeLoopCheckpoint } from './loop-checkpoint.js';
|
|
4
|
+
import { interruptLoopWorkers } from './loop-interrupt-registry.js';
|
|
5
|
+
export async function writeLoopKillRequest(root, missionId, target) {
|
|
6
|
+
const request = { schema: 'sks.loop-kill-request.v1', mission_id: missionId, target, requested_at: new Date().toISOString() };
|
|
7
|
+
await writeJsonAtomic(loopKillRequestPath(root, missionId), request);
|
|
8
|
+
await interruptLoopWorkers({ root, missionId, target }).catch(() => undefined);
|
|
9
|
+
return request;
|
|
10
|
+
}
|
|
11
|
+
export async function shouldKillLoop(root, missionId, loopId) {
|
|
12
|
+
const request = await readJson(loopKillRequestPath(root, missionId), null);
|
|
13
|
+
return request?.target === 'all' || request?.target === loopId;
|
|
14
|
+
}
|
|
15
|
+
export async function checkpointCancelledLoop(root, node, iteration, phase) {
|
|
16
|
+
await writeLoopCheckpoint({
|
|
17
|
+
root,
|
|
18
|
+
mission_id: node.mission_id,
|
|
19
|
+
loop_id: node.loop_id,
|
|
20
|
+
iteration,
|
|
21
|
+
phase,
|
|
22
|
+
state_path: loopStatePath(root, node.mission_id, node.loop_id),
|
|
23
|
+
proof_path: loopProofPath(root, node.mission_id, node.loop_id),
|
|
24
|
+
resumable: true
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=loop-runtime-control.js.map
|