sneakoscope 2.0.11 → 2.0.13
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 +9 -3
- 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/build-manifest.json +27 -8
- package/dist/core/agents/agent-orchestrator.js +279 -1
- package/dist/core/agents/agent-scheduler.js +12 -1
- package/dist/core/agents/agent-slot-pane-binding-proof.js +3 -3
- package/dist/core/agents/agent-work-queue.js +26 -2
- package/dist/core/agents/agent-worker-pipeline.js +2 -0
- package/dist/core/agents/native-cli-session-swarm.js +2 -2
- package/dist/core/codex-control/codex-sdk-adapter.js +10 -0
- package/dist/core/codex-control/codex-task-runner.js +4 -2
- package/dist/core/commands/naruto-command.js +104 -51
- package/dist/core/commands/research-command.js +43 -4
- package/dist/core/fsx.js +1 -1
- package/dist/core/git/git-worktree-merge-queue.js +34 -14
- package/dist/core/naruto/naruto-rebalance-policy.js +15 -3
- package/dist/core/naruto/naruto-work-graph.js +13 -0
- package/dist/core/research/claim-evidence-matrix.js +160 -0
- package/dist/core/research/experiment-plan.js +53 -0
- package/dist/core/research/falsification.js +18 -0
- package/dist/core/research/implementation-blueprint-markdown.js +31 -0
- package/dist/core/research/implementation-blueprint.js +66 -0
- package/dist/core/research/replication-pack.js +50 -0
- package/dist/core/research/research-cycle-runner.js +25 -0
- package/dist/core/research/research-final-reviewer.js +58 -0
- package/dist/core/research/research-handoff.js +51 -0
- package/dist/core/research/research-prompt-contract.js +24 -0
- package/dist/core/research/research-quality-contract.js +61 -0
- package/dist/core/research/research-report-quality.js +67 -0
- package/dist/core/research/research-stage-runner.js +16 -0
- package/dist/core/research/research-work-graph.js +75 -0
- package/dist/core/research/source-quality-report.js +94 -0
- package/dist/core/research.js +344 -44
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-column-anchor.js +165 -4
- package/dist/core/zellij/zellij-slot-pane-renderer.js +259 -16
- package/dist/core/zellij/zellij-worker-pane-manager.js +13 -7
- package/dist/scripts/agent-real-codex-in-zellij-worker-pane-check.js +8 -2
- package/dist/scripts/agent-slot-pane-binding-proof-check.js +4 -4
- package/dist/scripts/codex-sdk-release-review-pipeline-check.js +2 -1
- package/dist/scripts/codex-sdk-research-pipeline-check.js +7 -0
- package/dist/scripts/codex-sdk-zellij-pane-binding-check.js +2 -2
- package/dist/scripts/git-worktree-cross-rebase-check.js +13 -1
- package/dist/scripts/git-worktree-merge-queue-check.js +1 -0
- package/dist/scripts/local-collab-worktree-gpt-final-apply-policy-check.js +63 -0
- package/dist/scripts/naruto-actual-worker-control-plane-check.js +30 -3
- package/dist/scripts/naruto-allocation-runtime-wiring-check.js +92 -0
- package/dist/scripts/naruto-orchestrator-runtime-source-check.js +65 -6
- package/dist/scripts/naruto-rebalance-policy-check.js +15 -2
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +1 -1
- package/dist/scripts/packlist-performance-check.js +1 -1
- package/dist/scripts/release-dag-full-coverage-check.js +4 -0
- package/dist/scripts/release-real-check.js +258 -77
- package/dist/scripts/research-quality-gate-check.js +86 -0
- package/dist/scripts/zellij-first-slot-down-stack-check.js +1 -1
- package/dist/scripts/zellij-first-slot-down-stack-real-check.js +344 -4
- package/dist/scripts/zellij-right-column-manager-check.js +1 -1
- package/dist/scripts/zellij-slot-column-anchor-check.js +45 -3
- package/dist/scripts/zellij-slot-only-ui-check.js +3 -1
- package/dist/scripts/zellij-slot-pane-renderer-check.js +73 -5
- package/dist/scripts/zellij-slot-renderer-proof-semantics-check.js +59 -0
- package/dist/scripts/zellij-worker-pane-manager-check.js +23 -1
- package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +21 -4
- package/package.json +17 -2
- package/schemas/research/claim-evidence-matrix.schema.json +37 -0
- package/schemas/research/experiment-plan.schema.json +17 -0
- package/schemas/research/implementation-blueprint.schema.json +30 -0
- package/schemas/research/replication-pack.schema.json +17 -0
- package/schemas/research/research-final-review.schema.json +16 -0
- package/schemas/research/research-quality-contract.schema.json +37 -0
- package/schemas/research/source-quality-report.schema.json +18 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { appendJsonl } from '../fsx.js';
|
|
1
3
|
import { buildCodexSdkConfig } from './codex-sdk-config-policy.js';
|
|
2
4
|
import { buildCodexSdkEnv } from './codex-sdk-env-policy.js';
|
|
5
|
+
import { translateCodexSdkEvent } from './codex-event-translator.js';
|
|
3
6
|
export async function runRealCodexSdkTask(input, policy) {
|
|
4
7
|
const mod = await import('@openai/codex-sdk');
|
|
5
8
|
const Codex = mod.Codex || mod.default?.Codex || mod.default;
|
|
@@ -22,9 +25,15 @@ export async function runRealCodexSdkTask(input, policy) {
|
|
|
22
25
|
const thread = resumeId ? codex.resumeThread(resumeId, threadOptions) : codex.startThread(threadOptions);
|
|
23
26
|
const events = [];
|
|
24
27
|
let finalResponse = '';
|
|
28
|
+
let liveEventsWritten = false;
|
|
29
|
+
const liveEventPath = input.mutationLedgerRoot ? path.join(input.mutationLedgerRoot, 'codex-sdk-events.jsonl') : null;
|
|
25
30
|
const streamed = await thread.runStreamed(buildSdkInput(input), { outputSchema: input.outputSchema });
|
|
26
31
|
for await (const event of streamed.events) {
|
|
27
32
|
events.push(event);
|
|
33
|
+
if (liveEventPath) {
|
|
34
|
+
await appendJsonl(liveEventPath, translateCodexSdkEvent(event));
|
|
35
|
+
liveEventsWritten = true;
|
|
36
|
+
}
|
|
28
37
|
if (event?.type === 'item.completed' && event?.item?.type === 'agent_message')
|
|
29
38
|
finalResponse = String(event.item.text || '');
|
|
30
39
|
}
|
|
@@ -37,6 +46,7 @@ export async function runRealCodexSdkTask(input, policy) {
|
|
|
37
46
|
finalResponse,
|
|
38
47
|
structuredOutput,
|
|
39
48
|
blockers: [],
|
|
49
|
+
liveEventsWritten,
|
|
40
50
|
raw: { item_count: events.filter((event) => String(event?.type || '').startsWith('item.')).length }
|
|
41
51
|
};
|
|
42
52
|
}
|
|
@@ -67,8 +67,10 @@ export async function runCodexTask(input) {
|
|
|
67
67
|
}
|
|
68
68
|
const events = Array.isArray(adapterResult?.events) ? adapterResult.events : [];
|
|
69
69
|
const translatedEvents = translateCodexSdkEvents(events);
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
if (adapterResult?.liveEventsWritten !== true) {
|
|
71
|
+
for (const event of translatedEvents)
|
|
72
|
+
await appendJsonl(path.join(root, 'codex-sdk-events.jsonl'), event);
|
|
73
|
+
}
|
|
72
74
|
if (adapterResult?.reliabilityShield)
|
|
73
75
|
await writeJsonAtomic(path.join(root, 'codex-reliability-shield.json'), adapterResult.reliabilityShield);
|
|
74
76
|
const structuredOutput = adapterResult?.structuredOutput;
|
|
@@ -94,7 +94,7 @@ async function narutoRun(parsed) {
|
|
|
94
94
|
const localWorker = await resolveNarutoLocalWorkerMode(parsed);
|
|
95
95
|
const schedulerBackend = localWorker.auto_select_eligible ? 'ollama' : parsed.backend;
|
|
96
96
|
const safe = systemSafeNarutoConcurrency({ backend: schedulerBackend });
|
|
97
|
-
const
|
|
97
|
+
const baseWorkGraph = buildNarutoWorkGraph({
|
|
98
98
|
prompt: parsed.prompt,
|
|
99
99
|
requestedClones: roster.agent_count,
|
|
100
100
|
totalWorkItems: parsed.workItems,
|
|
@@ -104,9 +104,21 @@ async function narutoRun(parsed) {
|
|
|
104
104
|
maxActiveWorkers: parsed.concurrency || safe.cap,
|
|
105
105
|
worktreePolicy
|
|
106
106
|
});
|
|
107
|
+
const baseRoleDistribution = buildNarutoRoleDistribution(baseWorkGraph.work_items, { readonly: parsed.readonly });
|
|
108
|
+
const allocationWorkers = buildNarutoAllocationWorkers(baseWorkGraph, baseRoleDistribution, roster);
|
|
109
|
+
const allocationAssignments = allocateNarutoTasksToWorkers(baseWorkGraph.work_items, allocationWorkers);
|
|
110
|
+
const workGraph = buildNarutoWorkGraph({
|
|
111
|
+
prompt: parsed.prompt,
|
|
112
|
+
requestedClones: roster.agent_count,
|
|
113
|
+
totalWorkItems: parsed.workItems,
|
|
114
|
+
readonly: parsed.readonly,
|
|
115
|
+
writeCapable,
|
|
116
|
+
leaseBasePath: patchEnvelopeBasePath,
|
|
117
|
+
maxActiveWorkers: parsed.concurrency || safe.cap,
|
|
118
|
+
worktreePolicy,
|
|
119
|
+
allocationAssignments
|
|
120
|
+
});
|
|
107
121
|
const roleDistribution = buildNarutoRoleDistribution(workGraph.work_items, { readonly: parsed.readonly });
|
|
108
|
-
const allocationWorkers = buildNarutoAllocationWorkers(workGraph, roleDistribution, roster);
|
|
109
|
-
const allocationAssignments = allocateNarutoTasksToWorkers(workGraph.work_items, allocationWorkers);
|
|
110
122
|
const allocationPolicy = {
|
|
111
123
|
schema: 'sks.naruto-allocation-policy.v1',
|
|
112
124
|
generated_at: nowIso(),
|
|
@@ -135,7 +147,7 @@ async function narutoRun(parsed) {
|
|
|
135
147
|
blockers: allocationWorkers.length ? [] : ['naruto_allocation_workers_missing']
|
|
136
148
|
};
|
|
137
149
|
const rebalanceDecisions = rebalanceNarutoReadyWork({
|
|
138
|
-
tasks: workGraph.work_items.map((item) => ({ ...item,
|
|
150
|
+
tasks: workGraph.work_items.map((item) => ({ ...item, status: 'pending' })),
|
|
139
151
|
workers: allocationWorkers.map((worker) => ({ ...worker, alive: true, state: 'idle' })),
|
|
140
152
|
completedTaskIds: [],
|
|
141
153
|
reclaimedTaskIds: []
|
|
@@ -159,52 +171,38 @@ async function narutoRun(parsed) {
|
|
|
159
171
|
const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
|
|
160
172
|
const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
|
|
161
173
|
const activePool = await runNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
totalWorkItems: Math.min(2, workGraph.total_work_items),
|
|
166
|
-
readonly: true,
|
|
167
|
-
writeCapable: false,
|
|
168
|
-
leaseBasePath: patchEnvelopeBasePath,
|
|
169
|
-
maxActiveWorkers: Math.min(2, activeSlots),
|
|
170
|
-
worktreePolicy: {
|
|
171
|
-
mode: 'patch-envelope-only',
|
|
172
|
-
required: false,
|
|
173
|
-
main_repo_root: worktreePolicy.main_repo_root,
|
|
174
|
-
worktree_root: null,
|
|
175
|
-
fallback_reason: 'pre_run_smoke_never_owns_production_runtime'
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
const realRuntimeWorktreePolicy = {
|
|
179
|
-
mode: 'patch-envelope-only',
|
|
180
|
-
required: false,
|
|
181
|
-
main_repo_root: worktreePolicy.main_repo_root,
|
|
182
|
-
worktree_root: null,
|
|
183
|
-
fallback_reason: 'pre_run_smoke_never_owns_production_runtime'
|
|
184
|
-
};
|
|
185
|
-
const realActivePool = await runNarutoRealActivePool({
|
|
186
|
-
graph: realRuntimeSmokeGraph,
|
|
187
|
-
governor: { ...governor, safe_active_workers: Math.min(2, activeSlots), safe_zellij_visible_panes: Math.min(1, zellijVisiblePanes) },
|
|
188
|
-
spawnWorker: async (item, placement) => spawnActualNarutoWorker({
|
|
174
|
+
const runPreRunSmoke = parsed.smoke === true || process.env.SKS_NARUTO_PRE_RUN_SMOKE === '1';
|
|
175
|
+
const realActivePoolSmoke = runPreRunSmoke
|
|
176
|
+
? await runNarutoControlPlaneSmoke({
|
|
189
177
|
root,
|
|
190
178
|
missionId: mission.id,
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
179
|
+
prompt: parsed.prompt,
|
|
180
|
+
rosterCount: roster.agent_count,
|
|
181
|
+
totalWorkItems: workGraph.total_work_items,
|
|
182
|
+
patchEnvelopeBasePath,
|
|
183
|
+
worktreePolicy,
|
|
184
|
+
governor,
|
|
185
|
+
activeSlots,
|
|
186
|
+
zellijVisiblePanes
|
|
187
|
+
})
|
|
188
|
+
: {
|
|
189
|
+
schema: 'sks.naruto-active-pool.v1',
|
|
190
|
+
ok: true,
|
|
191
|
+
status: 'skipped',
|
|
192
|
+
runtime_source_of_truth: 'agent-orchestrator-scheduler',
|
|
193
|
+
production_runtime_source_of_truth: 'agent-orchestrator-scheduler',
|
|
194
|
+
fallback_reason: 'pre_run_smoke_never_owns_production_runtime',
|
|
195
|
+
reason: 'pre_run_smoke_disabled_for_production',
|
|
196
|
+
active_cap: 0,
|
|
197
|
+
max_observed_active_workers: 0,
|
|
198
|
+
average_active_workers: 0,
|
|
199
|
+
active_pool_utilization: 0,
|
|
200
|
+
refill_latency_ms_p95: 0,
|
|
201
|
+
visible_workers: 0,
|
|
202
|
+
headless_workers: 0,
|
|
203
|
+
worker_lifecycle: [],
|
|
204
|
+
smoke_graph_total_work_items: 0
|
|
205
|
+
};
|
|
208
206
|
const verificationDag = buildNarutoVerificationDag(workGraph, { cwd: root });
|
|
209
207
|
const gptFinalPack = buildNarutoGptFinalPack({
|
|
210
208
|
missionId: mission.id,
|
|
@@ -284,7 +282,8 @@ async function narutoRun(parsed) {
|
|
|
284
282
|
concurrency: activeSlots,
|
|
285
283
|
targetActiveSlots: activeSlots,
|
|
286
284
|
visualLaneCount: zellijVisiblePanes,
|
|
287
|
-
desiredWorkItemCount:
|
|
285
|
+
desiredWorkItemCount: workGraph.total_work_items,
|
|
286
|
+
minimumWorkItems: workGraph.total_work_items,
|
|
288
287
|
maxAgentCount: MAX_NARUTO_AGENT_COUNT,
|
|
289
288
|
narutoMode: true,
|
|
290
289
|
clones: roster.agent_count,
|
|
@@ -307,6 +306,9 @@ async function narutoRun(parsed) {
|
|
|
307
306
|
noFast: false,
|
|
308
307
|
writeMode: writeCapable ? parsed.writeMode || 'parallel' : 'off',
|
|
309
308
|
gitWorktreePolicy: worktreePolicy,
|
|
309
|
+
narutoWorkGraph: workGraph,
|
|
310
|
+
narutoAllocationPolicy: allocationPolicy,
|
|
311
|
+
narutoRebalancePolicy: rebalancePolicy,
|
|
310
312
|
json: parsed.json
|
|
311
313
|
});
|
|
312
314
|
const clones = result.roster?.agent_count ?? roster.agent_count;
|
|
@@ -323,7 +325,7 @@ async function narutoRun(parsed) {
|
|
|
323
325
|
concurrency: result.target_active_slots ?? activeSlots,
|
|
324
326
|
target_active_slots: result.target_active_slots ?? activeSlots,
|
|
325
327
|
runtime_source_of_truth: 'agent-orchestrator-scheduler',
|
|
326
|
-
pre_run_real_active_pool_source: 'smoke_only',
|
|
328
|
+
pre_run_real_active_pool_source: runPreRunSmoke ? 'smoke_only' : 'skipped',
|
|
327
329
|
concurrency_capped: clones > (result.target_active_slots ?? activeSlots),
|
|
328
330
|
system: { cores: safe.cores, free_gb: safe.free_gb, safe_concurrency: safe.cap, heavy_backend: safe.heavy },
|
|
329
331
|
work_graph: {
|
|
@@ -409,6 +411,56 @@ function compactNarutoRunResult(result) {
|
|
|
409
411
|
}
|
|
410
412
|
};
|
|
411
413
|
}
|
|
414
|
+
async function runNarutoControlPlaneSmoke(input) {
|
|
415
|
+
const smokeGraph = buildNarutoWorkGraph({
|
|
416
|
+
prompt: input.prompt,
|
|
417
|
+
requestedClones: Math.min(2, input.rosterCount),
|
|
418
|
+
totalWorkItems: Math.min(2, input.totalWorkItems),
|
|
419
|
+
readonly: true,
|
|
420
|
+
writeCapable: false,
|
|
421
|
+
leaseBasePath: input.patchEnvelopeBasePath,
|
|
422
|
+
maxActiveWorkers: Math.min(2, input.activeSlots),
|
|
423
|
+
worktreePolicy: {
|
|
424
|
+
mode: 'patch-envelope-only',
|
|
425
|
+
required: false,
|
|
426
|
+
main_repo_root: input.worktreePolicy.main_repo_root,
|
|
427
|
+
worktree_root: null,
|
|
428
|
+
fallback_reason: 'pre_run_smoke_never_owns_production_runtime'
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
const smokeWorktreePolicy = {
|
|
432
|
+
mode: 'patch-envelope-only',
|
|
433
|
+
required: false,
|
|
434
|
+
main_repo_root: input.worktreePolicy.main_repo_root,
|
|
435
|
+
worktree_root: null,
|
|
436
|
+
fallback_reason: 'pre_run_smoke_never_owns_production_runtime'
|
|
437
|
+
};
|
|
438
|
+
const realActivePool = await runNarutoRealActivePool({
|
|
439
|
+
graph: smokeGraph,
|
|
440
|
+
governor: { ...input.governor, safe_active_workers: Math.min(2, input.activeSlots), safe_zellij_visible_panes: Math.min(1, input.zellijVisiblePanes) },
|
|
441
|
+
spawnWorker: async (item, placement) => spawnActualNarutoWorker({
|
|
442
|
+
root: input.root,
|
|
443
|
+
missionId: input.missionId,
|
|
444
|
+
item,
|
|
445
|
+
placement,
|
|
446
|
+
backend: 'fake',
|
|
447
|
+
worktreePolicy: smokeWorktreePolicy,
|
|
448
|
+
zellijSessionName: `sks-${input.missionId}`,
|
|
449
|
+
visiblePaneCap: input.zellijVisiblePanes
|
|
450
|
+
}),
|
|
451
|
+
collectWorker: async (handle) => collectActualNarutoWorker(handle),
|
|
452
|
+
enqueueVerification: async () => undefined,
|
|
453
|
+
updateDashboard: async () => undefined
|
|
454
|
+
});
|
|
455
|
+
return {
|
|
456
|
+
...realActivePool,
|
|
457
|
+
status: 'smoke_completed',
|
|
458
|
+
runtime_source_of_truth: 'pre_run_smoke_only',
|
|
459
|
+
production_runtime_source_of_truth: 'agent-orchestrator-scheduler',
|
|
460
|
+
fallback_reason: 'pre_run_smoke_never_owns_production_runtime',
|
|
461
|
+
smoke_graph_total_work_items: smokeGraph.total_work_items
|
|
462
|
+
};
|
|
463
|
+
}
|
|
412
464
|
function buildNarutoAllocationWorkers(workGraph, roleDistribution, roster) {
|
|
413
465
|
const workItems = Array.isArray(workGraph?.work_items) ? workGraph.work_items : [];
|
|
414
466
|
const roleByWorkItem = new Map((roleDistribution?.work_item_roles || []).map((row) => [String(row.work_item_id), String(row.role || '')]));
|
|
@@ -582,9 +634,10 @@ function parseNarutoArgs(args = []) {
|
|
|
582
634
|
const ollamaBaseUrl = String(readOption(args, '--ollama-base-url', readOption(args, '--local-model-base-url', '')) || '') || null;
|
|
583
635
|
const noOpenZellij = hasFlag(args, '--no-open-zellij') || hasFlag(args, '--no-zellij');
|
|
584
636
|
const attach = hasFlag(args, '--attach');
|
|
637
|
+
const smoke = hasFlag(args, '--smoke');
|
|
585
638
|
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']);
|
|
586
639
|
const prompt = positionalArgs(rest, valueFlags).join(' ').trim() || 'Naruto shadow clone swarm run';
|
|
587
|
-
return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach };
|
|
640
|
+
return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach, smoke };
|
|
588
641
|
}
|
|
589
642
|
async function writeNarutoArtifacts(ledgerRoot, artifacts) {
|
|
590
643
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-work-graph.json'), artifacts.workGraph);
|
|
@@ -14,6 +14,15 @@ import { scanDbSafety } from '../db-safety.js';
|
|
|
14
14
|
import { maybeFinalizeRoute } from '../proof/auto-finalize.js';
|
|
15
15
|
import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
|
|
16
16
|
import { flag, positionalArgs, readFlagValue, readMaxCycles, readBoundedIntegerFlag, resolveMissionId, safeReadTextFile } from './command-utils.js';
|
|
17
|
+
import { writeResearchWorkGraph } from '../research/research-work-graph.js';
|
|
18
|
+
import { runResearchCycle } from '../research/research-cycle-runner.js';
|
|
19
|
+
import { readResearchQualityContract } from '../research/research-quality-contract.js';
|
|
20
|
+
import { readClaimEvidenceMatrix } from '../research/claim-evidence-matrix.js';
|
|
21
|
+
import { readSourceQualityReport } from '../research/source-quality-report.js';
|
|
22
|
+
import { readImplementationBlueprint, validateImplementationBlueprint } from '../research/implementation-blueprint.js';
|
|
23
|
+
import { readExperimentPlan, validateExperimentPlan } from '../research/experiment-plan.js';
|
|
24
|
+
import { readReplicationPack, validateReplicationPack } from '../research/replication-pack.js';
|
|
25
|
+
import { readResearchFinalReview } from '../research/research-final-reviewer.js';
|
|
17
26
|
const RESEARCH_DEFAULT_MAX_CYCLES = 12;
|
|
18
27
|
const RESEARCH_DEFAULT_CYCLE_TIMEOUT_MINUTES = 120;
|
|
19
28
|
const RESEARCH_MIN_CYCLE_TIMEOUT_MINUTES = 15;
|
|
@@ -128,9 +137,12 @@ async function researchRun(args) {
|
|
|
128
137
|
const dryRunPatches = flag(args, '--dry-run-patches') || flag(args, '--dryrun-patches');
|
|
129
138
|
const maxWriteAgents = readBoundedIntegerFlag(args, '--max-write-agents', Math.min(requestedAgents, 5), 1, 20);
|
|
130
139
|
const mock = flag(args, '--mock');
|
|
140
|
+
const researchWorkGraph = await writeResearchWorkGraph(dir, plan);
|
|
141
|
+
const graphWorkItemCount = Math.max(1, Number(researchWorkGraph.total_work_items || researchWorkGraph.work_items?.length || 0));
|
|
142
|
+
await runResearchCycle(dir, researchWorkGraph, { cycle: 0, status: mock ? 'mock_native_orchestrator_planned' : 'native_orchestrator_planned' });
|
|
131
143
|
await setCurrent(root, { mission_id: id, mode: 'RESEARCH', phase: 'RESEARCH_RUNNING_NO_QUESTIONS', questions_allowed: false, implementation_allowed: false, research_real_run_required: !mock, research_cycle_timeout_minutes: cycleTimeoutMinutes });
|
|
132
144
|
await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.run.started', maxCycles, mock, cycleTimeoutMinutes, real_run_required: !mock });
|
|
133
|
-
const nativeAgentRun = await runNativeAgentOrchestrator({ root, missionId: id, route: flag(args, '--autoresearch') ? '$AutoResearch' : '$Research', prompt: mission.prompt || plan.prompt || 'Research run', backend: mock ? 'fake' : 'codex-sdk', mock, agents: requestedAgents, targetActiveSlots, desiredWorkItemCount, minimumWorkItems, maxQueueExpansion, concurrency: Math.min(requestedAgents, 5), readonly:
|
|
145
|
+
const nativeAgentRun = await runNativeAgentOrchestrator({ root, missionId: id, route: flag(args, '--autoresearch') ? '$AutoResearch' : '$Research', prompt: mission.prompt || plan.prompt || 'Research run', backend: mock ? 'fake' : 'codex-sdk', mock, agents: requestedAgents, targetActiveSlots, desiredWorkItemCount: Math.max(desiredWorkItemCount, graphWorkItemCount), minimumWorkItems: Math.max(minimumWorkItems, Math.min(graphWorkItemCount, targetActiveSlots)), maxQueueExpansion, concurrency: Math.min(requestedAgents, 5), readonly: true, profile, writeMode: writeMode, applyPatches: false, dryRunPatches, maxWriteAgents, roster: plan.native_agent_plan, routeCommand: 'sks research run', routeBlackboxKind: 'actual_research_command', narutoWorkGraph: researchWorkGraph });
|
|
134
146
|
await writeJsonAtomic(path.join(dir, 'research-native-agent-run.json'), nativeAgentRun);
|
|
135
147
|
await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.native_agents.completed', backend: nativeAgentRun.backend, ok: nativeAgentRun.ok, proof: nativeAgentRun.proof?.status });
|
|
136
148
|
if (mock) {
|
|
@@ -141,7 +153,7 @@ async function researchRun(args) {
|
|
|
141
153
|
const proof = await maybeFinalizeRoute(root, { missionId: id, route: '$Research', gateFile: 'research-gate.json', gate: gate.gate || gate, artifacts: ['agents/agent-proof-evidence.json', 'research-native-agent-run.json', 'research-gate.json', 'research-report.md', researchPaperArtifactForPlan(plan), 'source-ledger.json', 'agent-ledger.json', 'debate-ledger.json', 'completion-proof.json'], mock, command: { cmd: `sks research run ${id} --mock`, status: 0 } });
|
|
142
154
|
await setCurrent(root, { mission_id: id, mode: 'RESEARCH', phase: gate.passed ? 'RESEARCH_DONE' : 'RESEARCH_PAUSED', questions_allowed: true, implementation_allowed: false });
|
|
143
155
|
if (flag(args, '--json'))
|
|
144
|
-
return console.log(JSON.stringify({ schema: flag(args, '--autoresearch') ? 'sks.autoresearch-run.v1' : 'sks.research-run.v1', ok: proof.ok, mission_id: id, gate, proof: proof.validation, native_agent_run: nativeAgentRun, agent_batches: plan.agent_batches, autoresearch_cycle_policy: plan.autoresearch_cycle_policy }, null, 2));
|
|
156
|
+
return console.log(JSON.stringify({ schema: flag(args, '--autoresearch') ? 'sks.autoresearch-run.v1' : 'sks.research-run.v1', ok: proof.ok, mission_id: id, gate, quality_metrics: gate.metrics || null, proof: proof.validation, native_agent_run: nativeAgentRun, research_work_graph: researchWorkGraph, agent_batches: plan.agent_batches, autoresearch_cycle_policy: plan.autoresearch_cycle_policy }, null, 2));
|
|
145
157
|
console.log(`Mock research done: ${id}`);
|
|
146
158
|
console.log(`Gate: ${gate.passed ? 'passed' : 'blocked'}`);
|
|
147
159
|
return;
|
|
@@ -178,6 +190,7 @@ async function researchRun(args) {
|
|
|
178
190
|
const cycleDir = path.join(dir, 'research', `cycle-${cycle}`);
|
|
179
191
|
const outputFile = path.join(cycleDir, 'final.md');
|
|
180
192
|
await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.cycle.start', cycle, timeoutMinutes: cycleTimeoutMinutes, profile, enforced_reasoning_effort: 'xhigh' });
|
|
193
|
+
await runResearchCycle(dir, researchWorkGraph, { cycle, status: 'codex_research_cycle_started' });
|
|
181
194
|
const prompt = buildResearchPrompt({ id, mission, plan, cycle, previous: last });
|
|
182
195
|
const result = await runCodexExec({ root, prompt, outputFile, json: true, profile, extraArgs: researchCodexArgs, logDir: cycleDir, timeoutMs: cycleTimeoutMs });
|
|
183
196
|
await writeJsonAtomic(path.join(cycleDir, 'process.json'), { code: result.code, stdout_tail: result.stdout, stderr_tail: result.stderr, stdout_bytes: result.stdoutBytes, stderr_bytes: result.stderrBytes, truncated: result.truncated, timed_out: result.timedOut });
|
|
@@ -212,7 +225,7 @@ async function researchRun(args) {
|
|
|
212
225
|
await appendJsonlBounded(path.join(dir, 'events.jsonl'), { ts: nowIso(), type: 'research.done', cycle });
|
|
213
226
|
await enforceRetention(root).catch(() => { });
|
|
214
227
|
if (flag(args, '--json'))
|
|
215
|
-
return console.log(JSON.stringify({ schema: flag(args, '--autoresearch') ? 'sks.autoresearch-run.v1' : 'sks.research-run.v1', ok: proof.ok, mission_id: id, gate, proof: proof.validation, agent_batches: plan.agent_batches, autoresearch_cycle_policy: plan.autoresearch_cycle_policy }, null, 2));
|
|
228
|
+
return console.log(JSON.stringify({ schema: flag(args, '--autoresearch') ? 'sks.autoresearch-run.v1' : 'sks.research-run.v1', ok: proof.ok, mission_id: id, gate, quality_metrics: gate.metrics || null, proof: proof.validation, research_work_graph: researchWorkGraph, agent_batches: plan.agent_batches, autoresearch_cycle_policy: plan.autoresearch_cycle_policy }, null, 2));
|
|
216
229
|
console.log(`Research done: ${id}`);
|
|
217
230
|
return;
|
|
218
231
|
}
|
|
@@ -246,6 +259,16 @@ async function researchStatus(args) {
|
|
|
246
259
|
const agentRows = Array.isArray(agentLedger?.agents) ? agentLedger.agents : [];
|
|
247
260
|
const sourceLayerRows = Array.isArray(sourceLedger?.source_layers) ? sourceLedger.source_layers : [];
|
|
248
261
|
const sourceLayersCovered = sourceLayerRows.filter((layer) => layer.status === 'covered' && ((Array.isArray(layer.source_ids) && layer.source_ids.length) || (Array.isArray(layer.counterevidence_ids) && layer.counterevidence_ids.length))).length;
|
|
262
|
+
const qualityContract = await readResearchQualityContract(dir);
|
|
263
|
+
const claimMatrix = await readClaimEvidenceMatrix(dir);
|
|
264
|
+
const sourceQualityReport = await readSourceQualityReport(dir);
|
|
265
|
+
const implementationBlueprint = await readImplementationBlueprint(dir);
|
|
266
|
+
const experimentPlan = await readExperimentPlan(dir);
|
|
267
|
+
const replicationPack = await readReplicationPack(dir);
|
|
268
|
+
const finalReview = await readResearchFinalReview(dir);
|
|
269
|
+
const blueprintValidation = validateImplementationBlueprint(implementationBlueprint, qualityContract);
|
|
270
|
+
const experimentValidation = validateExperimentPlan(experimentPlan, qualityContract);
|
|
271
|
+
const replicationValidation = validateReplicationPack(replicationPack);
|
|
249
272
|
console.log(JSON.stringify({
|
|
250
273
|
mission,
|
|
251
274
|
state,
|
|
@@ -275,7 +298,23 @@ async function researchStatus(args) {
|
|
|
275
298
|
research_paper_artifact: paperArtifact.name,
|
|
276
299
|
paper_present: Boolean(paperText.trim()),
|
|
277
300
|
paper_sections: countResearchPaperSections(paperText),
|
|
278
|
-
falsification_cases: falsificationLedger?.cases?.length ?? null
|
|
301
|
+
falsification_cases: falsificationLedger?.cases?.length ?? null,
|
|
302
|
+
research_quality: {
|
|
303
|
+
contract: qualityContract,
|
|
304
|
+
report_word_count: gate?.metrics?.report_word_count ?? null,
|
|
305
|
+
claim_evidence_matrix_present: claimMatrix.present,
|
|
306
|
+
key_claims: claimMatrix.key_claim_ids.length,
|
|
307
|
+
triangulated_claims: claimMatrix.triangulated_claim_count,
|
|
308
|
+
claim_matrix_blockers: claimMatrix.blockers,
|
|
309
|
+
source_quality_report_ok: sourceQualityReport?.ok === true,
|
|
310
|
+
implementation_blueprint_sections: Array.isArray(implementationBlueprint?.sections) ? implementationBlueprint.sections.length : null,
|
|
311
|
+
implementation_blueprint_ok: blueprintValidation.ok,
|
|
312
|
+
experiment_steps: Array.isArray(experimentPlan?.steps) ? experimentPlan.steps.length : null,
|
|
313
|
+
experiment_plan_ok: experimentValidation.ok,
|
|
314
|
+
replication_pack_ok: replicationValidation.ok,
|
|
315
|
+
final_review_approved: finalReview?.approved === true,
|
|
316
|
+
final_review_blockers: finalReview?.blockers || []
|
|
317
|
+
}
|
|
279
318
|
}, null, 2));
|
|
280
319
|
}
|
|
281
320
|
async function researchCodeMutationSnapshot(root, missionId = null) {
|
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 = '2.0.
|
|
8
|
+
export const PACKAGE_VERSION = '2.0.13';
|
|
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() {
|
|
@@ -54,14 +54,34 @@ export async function applyGitWorktreeMergeQueue(input) {
|
|
|
54
54
|
input: diff.diff,
|
|
55
55
|
timeoutMs: 30000
|
|
56
56
|
});
|
|
57
|
-
if (apply.ok)
|
|
57
|
+
if (apply.ok) {
|
|
58
58
|
appliedCount += 1;
|
|
59
|
+
strategyResults.push({
|
|
60
|
+
ok: true,
|
|
61
|
+
worker_id: diff.worker_id,
|
|
62
|
+
strategy: 'diff-apply-3way',
|
|
63
|
+
commit_hash: null,
|
|
64
|
+
conflict_files: [],
|
|
65
|
+
changed_files: diff.changed_files,
|
|
66
|
+
blockers: []
|
|
67
|
+
});
|
|
68
|
+
}
|
|
59
69
|
else {
|
|
60
|
-
|
|
70
|
+
const conflict = summarizeGitWorktreeConflict({
|
|
61
71
|
workerId: diff.worker_id,
|
|
62
72
|
changedFiles: diff.changed_files,
|
|
63
73
|
stderr: apply.stderr || apply.stdout
|
|
64
|
-
})
|
|
74
|
+
});
|
|
75
|
+
strategyResults.push({
|
|
76
|
+
ok: false,
|
|
77
|
+
worker_id: diff.worker_id,
|
|
78
|
+
strategy: 'diff-apply-3way',
|
|
79
|
+
commit_hash: null,
|
|
80
|
+
conflict_files: conflict.conflict_files || diff.changed_files,
|
|
81
|
+
changed_files: diff.changed_files,
|
|
82
|
+
blockers: conflict.blockers || ['git_worktree_diff_apply_failed']
|
|
83
|
+
});
|
|
84
|
+
conflicts.push(conflict);
|
|
65
85
|
}
|
|
66
86
|
}
|
|
67
87
|
const blockers = conflicts.length ? ['git_worktree_merge_queue_conflicts'] : [];
|
|
@@ -80,44 +100,44 @@ export async function applyGitWorktreeMergeQueue(input) {
|
|
|
80
100
|
};
|
|
81
101
|
}
|
|
82
102
|
async function applyCheckpointCommit(integrationWorktreePath, checkpoint) {
|
|
83
|
-
const
|
|
103
|
+
const cherryPick = await runGitCommand(integrationWorktreePath, ['cherry-pick', '--allow-empty', '-X', 'theirs', checkpoint.commit_hash || ''], {
|
|
84
104
|
timeoutMs: 120000
|
|
85
105
|
});
|
|
86
|
-
if (
|
|
106
|
+
if (cherryPick.ok) {
|
|
87
107
|
return {
|
|
88
108
|
ok: true,
|
|
89
109
|
worker_id: checkpoint.worker_id,
|
|
90
|
-
strategy: '
|
|
110
|
+
strategy: 'checkpoint-cherry-pick',
|
|
91
111
|
commit_hash: checkpoint.commit_hash,
|
|
92
112
|
conflict_files: [],
|
|
93
113
|
blockers: []
|
|
94
114
|
};
|
|
95
115
|
}
|
|
96
|
-
await runGitCommand(integrationWorktreePath, ['
|
|
97
|
-
const
|
|
116
|
+
await runGitCommand(integrationWorktreePath, ['cherry-pick', '--abort'], { timeoutMs: 30000 }).catch(() => null);
|
|
117
|
+
const merge = await runGitCommand(integrationWorktreePath, ['merge', '--no-ff', '--no-edit', '-X', 'theirs', checkpoint.commit_hash || ''], {
|
|
98
118
|
timeoutMs: 120000
|
|
99
119
|
});
|
|
100
|
-
if (
|
|
120
|
+
if (merge.ok) {
|
|
101
121
|
return {
|
|
102
122
|
ok: true,
|
|
103
123
|
worker_id: checkpoint.worker_id,
|
|
104
|
-
strategy: '
|
|
124
|
+
strategy: 'checkpoint-merge',
|
|
105
125
|
commit_hash: checkpoint.commit_hash,
|
|
106
126
|
conflict_files: [],
|
|
107
127
|
blockers: []
|
|
108
128
|
};
|
|
109
129
|
}
|
|
110
130
|
const conflictFiles = await runGitCommand(integrationWorktreePath, ['diff', '--name-only', '--diff-filter=U'], { timeoutMs: 30000 }).catch(() => null);
|
|
111
|
-
await runGitCommand(integrationWorktreePath, ['
|
|
131
|
+
await runGitCommand(integrationWorktreePath, ['merge', '--abort'], { timeoutMs: 30000 }).catch(() => null);
|
|
112
132
|
return {
|
|
113
133
|
ok: false,
|
|
114
134
|
worker_id: checkpoint.worker_id,
|
|
115
|
-
strategy: '
|
|
135
|
+
strategy: 'checkpoint-cherry-pick-then-merge',
|
|
116
136
|
commit_hash: checkpoint.commit_hash,
|
|
117
137
|
conflict_files: String(conflictFiles?.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean),
|
|
118
138
|
blockers: [
|
|
119
|
-
`
|
|
120
|
-
`
|
|
139
|
+
`git_worktree_checkpoint_cherry_pick_failed:${cherryPick.stderr_tail || cherryPick.stdout_tail}`,
|
|
140
|
+
`git_worktree_checkpoint_merge_failed:${merge.stderr_tail || merge.stdout_tail}`
|
|
121
141
|
]
|
|
122
142
|
};
|
|
123
143
|
}
|
|
@@ -3,11 +3,14 @@ export function rebalanceNarutoReadyWork(input) {
|
|
|
3
3
|
const completed = new Set((input.completedTaskIds || []).map(String));
|
|
4
4
|
const reclaimed = new Set((input.reclaimedTaskIds || []).map(String));
|
|
5
5
|
const idle = input.workers.filter((worker) => worker.alive && ['idle', 'done', 'unknown'].includes(worker.state));
|
|
6
|
+
const activeWorkerIds = new Set(input.workers.filter((worker) => worker.alive).map((worker) => worker.id));
|
|
7
|
+
const activeWritePaths = new Set([...(input.activeWritePaths || []), ...(input.currentAssignments || []).flatMap((row) => row.write_paths || [])].map(normalizePath));
|
|
6
8
|
if (!idle.length)
|
|
7
9
|
return [];
|
|
8
10
|
const ready = input.tasks
|
|
9
|
-
.filter((task) => (task.status || 'pending') === 'pending'
|
|
11
|
+
.filter((task) => (task.status || 'pending') === 'pending')
|
|
10
12
|
.filter((task) => task.dependencies.every((dep) => completed.has(dep)))
|
|
13
|
+
.filter((task) => task.write_paths.every((file) => !activeWritePaths.has(normalizePath(file))))
|
|
11
14
|
.sort((left, right) => {
|
|
12
15
|
const reclaimedOrder = Number(!reclaimed.has(left.id)) - Number(!reclaimed.has(right.id));
|
|
13
16
|
return reclaimedOrder || left.id.localeCompare(right.id);
|
|
@@ -15,12 +18,18 @@ export function rebalanceNarutoReadyWork(input) {
|
|
|
15
18
|
const decisions = [];
|
|
16
19
|
const assignments = [...(input.currentAssignments || [])];
|
|
17
20
|
for (const task of ready) {
|
|
18
|
-
const
|
|
21
|
+
const requestedOwner = task.owner ? String(task.owner) : '';
|
|
22
|
+
const ownerActive = requestedOwner && activeWorkerIds.has(requestedOwner);
|
|
23
|
+
const ownerIdle = ownerActive ? idle.some((worker) => worker.id === requestedOwner) : false;
|
|
24
|
+
if (requestedOwner && ownerActive && !ownerIdle)
|
|
25
|
+
continue;
|
|
26
|
+
const candidateWorkers = ownerIdle ? idle.filter((worker) => worker.id === requestedOwner) : idle;
|
|
27
|
+
const decision = chooseNarutoTaskOwner({ ...task, owner: null }, candidateWorkers, assignments);
|
|
19
28
|
decisions.push({
|
|
20
29
|
type: 'assign',
|
|
21
30
|
task_id: task.id,
|
|
22
31
|
worker_id: decision.owner,
|
|
23
|
-
reason: `${reclaimed.has(task.id) ? 'reclaimed ready work' : 'idle worker pickup'}; ${decision.reason}`
|
|
32
|
+
reason: `${reclaimed.has(task.id) ? 'reclaimed ready work' : requestedOwner && !ownerActive ? `owner inactive:${requestedOwner}` : 'idle worker pickup'}; ${decision.reason}`
|
|
24
33
|
});
|
|
25
34
|
assignments.push({
|
|
26
35
|
task_id: task.id,
|
|
@@ -33,4 +42,7 @@ export function rebalanceNarutoReadyWork(input) {
|
|
|
33
42
|
}
|
|
34
43
|
return decisions;
|
|
35
44
|
}
|
|
45
|
+
function normalizePath(file) {
|
|
46
|
+
return String(file || '').replace(/\\/g, '/').replace(/^\.\/+/, '').replace(/\/+$/, '');
|
|
47
|
+
}
|
|
36
48
|
//# sourceMappingURL=naruto-rebalance-policy.js.map
|
|
@@ -42,6 +42,12 @@ export function buildNarutoWorkGraph(input = {}) {
|
|
|
42
42
|
fallback_reason: writeCapable ? 'git_capability_not_evaluated' : 'readonly_or_write_disabled'
|
|
43
43
|
};
|
|
44
44
|
const workItems = [];
|
|
45
|
+
const assignmentById = new Map();
|
|
46
|
+
for (const row of input.allocationAssignments || []) {
|
|
47
|
+
const id = String(row.task_id || row.id || '');
|
|
48
|
+
if (id)
|
|
49
|
+
assignmentById.set(id, row);
|
|
50
|
+
}
|
|
45
51
|
for (let index = 0; index < totalWorkItems; index += 1) {
|
|
46
52
|
const id = `NW-${String(index + 1).padStart(6, '0')}`;
|
|
47
53
|
const kind = kindCycle[index % kindCycle.length] || 'verification';
|
|
@@ -53,6 +59,8 @@ export function buildNarutoWorkGraph(input = {}) {
|
|
|
53
59
|
...writePaths.map((file) => ({ path: file, kind: 'write' })),
|
|
54
60
|
...readPaths.map((file) => ({ path: file, kind: 'read' }))
|
|
55
61
|
];
|
|
62
|
+
const assignment = assignmentById.get(id);
|
|
63
|
+
const allocationHints = assignment?.allocation_hints || assignment?.hints || null;
|
|
56
64
|
workItems.push({
|
|
57
65
|
id,
|
|
58
66
|
kind,
|
|
@@ -73,6 +81,11 @@ export function buildNarutoWorkGraph(input = {}) {
|
|
|
73
81
|
requires_verification: kind !== 'research' && kind !== 'final_review_input_pack',
|
|
74
82
|
requires_gpt_final: writePaths.length > 0 || kind === 'final_review_input_pack'
|
|
75
83
|
},
|
|
84
|
+
owner: assignment?.owner ?? null,
|
|
85
|
+
allocation_reason: assignment?.allocation_reason ?? null,
|
|
86
|
+
allocation_score: assignment?.allocation_score ?? null,
|
|
87
|
+
allocation_hints: allocationHints,
|
|
88
|
+
lane: assignment?.owner ?? null,
|
|
76
89
|
...(writePaths.length > 0 ? {
|
|
77
90
|
worktree: {
|
|
78
91
|
mode: worktreePolicy.mode,
|