sneakoscope 3.1.0 → 3.1.2

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 (84) 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/install-helpers.js +6 -7
  8. package/dist/commands/zellij-slot-column-anchor.js +3 -1
  9. package/dist/commands/zellij-slot-pane.js +19 -2
  10. package/dist/core/agents/agent-janitor.js +10 -1
  11. package/dist/core/agents/agent-orchestrator.js +8 -2
  12. package/dist/core/agents/agent-proof-evidence.js +20 -0
  13. package/dist/core/agents/agent-runner-ollama.js +11 -4
  14. package/dist/core/agents/fast-mode-policy.js +7 -5
  15. package/dist/core/agents/intelligent-work-graph.js +93 -14
  16. package/dist/core/agents/native-cli-session-swarm.js +115 -9
  17. package/dist/core/agents/no-subagent-scaling-policy.js +10 -1
  18. package/dist/core/agents/official-subagent-helper-policy.js +62 -0
  19. package/dist/core/codex-app.js +0 -2
  20. package/dist/core/codex-control/codex-task-runner.js +9 -0
  21. package/dist/core/commands/fast-mode-command.js +1 -1
  22. package/dist/core/commands/loop-command.js +86 -13
  23. package/dist/core/commands/naruto-command.js +34 -21
  24. package/dist/core/commands/team-command.js +1 -0
  25. package/dist/core/commands/wiki-command.js +35 -1
  26. package/dist/core/fsx.js +1 -1
  27. package/dist/core/init.js +1 -2
  28. package/dist/core/locks/file-lock.js +88 -0
  29. package/dist/core/loops/loop-artifacts.js +54 -2
  30. package/dist/core/loops/loop-checkpoint.js +22 -0
  31. package/dist/core/loops/loop-concurrency-budget.js +55 -0
  32. package/dist/core/loops/loop-final-arbiter-contract.js +28 -0
  33. package/dist/core/loops/loop-finalizer.js +55 -7
  34. package/dist/core/loops/loop-fixture-policy.js +58 -0
  35. package/dist/core/loops/loop-gate-registry.js +96 -0
  36. package/dist/core/loops/loop-gate-runner.js +206 -17
  37. package/dist/core/loops/loop-gpt-final-arbiter.js +81 -0
  38. package/dist/core/loops/loop-integration-merge.js +80 -0
  39. package/dist/core/loops/loop-interrupt-registry.js +118 -0
  40. package/dist/core/loops/loop-lease.js +35 -20
  41. package/dist/core/loops/loop-merge-strategy.js +105 -0
  42. package/dist/core/loops/loop-mutation-ledger.js +103 -0
  43. package/dist/core/loops/loop-planner.js +36 -5
  44. package/dist/core/loops/loop-runtime-control.js +27 -0
  45. package/dist/core/loops/loop-runtime.js +254 -96
  46. package/dist/core/loops/loop-scheduler.js +14 -5
  47. package/dist/core/loops/loop-side-effect-scanner.js +91 -0
  48. package/dist/core/loops/loop-worker-prompts.js +43 -0
  49. package/dist/core/loops/loop-worker-runtime.js +281 -0
  50. package/dist/core/loops/loop-worktree-runtime.js +92 -0
  51. package/dist/core/naruto/naruto-finalizer.js +7 -2
  52. package/dist/core/naruto/naruto-loop-mesh.js +10 -1
  53. package/dist/core/proof/auto-finalize.js +3 -2
  54. package/dist/core/proof/proof-schema.js +6 -0
  55. package/dist/core/proof/proof-writer.js +5 -2
  56. package/dist/core/proof/root-cause-policy.js +70 -0
  57. package/dist/core/proof/route-adapter.js +18 -1
  58. package/dist/core/proof/route-finalizer.js +71 -6
  59. package/dist/core/proof/route-proof-gate.js +4 -0
  60. package/dist/core/release/release-gate-batch-runner.js +56 -10
  61. package/dist/core/release/release-gate-cache-v2.js +18 -3
  62. package/dist/core/release/release-gate-dag.js +121 -18
  63. package/dist/core/release/release-gate-node.js +2 -1
  64. package/dist/core/release/release-gate-resource-governor.js +27 -6
  65. package/dist/core/skills/core-skill-meta-update.js +24 -0
  66. package/dist/core/skills/core-skill-reflection.js +94 -0
  67. package/dist/core/skills/core-skill-trainer.js +103 -0
  68. package/dist/core/trust-kernel/completion-contract.js +4 -0
  69. package/dist/core/trust-kernel/route-contract.js +4 -1
  70. package/dist/core/version.js +1 -1
  71. package/dist/core/zellij/zellij-right-column-manager.js +13 -2
  72. package/dist/core/zellij/zellij-slot-column-anchor.js +40 -3
  73. package/dist/core/zellij/zellij-slot-pane-renderer.js +36 -11
  74. package/dist/core/zellij/zellij-slot-telemetry.js +96 -44
  75. package/dist/core/zellij/zellij-worker-pane-manager.js +42 -4
  76. package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +14 -2
  77. package/dist/scripts/loop-directive-check-lib.js +225 -2
  78. package/dist/scripts/loop-hardening-check-lib.js +289 -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/dist/scripts/prepublish-release-check-or-fast.js +38 -10
  82. package/dist/scripts/release-check-stamp.js +29 -4
  83. package/dist/scripts/release-gate-existence-audit.js +1 -0
  84. package/package.json +32 -2
@@ -0,0 +1,281 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
4
+ import { ensureDir, nowIso, readJson, runProcess, writeJsonAtomic } from '../fsx.js';
5
+ import { loopNodeRoot } from './loop-artifacts.js';
6
+ import { computeLoopConcurrencyBudget, loopWorkerBudgetFor } from './loop-concurrency-budget.js';
7
+ import { decideLoopFixturePolicy, writeLoopFixturePolicyDecision } from './loop-fixture-policy.js';
8
+ import { buildLoopCheckerPrompt, buildLoopMakerPrompt } from './loop-worker-prompts.js';
9
+ export async function runLoopMakerWorkers(input) {
10
+ return runLoopWorkers({ ...input, phase: 'maker' });
11
+ }
12
+ export async function runLoopCheckerWorkers(input) {
13
+ return runLoopWorkers({ ...input, phase: 'checker', noMutation: true });
14
+ }
15
+ async function runLoopWorkers(input) {
16
+ if (shouldUseFixture(input))
17
+ return runLoopWorkerFixture(input);
18
+ return runLoopWorkerNative(input);
19
+ }
20
+ // `noMutation` used to force fixture mode here, which silently turned EVERY
21
+ // checker run into a deterministic fixture (checkers always pass
22
+ // noMutation: true for read-only semantics) — real model verification never
23
+ // happened. Fixture mode is now an explicit, separate test-only signal.
24
+ function shouldUseFixture(input) {
25
+ const requested = input.fixture === true || process.env.SKS_LOOP_RUNTIME_FIXTURE === '1';
26
+ if (!requested)
27
+ return false;
28
+ const decision = decideLoopFixturePolicy({
29
+ root: input.root,
30
+ missionId: input.plan.mission_id,
31
+ mode: 'worker',
32
+ requested
33
+ });
34
+ void writeLoopFixturePolicyDecision(input.root, input.plan.mission_id, decision).catch(() => undefined);
35
+ if (!decision.allowed) {
36
+ throw new Error(`loop_fixture_runtime_forbidden:${decision.reason}:${decision.blockers.join(',')}`);
37
+ }
38
+ return true;
39
+ }
40
+ async function runLoopWorkerNative(input) {
41
+ const prompt = input.phase === 'maker'
42
+ ? buildLoopMakerPrompt({ plan: input.plan, node: input.node, worktreePath: input.worktree?.path || null })
43
+ : buildLoopCheckerPrompt({ plan: input.plan, node: input.node, makerArtifacts: input.makerArtifacts || [] });
44
+ const workerCount = effectiveLoopWorkerCount(input);
45
+ const workGraph = buildLoopNarutoWorkGraph(input, workerCount);
46
+ // Root-cause-1 fix: keep the ORCHESTRATOR root on the MAIN repo (input.root), not the
47
+ // loop worktree. All zellij/right-column/slot-telemetry state derives from the orchestrator
48
+ // root, so anchoring it on input.root makes the SLOTS snapshot land under
49
+ // <main repo>/.sneakoscope/missions/<missionId>/... where the main session's anchor + slot
50
+ // renderer panes watch it (previously it landed under the worktree and went permanently stale).
51
+ // The loop worktree is still where workers cwd + write: it is threaded through the per-worker
52
+ // `worktree` opt below, which launchWorker reads as ctx.opts.worktree -> workerCwd.
53
+ const insideZellij = Boolean(process.env.SKS_ZELLIJ_SESSION_NAME || process.env.ZELLIJ);
54
+ const visiblePaneCap = Math.min(resolveLoopVisiblePaneCap(workerCount), Math.max(1, workerCount));
55
+ const zellijPlacementOpts = insideZellij ? {
56
+ workerPlacement: 'zellij-pane',
57
+ ...(process.env.SKS_ZELLIJ_SESSION_NAME ? { zellijSessionName: process.env.SKS_ZELLIJ_SESSION_NAME } : {}),
58
+ zellijVisiblePaneCap: visiblePaneCap
59
+ } : {};
60
+ const orchestrator = await runNativeAgentOrchestrator({
61
+ root: input.root,
62
+ missionId: input.plan.mission_id,
63
+ prompt,
64
+ route: '$Naruto',
65
+ backend: 'codex-sdk',
66
+ readonly: input.phase === 'checker',
67
+ workspaceWrite: input.phase === 'maker',
68
+ desiredWorkItemCount: workGraph.total_work_items,
69
+ minimumWorkItems: 1,
70
+ maxAgentCount: Math.max(1, workerCount),
71
+ targetActiveSlots: Math.max(1, workerCount),
72
+ visualLaneCount: visiblePaneCap,
73
+ narutoMode: true,
74
+ narutoWorkGraph: workGraph,
75
+ ...zellijPlacementOpts,
76
+ env: {
77
+ SKS_LOOP_ID: input.node.loop_id,
78
+ SKS_LOOP_PHASE: input.phase,
79
+ SKS_LOOP_MAIN_ROOT: input.root,
80
+ SKS_LOOP_WORKER_BUDGET: String(workerCount)
81
+ },
82
+ ...(input.worktree?.path ? {
83
+ worktree: {
84
+ id: input.worktree.id || `loop-${input.node.loop_id}-${input.phase}`,
85
+ path: input.worktree.path,
86
+ branch: input.worktree.branch || 'unknown',
87
+ main_repo_root: input.root
88
+ }
89
+ } : {}),
90
+ gitWorktreePolicy: input.worktree?.path ? {
91
+ mode: 'patch-envelope-only',
92
+ required: false,
93
+ main_repo_root: input.root,
94
+ worktree_root: input.worktree.path,
95
+ fallback_reason: null
96
+ } : null
97
+ });
98
+ return normalizeNativeResult(input, orchestrator);
99
+ }
100
+ async function normalizeNativeResult(input, result) {
101
+ const artifacts = collectArtifactPaths(result);
102
+ const changedFiles = stringArray(result?.changed_files || result?.proof?.changed_files || result?.results?.flatMap?.((row) => row?.changed_files || []));
103
+ const blockers = [
104
+ ...(result?.ok === true ? [] : ['loop_worker_native_orchestrator_not_ok']),
105
+ ...stringArray(result?.blockers || result?.proof?.blockers)
106
+ ];
107
+ const proofPath = path.join(loopNodeRoot(input.root, input.plan.mission_id, input.node.loop_id), input.phase, 'worker-runtime-result.json');
108
+ const normalized = {
109
+ schema: 'sks.loop-worker-run-result.v1',
110
+ ok: blockers.length === 0,
111
+ mission_id: input.plan.mission_id,
112
+ loop_id: input.node.loop_id,
113
+ phase: input.phase,
114
+ worker_count: effectiveLoopWorkerCount(input),
115
+ backend: 'native-agent-orchestrator',
116
+ artifacts,
117
+ patch_candidates: input.phase === 'maker' ? artifacts.filter((artifact) => artifact.includes('patch')) : [],
118
+ checker_findings: input.phase === 'checker' ? artifacts.filter((artifact) => artifact.includes('checker') || artifact.includes('finding')) : [],
119
+ changed_files: changedFiles,
120
+ blockers: [...new Set(blockers)],
121
+ runtime_proof_path: proofPath,
122
+ worker_ids: stringArray(result?.results?.map?.((row) => row?.agent_id || row?.id)),
123
+ session_ids: stringArray(result?.results?.map?.((row) => row?.session_id))
124
+ };
125
+ await writeJsonAtomic(proofPath, { ...normalized, native_result_summary: summarizeNativeResult(result), generated_at: nowIso() });
126
+ return normalized;
127
+ }
128
+ async function runLoopWorkerFixture(input) {
129
+ const fixturePolicy = decideLoopFixturePolicy({
130
+ root: input.root,
131
+ missionId: input.plan.mission_id,
132
+ mode: 'worker',
133
+ requested: true
134
+ });
135
+ const dir = path.join(loopNodeRoot(input.root, input.plan.mission_id, input.node.loop_id), input.phase);
136
+ await ensureDir(dir);
137
+ const resultPath = path.join(dir, 'worker-runtime-result.json');
138
+ const childInputPath = path.join(dir, 'worker-fixture-intake.json');
139
+ await writeJsonAtomic(childInputPath, {
140
+ schema: 'sks.loop-worker-fixture-intake.v1',
141
+ root: input.root,
142
+ mission_id: input.plan.mission_id,
143
+ loop_id: input.node.loop_id,
144
+ phase: input.phase,
145
+ worker_count: input.phase === 'maker' ? input.node.maker.worker_count : input.node.checker.worker_count,
146
+ result_path: resultPath,
147
+ owner_scope: input.node.owner_scope,
148
+ maker_artifacts: input.makerArtifacts || []
149
+ });
150
+ const child = await runProcess(process.execPath, [fixtureChildEntrypoint(), childInputPath], {
151
+ cwd: input.root,
152
+ timeoutMs: input.timeoutMs || 30000,
153
+ maxOutputBytes: 64 * 1024
154
+ });
155
+ const result = await readJson(resultPath, null);
156
+ if (!result) {
157
+ return {
158
+ schema: 'sks.loop-worker-run-result.v1',
159
+ ok: false,
160
+ mission_id: input.plan.mission_id,
161
+ loop_id: input.node.loop_id,
162
+ phase: input.phase,
163
+ worker_count: 0,
164
+ backend: 'deterministic-fixture',
165
+ artifacts: [],
166
+ patch_candidates: [],
167
+ checker_findings: [],
168
+ changed_files: [],
169
+ blockers: [`loop_worker_fixture_child_missing_result:${child.code}`],
170
+ runtime_proof_path: resultPath,
171
+ worker_ids: [],
172
+ session_ids: [],
173
+ fixture_policy: fixturePolicy,
174
+ fixture_allowed_reason: fixturePolicy.allowed ? fixturePolicy.reason : null
175
+ };
176
+ }
177
+ return {
178
+ ...result,
179
+ ok: result.ok && child.code === 0,
180
+ blockers: [
181
+ ...result.blockers,
182
+ ...(child.code === 0 ? [] : [`loop_worker_fixture_child_exit:${child.code}`])
183
+ ],
184
+ fixture_policy: fixturePolicy,
185
+ fixture_allowed_reason: fixturePolicy.allowed ? fixturePolicy.reason : null
186
+ };
187
+ }
188
+ function buildLoopNarutoWorkGraph(input, workerCount) {
189
+ const workItems = Array.from({ length: Math.max(1, workerCount) }, (_, index) => {
190
+ const id = `${input.node.loop_id}-${input.phase}-${index + 1}`;
191
+ const writeAllowed = input.phase === 'maker';
192
+ return {
193
+ id,
194
+ kind: writeAllowed ? 'code_modification' : 'verification',
195
+ title: `${input.phase} worker ${index + 1} for ${input.node.loop_id}`,
196
+ target_paths: [...input.node.owner_scope.files, ...input.node.owner_scope.directories],
197
+ readonly_paths: input.phase === 'checker' ? [...input.node.owner_scope.files, ...input.node.owner_scope.directories] : [],
198
+ write_paths: writeAllowed ? [...input.node.owner_scope.files, ...input.node.owner_scope.directories] : [],
199
+ required_role: input.phase,
200
+ write_allowed: writeAllowed,
201
+ verification_required: input.phase === 'checker',
202
+ dependencies: [],
203
+ can_run_in_parallel_with: [],
204
+ conflicts_with: [],
205
+ estimated_cost: { tokens: 8000, latency_ms: 30000, cpu_weight: 1, memory_mb: 512, gpu_weight: 0 },
206
+ lease_requirements: input.node.owner_scope.files.map((file) => ({ path: file, kind: writeAllowed ? 'write' : 'read' })),
207
+ acceptance: { requires_patch_envelope: writeAllowed, requires_verification: !writeAllowed, requires_gpt_final: input.node.risk.requires_gpt_final },
208
+ owner: input.node.loop_id,
209
+ allocation_reason: 'loop-worker-runtime',
210
+ allocation_score: 1,
211
+ allocation_hints: null,
212
+ lane: input.phase,
213
+ worktree: {
214
+ mode: input.worktree?.path ? 'patch-envelope-only' : 'git-worktree',
215
+ required: input.node.worktree.required,
216
+ allocation_required: false
217
+ }
218
+ };
219
+ });
220
+ return {
221
+ schema: 'sks.naruto-work-graph.v1',
222
+ route: '$Naruto',
223
+ requested_clones: workerCount,
224
+ total_work_items: workItems.length,
225
+ readonly: input.phase === 'checker',
226
+ write_capable: input.phase === 'maker',
227
+ work_items: workItems,
228
+ active_waves: [{ wave_id: `${input.node.loop_id}-${input.phase}`, work_item_ids: workItems.map((item) => item.id), write_paths: workItems.flatMap((item) => item.write_paths), conflict_count: 0 }],
229
+ mixed_work_kinds: [...new Set(workItems.map((item) => item.kind))],
230
+ write_allowed_count: workItems.filter((item) => item.write_allowed).length,
231
+ worktree_policy: {
232
+ mode: input.worktree?.path ? 'patch-envelope-only' : 'git-worktree',
233
+ required: input.node.worktree.required,
234
+ main_repo_root: input.root,
235
+ worktree_root: null,
236
+ fallback_reason: input.worktree?.path ? 'loop_worktree_already_allocated' : null
237
+ },
238
+ blockers: [],
239
+ ok: true
240
+ };
241
+ }
242
+ function collectArtifactPaths(result) {
243
+ return stringArray([
244
+ result?.ledger_root,
245
+ result?.proof?.artifact,
246
+ ...(Array.isArray(result?.results) ? result.results.flatMap((row) => row?.artifacts || row?.patch_queue_refs || []) : [])
247
+ ]);
248
+ }
249
+ function summarizeNativeResult(result) {
250
+ return {
251
+ ok: result?.ok === true,
252
+ status: result?.status || null,
253
+ mission_id: result?.mission_id || null,
254
+ backend: result?.backend || null,
255
+ result_count: Array.isArray(result?.results) ? result.results.length : 0,
256
+ blockers: stringArray(result?.blockers || result?.proof?.blockers).slice(0, 20)
257
+ };
258
+ }
259
+ function stringArray(value) {
260
+ if (!Array.isArray(value))
261
+ return [];
262
+ return [...new Set(value.flat().map((item) => String(item || '').trim()).filter(Boolean))];
263
+ }
264
+ // Visible pane cap for loop workers: defaults to min(4, workers) so the right
265
+ // column stays readable; SKS_ZELLIJ_VISIBLE_PANE_CAP overrides for tall
266
+ // terminals (overflow workers run headless and stay visible in SLOTS rows).
267
+ function resolveLoopVisiblePaneCap(workerCount) {
268
+ const fromEnv = Number(process.env.SKS_ZELLIJ_VISIBLE_PANE_CAP || 0);
269
+ if (Number.isFinite(fromEnv) && fromEnv >= 1)
270
+ return Math.floor(fromEnv);
271
+ return Math.min(4, Math.max(1, workerCount));
272
+ }
273
+ function fixtureChildEntrypoint() {
274
+ return fileURLToPath(new URL('../../scripts/loop-worker-fixture-child.js', import.meta.url));
275
+ }
276
+ function effectiveLoopWorkerCount(input) {
277
+ const requested = input.phase === 'maker' ? input.node.maker.worker_count : input.node.checker.worker_count;
278
+ const budget = computeLoopConcurrencyBudget({ plan: input.plan });
279
+ return loopWorkerBudgetFor(budget, input.node.loop_id, input.phase, requested);
280
+ }
281
+ //# sourceMappingURL=loop-worker-runtime.js.map
@@ -0,0 +1,92 @@
1
+ import path from 'node:path';
2
+ import { exists, nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { allocateWorkerWorktree } from '../git/git-worktree-manager.js';
4
+ import { gitOutputLine, runGitCommand } from '../git/git-worktree-runner.js';
5
+ import { loopNodeRoot } from './loop-artifacts.js';
6
+ export async function allocateLoopWorktree(input) {
7
+ const blockers = [];
8
+ let worktreeId = null;
9
+ let worktreePath = null;
10
+ let branch = null;
11
+ let baseRef = null;
12
+ if (input.node.worktree.required && !input.noMutation) {
13
+ const gitPresent = await exists(path.join(input.root, '.git'));
14
+ if (!gitPresent) {
15
+ blockers.push('loop_worktree_required_but_git_missing');
16
+ }
17
+ else {
18
+ const allocation = await allocateWorkerWorktree({
19
+ repoRoot: input.root,
20
+ missionId: input.plan.mission_id,
21
+ workerId: input.node.loop_id,
22
+ slotId: input.node.loop_id,
23
+ generationIndex: 1,
24
+ branchPrefix: input.node.worktree.branch_prefix
25
+ }).catch((err) => ({ ok: false, blockers: [`loop_worktree_allocate_exception:${err instanceof Error ? err.message : String(err)}`] }));
26
+ if (allocation.ok) {
27
+ worktreeId = allocation.worker_id || input.node.loop_id;
28
+ worktreePath = allocation.worktree_path || null;
29
+ branch = allocation.branch || null;
30
+ baseRef = allocation.base_ref || null;
31
+ }
32
+ else {
33
+ blockers.push(...stringArray(allocation.blockers));
34
+ }
35
+ }
36
+ }
37
+ const record = {
38
+ schema: 'sks.loop-worktree.v1',
39
+ loop_id: input.node.loop_id,
40
+ worktree_id: worktreeId,
41
+ path: worktreePath,
42
+ branch,
43
+ base_ref: baseRef,
44
+ allocated_at: nowIso(),
45
+ cleanup_policy: input.node.worktree.cleanup,
46
+ blockers
47
+ };
48
+ await writeJsonAtomic(path.join(loopNodeRoot(input.root, input.plan.mission_id, input.node.loop_id), 'worktree.json'), record);
49
+ return record;
50
+ }
51
+ export async function computeLoopDiff(input) {
52
+ const cwd = input.worktreePath || input.root;
53
+ const blockers = [];
54
+ const names = await runGitCommand(cwd, ['diff', '--name-only', 'HEAD'], { timeoutMs: 30000 }).catch(() => null);
55
+ const stat = await runGitCommand(cwd, ['diff', '--stat', 'HEAD'], { timeoutMs: 30000 }).catch(() => null);
56
+ const diff = await runGitCommand(cwd, ['diff', '--binary', '--full-index', 'HEAD'], { timeoutMs: 60000 }).catch(() => null);
57
+ if (!names?.ok)
58
+ blockers.push('loop_git_diff_name_only_failed');
59
+ if (!diff?.ok)
60
+ blockers.push('loop_git_diff_failed');
61
+ const changedFiles = [...new Set((names?.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean))];
62
+ blockers.push(...enforceLoopOwnerScope(changedFiles, input.ownerScope));
63
+ return {
64
+ changed_files: changedFiles,
65
+ patch_bytes: Buffer.byteLength(diff?.stdout || ''),
66
+ diff_stat: stat ? gitOutputLine(stat) || stat.stdout.slice(-4000) : '',
67
+ blockers: [...new Set(blockers)]
68
+ };
69
+ }
70
+ export function enforceLoopOwnerScope(changedFiles, ownerScope) {
71
+ const blockers = [];
72
+ for (const file of changedFiles) {
73
+ if (!isInOwnerScope(file, ownerScope))
74
+ blockers.push(`loop_owner_scope_violation:${file}`);
75
+ }
76
+ return blockers;
77
+ }
78
+ function isInOwnerScope(file, ownerScope) {
79
+ const normalized = normalizePath(file);
80
+ if (ownerScope.files.map(normalizePath).includes(normalized))
81
+ return true;
82
+ return ownerScope.directories.map(normalizePath).some((dir) => normalized === dir || normalized.startsWith(`${dir}/`));
83
+ }
84
+ function normalizePath(value) {
85
+ return String(value || '').replace(/\\/g, '/').replace(/^\.\/+/, '');
86
+ }
87
+ function stringArray(value) {
88
+ if (!Array.isArray(value))
89
+ return [];
90
+ return value.map((item) => String(item || '').trim()).filter(Boolean);
91
+ }
92
+ //# sourceMappingURL=loop-worktree-runtime.js.map
@@ -5,14 +5,19 @@ export function evaluateNarutoFinalizer(input = {}) {
5
5
  const blockers = [
6
6
  ...(gptFinalRequired && !gptFinalAccepted ? ['naruto_local_worker_output_needs_gpt_final_arbiter'] : [])
7
7
  ];
8
+ const finalStatus = blockers.length ? 'blocked' : input.applyPatches === true ? 'accepted' : 'draft';
9
+ const applyFinalized = finalStatus === 'accepted';
8
10
  return {
9
11
  schema: 'sks.naruto-finalizer.v1',
10
12
  local_participated: localParticipated,
11
13
  gpt_final_required: gptFinalRequired,
12
- final_status: blockers.length ? 'blocked' : input.applyPatches === true ? 'accepted' : 'draft',
14
+ final_status: finalStatus,
13
15
  final_patch_source: gptFinalRequired ? 'gpt_final_arbiter' : 'deterministic_no_local',
14
16
  blockers,
15
- ok: blockers.length === 0
17
+ run_ok: blockers.length === 0,
18
+ release_proof_allowed: applyFinalized,
19
+ apply_finalized: applyFinalized,
20
+ ok: applyFinalized
16
21
  };
17
22
  }
18
23
  //# sourceMappingURL=naruto-finalizer.js.map
@@ -1,17 +1,26 @@
1
1
  import { writeJsonAtomic } from '../fsx.js';
2
+ import { computeLoopConcurrencyBudget } from '../loops/loop-concurrency-budget.js';
2
3
  import { loopRoot } from '../loops/loop-artifacts.js';
3
4
  import { runLoopPlan } from '../loops/loop-runtime.js';
4
5
  import { routeNarutoLoopWorker } from './naruto-loop-worker-router.js';
5
6
  export async function runNarutoLoopMesh(input) {
6
7
  const routes = input.plan.graph.nodes.flatMap((node) => [routeNarutoLoopWorker(node, 'maker'), routeNarutoLoopWorker(node, 'checker')]);
7
8
  const activeWorkerBudget = splitActiveWorkerBudget(input.plan, input.parallelism);
9
+ const loopConcurrencyBudget = computeLoopConcurrencyBudget({ plan: input.plan, parallelism: input.parallelism });
8
10
  await writeJsonAtomic(`${loopRoot(input.root, input.plan.mission_id)}/naruto-loop-worker-routes.json`, {
9
11
  schema: 'sks.naruto-loop-worker-routes.v1',
10
12
  mission_id: input.plan.mission_id,
11
13
  active_worker_budget: activeWorkerBudget,
14
+ loop_concurrency_budget: loopConcurrencyBudget,
12
15
  routes
13
16
  });
14
- return runLoopPlan({ root: input.root, plan: input.plan, parallelism: input.parallelism });
17
+ return runLoopPlan({
18
+ root: input.root,
19
+ plan: input.plan,
20
+ parallelism: input.parallelism,
21
+ ...(input.dryRun === undefined ? {} : { dryRun: input.dryRun }),
22
+ ...(input.noMutation === undefined ? {} : { noMutation: input.noMutation })
23
+ });
15
24
  }
16
25
  export function splitActiveWorkerBudget(plan, parallelism) {
17
26
  const cap = parallelism === 'safe' ? 8 : parallelism === 'extreme' ? 32 : 16;
@@ -13,7 +13,7 @@ const AGENT_ARTIFACTS = [
13
13
  'agents/agent-task-board.json',
14
14
  'agents/agent-concurrency-policy.json'
15
15
  ];
16
- export async function maybeFinalizeRoute(root, { missionId, route, gateFile = null, gate = null, artifacts = [], claims = [], visualEvidence = null, visual = false, fixClaim = false, requireRelation = false, mock = false, statusHint = null, reason = null, command = null, dbEvidence = null, testEvidence = null, blockers = [], unverified = [], agents = undefined, allowActiveWrongnessPartial = false } = {}) {
16
+ export async function maybeFinalizeRoute(root, { missionId, route, gateFile = null, gate = null, artifacts = [], claims = [], visualEvidence = null, visual = false, fixClaim = false, requireRelation = false, mock = false, statusHint = null, reason = null, command = null, dbEvidence = null, testEvidence = null, blockers = [], unverified = [], agents = undefined, allowActiveWrongnessPartial = false, failureAnalysis = null } = {}) {
17
17
  if (!missionId || !route) {
18
18
  return { ok: false, skipped: true, reason: 'mission_id_or_route_missing' };
19
19
  }
@@ -61,7 +61,8 @@ export async function maybeFinalizeRoute(root, { missionId, route, gateFile = nu
61
61
  requireRelation,
62
62
  visualClaim: visual,
63
63
  agents,
64
- allowActiveWrongnessPartial
64
+ allowActiveWrongnessPartial,
65
+ failureAnalysis
65
66
  });
66
67
  return { ...proof, auto_finalized: true, gate_passed: passed, status_hint: finalStatus };
67
68
  }
@@ -38,6 +38,12 @@ export function emptyCompletionProof(overrides = {}) {
38
38
  claims: [],
39
39
  unverified: [],
40
40
  blockers: [],
41
+ failure_analysis: {
42
+ status: 'not_required',
43
+ root_cause: null,
44
+ corrective_action: null,
45
+ evidence: []
46
+ },
41
47
  next_human_actions: [],
42
48
  ...overrides
43
49
  };
@@ -76,10 +76,13 @@ export function renderProofMarkdown(proof = {}, validation = validateCompletionP
76
76
  `- Wrongness: ${proof.evidence?.wrongness?.active_count ?? 0} active (${proof.evidence?.wrongness?.high_severity_active ?? 0} high)`,
77
77
  `- Evidence router: ${proof.evidence?.evidence_router?.records ?? 0} record(s)`,
78
78
  `- Trust report: ${proof.evidence?.trust_report || 'not_recorded'}`,
79
- '',
80
- '## Unverified',
81
79
  ''
82
80
  ];
81
+ const failureAnalysis = proof.failure_analysis;
82
+ if (failureAnalysis && (failureAnalysis.status !== 'not_required' || failureAnalysis.root_cause || failureAnalysis.corrective_action)) {
83
+ lines.push('## Failure Analysis', '', `- Status: ${failureAnalysis.status || 'unknown'}`, `- Root cause: ${failureAnalysis.root_cause || 'not_recorded'}`, `- Corrective action: ${failureAnalysis.corrective_action || 'not_recorded'}`, `- Evidence: ${Array.isArray(failureAnalysis.evidence) ? failureAnalysis.evidence.length : failureAnalysis.evidence ? 1 : 0}`, '');
84
+ }
85
+ lines.push('## Unverified', '');
83
86
  const unverified = proof.unverified?.length ? proof.unverified : ['No unverified claims recorded.'];
84
87
  for (const item of unverified)
85
88
  lines.push(`- ${typeof item === 'string' ? item : JSON.stringify(item)}`);
@@ -0,0 +1,70 @@
1
+ const PROBLEM_PATTERN = /\b(fallback|workaround|bypass|temporary|synthetic|stale|missing|failed|failure|error|blocked|not_ok|not ok|fixture_child_missing|native_agent_proof_false)\b/i;
2
+ const COMPLETE_STATUSES = new Set(['complete', 'completed', 'corrected', 'resolved', 'fixed']);
3
+ const BLOCKING_STATUSES = new Set(['blocked', 'failed', 'not_verified']);
4
+ function asRecord(value) {
5
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
6
+ }
7
+ function asList(value) {
8
+ return Array.isArray(value) ? value : [];
9
+ }
10
+ function meaningfulString(value, minLength = 12) {
11
+ return typeof value === 'string' && value.trim().length >= minLength;
12
+ }
13
+ function hasEvidence(value) {
14
+ if (typeof value === 'string')
15
+ return value.trim().length >= 6;
16
+ if (Array.isArray(value))
17
+ return value.length > 0;
18
+ if (value && typeof value === 'object')
19
+ return Object.keys(value).length > 0;
20
+ return false;
21
+ }
22
+ export function rootCauseAnalysisRequired(proof = {}, validationIssues = []) {
23
+ const proofRecord = asRecord(proof);
24
+ if (!Object.keys(proofRecord).length)
25
+ return false;
26
+ const evidence = asRecord(proofRecord.evidence);
27
+ const agents = asRecord(evidence.agents);
28
+ const routeGate = asRecord(evidence.route_gate);
29
+ if (BLOCKING_STATUSES.has(String(proofRecord.status || '')))
30
+ return true;
31
+ if (asList(proofRecord.blockers).length > 0)
32
+ return true;
33
+ if (validationIssues.some((issue) => String(issue) !== 'root_cause_analysis_missing'))
34
+ return true;
35
+ const problemSurface = {
36
+ status: proofRecord.status,
37
+ unverified: proofRecord.unverified,
38
+ blockers: proofRecord.blockers,
39
+ claims: proofRecord.claims,
40
+ route_gate: routeGate,
41
+ agents: {
42
+ ok: agents.ok,
43
+ status: agents.status,
44
+ blockers: agents.blockers,
45
+ issues: agents.issues
46
+ },
47
+ wrongness: evidence.wrongness,
48
+ trust_report: evidence.trust_report
49
+ };
50
+ return PROBLEM_PATTERN.test(JSON.stringify(problemSurface));
51
+ }
52
+ export function rootCauseAnalysisComplete(proof = {}) {
53
+ const proofRecord = asRecord(proof);
54
+ const analysis = asRecord(proofRecord.failure_analysis || asRecord(proofRecord.evidence).root_cause_analysis);
55
+ if (!Object.keys(analysis).length)
56
+ return false;
57
+ const status = String(analysis.status || '').toLowerCase();
58
+ if (!COMPLETE_STATUSES.has(status))
59
+ return false;
60
+ const rootCause = analysis.root_cause ?? analysis.cause;
61
+ const correctiveAction = analysis.corrective_action ?? analysis.fix ?? analysis.correction;
62
+ const evidence = analysis.evidence ?? analysis.proof ?? analysis.references;
63
+ return meaningfulString(rootCause) && meaningfulString(correctiveAction) && hasEvidence(evidence);
64
+ }
65
+ export function rootCauseAnalysisIssue(proof = {}, validationIssues = []) {
66
+ if (!rootCauseAnalysisRequired(proof, validationIssues))
67
+ return null;
68
+ return rootCauseAnalysisComplete(proof) ? null : 'root_cause_analysis_missing';
69
+ }
70
+ //# sourceMappingURL=root-cause-policy.js.map
@@ -5,7 +5,7 @@ import { normalizeProofRoute, routeRequiresImageVoxelAnchors } from './route-pro
5
5
  import { linkProofClaimsToEvidence, proofEvidenceSummary } from '../evidence/evidence-proof-linker.js';
6
6
  import { writeTrustArtifactsForProof } from '../trust-kernel/trust-report.js';
7
7
  import { enforceRetention } from '../retention.js';
8
- export async function writeRouteCompletionProof(root, { missionId = null, route = null, status = 'verified_partial', gate = null, summary = {}, artifacts = [], evidence = {}, claims = [], unverified = [], blockers = [], nextHumanActions = [] } = {}) {
8
+ export async function writeRouteCompletionProof(root, { missionId = null, route = null, status = 'verified_partial', gate = null, summary = {}, artifacts = [], evidence = {}, claims = [], unverified = [], blockers = [], failureAnalysis = null, nextHumanActions = [] } = {}) {
9
9
  const collected = await collectProofEvidence(root);
10
10
  const normalizedRoute = normalizeProofRoute(route);
11
11
  const mergedEvidence = {
@@ -36,6 +36,7 @@ export async function writeRouteCompletionProof(root, { missionId = null, route
36
36
  claims,
37
37
  unverified,
38
38
  blockers,
39
+ failure_analysis: normalizeFailureAnalysis(failureAnalysis || evidence.failure_analysis || evidence.root_cause_analysis),
39
40
  next_human_actions: nextHumanActions
40
41
  }, {
41
42
  command: {
@@ -68,6 +69,22 @@ export async function writeRouteCompletionProof(root, { missionId = null, route
68
69
  const retention = await runPostRouteRetention(root, missionId);
69
70
  return { ...enriched, trust, retention };
70
71
  }
72
+ function normalizeFailureAnalysis(value) {
73
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
74
+ return {
75
+ status: 'not_required',
76
+ root_cause: null,
77
+ corrective_action: null,
78
+ evidence: []
79
+ };
80
+ }
81
+ return {
82
+ status: value.status || 'complete',
83
+ root_cause: value.root_cause || value.cause || null,
84
+ corrective_action: value.corrective_action || value.fix || value.correction || null,
85
+ evidence: value.evidence || value.proof || value.references || []
86
+ };
87
+ }
71
88
  function normalizeRouteProofStatus(status, { route, evidence, blockers, unverified }) {
72
89
  if (blockers?.length)
73
90
  return status === 'failed' ? 'failed' : 'blocked';