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.
Files changed (85) hide show
  1. package/README.md +1 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/cli/command-registry.js +1 -0
  8. package/dist/cli/context7-command.js +29 -5
  9. package/dist/cli/install-helpers.js +15 -7
  10. package/dist/commands/zellij-slot-column-anchor.js +3 -1
  11. package/dist/commands/zellij-slot-pane.js +19 -2
  12. package/dist/core/agents/agent-janitor.js +10 -1
  13. package/dist/core/agents/agent-orchestrator.js +1 -0
  14. package/dist/core/agents/agent-runner-ollama.js +11 -4
  15. package/dist/core/agents/native-cli-session-swarm.js +69 -9
  16. package/dist/core/agents/runtime-proof-summary.js +4 -0
  17. package/dist/core/codex-control/codex-task-runner.js +9 -0
  18. package/dist/core/commands/goal-command.js +19 -1
  19. package/dist/core/commands/loop-command.js +176 -0
  20. package/dist/core/commands/naruto-command.js +26 -17
  21. package/dist/core/commands/team-command.js +1 -0
  22. package/dist/core/fsx.js +1 -1
  23. package/dist/core/init.js +6 -1
  24. package/dist/core/locks/file-lock.js +88 -0
  25. package/dist/core/loops/goal-to-loop-compat.js +23 -0
  26. package/dist/core/loops/loop-artifacts.js +72 -0
  27. package/dist/core/loops/loop-checkpoint.js +22 -0
  28. package/dist/core/loops/loop-decomposer.js +56 -0
  29. package/dist/core/loops/loop-finalizer.js +54 -0
  30. package/dist/core/loops/loop-gate-ladder.js +16 -0
  31. package/dist/core/loops/loop-gate-registry.js +96 -0
  32. package/dist/core/loops/loop-gate-runner.js +177 -0
  33. package/dist/core/loops/loop-gate-selector.js +52 -0
  34. package/dist/core/loops/loop-gpt-final-arbiter.js +61 -0
  35. package/dist/core/loops/loop-integration-merge.js +75 -0
  36. package/dist/core/loops/loop-iteration-runner.js +2 -0
  37. package/dist/core/loops/loop-lease.js +91 -0
  38. package/dist/core/loops/loop-observability.js +19 -0
  39. package/dist/core/loops/loop-owner-inference.js +57 -0
  40. package/dist/core/loops/loop-owner-ledger.js +2 -0
  41. package/dist/core/loops/loop-planner.js +170 -0
  42. package/dist/core/loops/loop-proof-summary.js +10 -0
  43. package/dist/core/loops/loop-proof.js +2 -0
  44. package/dist/core/loops/loop-risk-classifier.js +42 -0
  45. package/dist/core/loops/loop-runtime-control.js +25 -0
  46. package/dist/core/loops/loop-runtime.js +314 -0
  47. package/dist/core/loops/loop-scheduler.js +69 -0
  48. package/dist/core/loops/loop-schema.js +63 -0
  49. package/dist/core/loops/loop-state.js +61 -0
  50. package/dist/core/loops/loop-worker-prompts.js +43 -0
  51. package/dist/core/loops/loop-worker-runtime.js +275 -0
  52. package/dist/core/loops/loop-worktree-runtime.js +92 -0
  53. package/dist/core/naruto/naruto-finalizer.js +7 -2
  54. package/dist/core/naruto/naruto-loop-mesh.js +39 -0
  55. package/dist/core/naruto/naruto-loop-worker-router.js +38 -0
  56. package/dist/core/pipeline-internals/runtime-core.js +82 -2
  57. package/dist/core/proof/proof-schema.js +6 -0
  58. package/dist/core/proof/proof-writer.js +5 -2
  59. package/dist/core/proof/root-cause-policy.js +70 -0
  60. package/dist/core/proof/route-adapter.js +18 -1
  61. package/dist/core/proof/route-proof-gate.js +4 -0
  62. package/dist/core/release/release-gate-batch-runner.js +56 -10
  63. package/dist/core/release/release-gate-cache-v2.js +18 -3
  64. package/dist/core/release/release-gate-dag.js +65 -17
  65. package/dist/core/release/release-gate-node.js +2 -1
  66. package/dist/core/release/release-gate-resource-governor.js +27 -6
  67. package/dist/core/skills/core-skill-meta-update.js +24 -0
  68. package/dist/core/skills/core-skill-reflection.js +94 -0
  69. package/dist/core/skills/core-skill-trainer.js +103 -0
  70. package/dist/core/trust-kernel/completion-contract.js +4 -0
  71. package/dist/core/trust-kernel/route-contract.js +4 -1
  72. package/dist/core/version.js +1 -1
  73. package/dist/core/zellij/zellij-right-column-manager.js +13 -2
  74. package/dist/core/zellij/zellij-slot-column-anchor.js +45 -5
  75. package/dist/core/zellij/zellij-slot-pane-renderer.js +37 -10
  76. package/dist/core/zellij/zellij-slot-telemetry.js +96 -44
  77. package/dist/core/zellij/zellij-worker-pane-manager.js +42 -4
  78. package/dist/scripts/loop-directive-check-lib.js +388 -0
  79. package/dist/scripts/loop-worker-fixture-child.js +53 -0
  80. package/dist/scripts/naruto-real-local-gpt-final-smoke.js +10 -1
  81. package/package.json +38 -3
  82. package/schemas/loops/loop-node.schema.json +21 -0
  83. package/schemas/loops/loop-plan.schema.json +21 -0
  84. package/schemas/loops/loop-proof.schema.json +20 -0
  85. 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: writeCapable
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: result.ok === true,
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.0.4';
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
- { table: 'mcp_servers.context7', text: context7ConfigToml().trim() },
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