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,16 @@
1
+ export const LOOP_LEVEL_ORDER = ['L0-report', 'L1-assisted', 'L2-action', 'L3-unattended'];
2
+ export function canEscalateLoopLevel(input) {
3
+ const blockers = [
4
+ ...(!input.previousProof?.gate_result.ok ? ['previous_level_proof_not_passed'] : []),
5
+ ...(input.previousProof && input.previousProof.budget.used.iterations >= input.node.budget.max_iterations ? ['loop_budget_exhausted'] : []),
6
+ ...(['high', 'critical'].includes(input.node.risk.level) && input.node.level === 'L3-unattended' ? ['high_risk_l3_blocked'] : []),
7
+ ...(!input.ownerLeaseAcquired ? ['owner_lease_missing'] : []),
8
+ ...(input.node.level === 'L2-action' && !input.node.checker.required_before_next_iteration ? ['checker_required_for_l2'] : []),
9
+ ...(input.node.level === 'L3-unattended' && input.node.dependencies.length === 0 ? ['new_domain_cannot_start_l3'] : [])
10
+ ];
11
+ return { ok: blockers.length === 0, blockers };
12
+ }
13
+ export function sourceMutationRequiresGptFinal(node) {
14
+ return node.level === 'L2-action' || node.risk.requires_gpt_final || node.gates.final.includes('gpt:final-arbiter');
15
+ }
16
+ //# sourceMappingURL=loop-gate-ladder.js.map
@@ -0,0 +1,96 @@
1
+ import path from 'node:path';
2
+ import { readJson } from '../fsx.js';
3
+ export async function resolveLoopGate(root, gateId) {
4
+ const builtin = builtinLoopGate(gateId);
5
+ if (builtin)
6
+ return builtin;
7
+ const releaseGate = await resolveReleaseGate(root, gateId);
8
+ if (releaseGate)
9
+ return releaseGate;
10
+ const packageGate = await resolvePackageScriptGate(root, gateId);
11
+ if (packageGate)
12
+ return packageGate;
13
+ return null;
14
+ }
15
+ export async function listLoopGateDefinitions(root) {
16
+ const packageJson = await readJson(path.join(root, 'package.json'), {});
17
+ const release = await readJson(path.join(root, 'release-gates.v2.json'), {});
18
+ const packageScripts = packageJson && typeof packageJson === 'object' && packageJson.scripts && typeof packageJson.scripts === 'object'
19
+ ? Object.keys(packageJson.scripts)
20
+ : [];
21
+ const releaseGates = Array.isArray(release.gates) ? release.gates : [];
22
+ const definitions = [
23
+ ...['gpt:final-arbiter', 'human:handoff-required', 'loop:checker-fresh-session', 'loop:state-valid', 'loop:budget-valid'].map((id) => builtinLoopGate(id)).filter((row) => Boolean(row)),
24
+ ...releaseGates.map((gate) => normalizeReleaseGate(gate)).filter((row) => Boolean(row)),
25
+ ...packageScripts.map((id) => ({
26
+ id,
27
+ command: `npm run ${shellQuote(id)} --silent`,
28
+ source: 'package-json',
29
+ side_effect: 'hermetic',
30
+ timeout_ms: 300000,
31
+ cache_allowed: true
32
+ }))
33
+ ];
34
+ const byId = new Map();
35
+ for (const definition of definitions)
36
+ if (!byId.has(definition.id))
37
+ byId.set(definition.id, definition);
38
+ return [...byId.values()];
39
+ }
40
+ function builtinLoopGate(gateId) {
41
+ if (gateId === 'gpt:final-arbiter') {
42
+ return { id: gateId, command: 'builtin:gpt-final-arbiter', source: 'builtin-pseudo', side_effect: 'read-only', timeout_ms: 300000, cache_allowed: false };
43
+ }
44
+ if (gateId === 'human:handoff-required') {
45
+ return { id: gateId, command: 'builtin:human-handoff-required', source: 'builtin-pseudo', side_effect: 'human', timeout_ms: 0, cache_allowed: false };
46
+ }
47
+ if (gateId === 'loop:checker-fresh-session') {
48
+ return { id: gateId, command: 'builtin:loop-checker-fresh-session', source: 'builtin-pseudo', side_effect: 'read-only', timeout_ms: 30000, cache_allowed: false };
49
+ }
50
+ if (gateId === 'loop:state-valid') {
51
+ return { id: gateId, command: 'builtin:loop-state-valid', source: 'builtin-pseudo', side_effect: 'read-only', timeout_ms: 30000, cache_allowed: true };
52
+ }
53
+ if (gateId === 'loop:budget-valid') {
54
+ return { id: gateId, command: 'builtin:loop-budget-valid', source: 'builtin-pseudo', side_effect: 'read-only', timeout_ms: 30000, cache_allowed: true };
55
+ }
56
+ return null;
57
+ }
58
+ async function resolveReleaseGate(root, gateId) {
59
+ const release = await readJson(path.join(root, 'release-gates.v2.json'), {});
60
+ const gate = Array.isArray(release.gates) ? release.gates.find((row) => row.id === gateId) : null;
61
+ return gate ? normalizeReleaseGate(gate) : null;
62
+ }
63
+ function normalizeReleaseGate(gate) {
64
+ const id = String(gate.id || '');
65
+ const command = String(gate.command || '');
66
+ if (!id || !command)
67
+ return null;
68
+ return {
69
+ id,
70
+ command,
71
+ source: 'release-gates-v2',
72
+ side_effect: normalizeSideEffect(gate.side_effect),
73
+ timeout_ms: Number.isFinite(Number(gate.timeout_ms)) ? Math.max(1, Number(gate.timeout_ms)) : 300000,
74
+ cache_allowed: gate.cache?.enabled !== false
75
+ };
76
+ }
77
+ async function resolvePackageScriptGate(root, gateId) {
78
+ const packageJson = await readJson(path.join(root, 'package.json'), {});
79
+ if (!packageJson?.scripts || typeof packageJson.scripts !== 'object' || !(gateId in packageJson.scripts))
80
+ return null;
81
+ return {
82
+ id: gateId,
83
+ command: `npm run ${shellQuote(gateId)} --silent`,
84
+ source: 'package-json',
85
+ side_effect: 'hermetic',
86
+ timeout_ms: 300000,
87
+ cache_allowed: true
88
+ };
89
+ }
90
+ function normalizeSideEffect(value) {
91
+ return value === 'read-only' || value === 'mutation' || value === 'human' ? value : 'hermetic';
92
+ }
93
+ function shellQuote(value) {
94
+ return `'${String(value).replace(/'/g, `'\\''`)}'`;
95
+ }
96
+ //# sourceMappingURL=loop-gate-registry.js.map
@@ -0,0 +1,177 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { readJson, runProcess, writeJsonAtomic } from '../fsx.js';
4
+ import { allGateIds } from './loop-schema.js';
5
+ import { loopBudgetPath, loopGatePath, loopStatePath } from './loop-artifacts.js';
6
+ import { resolveLoopGate } from './loop-gate-registry.js';
7
+ export async function runLoopGates(input) {
8
+ const selected = allGateIds(input.gates);
9
+ const failed = [];
10
+ const passed = [];
11
+ const skipped = [];
12
+ const blockers = [];
13
+ for (const gate of selected) {
14
+ const result = await runOneGate(input, gate);
15
+ if (result.skipped)
16
+ skipped.push(gate);
17
+ else if (result.ok)
18
+ passed.push(gate);
19
+ else
20
+ failed.push(gate);
21
+ blockers.push(...result.blockers);
22
+ }
23
+ return {
24
+ ok: failed.length === 0,
25
+ selected_gates: selected,
26
+ passed_gates: passed,
27
+ failed_gates: failed,
28
+ skipped_gates: skipped,
29
+ blockers
30
+ };
31
+ }
32
+ async function runOneGate(input, gateId) {
33
+ const started = Date.now();
34
+ const definition = await resolveLoopGate(input.root, gateId);
35
+ const fullReleaseCheckInsideLoop = gateId === 'release:check' && input.node.route !== '$Integration';
36
+ const unknown = !definition;
37
+ const packageJson = unknown ? await readJson(path.join(input.root, 'package.json'), null) : null;
38
+ const skipUnknownFixtureGate = unknown && !packageJson;
39
+ const blockers = [
40
+ ...(unknown && !skipUnknownFixtureGate ? [`unknown_loop_gate:${gateId}`] : []),
41
+ ...(fullReleaseCheckInsideLoop ? ['full_release_check_inside_non_integration_loop'] : [])
42
+ ];
43
+ let ok = blockers.length === 0;
44
+ let skipped = skipUnknownFixtureGate;
45
+ let exitCode = null;
46
+ let stdoutTail = '';
47
+ let stderrTail = '';
48
+ let timedOut = false;
49
+ const fixtureMode = process.env.SKS_LOOP_GATE_FIXTURE === '1';
50
+ if (definition && ok) {
51
+ if (fixtureMode && definition.source !== 'builtin-pseudo') {
52
+ ok = true;
53
+ }
54
+ else if (definition.source === 'builtin-pseudo') {
55
+ const builtin = await runBuiltinGate(input.root, input.missionId, input.node.loop_id, definition, input.checkerArtifacts || []);
56
+ ok = builtin.ok;
57
+ skipped = builtin.skipped;
58
+ blockers.push(...builtin.blockers);
59
+ }
60
+ else {
61
+ const command = definition.command;
62
+ const result = await runProcess(process.env.SHELL || '/bin/sh', ['-lc', command], {
63
+ cwd: input.root,
64
+ timeoutMs: input.timeoutMs || definition.timeout_ms,
65
+ maxOutputBytes: 512 * 1024,
66
+ env: {
67
+ SKS_LOOP_ID: input.node.loop_id,
68
+ SKS_MISSION_ID: input.missionId,
69
+ SKS_LOOP_GATE: gateId
70
+ }
71
+ });
72
+ exitCode = result.code;
73
+ stdoutTail = result.stdout.slice(-8000);
74
+ stderrTail = result.stderr.slice(-8000);
75
+ timedOut = result.timedOut;
76
+ ok = result.code === 0;
77
+ if (!ok)
78
+ blockers.push(`gate_command_failed:${gateId}:${result.code}`);
79
+ }
80
+ }
81
+ const artifact = {
82
+ schema: 'sks.loop-gate-result.v1',
83
+ ok,
84
+ gate_id: gateId,
85
+ loop_id: input.node.loop_id,
86
+ command: definition?.command || null,
87
+ source: definition?.source || null,
88
+ exit_code: exitCode,
89
+ duration_ms: Math.max(1, Date.now() - started),
90
+ stdout_tail: stdoutTail,
91
+ stderr_tail: stderrTail,
92
+ cached_allowed: definition?.cache_allowed ?? false,
93
+ fixture_mode: fixtureMode,
94
+ skipped,
95
+ deferred_unknown_fixture_gate: skipUnknownFixtureGate,
96
+ timed_out: timedOut,
97
+ full_release_check_inside_loop: fullReleaseCheckInsideLoop,
98
+ generated_at: new Date().toISOString(),
99
+ blockers
100
+ };
101
+ await writeJsonAtomic(loopGatePath(input.root, input.missionId, input.node.loop_id, gateId), artifact);
102
+ return { ok, skipped, blockers };
103
+ }
104
+ async function runBuiltinGate(root, missionId, loopId, definition, checkerArtifacts) {
105
+ if (definition.id === 'gpt:final-arbiter')
106
+ return { ok: true, skipped: true, blockers: [] };
107
+ if (definition.id === 'human:handoff-required')
108
+ return { ok: false, skipped: false, blockers: ['human_handoff_required'] };
109
+ if (definition.id === 'loop:state-valid') {
110
+ const state = await readJson(loopStatePath(root, missionId, loopId), null);
111
+ return state?.schema === 'sks.loop-state.v1' ? { ok: true, skipped: false, blockers: [] } : { ok: false, skipped: false, blockers: ['loop_state_invalid'] };
112
+ }
113
+ if (definition.id === 'loop:budget-valid') {
114
+ const budget = await readJson(loopBudgetPath(root, missionId, loopId), null);
115
+ return budget && typeof budget === 'object' ? { ok: true, skipped: false, blockers: [] } : { ok: false, skipped: false, blockers: ['loop_budget_invalid'] };
116
+ }
117
+ if (definition.id === 'loop:checker-fresh-session') {
118
+ const artifacts = await Promise.all(checkerArtifacts.map((artifact) => readCheckerArtifact(root, missionId, artifact)));
119
+ const fresh = artifacts.some((artifact) => artifact?.fresh_session === true && artifact?.approved === true);
120
+ return fresh ? { ok: true, skipped: false, blockers: [] } : { ok: false, skipped: false, blockers: ['loop_checker_fresh_session_missing'] };
121
+ }
122
+ return { ok: false, skipped: false, blockers: [`unknown_builtin_gate:${definition.id}`] };
123
+ }
124
+ async function readCheckerArtifact(root, missionId, artifact) {
125
+ for (const candidate of checkerArtifactPathCandidates(root, missionId, artifact)) {
126
+ const readable = await checkerArtifactReadablePath(root, missionId, candidate);
127
+ if (!readable)
128
+ continue;
129
+ const row = await readJson(readable, null);
130
+ if (row)
131
+ return row;
132
+ }
133
+ return null;
134
+ }
135
+ function checkerArtifactPathCandidates(root, missionId, artifact) {
136
+ const raw = String(artifact || '').trim();
137
+ if (!raw)
138
+ return [];
139
+ const missionRoot = path.join(root, '.sneakoscope', 'missions', missionId);
140
+ const resolvedMissionRoot = path.resolve(missionRoot);
141
+ if (path.isAbsolute(raw)) {
142
+ return [path.resolve(raw)];
143
+ }
144
+ return uniqueStrings([
145
+ safeResolveWithin(path.join(resolvedMissionRoot, 'agents'), raw),
146
+ safeResolveWithin(resolvedMissionRoot, raw),
147
+ safeResolveWithin(path.join(resolvedMissionRoot, 'loops'), raw)
148
+ ].filter((value) => Boolean(value)));
149
+ }
150
+ function uniqueStrings(values) {
151
+ return [...new Set(values.filter(Boolean))];
152
+ }
153
+ async function checkerArtifactReadablePath(root, missionId, candidate) {
154
+ const resolvedMissionRoot = path.resolve(root, '.sneakoscope', 'missions', missionId);
155
+ const resolvedCandidate = path.resolve(candidate);
156
+ try {
157
+ const [realMissionRoot, realCandidate] = await Promise.all([
158
+ fsp.realpath(resolvedMissionRoot),
159
+ fsp.realpath(resolvedCandidate)
160
+ ]);
161
+ return isWithinPath(realMissionRoot, realCandidate) ? realCandidate : null;
162
+ }
163
+ catch {
164
+ return null;
165
+ }
166
+ }
167
+ function safeResolveWithin(base, target) {
168
+ const resolvedBase = path.resolve(base);
169
+ const resolvedTarget = path.resolve(resolvedBase, target);
170
+ return isWithinPath(resolvedBase, resolvedTarget) ? resolvedTarget : null;
171
+ }
172
+ function isWithinPath(base, target) {
173
+ const resolvedBase = path.resolve(base);
174
+ const resolvedTarget = path.resolve(target);
175
+ return resolvedTarget === resolvedBase || resolvedTarget.startsWith(`${resolvedBase}${path.sep}`);
176
+ }
177
+ //# sourceMappingURL=loop-gate-runner.js.map
@@ -0,0 +1,52 @@
1
+ export function selectLoopGates(input) {
2
+ const files = input.changedFiles.join(' ');
3
+ const triage = ['loop:state-valid', 'loop:budget-valid'];
4
+ const local = new Set();
5
+ if (input.node.level === 'L0-report')
6
+ return { triage, local: [], checker: [], integration: [], final: [] };
7
+ if (isDocsOnly(input.changedFiles, input.node))
8
+ add(local, ['docs:loop-runtime', 'changelog:check']);
9
+ else if (/zellij/.test(files) || input.node.loop_id.includes('zellij'))
10
+ add(local, ['zellij:slot-telemetry-live-flush', 'zellij:slot-pane-stale-detection']);
11
+ else if (/release/.test(files) || input.node.loop_id.includes('release'))
12
+ add(local, ['release:affected-selector', 'release:dynamic-presets']);
13
+ else if (/research/.test(files) || input.node.loop_id.includes('research'))
14
+ add(local, ['research:quality-contract']);
15
+ else if (/qa-loop/.test(files) || input.node.loop_id.includes('qa-loop'))
16
+ add(local, ['qa-loop:app-handoff-gate-lifecycle']);
17
+ else if (/codex/.test(files) || input.node.loop_id.includes('codex'))
18
+ add(local, ['codex:0139-capability', 'codex-sdk:version-compat']);
19
+ else if (/mad-db|db-safety/.test(files) || input.node.loop_id.includes('mad-db'))
20
+ add(local, ['mad-db:safety-conflict-matrix', 'mad-db:operation-lifecycle-ledger']);
21
+ else if (/agent|scheduler|native-swarm/.test(files) || input.node.loop_id.includes('naruto'))
22
+ add(local, ['parallel:runtime-real-blackbox', 'scheduler:utilization-proof']);
23
+ else
24
+ add(local, ['loop:affected']);
25
+ const integration = new Set();
26
+ if ((input.packageScriptsChanged || []).length || (input.releaseGateIdsChanged || []).length || files.includes('package.json') || files.includes('release-gates.v2.json')) {
27
+ integration.add('release:dag-full-coverage');
28
+ }
29
+ if (input.risk.level === 'high')
30
+ integration.add('loop:integration-required');
31
+ const final = new Set();
32
+ if (input.changedFiles.length || input.risk.requires_gpt_final)
33
+ final.add('gpt:final-arbiter');
34
+ if (input.risk.level === 'critical')
35
+ final.add('human:handoff-required');
36
+ return {
37
+ triage,
38
+ local: [...local],
39
+ checker: input.node.level === 'L2-action' ? ['loop:checker-fresh-session'] : [],
40
+ integration: [...integration],
41
+ final: [...final]
42
+ };
43
+ }
44
+ function isDocsOnly(files, node) {
45
+ const scoped = [...files, ...node.owner_scope.files, ...node.owner_scope.directories];
46
+ return scoped.length > 0 && scoped.every((file) => file === 'README.md' || file === 'CHANGELOG.md' || file.startsWith('docs'));
47
+ }
48
+ function add(target, values) {
49
+ for (const value of values)
50
+ target.add(value);
51
+ }
52
+ //# sourceMappingURL=loop-gate-selector.js.map
@@ -0,0 +1,61 @@
1
+ import { runGptFinalArbiter } from '../codex-control/gpt-final-arbiter.js';
2
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { loopGptFinalArbiterPath } from './loop-artifacts.js';
4
+ export async function runLoopGptFinalArbiter(input) {
5
+ const artifactPath = loopGptFinalArbiterPath(input.root, input.plan.mission_id);
6
+ const changedFiles = [...new Set([
7
+ ...input.integrationMerge.changed_files,
8
+ ...input.proofs.flatMap((proof) => proof.changed_files)
9
+ ])];
10
+ const reviewedLoopIds = input.proofs.map((proof) => proof.loop_id);
11
+ if (process.env.SKS_LOOP_GPT_FINAL_FIXTURE === '1' || input.forceVerdict) {
12
+ const verdict = input.forceVerdict || (process.env.SKS_LOOP_GPT_FINAL_REJECT === '1' ? 'reject' : 'approve');
13
+ const result = buildResult(input.plan.mission_id, reviewedLoopIds, changedFiles, verdict, verdict === 'approve' ? [] : ['fixture_revision_required'], artifactPath, []);
14
+ await writeJsonAtomic(artifactPath, { ...result, generated_at: nowIso(), backend: 'fixture' });
15
+ return result;
16
+ }
17
+ const arbiter = await runGptFinalArbiter({
18
+ schema: 'sks.gpt-final-arbiter-input.v1',
19
+ route: '$Loop',
20
+ mission_id: input.plan.mission_id,
21
+ local_mode: 'local-parallel-gpt-final',
22
+ local_outputs: input.proofs.map((proof) => ({
23
+ id: proof.loop_id,
24
+ backend: proof.maker_result.backend || 'loop-worker',
25
+ status: proof.status,
26
+ summary: proof.blockers.join(', ') || 'loop proof completed',
27
+ changed_files: proof.changed_files,
28
+ blockers: proof.blockers
29
+ })),
30
+ candidate_diff: JSON.stringify({ changed_files: changedFiles, integration_merge: input.integrationMerge }),
31
+ verification_results: input.proofs.map((proof) => ({ id: proof.loop_id, ok: proof.status === 'completed', blockers: proof.blockers })),
32
+ side_effect_report: { schema: 'sks.loop-side-effect-report.v1', ok: true, changed_files: changedFiles },
33
+ mutation_ledger: { schema: 'sks.loop-mutation-ledger.v1', proofs: input.proofs },
34
+ rollback_plan: { schema: 'sks.loop-rollback-plan.v1', strategy: 'git-worktree-or-human-handoff' }
35
+ }, { cwd: input.root, mutationLedgerRoot: `${input.root}/.sneakoscope/missions/${input.plan.mission_id}/loops/gpt-final-arbiter` });
36
+ const status = String(arbiter.result?.status || '');
37
+ const verdict = status === 'approved' || status === 'modified' ? 'approve' : status === 'needs_more_work' ? 'revise' : 'reject';
38
+ const blockers = stringArray(arbiter.blockers);
39
+ const result = buildResult(input.plan.mission_id, reviewedLoopIds, changedFiles, verdict, stringArray(arbiter.result?.required_followup_work), artifactPath, blockers);
40
+ await writeJsonAtomic(artifactPath, { ...result, generated_at: nowIso(), backend: arbiter.backend || null, arbiter });
41
+ return result;
42
+ }
43
+ function buildResult(missionId, reviewedLoopIds, changedFiles, verdict, revisions, artifactPath, blockers) {
44
+ return {
45
+ schema: 'sks.loop-gpt-final-arbiter.v1',
46
+ ok: verdict === 'approve' && blockers.length === 0,
47
+ mission_id: missionId,
48
+ reviewed_loop_ids: reviewedLoopIds,
49
+ changed_files: changedFiles,
50
+ verdict,
51
+ required_revisions: revisions,
52
+ blockers,
53
+ artifact_path: artifactPath
54
+ };
55
+ }
56
+ function stringArray(value) {
57
+ if (!Array.isArray(value))
58
+ return [];
59
+ return value.map((item) => typeof item === 'string' ? item : JSON.stringify(item)).filter(Boolean);
60
+ }
61
+ //# sourceMappingURL=loop-gpt-final-arbiter.js.map
@@ -0,0 +1,75 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { runGitCommand } from '../git/git-worktree-runner.js';
4
+ import { guardedWriteFile, guardContextForRoute } from '../safety/mutation-guard.js';
5
+ import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
6
+ import { loopIntegrationMergePath } from './loop-artifacts.js';
7
+ export async function mergeLoopWorktrees(input) {
8
+ const completed = input.proofs.filter((proof) => proof.status === 'completed' && proof.loop_id !== input.plan.integration_loop_id);
9
+ const blockers = [];
10
+ const appliedLoops = [];
11
+ const conflictLoops = new Set();
12
+ const changedFiles = new Set();
13
+ const owners = new Map();
14
+ for (const proof of completed) {
15
+ for (const file of proof.changed_files) {
16
+ const previous = owners.get(file);
17
+ if (previous && previous !== proof.loop_id) {
18
+ blockers.push(`loop_integration_file_conflict:${file}:${previous}:${proof.loop_id}`);
19
+ conflictLoops.add(previous);
20
+ conflictLoops.add(proof.loop_id);
21
+ }
22
+ else {
23
+ owners.set(file, proof.loop_id);
24
+ }
25
+ }
26
+ }
27
+ if (!blockers.length) {
28
+ for (const proof of completed) {
29
+ const worktreePath = proof.worktree.path;
30
+ if (!worktreePath)
31
+ continue;
32
+ const diff = await runGitCommand(worktreePath, ['diff', '--binary', '--full-index', 'HEAD'], { timeoutMs: 60000 }).catch(() => null);
33
+ if (!diff?.ok) {
34
+ blockers.push(`loop_integration_diff_failed:${proof.loop_id}`);
35
+ conflictLoops.add(proof.loop_id);
36
+ continue;
37
+ }
38
+ if (!diff.stdout.trim())
39
+ continue;
40
+ const apply = await runGitCommand(input.root, ['apply', '--whitespace=nowarn', '-'], { input: diff.stdout, timeoutMs: 60000 }).catch(() => null);
41
+ if (!apply?.ok) {
42
+ blockers.push(`loop_integration_apply_conflict:${proof.loop_id}`);
43
+ conflictLoops.add(proof.loop_id);
44
+ await writeHandoff(input.root, proof.loop_id, apply?.stderr_tail || apply?.stdout_tail || 'git apply failed');
45
+ continue;
46
+ }
47
+ appliedLoops.push(proof.loop_id);
48
+ for (const file of proof.changed_files)
49
+ changedFiles.add(file);
50
+ }
51
+ }
52
+ const result = {
53
+ schema: 'sks.loop-integration-merge.v1',
54
+ ok: blockers.length === 0,
55
+ applied_loops: appliedLoops,
56
+ conflict_loops: [...conflictLoops],
57
+ changed_files: [...changedFiles],
58
+ blockers: [...new Set(blockers)]
59
+ };
60
+ await writeJsonAtomic(loopIntegrationMergePath(input.root, input.plan.mission_id), { ...result, generated_at: nowIso() });
61
+ return result;
62
+ }
63
+ async function writeHandoff(root, loopId, detail) {
64
+ const contract = createRequestedScopeContract({
65
+ route: '$Loop',
66
+ userRequest: 'Write loop integration conflict handoff inside project .sneakoscope.',
67
+ projectRoot: root
68
+ });
69
+ const handoffPath = path.join(root, '.sneakoscope', `loop-integration-conflict-${safeArtifactId(loopId)}.txt`);
70
+ await guardedWriteFile(guardContextForRoute(root, contract, 'loop integration conflict handoff'), handoffPath, detail).catch(() => undefined);
71
+ }
72
+ function safeArtifactId(value) {
73
+ return String(value || 'unknown').replace(/[^A-Za-z0-9_.-]/g, '_').slice(0, 80) || 'unknown';
74
+ }
75
+ //# sourceMappingURL=loop-integration-merge.js.map
@@ -0,0 +1,2 @@
1
+ export { runLoopNode } from './loop-runtime.js';
2
+ //# sourceMappingURL=loop-iteration-runner.js.map
@@ -0,0 +1,91 @@
1
+ import { loopOwnerLedgerPath } from './loop-artifacts.js';
2
+ import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ import { withFileLock } from '../locks/file-lock.js';
4
+ export async function acquireLoopLease(root, plan, node) {
5
+ return withLoopOwnerLedgerLock(root, plan.mission_id, async () => {
6
+ const blockers = await detectLoopLeaseConflictsUnderLock(root, plan.mission_id, node);
7
+ const lease = {
8
+ schema: 'sks.loop-lease.v1',
9
+ mission_id: plan.mission_id,
10
+ loop_id: node.loop_id,
11
+ owner_scope: node.owner_scope,
12
+ acquired_at: nowIso(),
13
+ expires_at: new Date(Date.now() + Math.max(60_000, node.budget.max_wall_ms)).toISOString(),
14
+ status: blockers.length ? 'conflict' : 'active',
15
+ worktree_id: node.worktree.required ? `sks-loop-${node.loop_id}` : null,
16
+ blockers
17
+ };
18
+ const ledger = await readLoopOwnerLedger(root, plan.mission_id);
19
+ const leases = ledger.leases.filter((row) => row.loop_id !== node.loop_id);
20
+ leases.push(lease);
21
+ await writeLoopOwnerLedger(root, plan.mission_id, leases);
22
+ return lease;
23
+ });
24
+ }
25
+ export async function releaseLoopLease(root, missionId, loopId) {
26
+ await withLoopOwnerLedgerLock(root, missionId, async () => {
27
+ const ledger = await readLoopOwnerLedger(root, missionId);
28
+ const leases = ledger.leases.map((lease) => lease.loop_id === loopId ? { ...lease, status: 'released' } : lease);
29
+ await writeLoopOwnerLedger(root, missionId, leases);
30
+ });
31
+ }
32
+ export async function detectLoopLeaseConflicts(root, missionId, node) {
33
+ return withLoopOwnerLedgerLock(root, missionId, () => detectLoopLeaseConflictsUnderLock(root, missionId, node));
34
+ }
35
+ async function detectLoopLeaseConflictsUnderLock(root, missionId, node) {
36
+ const ledger = await readLoopOwnerLedger(root, missionId);
37
+ const active = ledger.leases.filter((lease) => lease.status === 'active' && Date.parse(lease.expires_at) > Date.now());
38
+ const blockers = [];
39
+ for (const lease of active) {
40
+ if (lease.loop_id === node.loop_id)
41
+ continue;
42
+ if (overlap(lease.owner_scope.files, node.owner_scope.files).length && (lease.owner_scope.exclusive || node.owner_scope.exclusive)) {
43
+ blockers.push(`lease_file_conflict:${lease.loop_id}`);
44
+ }
45
+ if (overlap(lease.owner_scope.package_scripts, node.owner_scope.package_scripts).length)
46
+ blockers.push(`lease_package_script_conflict:${lease.loop_id}`);
47
+ if (node.owner_scope.files.includes('release-gates.v2.json') || lease.owner_scope.files.includes('release-gates.v2.json')) {
48
+ blockers.push(`lease_release_gates_integration_only:${lease.loop_id}`);
49
+ }
50
+ const docsOnly = allDocs(lease.owner_scope) && allDocs(node.owner_scope);
51
+ if (docsOnly) {
52
+ for (let i = blockers.length - 1; i >= 0; i -= 1) {
53
+ if (blockers[i]?.includes(lease.loop_id))
54
+ blockers.splice(i, 1);
55
+ }
56
+ }
57
+ }
58
+ return [...new Set(blockers)];
59
+ }
60
+ async function withLoopOwnerLedgerLock(root, missionId, fn) {
61
+ return withFileLock({
62
+ lockPath: `${root}/.sneakoscope/locks/loop-owner-ledger-${missionId}.lock`,
63
+ timeoutMs: 30000,
64
+ staleMs: 5 * 60 * 1000
65
+ }, fn);
66
+ }
67
+ async function readLoopOwnerLedger(root, missionId) {
68
+ return readJson(loopOwnerLedgerPath(root, missionId), {
69
+ schema: 'sks.loop-owner-ledger.v1',
70
+ mission_id: missionId,
71
+ updated_at: nowIso(),
72
+ leases: []
73
+ });
74
+ }
75
+ async function writeLoopOwnerLedger(root, missionId, leases) {
76
+ await writeJsonAtomic(loopOwnerLedgerPath(root, missionId), {
77
+ schema: 'sks.loop-owner-ledger.v1',
78
+ mission_id: missionId,
79
+ updated_at: nowIso(),
80
+ leases
81
+ });
82
+ }
83
+ function overlap(a, b) {
84
+ const rhs = new Set(b);
85
+ return a.filter((value) => rhs.has(value));
86
+ }
87
+ function allDocs(scope) {
88
+ const values = [...scope.files, ...scope.directories];
89
+ return values.length > 0 && values.every((value) => value === 'README.md' || value === 'CHANGELOG.md' || value.startsWith('docs'));
90
+ }
91
+ //# sourceMappingURL=loop-lease.js.map
@@ -0,0 +1,19 @@
1
+ import { readJson } from '../fsx.js';
2
+ import { loopGraphProofPath } from './loop-artifacts.js';
3
+ export async function readLoopGraphProof(root, missionId) {
4
+ return readJson(loopGraphProofPath(root, missionId), null);
5
+ }
6
+ export function summarizeLoopGraphProof(proof) {
7
+ if (!proof)
8
+ return { total: 0, running: 0, completed: 0, blocked: 0, speedup_ratio: 0, active_loop_ids: [], blocked_loop_ids: [] };
9
+ return {
10
+ total: proof.total_loops,
11
+ running: Math.max(0, proof.total_loops - proof.completed_loops - proof.blocked_loops - proof.failed_loops - proof.handoff_loops),
12
+ completed: proof.completed_loops,
13
+ blocked: proof.blocked_loops + proof.failed_loops + proof.handoff_loops,
14
+ speedup_ratio: proof.parallelism.speedup_ratio,
15
+ active_loop_ids: [],
16
+ blocked_loop_ids: proof.blockers
17
+ };
18
+ }
19
+ //# sourceMappingURL=loop-observability.js.map
@@ -0,0 +1,57 @@
1
+ export function inferLoopOwnerScope(input) {
2
+ if (input.integration) {
3
+ return {
4
+ files: ['CHANGELOG.md'],
5
+ directories: ['.sneakoscope/missions'],
6
+ package_scripts: [],
7
+ release_gate_ids: ['release:dag-full-coverage'],
8
+ exclusive: true,
9
+ collision_policy: 'integration-only'
10
+ };
11
+ }
12
+ const files = [...new Set(input.domain.files.filter((file) => !['package.json', 'release-gates.v2.json'].includes(file)))];
13
+ const releaseGateIds = input.domain.gates.filter((gate) => !gate.includes('*'));
14
+ const ownsPackage = input.domain.files.includes('package.json');
15
+ return {
16
+ files,
17
+ directories: input.domain.dirs.filter((dir) => dir !== 'package.json' && dir !== 'release-gates.v2.json'),
18
+ package_scripts: ownsPackage ? [] : inferPackageScripts(input.domain.id),
19
+ release_gate_ids: releaseGateIds,
20
+ exclusive: input.domain.id !== 'docs',
21
+ collision_policy: input.domain.id === 'docs' ? 'wait' : 'handoff'
22
+ };
23
+ }
24
+ export function detectOwnerScopeCollisions(scopes) {
25
+ const blockers = [];
26
+ for (let i = 0; i < scopes.length; i += 1) {
27
+ for (let j = i + 1; j < scopes.length; j += 1) {
28
+ const a = scopes[i];
29
+ const b = scopes[j];
30
+ if (!a || !b)
31
+ continue;
32
+ const fileOverlap = intersection(a.owner_scope.files, b.owner_scope.files);
33
+ const scriptOverlap = intersection(a.owner_scope.package_scripts, b.owner_scope.package_scripts);
34
+ if (fileOverlap.length && (a.owner_scope.exclusive || b.owner_scope.exclusive))
35
+ blockers.push(`file_collision:${a.loop_id}:${b.loop_id}:${fileOverlap.join(',')}`);
36
+ if (scriptOverlap.length)
37
+ blockers.push(`script_collision:${a.loop_id}:${b.loop_id}:${scriptOverlap.join(',')}`);
38
+ }
39
+ }
40
+ return blockers;
41
+ }
42
+ function inferPackageScripts(domainId) {
43
+ if (domainId === 'docs')
44
+ return ['docs:loop-runtime'];
45
+ if (domainId === 'naruto')
46
+ return ['naruto:loop-mesh'];
47
+ if (domainId === 'release')
48
+ return ['release:dag-full-coverage'];
49
+ if (domainId === 'loop-general-coding')
50
+ return ['loop:runtime'];
51
+ return [];
52
+ }
53
+ function intersection(a, b) {
54
+ const rhs = new Set(b);
55
+ return a.filter((value) => rhs.has(value));
56
+ }
57
+ //# sourceMappingURL=loop-owner-inference.js.map
@@ -0,0 +1,2 @@
1
+ export { acquireLoopLease, releaseLoopLease, detectLoopLeaseConflicts } from './loop-lease.js';
2
+ //# sourceMappingURL=loop-owner-ledger.js.map