sneakoscope 2.0.10 → 2.0.12

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 (65) hide show
  1. package/README.md +5 -3
  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/build-manifest.json +27 -8
  8. package/dist/cli/command-registry.js +1 -0
  9. package/dist/cli/install-helpers.js +8 -20
  10. package/dist/commands/doctor.js +5 -9
  11. package/dist/commands/zellij-slot-column-anchor.js +23 -0
  12. package/dist/core/agents/agent-orchestrator.js +338 -12
  13. package/dist/core/agents/agent-patch-schema.js +8 -1
  14. package/dist/core/agents/agent-scheduler.js +12 -1
  15. package/dist/core/agents/agent-slot-pane-binding-proof.js +3 -3
  16. package/dist/core/agents/agent-work-queue.js +26 -2
  17. package/dist/core/agents/agent-worker-pipeline.js +2 -0
  18. package/dist/core/agents/native-cli-session-swarm.js +2 -2
  19. package/dist/core/commands/naruto-command.js +191 -39
  20. package/dist/core/fsx.js +1 -1
  21. package/dist/core/git/git-worktree-checkpoint.js +52 -0
  22. package/dist/core/git/git-worktree-cross-rebase.js +54 -0
  23. package/dist/core/git/git-worktree-merge-queue.js +92 -3
  24. package/dist/core/git/git-worktree-patch-envelope.js +8 -1
  25. package/dist/core/init.js +2 -2
  26. package/dist/core/naruto/naruto-allocation-policy.js +99 -0
  27. package/dist/core/naruto/naruto-real-worker-child.js +110 -11
  28. package/dist/core/naruto/naruto-rebalance-policy.js +48 -0
  29. package/dist/core/naruto/naruto-task-hints.js +71 -0
  30. package/dist/core/naruto/naruto-work-graph.js +13 -0
  31. package/dist/core/pipeline/finalize-pipeline-result.js +3 -1
  32. package/dist/core/pipeline/gpt-final-required.js +22 -2
  33. package/dist/core/version.js +1 -1
  34. package/dist/core/zellij/zellij-right-column-manager.js +45 -2
  35. package/dist/core/zellij/zellij-slot-column-anchor.js +218 -0
  36. package/dist/core/zellij/zellij-worker-pane-manager.js +81 -14
  37. package/dist/scripts/agent-real-codex-in-zellij-worker-pane-check.js +8 -2
  38. package/dist/scripts/agent-slot-pane-binding-proof-check.js +4 -4
  39. package/dist/scripts/codex-sdk-release-review-pipeline-check.js +2 -1
  40. package/dist/scripts/codex-sdk-zellij-pane-binding-check.js +2 -2
  41. package/dist/scripts/git-worktree-checkpoint-check.js +20 -0
  42. package/dist/scripts/git-worktree-cross-rebase-check.js +39 -0
  43. package/dist/scripts/git-worktree-merge-queue-check.js +1 -0
  44. package/dist/scripts/local-collab-worktree-gpt-final-apply-policy-check.js +63 -0
  45. package/dist/scripts/naruto-actual-worker-control-plane-check.js +56 -0
  46. package/dist/scripts/naruto-allocation-policy-check.js +33 -0
  47. package/dist/scripts/naruto-allocation-runtime-wiring-check.js +92 -0
  48. package/dist/scripts/naruto-extreme-parallelism-real-check.js +5 -4
  49. package/dist/scripts/naruto-orchestrator-runtime-source-check.js +70 -0
  50. package/dist/scripts/naruto-real-active-pool-runtime-check.js +4 -2
  51. package/dist/scripts/naruto-rebalance-policy-check.js +41 -0
  52. package/dist/scripts/naruto-shadow-clone-swarm-check.js +8 -4
  53. package/dist/scripts/release-dag-full-coverage-check.js +19 -1
  54. package/dist/scripts/release-readiness-report.js +1 -1
  55. package/dist/scripts/release-real-check.js +258 -77
  56. package/dist/scripts/zellij-first-slot-down-stack-check.js +20 -0
  57. package/dist/scripts/zellij-first-slot-down-stack-real-check.js +356 -0
  58. package/dist/scripts/zellij-right-column-manager-check.js +7 -2
  59. package/dist/scripts/zellij-slot-column-anchor-check.js +45 -0
  60. package/dist/scripts/zellij-slot-only-ui-check.js +6 -2
  61. package/dist/scripts/zellij-slot-renderer-proof-semantics-check.js +59 -0
  62. package/dist/scripts/zellij-worker-pane-manager-check.js +23 -1
  63. package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +11 -4
  64. package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +21 -4
  65. package/package.json +15 -3
@@ -0,0 +1,99 @@
1
+ import { extractNarutoTaskHints, pathPrefix } from './naruto-task-hints.js';
2
+ export function chooseNarutoTaskOwner(task, workers, currentAssignments = [], leaseState = {}) {
3
+ if (!workers.length)
4
+ throw new Error('at least one Naruto worker is required');
5
+ const hints = extractNarutoTaskHints(task);
6
+ const activeWritePaths = new Set((leaseState.active_write_paths || []).map(String));
7
+ const completedTaskIds = new Set((leaseState.completed_task_ids || []).map(String));
8
+ const writeConflict = hints.writePaths.some((file) => activeWritePaths.has(file));
9
+ const dependencyIncomplete = task.dependencies.some((dep) => !completedTaskIds.has(dep));
10
+ const ranked = workers.map((worker, index) => {
11
+ const assigned = currentAssignments.filter((row) => row.owner === worker.id);
12
+ const assignedHints = assigned.map((row) => ({
13
+ role: row.role || null,
14
+ paths: row.paths || [],
15
+ domains: row.domains || [],
16
+ writePaths: row.write_paths || []
17
+ }));
18
+ const primaryRole = worker.primary_role || worker.role || null;
19
+ const declaredRoles = new Set([worker.role, ...(worker.declared_roles || [])].filter(Boolean).map(String));
20
+ const primaryRoleMatches = Boolean(hints.role && primaryRole === hints.role);
21
+ const declaredRoleMatches = Boolean(hints.role && declaredRoles.has(hints.role));
22
+ const assignmentRoleMatches = Boolean(hints.role && assigned.some((row) => row.role === hints.role));
23
+ const sameLane = samePathLane(hints.paths, assignedHints.flatMap((row) => row.paths));
24
+ const overlap = overlapCount(hints.paths, assignedHints.flatMap((row) => row.paths))
25
+ + overlapCount(hints.domains, assignedHints.flatMap((row) => row.domains));
26
+ const laneMatches = Boolean(worker.lane && hints.paths.some((file) => pathLaneMatches(file, String(worker.lane))));
27
+ const score = dependencyIncomplete
28
+ ? Number.NEGATIVE_INFINITY
29
+ : (primaryRoleMatches ? 18 : 0)
30
+ + (declaredRoleMatches ? 12 : 0)
31
+ + (sameLane || laneMatches ? 12 : 0)
32
+ + (overlap * 4)
33
+ - (assigned.length * 4)
34
+ - (writeConflict ? 20 : 0);
35
+ return { worker, index, assigned, score, overlap, primaryRoleMatches, declaredRoleMatches, assignmentRoleMatches, sameLane: sameLane || laneMatches, writeConflict, dependencyIncomplete };
36
+ }).sort((left, right) => {
37
+ if (right.score !== left.score)
38
+ return right.score - left.score;
39
+ if (right.overlap !== left.overlap)
40
+ return right.overlap - left.overlap;
41
+ if (left.assigned.length !== right.assigned.length)
42
+ return left.assigned.length - right.assigned.length;
43
+ return left.index - right.index;
44
+ });
45
+ const selected = ranked[0];
46
+ const reasons = [
47
+ selected.primaryRoleMatches ? 'same primary role' : null,
48
+ selected.declaredRoleMatches ? 'same declared role' : null,
49
+ selected.assignmentRoleMatches ? 'same assigned role history' : null,
50
+ selected.sameLane ? 'same path/domain lane' : null,
51
+ selected.overlap ? `overlap:${selected.overlap}` : null,
52
+ selected.writeConflict ? 'write lease conflict penalty applied' : null,
53
+ selected.dependencyIncomplete ? 'dependency incomplete' : null,
54
+ `load:${selected.assigned.length}`
55
+ ].filter(Boolean);
56
+ return {
57
+ owner: selected.worker.id,
58
+ score: selected.score,
59
+ reason: reasons.join('; '),
60
+ hints
61
+ };
62
+ }
63
+ export function allocateNarutoTasksToWorkers(tasks, workers) {
64
+ const assignments = [];
65
+ for (const task of tasks) {
66
+ const decision = chooseNarutoTaskOwner(task, workers, assignments.map((row) => ({
67
+ task_id: row.id,
68
+ owner: row.owner,
69
+ role: row.required_role,
70
+ paths: row.hints.paths,
71
+ domains: row.hints.domains,
72
+ write_paths: row.hints.writePaths
73
+ })), {
74
+ active_write_paths: assignments.flatMap((row) => row.hints.writePaths)
75
+ });
76
+ assignments.push({
77
+ ...task,
78
+ owner: decision.owner,
79
+ allocation_reason: decision.reason,
80
+ allocation_score: decision.score,
81
+ hints: decision.hints
82
+ });
83
+ }
84
+ return assignments;
85
+ }
86
+ function samePathLane(left, right) {
87
+ const prefixes = new Set(right.map(pathPrefix).filter(Boolean));
88
+ return left.some((file) => prefixes.has(pathPrefix(file)));
89
+ }
90
+ function pathLaneMatches(file, lane) {
91
+ const normalizedLane = lane.replace(/^\.\/+/, '').replace(/\/+$/, '');
92
+ const normalizedFile = file.replace(/^\.\/+/, '');
93
+ return pathPrefix(normalizedFile) === normalizedLane || normalizedFile === normalizedLane || normalizedFile.startsWith(`${normalizedLane}/`);
94
+ }
95
+ function overlapCount(left, right) {
96
+ const rightSet = new Set(right);
97
+ return left.filter((item) => rightSet.has(item)).length;
98
+ }
99
+ //# sourceMappingURL=naruto-allocation-policy.js.map
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from 'node:fs/promises';
3
- import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
3
+ import path from 'node:path';
4
+ import { ensureDir, nowIso, readJson, writeJsonAtomic } from '../fsx.js';
5
+ import { runCodexTask } from '../codex-control/codex-control-plane.js';
6
+ import { CODEX_AGENT_WORKER_RESULT_SCHEMA_ID, codexAgentWorkerResultSchema } from '../codex-control/schemas/agent-worker-result.schema.js';
4
7
  async function main() {
5
8
  const intakePath = process.argv[2];
6
9
  if (!intakePath)
@@ -15,16 +18,112 @@ async function main() {
15
18
  item_id: intake.item.id,
16
19
  status: 'running'
17
20
  })}\n`);
18
- await new Promise((resolve) => setTimeout(resolve, 25));
19
- await writeJsonAtomic(intake.result_path, {
20
- schema: 'sks.naruto-actual-worker-result.v1',
21
- ok: true,
22
- generated_at: nowIso(),
23
- item_id: intake.item.id,
24
- placement: intake.placement,
25
- backend: intake.backend,
26
- worktree_path: intake.worktree_path
27
- });
21
+ if (intake.backend === 'fake')
22
+ process.env.SKS_CODEX_SDK_FAKE = '1';
23
+ const controlRoot = path.join(path.dirname(intake.result_path), 'codex-control');
24
+ await ensureDir(controlRoot);
25
+ try {
26
+ const taskResult = await runCodexTask({
27
+ route: '$Naruto',
28
+ tier: 'worker',
29
+ missionId: String(intake.mission_id || ''),
30
+ workItemId: String(intake.item.id || ''),
31
+ slotId: String(intake.item.id || ''),
32
+ generationIndex: 1,
33
+ sessionId: String(intake.item.id || ''),
34
+ cwd: String(intake.worktree_path || process.cwd()),
35
+ prompt: buildNarutoWorkerPrompt(intake.item),
36
+ outputSchemaId: CODEX_AGENT_WORKER_RESULT_SCHEMA_ID,
37
+ outputSchema: codexAgentWorkerResultSchema,
38
+ sandboxPolicy: intake.item.write_allowed === true ? 'workspace-write' : 'read-only',
39
+ requestedScopeContract: {
40
+ id: `naruto:${intake.item.id}`,
41
+ route: '$Naruto',
42
+ read_only: intake.item.write_allowed !== true,
43
+ allowed_paths: [...new Set([...(intake.item.target_paths || []), ...(intake.item.readonly_paths || []), ...(intake.item.write_paths || [])].map(String))],
44
+ write_paths: Array.isArray(intake.item.write_paths) ? intake.item.write_paths.map(String) : [],
45
+ user_confirmed_full_access: false,
46
+ mad_sks_authorized: false
47
+ },
48
+ backendPreference: backendPreference(intake.backend),
49
+ allowLocalLlm: intake.backend === 'ollama' || intake.backend === 'local-llm',
50
+ ...(intake.backend === 'ollama' || intake.backend === 'local-llm' ? { localLlmPolicy: { mode: 'local_preferred', requiresGptFinal: true } } : {}),
51
+ mutationLedgerRoot: controlRoot,
52
+ reliabilityPolicy: {
53
+ maxEmptyResultRetries: 1,
54
+ timeoutClass: 'short'
55
+ }
56
+ });
57
+ const workerResult = await readJson(taskResult.workerResultPath, null);
58
+ const blockers = [...(taskResult.blockers || []), ...(Array.isArray(workerResult?.blockers) ? workerResult.blockers : [])];
59
+ await writeJsonAtomic(intake.result_path, {
60
+ schema: 'sks.naruto-actual-worker-result.v1',
61
+ ok: taskResult.ok === true && blockers.length === 0,
62
+ generated_at: nowIso(),
63
+ item_id: intake.item.id,
64
+ placement: intake.placement,
65
+ backend: taskResult.backend,
66
+ backend_family: taskResult.backend_family,
67
+ worktree_path: intake.worktree_path,
68
+ control_plane_result: {
69
+ worker_result_path: taskResult.workerResultPath,
70
+ patch_envelope_path: taskResult.patchEnvelopePath || null,
71
+ stream_event_count: taskResult.streamEventCount,
72
+ structured_output_valid: taskResult.structuredOutputValid,
73
+ sdk_thread_id: taskResult.sdkThreadId,
74
+ sdk_run_id: taskResult.sdkRunId || null
75
+ },
76
+ changed_files: Array.isArray(workerResult?.changed_files) ? workerResult.changed_files : [],
77
+ blockers
78
+ });
79
+ await fs.appendFile(intake.heartbeat_path, `${JSON.stringify({
80
+ schema: 'sks.naruto-actual-worker-heartbeat.v1',
81
+ ts: nowIso(),
82
+ item_id: intake.item.id,
83
+ status: blockers.length ? 'blocked' : 'done'
84
+ })}\n`);
85
+ }
86
+ catch (err) {
87
+ await writeJsonAtomic(intake.result_path, {
88
+ schema: 'sks.naruto-actual-worker-result.v1',
89
+ ok: false,
90
+ generated_at: nowIso(),
91
+ item_id: intake.item.id,
92
+ placement: intake.placement,
93
+ backend: intake.backend,
94
+ worktree_path: intake.worktree_path,
95
+ blockers: [`naruto_actual_worker_control_plane_exception:${err?.message || String(err)}`]
96
+ });
97
+ await fs.appendFile(intake.heartbeat_path, `${JSON.stringify({
98
+ schema: 'sks.naruto-actual-worker-heartbeat.v1',
99
+ ts: nowIso(),
100
+ item_id: intake.item.id,
101
+ status: 'blocked'
102
+ })}\n`);
103
+ throw err;
104
+ }
105
+ }
106
+ function backendPreference(value) {
107
+ const backend = String(value || '');
108
+ if (backend === 'ollama' || backend === 'local-llm')
109
+ return ['local-llm', 'codex-sdk'];
110
+ return ['codex-sdk'];
111
+ }
112
+ function buildNarutoWorkerPrompt(item) {
113
+ const writeAllowed = item?.write_allowed === true;
114
+ return [
115
+ 'You are a Naruto route worker. Complete only this assigned work item and return JSON matching the required schema.',
116
+ `Work item: ${String(item?.id || '')} ${String(item?.title || item?.kind || '')}`,
117
+ `Role: ${String(item?.required_role || 'worker')}`,
118
+ `Kind: ${String(item?.kind || 'verification')}`,
119
+ `Target paths: ${JSON.stringify(item?.target_paths || [])}`,
120
+ `Readonly paths: ${JSON.stringify(item?.readonly_paths || [])}`,
121
+ `Write paths: ${JSON.stringify(item?.write_paths || [])}`,
122
+ writeAllowed
123
+ ? 'If changes are needed, return model-authored patch_envelopes scoped to write paths.'
124
+ : 'This is read-only work. Do not mutate files and return an empty patch_envelopes array.',
125
+ 'Include verification checks, rollback notes, blockers, findings, and changed_files.'
126
+ ].join('\n');
28
127
  }
29
128
  main().then(() => {
30
129
  process.exit(0);
@@ -0,0 +1,48 @@
1
+ import { chooseNarutoTaskOwner } from './naruto-allocation-policy.js';
2
+ export function rebalanceNarutoReadyWork(input) {
3
+ const completed = new Set((input.completedTaskIds || []).map(String));
4
+ const reclaimed = new Set((input.reclaimedTaskIds || []).map(String));
5
+ const idle = input.workers.filter((worker) => worker.alive && ['idle', 'done', 'unknown'].includes(worker.state));
6
+ const activeWorkerIds = new Set(input.workers.filter((worker) => worker.alive).map((worker) => worker.id));
7
+ const activeWritePaths = new Set([...(input.activeWritePaths || []), ...(input.currentAssignments || []).flatMap((row) => row.write_paths || [])].map(normalizePath));
8
+ if (!idle.length)
9
+ return [];
10
+ const ready = input.tasks
11
+ .filter((task) => (task.status || 'pending') === 'pending')
12
+ .filter((task) => task.dependencies.every((dep) => completed.has(dep)))
13
+ .filter((task) => task.write_paths.every((file) => !activeWritePaths.has(normalizePath(file))))
14
+ .sort((left, right) => {
15
+ const reclaimedOrder = Number(!reclaimed.has(left.id)) - Number(!reclaimed.has(right.id));
16
+ return reclaimedOrder || left.id.localeCompare(right.id);
17
+ });
18
+ const decisions = [];
19
+ const assignments = [...(input.currentAssignments || [])];
20
+ for (const task of ready) {
21
+ const requestedOwner = task.owner ? String(task.owner) : '';
22
+ const ownerActive = requestedOwner && activeWorkerIds.has(requestedOwner);
23
+ const ownerIdle = ownerActive ? idle.some((worker) => worker.id === requestedOwner) : false;
24
+ if (requestedOwner && ownerActive && !ownerIdle)
25
+ continue;
26
+ const candidateWorkers = ownerIdle ? idle.filter((worker) => worker.id === requestedOwner) : idle;
27
+ const decision = chooseNarutoTaskOwner({ ...task, owner: null }, candidateWorkers, assignments);
28
+ decisions.push({
29
+ type: 'assign',
30
+ task_id: task.id,
31
+ worker_id: decision.owner,
32
+ reason: `${reclaimed.has(task.id) ? 'reclaimed ready work' : requestedOwner && !ownerActive ? `owner inactive:${requestedOwner}` : 'idle worker pickup'}; ${decision.reason}`
33
+ });
34
+ assignments.push({
35
+ task_id: task.id,
36
+ owner: decision.owner,
37
+ role: task.required_role,
38
+ paths: decision.hints.paths,
39
+ domains: decision.hints.domains,
40
+ write_paths: decision.hints.writePaths
41
+ });
42
+ }
43
+ return decisions;
44
+ }
45
+ function normalizePath(file) {
46
+ return String(file || '').replace(/\\/g, '/').replace(/^\.\/+/, '').replace(/\/+$/, '');
47
+ }
48
+ //# sourceMappingURL=naruto-rebalance-policy.js.map
@@ -0,0 +1,71 @@
1
+ import { normalizeNarutoPath } from './naruto-work-item.js';
2
+ const PATH_PATTERN = /(?:^|[\s("'`])((?:src|scripts|schemas|docs|test|tests|packages|crates|bin)\/[A-Za-z0-9._/-]+)/g;
3
+ const DOMAIN_STOP_WORDS = new Set([
4
+ 'the', 'and', 'for', 'with', 'into', 'from', 'task', 'work', 'worker', 'workers',
5
+ 'implement', 'implementation', 'test', 'tests', 'check', 'gate', 'runtime',
6
+ 'naruto', 'sks', 'src', 'core', 'scripts', 'docs', 'file', 'files'
7
+ ]);
8
+ export function extractNarutoTaskHints(task) {
9
+ const record = task;
10
+ const writePaths = normalizePaths(readArray(record, 'write_paths', 'writePaths'));
11
+ const readPaths = normalizePaths([
12
+ ...readArray(record, 'readonly_paths', 'readPaths'),
13
+ ...readArray(record, 'target_paths')
14
+ ]);
15
+ const paths = normalizePaths([
16
+ ...readArray(record, 'target_paths'),
17
+ ...readArray(record, 'readonly_paths'),
18
+ ...readArray(record, 'write_paths'),
19
+ ...extractPathsFromText(`${readString(record, 'title')}\n${readString(record, 'description')}\n${readString(record, 'summary')}`)
20
+ ]);
21
+ const domains = [...new Set([
22
+ ...paths.flatMap(pathDomains),
23
+ ...extractDomainsFromText(`${readString(record, 'kind')} ${readString(record, 'title')} ${readString(record, 'description')}`)
24
+ ])].sort();
25
+ return {
26
+ paths,
27
+ domains,
28
+ role: readString(record, 'required_role') || readString(record, 'role') || null,
29
+ writePaths,
30
+ readPaths
31
+ };
32
+ }
33
+ export function pathPrefix(pathValue) {
34
+ const parts = normalizeNarutoPath(pathValue).split('/').filter(Boolean);
35
+ if (parts.length <= 1)
36
+ return parts[0] || '';
37
+ if (parts[0] === 'src' && parts.length >= 3)
38
+ return parts.slice(0, 3).join('/');
39
+ return parts.slice(0, 2).join('/');
40
+ }
41
+ function normalizePaths(paths) {
42
+ return [...new Set(paths.map((file) => normalizeNarutoPath(String(file || ''))).filter(Boolean))].sort();
43
+ }
44
+ function readArray(record, ...keys) {
45
+ return keys.flatMap((key) => Array.isArray(record[key]) ? record[key].map(String) : []);
46
+ }
47
+ function readString(record, key) {
48
+ const value = record[key];
49
+ return typeof value === 'string' ? value : value == null ? '' : String(value);
50
+ }
51
+ function extractPathsFromText(text) {
52
+ const out = [];
53
+ for (const match of String(text || '').matchAll(PATH_PATTERN)) {
54
+ if (match[1])
55
+ out.push(match[1]);
56
+ }
57
+ return out;
58
+ }
59
+ function extractDomainsFromText(text) {
60
+ return [...new Set(String(text || '').toLowerCase().match(/[a-z][a-z0-9_-]{2,}/g) || [])]
61
+ .filter((word) => !DOMAIN_STOP_WORDS.has(word))
62
+ .sort();
63
+ }
64
+ function pathDomains(pathValue) {
65
+ const normalized = normalizeNarutoPath(pathValue);
66
+ const parts = normalized.split('/').filter(Boolean);
67
+ const file = parts[parts.length - 1] || '';
68
+ const stem = file.replace(/\.[^.]+$/, '');
69
+ return [...new Set([pathPrefix(normalized), parts[0], parts[1], stem].filter((part) => Boolean(part)))];
70
+ }
71
+ //# sourceMappingURL=naruto-task-hints.js.map
@@ -42,6 +42,12 @@ export function buildNarutoWorkGraph(input = {}) {
42
42
  fallback_reason: writeCapable ? 'git_capability_not_evaluated' : 'readonly_or_write_disabled'
43
43
  };
44
44
  const workItems = [];
45
+ const assignmentById = new Map();
46
+ for (const row of input.allocationAssignments || []) {
47
+ const id = String(row.task_id || row.id || '');
48
+ if (id)
49
+ assignmentById.set(id, row);
50
+ }
45
51
  for (let index = 0; index < totalWorkItems; index += 1) {
46
52
  const id = `NW-${String(index + 1).padStart(6, '0')}`;
47
53
  const kind = kindCycle[index % kindCycle.length] || 'verification';
@@ -53,6 +59,8 @@ export function buildNarutoWorkGraph(input = {}) {
53
59
  ...writePaths.map((file) => ({ path: file, kind: 'write' })),
54
60
  ...readPaths.map((file) => ({ path: file, kind: 'read' }))
55
61
  ];
62
+ const assignment = assignmentById.get(id);
63
+ const allocationHints = assignment?.allocation_hints || assignment?.hints || null;
56
64
  workItems.push({
57
65
  id,
58
66
  kind,
@@ -73,6 +81,11 @@ export function buildNarutoWorkGraph(input = {}) {
73
81
  requires_verification: kind !== 'research' && kind !== 'final_review_input_pack',
74
82
  requires_gpt_final: writePaths.length > 0 || kind === 'final_review_input_pack'
75
83
  },
84
+ owner: assignment?.owner ?? null,
85
+ allocation_reason: assignment?.allocation_reason ?? null,
86
+ allocation_score: assignment?.allocation_score ?? null,
87
+ allocation_hints: allocationHints,
88
+ lane: assignment?.owner ?? null,
76
89
  ...(writePaths.length > 0 ? {
77
90
  worktree: {
78
91
  mode: worktreePolicy.mode,
@@ -7,7 +7,8 @@ export async function finalizePipelineResult(input) {
7
7
  const root = path.resolve(input.mutationLedgerRoot || path.join(cwd, '.sneakoscope', 'tmp', 'pipeline-finalize', safeName(input.missionId)));
8
8
  const requirement = gptFinalRequiredForPipeline({
9
9
  localParticipated: input.localParticipated,
10
- candidateResults: input.candidateResults
10
+ candidateResults: input.candidateResults,
11
+ candidatePatchEnvelopes: input.candidatePatchEnvelopes
11
12
  });
12
13
  let arbiter = null;
13
14
  let blockers = [];
@@ -36,6 +37,7 @@ export async function finalizePipelineResult(input) {
36
37
  route: input.route,
37
38
  mission_id: input.missionId,
38
39
  local_participated: requirement.local_participated,
40
+ worktree_participated: requirement.worktree_participated,
39
41
  gpt_final_required: requirement.gpt_final_required,
40
42
  gpt_final_arbiter: arbiter,
41
43
  final_status: blockers.length ? 'blocked' : 'accepted',
@@ -2,11 +2,31 @@ import { localCollaborationParticipated } from '../local-llm/local-collaboration
2
2
  export function gptFinalRequiredForPipeline(input) {
3
3
  const localParticipated = input.localParticipated === true
4
4
  || localCollaborationParticipated(Array.isArray(input.candidateResults) ? input.candidateResults : []);
5
+ const worktreeParticipated = worktreeCandidateParticipated(input.candidateResults)
6
+ || worktreeCandidateParticipated(input.candidatePatchEnvelopes);
5
7
  return {
6
8
  schema: 'sks.gpt-final-required.v1',
7
9
  local_participated: localParticipated,
8
- gpt_final_required: localParticipated,
9
- reason: localParticipated ? 'local_llm_outputs_are_drafts' : 'no_local_participation'
10
+ worktree_participated: worktreeParticipated,
11
+ gpt_final_required: localParticipated || worktreeParticipated,
12
+ reason: localParticipated
13
+ ? 'local_llm_outputs_are_drafts'
14
+ : worktreeParticipated
15
+ ? 'worktree_candidate_outputs_require_gpt_final'
16
+ : 'no_local_or_worktree_candidate_participation'
10
17
  };
11
18
  }
19
+ function worktreeCandidateParticipated(values) {
20
+ return (Array.isArray(values) ? values : []).some((value) => {
21
+ if (!value || typeof value !== 'object')
22
+ return false;
23
+ if (value.source === 'git-worktree-diff')
24
+ return true;
25
+ if (value.git_worktree?.worktree_path || value.git_worktree?.checkpoint?.commit_hash)
26
+ return true;
27
+ if (value.git_worktree_diff || value.git_worktree_checkpoint)
28
+ return true;
29
+ return Array.isArray(value.patch_envelopes) && worktreeCandidateParticipated(value.patch_envelopes);
30
+ });
31
+ }
12
32
  //# sourceMappingURL=gpt-final-required.js.map
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '2.0.10';
1
+ export const PACKAGE_VERSION = '2.0.12';
2
2
  //# sourceMappingURL=version.js.map
@@ -13,6 +13,7 @@ export async function ensureRightColumn(input) {
13
13
  return writeRightColumnState(paths.statePath, {
14
14
  ...existing,
15
15
  updated_at: nowIso(),
16
+ slot_column_anchor_pane_id: existing.slot_column_anchor_pane_id || null,
16
17
  ui_mode: existing.ui_mode || uiMode
17
18
  });
18
19
  }
@@ -25,6 +26,7 @@ export async function ensureRightColumn(input) {
25
26
  status: 'creating',
26
27
  dashboard_pane_id: null,
27
28
  right_anchor_pane_id: null,
29
+ slot_column_anchor_pane_id: null,
28
30
  ui_mode: uiMode,
29
31
  visible_worker_panes: [],
30
32
  headless_workers: [],
@@ -38,6 +40,7 @@ export async function ensureRightColumn(input) {
38
40
  status: 'active',
39
41
  dashboard_pane_id: null,
40
42
  right_anchor_pane_id: null,
43
+ slot_column_anchor_pane_id: null,
41
44
  ui_mode: uiMode,
42
45
  blockers: []
43
46
  });
@@ -70,6 +73,7 @@ export async function ensureRightColumn(input) {
70
73
  status: blockers.length ? 'creating' : 'active',
71
74
  dashboard_pane_id: dashboard.pane_id ? String(dashboard.pane_id) : null,
72
75
  right_anchor_pane_id: dashboard.pane_id ? String(dashboard.pane_id) : null,
76
+ slot_column_anchor_pane_id: null,
73
77
  ui_mode: uiMode,
74
78
  blockers
75
79
  });
@@ -106,7 +110,7 @@ async function prepareWorkerInRightColumnUnlocked(input) {
106
110
  return { state: next, placement: 'headless', focusPaneId: null, yOrder: null };
107
111
  }
108
112
  const lastVisible = activeVisible[activeVisible.length - 1];
109
- const focusPaneId = lastVisible?.pane_id || state.right_anchor_pane_id || state.dashboard_pane_id || null;
113
+ const focusPaneId = lastVisible?.pane_id || state.slot_column_anchor_pane_id || state.right_anchor_pane_id || state.dashboard_pane_id || null;
110
114
  const yOrder = Math.max(1, ...state.visible_worker_panes.map((pane) => Number(pane.y_order || 0) + 1));
111
115
  const next = await writeRightColumnState(paths.statePath, {
112
116
  ...state,
@@ -159,10 +163,48 @@ export async function recordWorkerPaneInRightColumn(input) {
159
163
  pane_id: input.record.pane_id,
160
164
  y_order: yOrder,
161
165
  direction_applied: input.record.direction_applied,
166
+ column_creation_direction_requested: input.record.column_creation_direction_requested || null,
167
+ column_creation_direction_applied: input.record.column_creation_direction_applied || null,
168
+ worker_direction_requested: input.record.worker_direction_requested,
169
+ worker_direction_applied: input.record.worker_direction_applied,
170
+ slot_column_anchor_pane_id: input.record.slot_column_anchor_pane_id || state.slot_column_anchor_pane_id || null,
162
171
  blockers: input.record.blockers
163
172
  });
164
173
  return next;
165
174
  }
175
+ export async function recordSlotColumnAnchorInRightColumn(input) {
176
+ const paths = resolveRightColumnPaths(input.root, input.missionId, input.projectRoot);
177
+ const state = await readRightColumnState(input.root, input.missionId, input.projectRoot) || {
178
+ schema: ZELLIJ_RIGHT_COLUMN_STATE_SCHEMA,
179
+ generated_at: nowIso(),
180
+ updated_at: nowIso(),
181
+ mission_id: input.missionId,
182
+ session_name: input.sessionName,
183
+ status: 'active',
184
+ dashboard_pane_id: null,
185
+ right_anchor_pane_id: null,
186
+ slot_column_anchor_pane_id: null,
187
+ ui_mode: 'compact-slots',
188
+ visible_worker_panes: [],
189
+ headless_workers: [],
190
+ blockers: []
191
+ };
192
+ const paneId = input.paneId ? String(input.paneId) : null;
193
+ const next = await writeRightColumnState(paths.statePath, {
194
+ ...state,
195
+ updated_at: nowIso(),
196
+ status: 'active',
197
+ session_name: input.sessionName || state.session_name,
198
+ right_anchor_pane_id: paneId || state.right_anchor_pane_id,
199
+ slot_column_anchor_pane_id: paneId || state.slot_column_anchor_pane_id
200
+ });
201
+ await appendRightColumnEvent(paths.eventsPath, 'slot_column_anchor_created', next, {
202
+ pane_id: paneId,
203
+ column_creation_direction_requested: 'right',
204
+ column_creation_direction_applied: paneId ? 'right' : 'unknown'
205
+ });
206
+ return next;
207
+ }
166
208
  export async function recordHeadlessWorkerInRightColumn(input) {
167
209
  return withRightColumnLock(input.root, input.missionId, async () => recordHeadlessWorkerInRightColumnUnlocked(input));
168
210
  }
@@ -177,6 +219,7 @@ async function recordHeadlessWorkerInRightColumnUnlocked(input) {
177
219
  status: 'absent',
178
220
  dashboard_pane_id: null,
179
221
  right_anchor_pane_id: null,
222
+ slot_column_anchor_pane_id: null,
180
223
  ui_mode: 'compact-slots',
181
224
  visible_worker_panes: [],
182
225
  headless_workers: [],
@@ -213,7 +256,7 @@ export async function closeWorkerInRightColumn(input) {
213
256
  ...state,
214
257
  updated_at: nowIso(),
215
258
  status: visibleStillActive.length || headlessStillActive.length ? 'active' : 'draining',
216
- right_anchor_pane_id: visibleStillActive[visibleStillActive.length - 1]?.pane_id || state.dashboard_pane_id,
259
+ right_anchor_pane_id: visibleStillActive[visibleStillActive.length - 1]?.pane_id || state.slot_column_anchor_pane_id || state.dashboard_pane_id,
217
260
  visible_worker_panes: panes,
218
261
  headless_workers: headless
219
262
  });