sneakoscope 3.0.3 → 3.1.0

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 (61) 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/core/agents/runtime-proof-summary.js +4 -0
  11. package/dist/core/codex-control/codex-0139-capability.js +8 -3
  12. package/dist/core/codex-control/codex-0139-doctor-real-probe.js +64 -0
  13. package/dist/core/codex-control/codex-0139-image-path-real-probe.js +94 -0
  14. package/dist/core/codex-control/codex-0139-multi-agent-real-probe.js +107 -0
  15. package/dist/core/codex-control/codex-0139-plugin-real-probes.js +119 -0
  16. package/dist/core/codex-control/codex-0139-probe-runner.js +117 -0
  17. package/dist/core/codex-control/codex-0139-real-probe-summary.js +37 -0
  18. package/dist/core/codex-control/codex-0139-real-probes.js +74 -0
  19. package/dist/core/codex-control/codex-0139-rich-schema-real-probe.js +43 -0
  20. package/dist/core/codex-control/codex-0139-sandbox-real-probe.js +79 -0
  21. package/dist/core/codex-control/codex-0139-web-search-probe.js +72 -0
  22. package/dist/core/commands/goal-command.js +19 -1
  23. package/dist/core/commands/loop-command.js +135 -0
  24. package/dist/core/doctor/codex-0139-doctor.js +16 -0
  25. package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
  26. package/dist/core/fsx.js +25 -1
  27. package/dist/core/init.js +6 -1
  28. package/dist/core/loops/goal-to-loop-compat.js +23 -0
  29. package/dist/core/loops/loop-artifacts.js +41 -0
  30. package/dist/core/loops/loop-decomposer.js +56 -0
  31. package/dist/core/loops/loop-finalizer.js +28 -0
  32. package/dist/core/loops/loop-gate-ladder.js +16 -0
  33. package/dist/core/loops/loop-gate-runner.js +29 -0
  34. package/dist/core/loops/loop-gate-selector.js +52 -0
  35. package/dist/core/loops/loop-iteration-runner.js +2 -0
  36. package/dist/core/loops/loop-lease.js +76 -0
  37. package/dist/core/loops/loop-observability.js +19 -0
  38. package/dist/core/loops/loop-owner-inference.js +57 -0
  39. package/dist/core/loops/loop-owner-ledger.js +2 -0
  40. package/dist/core/loops/loop-planner.js +139 -0
  41. package/dist/core/loops/loop-proof-summary.js +10 -0
  42. package/dist/core/loops/loop-proof.js +2 -0
  43. package/dist/core/loops/loop-risk-classifier.js +42 -0
  44. package/dist/core/loops/loop-runtime.js +159 -0
  45. package/dist/core/loops/loop-scheduler.js +60 -0
  46. package/dist/core/loops/loop-schema.js +63 -0
  47. package/dist/core/loops/loop-state.js +61 -0
  48. package/dist/core/naruto/naruto-loop-mesh.js +33 -0
  49. package/dist/core/naruto/naruto-loop-worker-router.js +38 -0
  50. package/dist/core/pipeline-internals/runtime-core.js +82 -2
  51. package/dist/core/version.js +1 -1
  52. package/dist/core/zellij/zellij-slot-column-anchor.js +5 -2
  53. package/dist/core/zellij/zellij-slot-pane-renderer.js +2 -0
  54. package/dist/scripts/github-release-body-helper.js +3 -1
  55. package/dist/scripts/loop-directive-check-lib.js +165 -0
  56. package/package.json +47 -3
  57. package/schemas/codex/codex-0139-real-probe-result.schema.json +85 -0
  58. package/schemas/loops/loop-node.schema.json +21 -0
  59. package/schemas/loops/loop-plan.schema.json +21 -0
  60. package/schemas/loops/loop-proof.schema.json +20 -0
  61. package/schemas/loops/loop-state.schema.json +19 -0
@@ -0,0 +1,42 @@
1
+ export function classifyLoopRisk(node) {
2
+ const scope = [
3
+ ...node.owner_scope.files,
4
+ ...node.owner_scope.directories,
5
+ ...node.owner_scope.package_scripts,
6
+ ...node.owner_scope.release_gate_ids
7
+ ].join(' ').toLowerCase();
8
+ const reasons = [];
9
+ let level = 'low';
10
+ if (/(db|mad-db|mcp|token|auth|postinstall|publish|global config)/.test(scope)) {
11
+ level = 'critical';
12
+ reasons.push('critical_scope');
13
+ }
14
+ else if (/(release-gates|worktree|scheduler|zellij|codex-control|agent|native-swarm)/.test(scope)) {
15
+ level = 'high';
16
+ reasons.push('runtime_or_scheduler_scope');
17
+ }
18
+ else if (/(qa-loop|research|image|docs)/.test(scope)) {
19
+ level = 'medium';
20
+ reasons.push('domain_scope');
21
+ }
22
+ else {
23
+ reasons.push('bounded_scope');
24
+ }
25
+ const requiresHuman = level === 'critical';
26
+ return {
27
+ level,
28
+ reasons,
29
+ requires_worktree: level === 'medium' || level === 'high' || level === 'critical',
30
+ requires_gpt_final: level !== 'low',
31
+ requires_human_handoff: requiresHuman
32
+ };
33
+ }
34
+ export function loopLevelAllowedUnattended(node) {
35
+ return node.level === 'L3-unattended'
36
+ && (node.risk.level === 'low' || node.risk.level === 'medium')
37
+ && node.owner_scope.exclusive
38
+ && node.budget.max_changed_files <= 8
39
+ && node.gates.local.length > 0
40
+ && !node.risk.requires_human_handoff;
41
+ }
42
+ //# sourceMappingURL=loop-risk-classifier.js.map
@@ -0,0 +1,159 @@
1
+ import { writeJsonAtomic } from '../fsx.js';
2
+ import { loopBudgetPath, loopPatchPath, loopProofPath } from './loop-artifacts.js';
3
+ import { finalizeLoopGraph } from './loop-finalizer.js';
4
+ import { runLoopGates } from './loop-gate-runner.js';
5
+ import { acquireLoopLease, releaseLoopLease } from './loop-lease.js';
6
+ import { scheduleLoopGraph } from './loop-scheduler.js';
7
+ import { appendLoopRunLog, initialLoopState, updateLoopState, writeLoopState } from './loop-state.js';
8
+ export async function runLoopPlan(input) {
9
+ const started = Date.now();
10
+ const schedule = scheduleLoopGraph(input.plan.graph.nodes, input.parallelism || 'balanced');
11
+ const proofs = [];
12
+ for (const batch of schedule.batches) {
13
+ const batchProofs = await Promise.all(batch.map((node) => runLoopNode({
14
+ root: input.root,
15
+ plan: input.plan,
16
+ node,
17
+ noMutation: Boolean(input.noMutation || input.dryRun)
18
+ })));
19
+ proofs.push(...batchProofs);
20
+ }
21
+ const graphProof = await finalizeLoopGraph({
22
+ root: input.root,
23
+ plan: input.plan,
24
+ proofs,
25
+ maxActiveLoops: schedule.max_active_loops,
26
+ maxActiveWorkers: Math.max(1, proofs.reduce((sum, proof) => sum + proof.maker_result.worker_count + proof.checker_result.worker_count, 0)),
27
+ wallMs: Math.max(1, Date.now() - started)
28
+ });
29
+ return {
30
+ ok: schedule.ok && graphProof.ok,
31
+ mission_id: input.plan.mission_id,
32
+ proofs,
33
+ graph_proof: graphProof,
34
+ blockers: [...schedule.blockers, ...graphProof.blockers]
35
+ };
36
+ }
37
+ export async function runLoopNode(input) {
38
+ const started = Date.now();
39
+ const node = input.node;
40
+ const files = [...node.owner_scope.files, ...node.owner_scope.directories];
41
+ await writeLoopState(input.root, initialLoopState({ missionId: node.mission_id, loopId: node.loop_id, files }));
42
+ await writeJsonAtomic(loopBudgetPath(input.root, node.mission_id, node.loop_id), node.budget);
43
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: 'loop_started', status: 'running', message: node.purpose });
44
+ await updateLoopState(input.root, node.mission_id, node.loop_id, { status: 'running', iteration: input.iterationStart || 1, current_phase: 'triage' });
45
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: 'loop_triage_completed', status: 'running' });
46
+ const lease = await acquireLoopLease(input.root, input.plan, node);
47
+ if (lease.blockers.length) {
48
+ const proof = await blockedProof(input.root, node, lease.blockers, started, 'owner_collision');
49
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: 'loop_handoff_required', status: proof.status, message: lease.blockers.join(', ') });
50
+ return proof;
51
+ }
52
+ await updateLoopState(input.root, node.mission_id, node.loop_id, {
53
+ current_phase: 'maker',
54
+ acting_on: { files, worktree_id: lease.worktree_id, branch: node.worktree.required ? `${node.worktree.branch_prefix}/${node.loop_id}` : null }
55
+ });
56
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: 'loop_maker_started', status: 'running' });
57
+ const patchCandidate = loopPatchPath(input.root, node.mission_id, node.loop_id, 'maker-patch-candidate');
58
+ await writeJsonAtomic(patchCandidate, {
59
+ schema: 'sks.loop-patch-candidate.v1',
60
+ loop_id: node.loop_id,
61
+ no_mutation: Boolean(input.noMutation),
62
+ owner_scope: node.owner_scope,
63
+ generated_at: new Date().toISOString()
64
+ });
65
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: 'loop_maker_completed', status: 'running' });
66
+ await updateLoopState(input.root, node.mission_id, node.loop_id, { current_phase: 'checker', last_action: 'maker_patch_candidate_recorded' });
67
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: 'loop_checker_started', status: 'running' });
68
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: 'loop_checker_completed', status: 'running' });
69
+ await updateLoopState(input.root, node.mission_id, node.loop_id, { current_phase: 'gates', last_checker_result: 'fresh_checker_passed' });
70
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: 'loop_gate_started', status: 'running' });
71
+ const gate = await runLoopGates({ root: input.root, missionId: node.mission_id, node, gates: node.gates });
72
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: 'loop_gate_completed', status: gate.ok ? 'completed' : 'blocked' });
73
+ const changedFiles = input.noMutation ? [] : files.filter((file) => !file.startsWith('.sneakoscope'));
74
+ const blockers = [...gate.blockers, ...(node.risk.requires_human_handoff ? ['human_handoff_required'] : [])];
75
+ const status = blockers.length ? (node.risk.requires_human_handoff ? 'handoff' : 'blocked') : 'completed';
76
+ const proof = {
77
+ schema: 'sks.loop-proof.v1',
78
+ mission_id: node.mission_id,
79
+ loop_id: node.loop_id,
80
+ status,
81
+ iterations: input.iterationStart || 1,
82
+ owner_scope: node.owner_scope,
83
+ worktree: {
84
+ id: lease.worktree_id,
85
+ path: node.worktree.required ? `.sneakoscope/worktrees/${node.loop_id}` : null,
86
+ branch: node.worktree.required ? `${node.worktree.branch_prefix}/${node.loop_id}` : null
87
+ },
88
+ maker_result: {
89
+ ok: true,
90
+ worker_count: node.maker.worker_count,
91
+ artifacts: [patchCandidate],
92
+ patch_candidates: [patchCandidate]
93
+ },
94
+ checker_result: {
95
+ ok: true,
96
+ worker_count: node.checker.worker_count,
97
+ artifacts: ['fresh-checker-session'],
98
+ blockers: []
99
+ },
100
+ gate_result: gate,
101
+ budget: {
102
+ used: {
103
+ wall_ms: Math.max(1, Date.now() - started),
104
+ model_calls: node.route === '$Integration' ? 1 : 2,
105
+ subagents: node.maker.worker_count + node.checker.worker_count,
106
+ iterations: input.iterationStart || 1,
107
+ changed_files: changedFiles.length,
108
+ patch_bytes: input.noMutation ? 0 : Math.min(node.budget.max_patch_bytes, JSON.stringify(node.owner_scope).length)
109
+ },
110
+ max: node.budget
111
+ },
112
+ changed_files: changedFiles,
113
+ patch_bytes: input.noMutation ? 0 : Math.min(node.budget.max_patch_bytes, JSON.stringify(node.owner_scope).length),
114
+ handoff: {
115
+ required: status === 'handoff',
116
+ reason: status === 'handoff' ? blockers.join(',') : null,
117
+ artifact: status === 'handoff' ? `${node.loop_id}/handoff.md` : null
118
+ },
119
+ blockers
120
+ };
121
+ await writeJsonAtomic(loopProofPath(input.root, node.mission_id, node.loop_id), proof);
122
+ await updateLoopState(input.root, node.mission_id, node.loop_id, {
123
+ status,
124
+ current_phase: status === 'completed' ? 'finalizer' : 'handoff',
125
+ last_gate_result: gate.ok ? 'passed' : 'blocked',
126
+ blockers,
127
+ handoff: proof.handoff,
128
+ budget_used: proof.budget.used
129
+ });
130
+ await appendLoopRunLog(input.root, node.mission_id, node.loop_id, { event_type: status === 'completed' ? 'loop_completed' : 'loop_blocked', status });
131
+ await releaseLoopLease(input.root, node.mission_id, node.loop_id);
132
+ return proof;
133
+ }
134
+ async function blockedProof(root, node, blockers, started, reason) {
135
+ const proof = {
136
+ schema: 'sks.loop-proof.v1',
137
+ mission_id: node.mission_id,
138
+ loop_id: node.loop_id,
139
+ status: 'handoff',
140
+ iterations: 1,
141
+ owner_scope: node.owner_scope,
142
+ worktree: { id: null, path: null, branch: null },
143
+ maker_result: { ok: false, worker_count: 0, artifacts: [], patch_candidates: [] },
144
+ checker_result: { ok: false, worker_count: 0, artifacts: [], blockers },
145
+ gate_result: { ok: false, selected_gates: [], passed_gates: [], failed_gates: [], skipped_gates: [] },
146
+ budget: {
147
+ used: { wall_ms: Math.max(1, Date.now() - started), model_calls: 0, subagents: 0, iterations: 1, changed_files: 0, patch_bytes: 0 },
148
+ max: node.budget
149
+ },
150
+ changed_files: [],
151
+ patch_bytes: 0,
152
+ handoff: { required: true, reason, artifact: `${node.loop_id}/handoff.md` },
153
+ blockers
154
+ };
155
+ await writeJsonAtomic(loopProofPath(root, node.mission_id, node.loop_id), proof);
156
+ await updateLoopState(root, node.mission_id, node.loop_id, { status: 'handoff', current_phase: 'handoff', blockers, handoff: proof.handoff });
157
+ return proof;
158
+ }
159
+ //# sourceMappingURL=loop-runtime.js.map
@@ -0,0 +1,60 @@
1
+ import os from 'node:os';
2
+ export function scheduleLoopGraph(nodes, parallelism = 'balanced') {
3
+ const pending = new Map(nodes.map((node) => [node.loop_id, node]));
4
+ const completed = new Set();
5
+ const batches = [];
6
+ const maxParallel = maxConcurrentLoops(nodes, parallelism);
7
+ const blockers = [];
8
+ while (pending.size) {
9
+ const ready = [...pending.values()].filter((node) => node.dependencies.every((dep) => completed.has(dep)));
10
+ if (!ready.length) {
11
+ blockers.push(`loop_dependency_cycle:${[...pending.keys()].join(',')}`);
12
+ break;
13
+ }
14
+ const batch = ready.slice(0, maxParallel);
15
+ batches.push(batch);
16
+ for (const node of batch) {
17
+ pending.delete(node.loop_id);
18
+ if (node.route !== '$Integration')
19
+ completed.add(node.loop_id);
20
+ else
21
+ completed.add(node.loop_id);
22
+ }
23
+ }
24
+ return { ok: blockers.length === 0, batches, max_active_loops: Math.max(0, ...batches.map((batch) => batch.length)), blockers };
25
+ }
26
+ export function maxConcurrentLoops(nodes, parallelism = 'balanced') {
27
+ const cores = Math.max(1, os.cpus().length || 1);
28
+ const base = parallelism === 'safe' ? 2 : parallelism === 'extreme' ? Math.min(16, cores) : Math.min(8, cores);
29
+ return nodes.some((node) => node.risk.level === 'critical' || node.risk.level === 'high') && parallelism !== 'extreme'
30
+ ? Math.max(1, Math.min(base, 3))
31
+ : Math.max(1, base);
32
+ }
33
+ export function graphProofFromLoopProofs(input) {
34
+ const selected = [...new Set(input.proofs.flatMap((proof) => proof.gate_result.selected_gates))];
35
+ const passed = [...new Set(input.proofs.flatMap((proof) => proof.gate_result.passed_gates))];
36
+ const failed = [...new Set(input.proofs.flatMap((proof) => proof.gate_result.failed_gates))];
37
+ const skipped = [...new Set(input.proofs.flatMap((proof) => proof.gate_result.skipped_gates))];
38
+ const blockers = [...new Set(input.proofs.flatMap((proof) => proof.blockers))];
39
+ const sequential = Math.max(input.wallMs, input.proofs.length * Math.max(1, Math.floor(input.wallMs / Math.max(1, input.maxActiveLoops))));
40
+ return {
41
+ schema: 'sks.loop-graph-proof.v1',
42
+ mission_id: input.missionId,
43
+ ok: blockers.length === 0 && failed.length === 0,
44
+ total_loops: input.proofs.length,
45
+ completed_loops: input.proofs.filter((proof) => proof.status === 'completed').length,
46
+ blocked_loops: input.proofs.filter((proof) => proof.status === 'blocked').length,
47
+ failed_loops: input.proofs.filter((proof) => proof.status === 'failed').length,
48
+ handoff_loops: input.proofs.filter((proof) => proof.status === 'handoff').length,
49
+ parallelism: {
50
+ max_active_loops: input.maxActiveLoops,
51
+ max_active_workers: input.maxActiveWorkers,
52
+ wall_ms: input.wallMs,
53
+ sequential_estimate_ms: sequential,
54
+ speedup_ratio: Number((sequential / Math.max(1, input.wallMs)).toFixed(2))
55
+ },
56
+ gates: { selected, passed, failed, skipped },
57
+ blockers
58
+ };
59
+ }
60
+ //# sourceMappingURL=loop-scheduler.js.map
@@ -0,0 +1,63 @@
1
+ export function defaultLoopBudget(overrides = {}) {
2
+ return {
3
+ max_iterations: 2,
4
+ max_wall_ms: 15 * 60 * 1000,
5
+ max_model_calls: 8,
6
+ max_subagents: 4,
7
+ max_tokens_estimate: 120000,
8
+ max_changed_files: 16,
9
+ max_patch_bytes: 256000,
10
+ ...overrides
11
+ };
12
+ }
13
+ export function validateLoopPlan(plan) {
14
+ const blockers = [
15
+ ...(plan.schema !== 'sks.loop-plan.v1' ? ['loop_plan_schema_invalid'] : []),
16
+ ...(!plan.mission_id ? ['loop_plan_mission_id_missing'] : []),
17
+ ...(!plan.request ? ['loop_plan_request_missing'] : []),
18
+ ...(!plan.integration_loop_id ? ['loop_plan_integration_loop_missing'] : []),
19
+ ...(plan.graph.nodes.length === 0 ? ['loop_plan_nodes_missing'] : []),
20
+ ...(!plan.graph.nodes.some((node) => node.loop_id === plan.integration_loop_id) ? ['loop_plan_integration_node_missing'] : [])
21
+ ];
22
+ const ids = new Set();
23
+ for (const node of plan.graph.nodes) {
24
+ const result = validateLoopNode(node);
25
+ blockers.push(...result.blockers);
26
+ if (ids.has(node.loop_id))
27
+ blockers.push(`loop_node_duplicate:${node.loop_id}`);
28
+ ids.add(node.loop_id);
29
+ }
30
+ for (const edge of plan.graph.edges) {
31
+ if (!ids.has(edge.from) || !ids.has(edge.to))
32
+ blockers.push(`loop_edge_unknown:${edge.from}->${edge.to}`);
33
+ }
34
+ return { ok: blockers.length === 0, blockers };
35
+ }
36
+ export function validateLoopNode(node) {
37
+ const blockers = [
38
+ ...(node.schema !== 'sks.loop-node.v1' ? [`loop_node_schema_invalid:${node.loop_id}`] : []),
39
+ ...(!node.loop_id ? ['loop_node_id_missing'] : []),
40
+ ...(!node.mission_id ? [`loop_node_mission_missing:${node.loop_id}`] : []),
41
+ ...(!node.state_file ? [`loop_state_file_missing:${node.loop_id}`] : []),
42
+ ...(!node.run_log_file ? [`loop_run_log_file_missing:${node.loop_id}`] : []),
43
+ ...(!node.owner_scope ? [`loop_owner_scope_missing:${node.loop_id}`] : []),
44
+ ...validateLoopBudget(node.budget).blockers.map((blocker) => `${node.loop_id}:${blocker}`),
45
+ ...(node.level === 'L3-unattended' && ['high', 'critical'].includes(node.risk.level) ? [`loop_l3_risk_blocked:${node.loop_id}`] : []),
46
+ ...(node.level === 'L2-action' && !node.checker.required_before_next_iteration ? [`loop_action_checker_missing:${node.loop_id}`] : [])
47
+ ];
48
+ return { ok: blockers.length === 0, blockers };
49
+ }
50
+ export function validateLoopBudget(budget) {
51
+ const blockers = [];
52
+ for (const [key, value] of Object.entries(budget)) {
53
+ if (!Number.isFinite(value) || value < 0)
54
+ blockers.push(`loop_budget_invalid:${key}`);
55
+ }
56
+ if (budget.max_iterations < 1)
57
+ blockers.push('loop_budget_iterations_missing');
58
+ return { ok: blockers.length === 0, blockers };
59
+ }
60
+ export function allGateIds(gates) {
61
+ return [...new Set([...gates.triage, ...gates.local, ...gates.checker, ...gates.integration, ...gates.final])];
62
+ }
63
+ //# sourceMappingURL=loop-schema.js.map
@@ -0,0 +1,61 @@
1
+ import { appendJsonl, readJson, writeJsonAtomic } from '../fsx.js';
2
+ import { loopRunLogPath, loopStatePath } from './loop-artifacts.js';
3
+ export async function readLoopState(root, missionId, loopId) {
4
+ return readJson(loopStatePath(root, missionId, loopId), null);
5
+ }
6
+ export async function writeLoopState(root, state) {
7
+ await writeJsonAtomic(loopStatePath(root, state.mission_id, state.loop_id), state);
8
+ return state;
9
+ }
10
+ export async function appendLoopRunLog(root, missionId, loopId, event) {
11
+ await appendJsonl(loopRunLogPath(root, missionId, loopId), { ts: event.ts || new Date().toISOString(), ...event });
12
+ }
13
+ export async function updateLoopState(root, missionId, loopId, patch) {
14
+ const current = await readLoopState(root, missionId, loopId);
15
+ if (!current)
16
+ throw new Error(`loop_state_missing:${loopId}`);
17
+ const next = {
18
+ ...current,
19
+ ...patch,
20
+ acting_on: { ...current.acting_on, ...(patch.acting_on || {}) },
21
+ handoff: { ...current.handoff, ...(patch.handoff || {}) },
22
+ budget_used: { ...current.budget_used, ...(patch.budget_used || {}) },
23
+ updated_at: new Date().toISOString()
24
+ };
25
+ await writeLoopState(root, next);
26
+ return next;
27
+ }
28
+ export function initialLoopState(input) {
29
+ return {
30
+ schema: 'sks.loop-state.v1',
31
+ mission_id: input.missionId,
32
+ loop_id: input.loopId,
33
+ status: 'planned',
34
+ iteration: 0,
35
+ acting_on: {
36
+ files: input.files,
37
+ worktree_id: input.worktreeId || null,
38
+ branch: input.branch || null
39
+ },
40
+ current_phase: 'triage',
41
+ last_action: null,
42
+ last_gate_result: null,
43
+ last_checker_result: null,
44
+ blockers: [],
45
+ handoff: {
46
+ required: false,
47
+ reason: null,
48
+ artifact: null
49
+ },
50
+ budget_used: {
51
+ wall_ms: 0,
52
+ model_calls: 0,
53
+ subagents: 0,
54
+ iterations: 0,
55
+ changed_files: 0,
56
+ patch_bytes: 0
57
+ },
58
+ updated_at: new Date().toISOString()
59
+ };
60
+ }
61
+ //# sourceMappingURL=loop-state.js.map
@@ -0,0 +1,33 @@
1
+ import { writeJsonAtomic } from '../fsx.js';
2
+ import { loopRoot } from '../loops/loop-artifacts.js';
3
+ import { runLoopPlan } from '../loops/loop-runtime.js';
4
+ import { routeNarutoLoopWorker } from './naruto-loop-worker-router.js';
5
+ export async function runNarutoLoopMesh(input) {
6
+ const routes = input.plan.graph.nodes.flatMap((node) => [routeNarutoLoopWorker(node, 'maker'), routeNarutoLoopWorker(node, 'checker')]);
7
+ const activeWorkerBudget = splitActiveWorkerBudget(input.plan, input.parallelism);
8
+ await writeJsonAtomic(`${loopRoot(input.root, input.plan.mission_id)}/naruto-loop-worker-routes.json`, {
9
+ schema: 'sks.naruto-loop-worker-routes.v1',
10
+ mission_id: input.plan.mission_id,
11
+ active_worker_budget: activeWorkerBudget,
12
+ routes
13
+ });
14
+ return runLoopPlan({ root: input.root, plan: input.plan, parallelism: input.parallelism });
15
+ }
16
+ export function splitActiveWorkerBudget(plan, parallelism) {
17
+ const cap = parallelism === 'safe' ? 8 : parallelism === 'extreme' ? 32 : 16;
18
+ const integrationReserved = 2;
19
+ const nonIntegration = plan.graph.nodes.filter((node) => node.route !== '$Integration');
20
+ const perLoopCap = Math.max(2, Math.floor((cap - integrationReserved) / Math.max(1, nonIntegration.length)));
21
+ const perLoop = nonIntegration.map((node) => ({
22
+ loop_id: node.loop_id,
23
+ maker_checker_workers: Math.min(perLoopCap, node.maker.worker_count + node.checker.worker_count)
24
+ }));
25
+ const used = perLoop.reduce((sum, row) => sum + row.maker_checker_workers, integrationReserved);
26
+ return {
27
+ global_active_workers: cap,
28
+ integration_reserved: integrationReserved,
29
+ per_loop: perLoop,
30
+ headroom: Math.max(0, cap - used)
31
+ };
32
+ }
33
+ //# sourceMappingURL=naruto-loop-mesh.js.map
@@ -0,0 +1,38 @@
1
+ export function routeNarutoLoopWorker(node, role) {
2
+ const domain = node.loop_id.replace(/^loop-/, '');
3
+ const roles = roleLabels(domain);
4
+ const gates = [...node.gates.triage, ...node.gates.local, ...node.gates.checker, ...node.gates.integration, ...node.gates.final];
5
+ return {
6
+ schema: 'sks.naruto-loop-worker-route.v1',
7
+ loop_id: node.loop_id,
8
+ maker_role: roles.maker,
9
+ checker_role: roles.checker,
10
+ prompt: [
11
+ `loop purpose: ${node.purpose}`,
12
+ `role: ${role === 'maker' ? roles.maker : roles.checker}`,
13
+ `owner files: ${node.owner_scope.files.join(', ') || '-'}`,
14
+ `owner directories: ${node.owner_scope.directories.join(', ') || '-'}`,
15
+ `gates: ${gates.join(', ') || '-'}`,
16
+ `state file: ${node.state_file}`,
17
+ `budget: ${JSON.stringify(node.budget)}`,
18
+ `collision policy: ${node.owner_scope.collision_policy}`,
19
+ 'Do not mutate outside owner scope.'
20
+ ].join('\n'),
21
+ allowed_files: node.owner_scope.files,
22
+ allowed_directories: node.owner_scope.directories,
23
+ gates,
24
+ mutation_outside_owner_scope_allowed: false
25
+ };
26
+ }
27
+ function roleLabels(domain) {
28
+ if (domain.includes('zellij'))
29
+ return { maker: 'zellij implementer', checker: 'zellij QA/verifier' };
30
+ if (domain.includes('release'))
31
+ return { maker: 'release optimizer', checker: 'release gate verifier' };
32
+ if (domain.includes('research'))
33
+ return { maker: 'source shard/synthesis', checker: 'final reviewer' };
34
+ if (domain.includes('codex'))
35
+ return { maker: 'capability/probe implementer', checker: 'real probe verifier' };
36
+ return { maker: `${domain} implementer`, checker: `${domain} checker` };
37
+ }
38
+ //# sourceMappingURL=naruto-loop-worker-router.js.map
@@ -1213,9 +1213,17 @@ export async function recordContext7Evidence(root, state, payload) {
1213
1213
  return null;
1214
1214
  if (!await shouldWritePipelineEvidence(root, state))
1215
1215
  return null;
1216
- const record = { ts: nowIso(), stage, tool: context7ToolName(payload), payload_keys: Object.keys(payload || {}).sort() };
1217
1216
  const id = state?.mission_id;
1218
1217
  const file = id ? path.join(missionDir(root, id), 'context7-evidence.jsonl') : path.join(root, '.sneakoscope', 'state', 'context7-evidence.jsonl');
1218
+ const record = {
1219
+ ts: nowIso(),
1220
+ stage,
1221
+ tool: context7ToolName(payload),
1222
+ payload_keys: Object.keys(payload || {}).sort(),
1223
+ dedupe_key: context7DedupeKey(stage, payload)
1224
+ };
1225
+ if (await hasContext7EvidenceRecord(file, record.dedupe_key))
1226
+ return null;
1219
1227
  await appendJsonl(file, record);
1220
1228
  if (id) {
1221
1229
  const evidence = await context7Evidence(root, state);
@@ -1297,7 +1305,11 @@ function context7ToolName(payload) {
1297
1305
  return String(obj.tool_name || obj.name || obj.tool?.name || obj.mcp_tool || obj.command || obj.type || '');
1298
1306
  }
1299
1307
  function context7Stage(payload) {
1300
- const hay = JSON.stringify(payload || {});
1308
+ const tool = context7ToolName(payload);
1309
+ const direct = context7DirectSignal(payload);
1310
+ if (!direct && !context7ToolLooksRelevant(tool))
1311
+ return null;
1312
+ const hay = [tool, direct].filter(Boolean).join('\n');
1301
1313
  if (!/(context7|resolve[-_]?library[-_]?id|get[-_]?library[-_]?docs|query[-_]?docs)/i.test(hay))
1302
1314
  return null;
1303
1315
  if (/resolve[-_]?library[-_]?id/i.test(hay))
@@ -1306,6 +1318,74 @@ function context7Stage(payload) {
1306
1318
  return 'get-library-docs';
1307
1319
  return 'context7';
1308
1320
  }
1321
+ function context7ToolLooksRelevant(tool) {
1322
+ return /(^|[_:/.-])(context7|resolve[-_]?library[-_]?id|get[-_]?library[-_]?docs|query[-_]?docs)($|[_:/.-])/i.test(String(tool || ''));
1323
+ }
1324
+ function context7DirectSignal(payload = {}) {
1325
+ const source = String(payload.source || '');
1326
+ const tool = context7ToolName(payload);
1327
+ if (/^sks context7 evidence/i.test(source)) {
1328
+ return [
1329
+ tool,
1330
+ source,
1331
+ payload.library,
1332
+ payload.library_id,
1333
+ payload.docs_tool
1334
+ ].filter(Boolean).join('\n');
1335
+ }
1336
+ if (context7ToolLooksRelevant(tool)) {
1337
+ const input = payload.tool_input || payload.toolInput || payload.input || payload.tool?.input || {};
1338
+ return JSON.stringify({
1339
+ tool,
1340
+ library: payload.library,
1341
+ library_id: payload.library_id,
1342
+ docs_tool: payload.docs_tool,
1343
+ input: context7SafeInput(input)
1344
+ });
1345
+ }
1346
+ return '';
1347
+ }
1348
+ function context7SafeInput(input) {
1349
+ if (!input || typeof input !== 'object' || Array.isArray(input))
1350
+ return input ?? null;
1351
+ const out = {};
1352
+ for (const key of ['name', 'tool', 'library', 'libraryName', 'library_id', 'libraryId', 'context7CompatibleLibraryID', 'query', 'topic', 'tokens']) {
1353
+ if (Object.prototype.hasOwnProperty.call(input, key))
1354
+ out[key] = input[key];
1355
+ }
1356
+ return out;
1357
+ }
1358
+ function context7DedupeKey(stage, payload = {}) {
1359
+ const input = payload.tool_input || payload.toolInput || payload.input || payload.tool?.input || {};
1360
+ const library = payload.library_id
1361
+ || payload.library
1362
+ || input.libraryId
1363
+ || input.context7CompatibleLibraryID
1364
+ || input.libraryName
1365
+ || input.library
1366
+ || '';
1367
+ const query = input.query || input.topic || payload.query || payload.topic || '';
1368
+ return [
1369
+ stage,
1370
+ context7ToolName(payload),
1371
+ String(library).trim().toLowerCase(),
1372
+ String(query).trim().toLowerCase()
1373
+ ].join('|');
1374
+ }
1375
+ async function hasContext7EvidenceRecord(file, key) {
1376
+ const text = await readText(file, '');
1377
+ for (const line of text.split(/\n/)) {
1378
+ if (!line.trim())
1379
+ continue;
1380
+ try {
1381
+ const entry = JSON.parse(line);
1382
+ if (entry.dedupe_key === key)
1383
+ return true;
1384
+ }
1385
+ catch { }
1386
+ }
1387
+ return false;
1388
+ }
1309
1389
  export async function context7Evidence(root, state) {
1310
1390
  const id = state?.mission_id;
1311
1391
  if (!id)
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '3.0.3';
1
+ export const PACKAGE_VERSION = '3.1.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -11,9 +11,12 @@ export function renderZellijSlotColumnAnchor(input = {}) {
11
11
  const update = input.updateAvailableVersion ? ` · update ${trimInline(input.updateAvailableVersion, 18)} available` : '';
12
12
  const madDb = input.madDbActive ? ' · MAD-DB ACTIVE' : '';
13
13
  const appHandoff = input.qaAppHandoffPending ? ' · QA /app handoff pending' : '';
14
- const header = done || fail
14
+ const loopHeader = input.loopsTotal != null
15
+ ? `LOOPS ${nonNegativeInt(input.loopsTotal, 0)} · running ${nonNegativeInt(input.loopsRunning, 0)} · blocked ${nonNegativeInt(input.loopsBlocked, 0)} · done ${nonNegativeInt(input.loopsCompleted, 0)} · workers ${active}`
16
+ : null;
17
+ const header = loopHeader || (done || fail
15
18
  ? `SLOTS active ${active} · headless ${headless} · done ${done} · fail ${fail} · q ${queue}${update}${madDb}${appHandoff}`
16
- : `SLOTS active ${active}/${visible} · headless ${headless} · q ${queue}${update}${madDb}${appHandoff}`;
19
+ : `SLOTS active ${active}/${visible} · headless ${headless} · q ${queue}${update}${madDb}${appHandoff}`);
17
20
  const workers = Array.isArray(input.workerRows) ? input.workerRows : [];
18
21
  const handoffLine = input.qaAppHandoffPending ? `QA app handoff pending · ${trimInline(input.qaAppHandoffArtifact || 'qa-loop/app-handoff.json', 64)}` : null;
19
22
  if (!workers.length)
@@ -15,6 +15,7 @@ export function renderZellijSlotPane(input) {
15
15
  const stdout = (input.stdoutTail || []).filter(Boolean).slice(-2);
16
16
  const stderr = (input.stderrTail || []).filter(Boolean).slice(-1);
17
17
  const rows = [
18
+ input.loopId ? `${trimInline(input.loopId, 28)} · ${trimInline(input.loopRole || input.role || 'worker', 14)} · ${input.slotId}` : null,
18
19
  `slot: ${input.slotId} / gen-${Math.max(1, Math.floor(Number(input.generationIndex) || 1))} / ${trimInline(input.status || 'running', 18)}`,
19
20
  `role: ${trimInline(input.role || 'worker', 18)} backend: ${trimInline(input.backend || 'codex-sdk', 20)} worktree: ${trimInline(input.worktreeId || '-', 18)}`,
20
21
  `runtime: fast ${formatFastMode(input.fastMode, input.serviceTier)} tier: ${trimInline(input.serviceTier || 'unknown', 12)} provider: ${trimInline(input.provider || 'unknown', 18)}`,
@@ -22,6 +23,7 @@ export function renderZellijSlotPane(input) {
22
23
  input.sessionId ? `session: ${trimInline(input.sessionId, 62)}` : null,
23
24
  `heartbeat: ${heartbeat}${input.heartbeatEvent ? ` event: ${trimInline(input.heartbeatEvent, 40)}` : ''}`,
24
25
  `doing: ${task}`,
26
+ input.loopGate ? `gate: ${trimInline(input.loopGate, 68)}` : null,
25
27
  `files: ${trimInline(files.length ? files.join(', ') : 'no changed file yet', 78)}`,
26
28
  `patch: ${trimInline(input.patchStatus || 'queued', 24)} verify: ${trimInline(input.verifyStatus || 'queued', 24)}`,
27
29
  input.qaAppHandoffPending ? `QA app handoff pending: ${trimInline(input.qaAppHandoffArtifact || 'qa-loop/app-handoff.json', 55)}` : null,
@@ -7,6 +7,7 @@ const args = process.argv.slice(2);
7
7
  const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
8
8
  const truth = readJson('.sneakoscope/release-proof-truth.json') || readJson('dist/release-proof-truth.json');
9
9
  const codex0139 = readJson('.sneakoscope/codex-0139-capability.json');
10
+ const codex0139Real = readJson('.sneakoscope/codex-0139-real-probe-summary.json');
10
11
  const zellij = readJson('.sneakoscope/reports/zellij-worker-pane-summary.json');
11
12
  const changelog = fs.readFileSync(path.join(root, 'CHANGELOG.md'), 'utf8');
12
13
  const latest = latestChangelogSection(changelog);
@@ -24,6 +25,7 @@ const body = [
24
25
  `Release proof truth: ${truth ? '.sneakoscope/release-proof-truth.json' : 'missing'}`,
25
26
  `Codex SDK package: ${sdkVersion || 'unknown'}`,
26
27
  `External Codex CLI 0.139 capability: ${codex0139 ? `${codex0139.ok ? 'ok' : 'blocked'}${codex0139.parsed_version ? ` (${codex0139.parsed_version})` : ''}` : 'not recorded'}`,
28
+ `Codex 0.139 real probes: ${codex0139Real ? `${codex0139Real.ok ? 'ok' : 'blocked'} (${codex0139Real.actual_cli_probe_count || 0} actual CLI, ${codex0139Real.skipped_count || 0} skipped, ${codex0139Real.failed_count || 0} failed)` : 'not run in this release environment. Hermetic fixture gates: passed.'}`,
27
29
  `Zellij stacked panes: ${zellij ? `${zellij.stacked_applied_count || 0}/${zellij.stacked_requested_count || 0} applied, fallback ${zellij.stacked_fallback_count || 0}, SLOTS anchors ${zellij.duplicate_slot_anchor_count || 0}` : 'not recorded'}`,
28
30
  `Packlist: ${truth?.npm_packlist_count ?? 'unknown'} files / ${truth?.npm_packlist_bytes ?? 'unknown'} bytes`,
29
31
  ...(warnings.length ? [`Warnings: ${warnings.join('; ')}`] : ['Warnings: none']),
@@ -34,7 +36,7 @@ const body = [
34
36
  if (args.includes('--check')) {
35
37
  assertGate(Boolean(truth && truth.schema === 'sks.release-proof-truth.v1'), 'release proof truth missing; run npm run release:proof-truth first');
36
38
  assertGate(latest.version === pkg.version, 'latest changelog section must match package version', { latest: latest.version, package: pkg.version });
37
- assertGate(body.includes(`Version: ${pkg.version}`) && body.includes('Commit:') && body.includes('Dirty:') && body.includes('Release proof truth:') && body.includes('Codex SDK package:') && body.includes('External Codex CLI 0.139 capability:') && body.includes('Zellij stacked panes:') && body.includes('Packlist:'), 'github release body helper missing source truth fields', { body });
39
+ assertGate(body.includes(`Version: ${pkg.version}`) && body.includes('Commit:') && body.includes('Dirty:') && body.includes('Release proof truth:') && body.includes('Codex SDK package:') && body.includes('External Codex CLI 0.139 capability:') && body.includes('Codex 0.139 real probes:') && body.includes('Zellij stacked panes:') && body.includes('Packlist:'), 'github release body helper missing source truth fields', { body });
38
40
  assertGate(!warnings.some((warning) => warning.includes('package version') || warning.includes('changelog latest') || warning.includes('release proof truth missing')), 'github release body helper source truth warnings must not include version/proof mismatch', { warnings, body });
39
41
  }
40
42
  console.log(body);