sneakoscope 3.0.4 → 3.1.1
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/command-registry.js +1 -0
- package/dist/cli/context7-command.js +29 -5
- package/dist/cli/install-helpers.js +15 -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 +1 -0
- package/dist/core/agents/agent-runner-ollama.js +11 -4
- package/dist/core/agents/native-cli-session-swarm.js +69 -9
- package/dist/core/agents/runtime-proof-summary.js +4 -0
- package/dist/core/codex-control/codex-task-runner.js +9 -0
- package/dist/core/commands/goal-command.js +19 -1
- package/dist/core/commands/loop-command.js +176 -0
- package/dist/core/commands/naruto-command.js +26 -17
- package/dist/core/commands/team-command.js +1 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +6 -1
- package/dist/core/locks/file-lock.js +88 -0
- package/dist/core/loops/goal-to-loop-compat.js +23 -0
- package/dist/core/loops/loop-artifacts.js +72 -0
- package/dist/core/loops/loop-checkpoint.js +22 -0
- package/dist/core/loops/loop-decomposer.js +56 -0
- package/dist/core/loops/loop-finalizer.js +54 -0
- package/dist/core/loops/loop-gate-ladder.js +16 -0
- package/dist/core/loops/loop-gate-registry.js +96 -0
- package/dist/core/loops/loop-gate-runner.js +177 -0
- package/dist/core/loops/loop-gate-selector.js +52 -0
- package/dist/core/loops/loop-gpt-final-arbiter.js +61 -0
- package/dist/core/loops/loop-integration-merge.js +75 -0
- package/dist/core/loops/loop-iteration-runner.js +2 -0
- package/dist/core/loops/loop-lease.js +91 -0
- package/dist/core/loops/loop-observability.js +19 -0
- package/dist/core/loops/loop-owner-inference.js +57 -0
- package/dist/core/loops/loop-owner-ledger.js +2 -0
- package/dist/core/loops/loop-planner.js +170 -0
- package/dist/core/loops/loop-proof-summary.js +10 -0
- package/dist/core/loops/loop-proof.js +2 -0
- package/dist/core/loops/loop-risk-classifier.js +42 -0
- package/dist/core/loops/loop-runtime-control.js +25 -0
- package/dist/core/loops/loop-runtime.js +314 -0
- package/dist/core/loops/loop-scheduler.js +69 -0
- package/dist/core/loops/loop-schema.js +63 -0
- package/dist/core/loops/loop-state.js +61 -0
- package/dist/core/loops/loop-worker-prompts.js +43 -0
- package/dist/core/loops/loop-worker-runtime.js +275 -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 +39 -0
- package/dist/core/naruto/naruto-loop-worker-router.js +38 -0
- package/dist/core/pipeline-internals/runtime-core.js +82 -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-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 +65 -17
- 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 +45 -5
- package/dist/core/zellij/zellij-slot-pane-renderer.js +37 -10
- package/dist/core/zellij/zellij-slot-telemetry.js +96 -44
- package/dist/core/zellij/zellij-worker-pane-manager.js +42 -4
- package/dist/scripts/loop-directive-check-lib.js +388 -0
- package/dist/scripts/loop-worker-fixture-child.js +53 -0
- package/dist/scripts/naruto-real-local-gpt-final-smoke.js +10 -1
- package/package.json +38 -3
- package/schemas/loops/loop-node.schema.json +21 -0
- package/schemas/loops/loop-plan.schema.json +21 -0
- package/schemas/loops/loop-proof.schema.json +20 -0
- package/schemas/loops/loop-state.schema.json +19 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { printJson } from '../../cli/output.js';
|
|
3
|
+
import { createMission, findLatestMission, loadMission, setCurrent } from '../mission.js';
|
|
4
|
+
import { readJson, sksRoot } from '../fsx.js';
|
|
5
|
+
import { loopLatestCheckpointPath, loopPlanPath, loopProofPath, loopRoot } from '../loops/loop-artifacts.js';
|
|
6
|
+
import { finalizeLoopGraph } from '../loops/loop-finalizer.js';
|
|
7
|
+
import { readLoopGraphProof } from '../loops/loop-observability.js';
|
|
8
|
+
import { planLoopsFromRequest } from '../loops/loop-planner.js';
|
|
9
|
+
import { renderLoopProofSummary } from '../loops/loop-proof-summary.js';
|
|
10
|
+
import { runLoopNode, runLoopPlan } from '../loops/loop-runtime.js';
|
|
11
|
+
import { scheduleLoopGraph } from '../loops/loop-scheduler.js';
|
|
12
|
+
import { writeLoopKillRequest } from '../loops/loop-runtime-control.js';
|
|
13
|
+
import { flag, promptOf, readFlagValue } from './command-utils.js';
|
|
14
|
+
export async function loopCommand(subcommand = 'help', args = []) {
|
|
15
|
+
const action = subcommand || 'help';
|
|
16
|
+
if (action === 'plan')
|
|
17
|
+
return loopPlan(args);
|
|
18
|
+
if (action === 'run')
|
|
19
|
+
return loopRun(args);
|
|
20
|
+
if (action === 'status')
|
|
21
|
+
return loopStatus(args);
|
|
22
|
+
if (action === 'proof')
|
|
23
|
+
return loopProof(args);
|
|
24
|
+
if (action === 'kill')
|
|
25
|
+
return loopKill(args);
|
|
26
|
+
if (action === 'resume')
|
|
27
|
+
return loopResume(args);
|
|
28
|
+
if (action === 'graph')
|
|
29
|
+
return loopGraph(args);
|
|
30
|
+
console.log(`SKS Loop
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
sks loop plan "<request>" [--json]
|
|
34
|
+
sks loop run latest [--parallelism safe|balanced|extreme] [--json]
|
|
35
|
+
sks loop status latest [--json]
|
|
36
|
+
sks loop proof latest [--json]
|
|
37
|
+
sks loop kill <loop-id|all>
|
|
38
|
+
sks loop resume latest [--rerun-completed]
|
|
39
|
+
sks loop graph latest
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
async function loopPlan(args) {
|
|
43
|
+
const root = await sksRoot();
|
|
44
|
+
const request = promptOf(args);
|
|
45
|
+
if (!request)
|
|
46
|
+
throw new Error('Usage: sks loop plan "<request>" [--json]');
|
|
47
|
+
const { id } = await createMission(root, { mode: 'loop', prompt: request });
|
|
48
|
+
const plan = await planLoopsFromRequest({ root, missionId: id, request, sourceCommand: 'loop' });
|
|
49
|
+
await setCurrent(root, { mission_id: id, mode: 'LOOP', route: 'Loop', route_command: '$Loop', phase: 'LOOP_PLANNED', stop_gate: 'loop-graph-proof.json' }, { replace: true });
|
|
50
|
+
if (flag(args, '--json'))
|
|
51
|
+
return printJson({ schema: 'sks.loop-plan-command.v1', ok: plan.blockers.length === 0, mission_id: id, plan });
|
|
52
|
+
console.log(`Loop plan: ${id}`);
|
|
53
|
+
console.log('Loops:');
|
|
54
|
+
for (const node of plan.graph.nodes) {
|
|
55
|
+
const owner = [...node.owner_scope.files, ...node.owner_scope.directories][0] || 'integration';
|
|
56
|
+
console.log(` ${node.loop_id.padEnd(18)} ${node.level.padEnd(12)} owner ${owner.padEnd(28)} gates ${[...node.gates.triage, ...node.gates.local, ...node.gates.checker, ...node.gates.integration, ...node.gates.final].length}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function loopRun(args) {
|
|
60
|
+
const root = await sksRoot();
|
|
61
|
+
const missionId = await resolveLoopMission(root, args[0]);
|
|
62
|
+
if (!missionId)
|
|
63
|
+
throw new Error('No loop plan exists. Run: sks loop plan "<request>"');
|
|
64
|
+
const plan = await readJson(loopPlanPath(root, missionId));
|
|
65
|
+
if (plan.blockers.length) {
|
|
66
|
+
console.log(`Loop plan blocked: ${plan.blockers.join(', ')}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const parallelism = normalizeParallelism(readFlagValue(args, '--parallelism', 'balanced'));
|
|
70
|
+
const result = await runLoopPlan({ root, plan, parallelism });
|
|
71
|
+
await setCurrent(root, { mission_id: missionId, mode: 'LOOP', route: 'Loop', route_command: '$Loop', phase: result.ok ? 'LOOP_COMPLETED' : 'LOOP_BLOCKED', stop_gate: 'loop-graph-proof.json' });
|
|
72
|
+
if (flag(args, '--json'))
|
|
73
|
+
return printJson({ schema: 'sks.loop-run-command.v1', ...result });
|
|
74
|
+
console.log(renderLoopProofSummary(result.graph_proof));
|
|
75
|
+
}
|
|
76
|
+
async function loopStatus(args) {
|
|
77
|
+
const root = await sksRoot();
|
|
78
|
+
const missionId = await resolveLoopMission(root, args[0]);
|
|
79
|
+
if (!missionId)
|
|
80
|
+
throw new Error('Usage: sks loop status <mission-id|latest>');
|
|
81
|
+
const plan = await readJson(loopPlanPath(root, missionId), null);
|
|
82
|
+
const proof = await readLoopGraphProof(root, missionId);
|
|
83
|
+
const states = await Promise.all((plan?.graph.nodes || []).map((node) => readJson(path.join(loopRoot(root, missionId), node.loop_id, 'loop-state.json'), null)));
|
|
84
|
+
const proofs = await Promise.all((plan?.graph.nodes || []).map((node) => readJson(loopProofPath(root, missionId, node.loop_id), null)));
|
|
85
|
+
const checkpoints = await Promise.all((plan?.graph.nodes || []).map((node) => readJson(loopLatestCheckpointPath(root, missionId, node.loop_id), null)));
|
|
86
|
+
const result = { schema: 'sks.loop-status-command.v1', mission_id: missionId, plan_ok: Boolean(plan && plan.blockers.length === 0), graph: proof, states, proofs, checkpoints };
|
|
87
|
+
if (flag(args, '--json'))
|
|
88
|
+
return printJson(result);
|
|
89
|
+
console.log(`Loop status: ${missionId}`);
|
|
90
|
+
for (const state of states.filter(Boolean)) {
|
|
91
|
+
const loopId = String(state.loop_id);
|
|
92
|
+
const nodeProof = proofs.find((row) => row?.loop_id === loopId);
|
|
93
|
+
const checkpoint = checkpoints.find((row) => row?.loop_id === loopId);
|
|
94
|
+
const backend = nodeProof?.maker_result?.backend || 'blocked';
|
|
95
|
+
const gates = nodeProof?.gate_result ? `${nodeProof.gate_result.passed_gates.length}/${nodeProof.gate_result.selected_gates.length}` : '-';
|
|
96
|
+
const worktree = nodeProof?.worktree?.id || state.acting_on?.worktree_id || '-';
|
|
97
|
+
const resumable = checkpoint?.resumable ? `resumable:${checkpoint.phase}` : 'resumable:-';
|
|
98
|
+
console.log(` ${loopId.padEnd(18)} ${String(state.status).padEnd(10)} backend ${String(backend).padEnd(24)} gates ${gates.padEnd(5)} worktree ${String(worktree).padEnd(18)} ${resumable}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function loopProof(args) {
|
|
102
|
+
const root = await sksRoot();
|
|
103
|
+
const missionId = await resolveLoopMission(root, args[0]);
|
|
104
|
+
if (!missionId)
|
|
105
|
+
throw new Error('Usage: sks loop proof <mission-id|latest>');
|
|
106
|
+
const proof = await readLoopGraphProof(root, missionId);
|
|
107
|
+
if (!proof)
|
|
108
|
+
throw new Error(`Loop graph proof missing: ${missionId}`);
|
|
109
|
+
if (flag(args, '--json'))
|
|
110
|
+
return printJson(proof);
|
|
111
|
+
console.log(renderLoopProofSummary(proof));
|
|
112
|
+
}
|
|
113
|
+
async function loopGraph(args) {
|
|
114
|
+
const root = await sksRoot();
|
|
115
|
+
const missionId = await resolveLoopMission(root, args[0]);
|
|
116
|
+
if (!missionId)
|
|
117
|
+
throw new Error('Usage: sks loop graph <mission-id|latest>');
|
|
118
|
+
const plan = await readJson(loopPlanPath(root, missionId));
|
|
119
|
+
printJson({ schema: 'sks.loop-graph-command.v1', mission_id: missionId, graph: plan.graph });
|
|
120
|
+
}
|
|
121
|
+
async function loopKill(args) {
|
|
122
|
+
const root = await sksRoot();
|
|
123
|
+
const missionId = await findLatestMission(root);
|
|
124
|
+
const target = args[0];
|
|
125
|
+
if (!missionId || !target)
|
|
126
|
+
throw new Error('Usage: sks loop kill <loop-id|all>');
|
|
127
|
+
await writeLoopKillRequest(root, missionId, target);
|
|
128
|
+
console.log(`Loop kill requested: ${target}`);
|
|
129
|
+
}
|
|
130
|
+
async function loopResume(args) {
|
|
131
|
+
const root = await sksRoot();
|
|
132
|
+
const missionId = await resolveLoopMission(root, args[0]);
|
|
133
|
+
if (!missionId)
|
|
134
|
+
throw new Error('Usage: sks loop resume <mission-id|latest> [--rerun-completed]');
|
|
135
|
+
const plan = await readJson(loopPlanPath(root, missionId));
|
|
136
|
+
const rerunCompleted = flag(args, '--rerun-completed');
|
|
137
|
+
const existingProofs = await Promise.all(plan.graph.nodes.map((node) => readJson(loopProofPath(root, missionId, node.loop_id), null)));
|
|
138
|
+
const completed = new Set(existingProofs.filter((proof) => proof !== null && proof.status === 'completed').map((proof) => proof.loop_id));
|
|
139
|
+
const runnable = rerunCompleted ? plan.graph.nodes : plan.graph.nodes.filter((node) => !completed.has(node.loop_id));
|
|
140
|
+
const schedule = scheduleLoopGraph(runnable, normalizeParallelism(readFlagValue(args, '--parallelism', 'balanced')));
|
|
141
|
+
const started = Date.now();
|
|
142
|
+
const resumedProofs = [];
|
|
143
|
+
for (const batch of schedule.batches) {
|
|
144
|
+
const batchProofs = await Promise.all(batch.map((node) => runLoopNode({ root, plan, node })));
|
|
145
|
+
resumedProofs.push(...batchProofs);
|
|
146
|
+
}
|
|
147
|
+
const mergedProofs = [
|
|
148
|
+
...existingProofs.filter((proof) => proof !== null && proof.status === 'completed' && !rerunCompleted),
|
|
149
|
+
...resumedProofs
|
|
150
|
+
];
|
|
151
|
+
const graphProof = await finalizeLoopGraph({
|
|
152
|
+
root,
|
|
153
|
+
plan,
|
|
154
|
+
proofs: mergedProofs,
|
|
155
|
+
maxActiveLoops: schedule.max_active_loops,
|
|
156
|
+
maxActiveWorkers: Math.max(1, mergedProofs.reduce((sum, proof) => sum + proof.maker_result.worker_count + proof.checker_result.worker_count, 0)),
|
|
157
|
+
wallMs: Math.max(1, Date.now() - started)
|
|
158
|
+
});
|
|
159
|
+
await setCurrent(root, { mission_id: missionId, mode: 'LOOP', route: 'Loop', route_command: '$Loop', phase: graphProof.ok ? 'LOOP_COMPLETED' : 'LOOP_BLOCKED', stop_gate: 'loop-graph-proof.json' });
|
|
160
|
+
if (flag(args, '--json'))
|
|
161
|
+
return printJson({ schema: 'sks.loop-resume-command.v1', ok: graphProof.ok, mission_id: missionId, resumed_loops: resumedProofs.map((proof) => proof.loop_id), skipped_completed: [...completed], graph_proof: graphProof });
|
|
162
|
+
console.log(renderLoopProofSummary(graphProof));
|
|
163
|
+
}
|
|
164
|
+
async function resolveLoopMission(root, arg) {
|
|
165
|
+
if (arg && arg !== 'latest')
|
|
166
|
+
return arg;
|
|
167
|
+
const latest = await findLatestMission(root);
|
|
168
|
+
if (!latest)
|
|
169
|
+
return null;
|
|
170
|
+
const loaded = await loadMission(root, latest).catch(() => null);
|
|
171
|
+
return loaded?.mission?.mode === 'loop' || await readJson(loopPlanPath(root, latest), null) ? latest : null;
|
|
172
|
+
}
|
|
173
|
+
function normalizeParallelism(value) {
|
|
174
|
+
return value === 'safe' || value === 'extreme' ? value : 'balanced';
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=loop-command.js.map
|
|
@@ -385,6 +385,9 @@ async function narutoRun(parsed) {
|
|
|
385
385
|
serviceTier: 'fast',
|
|
386
386
|
noFast: false,
|
|
387
387
|
writeMode: writeCapable ? parsed.writeMode || 'parallel' : 'off',
|
|
388
|
+
applyPatches: parsed.applyPatches,
|
|
389
|
+
dryRunPatches: parsed.dryRunPatches,
|
|
390
|
+
maxWriteAgents: parsed.maxWriteAgents,
|
|
388
391
|
gitWorktreePolicy: worktreePolicy,
|
|
389
392
|
narutoWorkGraph: workGraph,
|
|
390
393
|
narutoAllocationPolicy: allocationPolicy,
|
|
@@ -418,18 +421,6 @@ async function narutoRun(parsed) {
|
|
|
418
421
|
blockers: [...(result.proof?.blockers || []), ...(parallelRuntimeOk ? [] : ['naruto_parallel_runtime_proof_below_gate'])],
|
|
419
422
|
updated_at: nowIso()
|
|
420
423
|
});
|
|
421
|
-
await setCurrent(root, {
|
|
422
|
-
mission_id: mission.id,
|
|
423
|
-
route: 'Naruto',
|
|
424
|
-
route_command: '$Naruto',
|
|
425
|
-
mode: 'NARUTO',
|
|
426
|
-
phase: result.ok === true ? 'NARUTO_COMPLETE_OR_REVIEW' : 'NARUTO_BLOCKED',
|
|
427
|
-
native_sessions_verified: nativeProofOk,
|
|
428
|
-
subagents_verified: nativeProofOk,
|
|
429
|
-
naruto_gate_file: 'naruto-gate.json',
|
|
430
|
-
stop_gate: 'naruto-gate.json',
|
|
431
|
-
prompt: parsed.prompt
|
|
432
|
-
});
|
|
433
424
|
const clones = result.roster?.agent_count ?? roster.agent_count;
|
|
434
425
|
const localWorkerSummary = summarizeNarutoLocalWorkerResult(localWorker, result);
|
|
435
426
|
// Finalizer policy: when local LLM workers contributed patches, the GPT
|
|
@@ -437,16 +428,29 @@ async function narutoRun(parsed) {
|
|
|
437
428
|
const finalizer = evaluateNarutoFinalizer({
|
|
438
429
|
localParticipated: Number(localWorkerSummary?.selected_worker_count || 0) > 0,
|
|
439
430
|
gptFinalStatus: result.proof?.gpt_final_status || null,
|
|
440
|
-
applyPatches:
|
|
431
|
+
applyPatches: parsed.applyPatches
|
|
441
432
|
});
|
|
442
433
|
await writeJsonAtomic(path.join(mission.dir, 'naruto-finalizer.json'), {
|
|
443
434
|
...finalizer,
|
|
444
435
|
generated_at: nowIso(),
|
|
445
436
|
mission_id: mission.id
|
|
446
437
|
});
|
|
438
|
+
const summaryOk = result.ok === true && (parsed.applyPatches === true ? finalizer.ok === true : finalizer.run_ok === true);
|
|
439
|
+
await setCurrent(root, {
|
|
440
|
+
mission_id: mission.id,
|
|
441
|
+
route: 'Naruto',
|
|
442
|
+
route_command: '$Naruto',
|
|
443
|
+
mode: 'NARUTO',
|
|
444
|
+
phase: summaryOk ? 'NARUTO_COMPLETE_OR_REVIEW' : 'NARUTO_BLOCKED',
|
|
445
|
+
native_sessions_verified: nativeProofOk,
|
|
446
|
+
subagents_verified: nativeProofOk,
|
|
447
|
+
naruto_gate_file: 'naruto-gate.json',
|
|
448
|
+
stop_gate: 'naruto-gate.json',
|
|
449
|
+
prompt: parsed.prompt
|
|
450
|
+
});
|
|
447
451
|
const summary = {
|
|
448
452
|
schema: NARUTO_RESULT_SCHEMA,
|
|
449
|
-
ok:
|
|
453
|
+
ok: summaryOk,
|
|
450
454
|
mode: 'NARUTO',
|
|
451
455
|
jutsu: 'kage_bunshin_no_jutsu',
|
|
452
456
|
mission_id: result.mission_id,
|
|
@@ -502,6 +506,7 @@ async function narutoRun(parsed) {
|
|
|
502
506
|
headless_workers: parallelRuntime.headless_workers,
|
|
503
507
|
passed: parallelRuntime.passed
|
|
504
508
|
} : null,
|
|
509
|
+
parallel_write_policy: result.parallel_write_policy || null,
|
|
505
510
|
local_worker: localWorkerSummary,
|
|
506
511
|
finalizer,
|
|
507
512
|
proof: result.proof?.status || 'missing',
|
|
@@ -538,6 +543,7 @@ function compactNarutoRunResult(result) {
|
|
|
538
543
|
mission_id: result?.mission_id || null,
|
|
539
544
|
route: result?.route || NARUTO_ROUTE,
|
|
540
545
|
backend: result?.backend || null,
|
|
546
|
+
parallel_write_policy: result?.parallel_write_policy || null,
|
|
541
547
|
target_active_slots: result?.target_active_slots ?? null,
|
|
542
548
|
proof: result?.proof ? {
|
|
543
549
|
ok: result.proof.ok === true,
|
|
@@ -754,7 +760,7 @@ async function narutoHelp(parsed) {
|
|
|
754
760
|
mode: 'NARUTO',
|
|
755
761
|
description: 'Shadow Clone Swarm: fan out up to ' + MAX_NARUTO_AGENT_COUNT + ' parallel clone sessions.',
|
|
756
762
|
usage: [
|
|
757
|
-
'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--real] [--readonly] [--json]',
|
|
763
|
+
'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--write-mode parallel|serial|off] [--apply-patches] [--dry-run-patches] [--real] [--readonly] [--json]',
|
|
758
764
|
'sks naruto status [--mission <id>] [--json]',
|
|
759
765
|
'sks naruto proof latest [--messages 20] [--json]'
|
|
760
766
|
],
|
|
@@ -788,6 +794,9 @@ function parseNarutoArgs(args = []) {
|
|
|
788
794
|
const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
|
|
789
795
|
const writeModeRaw = String(readOption(args, '--write-mode', hasFlag(args, '--parallel-write') ? 'parallel' : '') || '');
|
|
790
796
|
const writeMode = (['proof-safe', 'parallel', 'serial', 'off'].includes(writeModeRaw) ? writeModeRaw : null);
|
|
797
|
+
const applyPatches = hasFlag(args, '--apply-patches');
|
|
798
|
+
const dryRunPatches = hasFlag(args, '--dry-run-patches') || hasFlag(args, '--dry-run-patch');
|
|
799
|
+
const maxWriteAgents = Math.max(0, Math.floor(Number(readOption(args, '--max-write-agents', '0')) || 0));
|
|
791
800
|
const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status' || action === 'proof'
|
|
792
801
|
? positionalArgs(rest, new Set()).find((arg) => /^latest$|^M-/.test(arg))
|
|
793
802
|
: null;
|
|
@@ -799,9 +808,9 @@ function parseNarutoArgs(args = []) {
|
|
|
799
808
|
const smoke = hasFlag(args, '--smoke');
|
|
800
809
|
const parallelism = normalizeParallelism(readOption(args, '--parallelism', 'extreme'));
|
|
801
810
|
const messages = normalizeMessages(readOption(args, '--messages', '8'));
|
|
802
|
-
const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--mission', '--mission-id', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url', '--parallelism', '--messages']);
|
|
811
|
+
const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--max-write-agents', '--mission', '--mission-id', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url', '--parallelism', '--messages']);
|
|
803
812
|
const prompt = positionalArgs(rest, valueFlags).join(' ').trim() || 'Naruto shadow clone swarm run';
|
|
804
|
-
return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach, smoke, parallelism, messages };
|
|
813
|
+
return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, applyPatches, dryRunPatches, maxWriteAgents, json, missionId, noOpenZellij, attach, smoke, parallelism, messages };
|
|
805
814
|
}
|
|
806
815
|
function normalizeParallelism(value) {
|
|
807
816
|
const text = String(value || 'extreme').toLowerCase();
|
|
@@ -25,6 +25,7 @@ async function redirectTeamCreateToNaruto(args = []) {
|
|
|
25
25
|
redirected_to: 'sks naruto run',
|
|
26
26
|
route_command: '$Naruto',
|
|
27
27
|
deprecated_route: '$Team',
|
|
28
|
+
parallel_write_policy: result?.parallel_write_policy || result?.run?.parallel_write_policy || null,
|
|
28
29
|
created_at: nowIso(),
|
|
29
30
|
args: list
|
|
30
31
|
});
|
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.
|
|
8
|
+
export const PACKAGE_VERSION = '3.1.1';
|
|
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
|
@@ -603,6 +603,8 @@ export async function initProject(root, opts = {}) {
|
|
|
603
603
|
next = upsertTomlTableKeyIfAbsent(next, 'agents', 'max_threads = 6');
|
|
604
604
|
next = upsertTomlTableKeyIfAbsent(next, 'agents', 'max_depth = 1');
|
|
605
605
|
for (const block of managedCodexConfigBlocks()) {
|
|
606
|
+
if (block.preserveExisting === true && hasTomlTable(next, block.table))
|
|
607
|
+
continue;
|
|
606
608
|
next = upsertTomlTable(next, block.table, block.text);
|
|
607
609
|
}
|
|
608
610
|
// Plugin tables broke the Codex App UI by force-reverting user `enabled=false`.
|
|
@@ -797,7 +799,10 @@ export async function initProject(root, opts = {}) {
|
|
|
797
799
|
}
|
|
798
800
|
function managedCodexConfigBlocks() {
|
|
799
801
|
return [
|
|
800
|
-
|
|
802
|
+
// Context7 credentials may live directly in this table as args/env/headers/url
|
|
803
|
+
// depending on the user's MCP client setup. Seed the default only when absent;
|
|
804
|
+
// never replace an existing Context7 block during setup/update.
|
|
805
|
+
{ table: 'mcp_servers.context7', text: context7ConfigToml().trim(), preserveExisting: true },
|
|
801
806
|
{ table: 'agents.native_agent', text: agentConfigBlock('native_agent', 'Read-only SKS analysis agent.', './agents/native-agent-intake.toml', ['Analysis', 'Mapper']) },
|
|
802
807
|
{ table: 'agents.team_consensus', text: agentConfigBlock('team_consensus', 'SKS planning/debate agent.', './agents/team-consensus.toml', ['Consensus', 'Atlas']) },
|
|
803
808
|
{ table: 'agents.implementation_worker', text: agentConfigBlock('implementation_worker', 'SKS bounded implementation worker.', './agents/implementation-worker.toml', ['Builder', 'Mason']) },
|
|
@@ -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
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { writeJsonAtomic } from '../fsx.js';
|
|
3
|
+
import { planLoopsFromRequest } from './loop-planner.js';
|
|
4
|
+
export async function compileGoalToLoopPlan(input) {
|
|
5
|
+
const plan = await planLoopsFromRequest({
|
|
6
|
+
root: input.root,
|
|
7
|
+
missionId: input.missionId,
|
|
8
|
+
request: input.goalText,
|
|
9
|
+
sourceCommand: 'goal'
|
|
10
|
+
});
|
|
11
|
+
await writeJsonAtomic(path.join(input.root, '.sneakoscope', 'missions', input.missionId, 'goal-compat.json'), {
|
|
12
|
+
schema: 'sks.goal-loop-compat.v1',
|
|
13
|
+
legacy_goal_text: input.goalText,
|
|
14
|
+
legacy_goal_options: input.legacyGoalOptions,
|
|
15
|
+
loop_plan_path: `.sneakoscope/missions/${input.missionId}/loops/loop-plan.json`,
|
|
16
|
+
loop_graph_proof_path: `.sneakoscope/missions/${input.missionId}/loops/loop-graph-proof.json`,
|
|
17
|
+
runtime: 'loop-graph',
|
|
18
|
+
compat_mode: true,
|
|
19
|
+
generated_at: new Date().toISOString()
|
|
20
|
+
});
|
|
21
|
+
return plan;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=goal-to-loop-compat.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export function loopRoot(root, missionId) {
|
|
3
|
+
const missionsRoot = path.resolve(root, '.sneakoscope', 'missions');
|
|
4
|
+
return containedJoin(missionsRoot, safeArtifactId('mission', missionId), 'loops');
|
|
5
|
+
}
|
|
6
|
+
export function loopNodeRoot(root, missionId, loopId) {
|
|
7
|
+
return containedJoin(loopRoot(root, missionId), safeArtifactId('loop', loopId));
|
|
8
|
+
}
|
|
9
|
+
export function loopPlanPath(root, missionId) {
|
|
10
|
+
return path.join(loopRoot(root, missionId), 'loop-plan.json');
|
|
11
|
+
}
|
|
12
|
+
export function loopStatePath(root, missionId, loopId) {
|
|
13
|
+
return path.join(loopNodeRoot(root, missionId, loopId), 'loop-state.json');
|
|
14
|
+
}
|
|
15
|
+
export function loopRunLogPath(root, missionId, loopId) {
|
|
16
|
+
return path.join(loopNodeRoot(root, missionId, loopId), 'loop-run-log.jsonl');
|
|
17
|
+
}
|
|
18
|
+
export function loopProofPath(root, missionId, loopId) {
|
|
19
|
+
return path.join(loopNodeRoot(root, missionId, loopId), 'loop-proof.json');
|
|
20
|
+
}
|
|
21
|
+
export function loopBudgetPath(root, missionId, loopId) {
|
|
22
|
+
return path.join(loopNodeRoot(root, missionId, loopId), 'loop-budget.json');
|
|
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
|
+
}
|
|
30
|
+
export function loopGraphProofPath(root, missionId) {
|
|
31
|
+
return path.join(loopRoot(root, missionId), 'loop-graph-proof.json');
|
|
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 loopKillRequestPath(root, missionId) {
|
|
40
|
+
return path.join(loopRoot(root, missionId), 'kill-request.json');
|
|
41
|
+
}
|
|
42
|
+
export function loopGatePath(root, missionId, loopId, gateId) {
|
|
43
|
+
return path.join(loopNodeRoot(root, missionId, loopId), 'gates', `${sanitizeArtifactPart(gateId)}.json`);
|
|
44
|
+
}
|
|
45
|
+
export function loopPatchPath(root, missionId, loopId, name) {
|
|
46
|
+
return path.join(loopNodeRoot(root, missionId, loopId), 'patches', `${sanitizeArtifactPart(name)}.json`);
|
|
47
|
+
}
|
|
48
|
+
export function loopHandoffPath(root, missionId, loopId) {
|
|
49
|
+
return path.join(loopNodeRoot(root, missionId, loopId), 'handoff.md');
|
|
50
|
+
}
|
|
51
|
+
export function loopOwnerLedgerPath(root, missionId) {
|
|
52
|
+
return path.join(loopRoot(root, missionId), 'loop-owner-ledger.json');
|
|
53
|
+
}
|
|
54
|
+
export function sanitizeArtifactPart(value) {
|
|
55
|
+
return String(value || 'artifact').replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 96) || 'artifact';
|
|
56
|
+
}
|
|
57
|
+
function safeArtifactId(kind, value) {
|
|
58
|
+
const text = String(value || '').trim();
|
|
59
|
+
const sanitized = sanitizeArtifactPart(text);
|
|
60
|
+
if (!text || sanitized !== text)
|
|
61
|
+
throw new Error(`invalid_loop_${kind}_id:${text || 'empty'}`);
|
|
62
|
+
return sanitized;
|
|
63
|
+
}
|
|
64
|
+
function containedJoin(base, ...parts) {
|
|
65
|
+
const resolvedBase = path.resolve(base);
|
|
66
|
+
const target = path.resolve(resolvedBase, ...parts);
|
|
67
|
+
if (target !== resolvedBase && !target.startsWith(`${resolvedBase}${path.sep}`)) {
|
|
68
|
+
throw new Error(`loop_artifact_path_escape:${target}`);
|
|
69
|
+
}
|
|
70
|
+
return target;
|
|
71
|
+
}
|
|
72
|
+
//# 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,56 @@
|
|
|
1
|
+
export const LOOP_DOMAIN_RULES = [
|
|
2
|
+
{ id: 'zellij', dirs: ['src/core/zellij', 'src/scripts/zellij-'], gates: ['zellij:*'] },
|
|
3
|
+
{ id: 'release', dirs: ['src/core/release', 'src/scripts/release-', 'release-gates.v2.json'], gates: ['release:*'] },
|
|
4
|
+
{ id: 'research', dirs: ['src/core/research', 'src/scripts/research-'], gates: ['research:*'] },
|
|
5
|
+
{ id: 'qa-loop', dirs: ['src/core/qa-loop', 'src/core/commands/qa-loop-command.ts'], gates: ['qa-loop:*'] },
|
|
6
|
+
{ id: 'naruto', dirs: ['src/core/naruto', 'src/core/commands/naruto-command.ts'], gates: ['naruto:*'] },
|
|
7
|
+
{ id: 'codex-control', dirs: ['src/core/codex-control', 'src/scripts/codex-'], gates: ['codex:*', 'codex-sdk:*'] },
|
|
8
|
+
{ id: 'image', dirs: ['src/core/image', 'src/core/image-generation'], gates: ['image:*'] },
|
|
9
|
+
{ id: 'mad-db', dirs: ['src/core/mad-db', 'src/core/db-safety.ts'], gates: ['mad-db:*'] },
|
|
10
|
+
{ id: 'docs', dirs: ['docs', 'README.md', 'CHANGELOG.md'], gates: ['docs:*', 'changelog:check'] }
|
|
11
|
+
];
|
|
12
|
+
export function decomposeRequestIntoLoopDomains(request, changedFiles = []) {
|
|
13
|
+
const text = `${request} ${changedFiles.join(' ')}`.toLowerCase();
|
|
14
|
+
const explicitFiles = extractFilePaths(request).concat(changedFiles).filter(Boolean);
|
|
15
|
+
const selected = new Map();
|
|
16
|
+
for (const rule of LOOP_DOMAIN_RULES) {
|
|
17
|
+
const matchedByText = [rule.id, ...domainAliases(rule.id)].some((needle) => text.includes(needle))
|
|
18
|
+
|| rule.dirs.some((dir) => text.includes(dir.toLowerCase()) || text.includes(lastPart(dir)));
|
|
19
|
+
const matchedFiles = explicitFiles.filter((file) => rule.dirs.some((dir) => file === dir || file.startsWith(dir.replace(/\*+$/, ''))));
|
|
20
|
+
if (!matchedByText && matchedFiles.length === 0)
|
|
21
|
+
continue;
|
|
22
|
+
selected.set(rule.id, {
|
|
23
|
+
id: rule.id,
|
|
24
|
+
dirs: rule.dirs.filter((dir) => !dir.includes('*')),
|
|
25
|
+
files: matchedFiles,
|
|
26
|
+
gates: rule.gates
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (selected.size === 0 && explicitFiles.length) {
|
|
30
|
+
selected.set('loop-general-coding', { id: 'loop-general-coding', dirs: [], files: explicitFiles, gates: ['loop:affected'] });
|
|
31
|
+
}
|
|
32
|
+
if (selected.size === 0) {
|
|
33
|
+
selected.set('loop-general-coding', { id: 'loop-general-coding', dirs: ['src'], files: [], gates: ['loop:affected'] });
|
|
34
|
+
}
|
|
35
|
+
return [...selected.values()];
|
|
36
|
+
}
|
|
37
|
+
function extractFilePaths(request) {
|
|
38
|
+
return [...request.matchAll(/(?:^|\s)([A-Za-z0-9_.@/-]+\.(?:ts|tsx|js|mjs|json|md|toml|yml|yaml))(?:\s|$)/g)]
|
|
39
|
+
.map((match) => match[1])
|
|
40
|
+
.filter((value) => Boolean(value));
|
|
41
|
+
}
|
|
42
|
+
function lastPart(value) {
|
|
43
|
+
return value.split('/').at(-1)?.replace(/[^a-z0-9-]/gi, '').toLowerCase() || value.toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
function domainAliases(id) {
|
|
46
|
+
if (id === 'codex-control')
|
|
47
|
+
return ['codex', 'probe', 'capability'];
|
|
48
|
+
if (id === 'release')
|
|
49
|
+
return ['cache', 'gate', 'dag'];
|
|
50
|
+
if (id === 'docs')
|
|
51
|
+
return ['doc', 'docs', 'readme', 'changelog'];
|
|
52
|
+
if (id === 'qa-loop')
|
|
53
|
+
return ['qa'];
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=loop-decomposer.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readJson, writeJsonAtomic } from '../fsx.js';
|
|
2
|
+
import { loopGraphProofPath, loopProofPath } from './loop-artifacts.js';
|
|
3
|
+
import { runLoopGptFinalArbiter } from './loop-gpt-final-arbiter.js';
|
|
4
|
+
import { mergeLoopWorktrees } from './loop-integration-merge.js';
|
|
5
|
+
import { graphProofFromLoopProofs } from './loop-scheduler.js';
|
|
6
|
+
export async function finalizeLoopGraph(input) {
|
|
7
|
+
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)));
|
|
8
|
+
const realProofs = proofs.filter((proof) => Boolean(proof));
|
|
9
|
+
const graph = graphProofFromLoopProofs({
|
|
10
|
+
missionId: input.plan.mission_id,
|
|
11
|
+
proofs: realProofs,
|
|
12
|
+
maxActiveLoops: input.maxActiveLoops || 1,
|
|
13
|
+
maxActiveWorkers: input.maxActiveWorkers || Math.max(1, realProofs.reduce((sum, proof) => sum + proof.maker_result.worker_count + proof.checker_result.worker_count, 0)),
|
|
14
|
+
wallMs: input.wallMs || 1
|
|
15
|
+
});
|
|
16
|
+
const integrationMerge = await mergeLoopWorktrees({
|
|
17
|
+
root: input.root,
|
|
18
|
+
plan: input.plan,
|
|
19
|
+
proofs: realProofs
|
|
20
|
+
});
|
|
21
|
+
const anyHandoff = realProofs.some((proof) => proof.handoff.required);
|
|
22
|
+
const anySourceMutation = realProofs.some((proof) => proof.changed_files.some((file) => !file.startsWith('.sneakoscope/')));
|
|
23
|
+
const arbiter = anySourceMutation
|
|
24
|
+
? await runLoopGptFinalArbiter({ root: input.root, plan: input.plan, proofs: realProofs, integrationMerge })
|
|
25
|
+
: null;
|
|
26
|
+
const blockers = [
|
|
27
|
+
...graph.blockers,
|
|
28
|
+
...(anyHandoff ? ['loop_handoff_required'] : []),
|
|
29
|
+
...(integrationMerge.ok ? [] : integrationMerge.blockers),
|
|
30
|
+
...(anySourceMutation && !arbiter ? ['gpt_final_arbiter_missing'] : []),
|
|
31
|
+
...(arbiter && !arbiter.ok ? ['gpt_final_arbiter_not_approved', ...arbiter.blockers] : [])
|
|
32
|
+
];
|
|
33
|
+
const finalGraph = {
|
|
34
|
+
...graph,
|
|
35
|
+
ok: graph.ok && blockers.length === 0,
|
|
36
|
+
blockers: [...new Set(blockers)],
|
|
37
|
+
integration_merge: {
|
|
38
|
+
ok: integrationMerge.ok,
|
|
39
|
+
artifact_path: `.sneakoscope/missions/${input.plan.mission_id}/loops/integration-merge.json`,
|
|
40
|
+
applied_loops: integrationMerge.applied_loops,
|
|
41
|
+
conflict_loops: integrationMerge.conflict_loops
|
|
42
|
+
},
|
|
43
|
+
...(arbiter ? {
|
|
44
|
+
gpt_final_arbiter: {
|
|
45
|
+
ok: arbiter.ok,
|
|
46
|
+
artifact_path: arbiter.artifact_path,
|
|
47
|
+
verdict: arbiter.verdict
|
|
48
|
+
}
|
|
49
|
+
} : {})
|
|
50
|
+
};
|
|
51
|
+
await writeJsonAtomic(loopGraphProofPath(input.root, input.plan.mission_id), finalGraph);
|
|
52
|
+
return finalGraph;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=loop-finalizer.js.map
|