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.
- package/README.md +5 -3
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/build-manifest.json +27 -8
- package/dist/cli/command-registry.js +1 -0
- package/dist/cli/install-helpers.js +8 -20
- package/dist/commands/doctor.js +5 -9
- package/dist/commands/zellij-slot-column-anchor.js +23 -0
- package/dist/core/agents/agent-orchestrator.js +338 -12
- package/dist/core/agents/agent-patch-schema.js +8 -1
- package/dist/core/agents/agent-scheduler.js +12 -1
- package/dist/core/agents/agent-slot-pane-binding-proof.js +3 -3
- package/dist/core/agents/agent-work-queue.js +26 -2
- package/dist/core/agents/agent-worker-pipeline.js +2 -0
- package/dist/core/agents/native-cli-session-swarm.js +2 -2
- package/dist/core/commands/naruto-command.js +191 -39
- package/dist/core/fsx.js +1 -1
- package/dist/core/git/git-worktree-checkpoint.js +52 -0
- package/dist/core/git/git-worktree-cross-rebase.js +54 -0
- package/dist/core/git/git-worktree-merge-queue.js +92 -3
- package/dist/core/git/git-worktree-patch-envelope.js +8 -1
- package/dist/core/init.js +2 -2
- package/dist/core/naruto/naruto-allocation-policy.js +99 -0
- package/dist/core/naruto/naruto-real-worker-child.js +110 -11
- package/dist/core/naruto/naruto-rebalance-policy.js +48 -0
- package/dist/core/naruto/naruto-task-hints.js +71 -0
- package/dist/core/naruto/naruto-work-graph.js +13 -0
- package/dist/core/pipeline/finalize-pipeline-result.js +3 -1
- package/dist/core/pipeline/gpt-final-required.js +22 -2
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-right-column-manager.js +45 -2
- package/dist/core/zellij/zellij-slot-column-anchor.js +218 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +81 -14
- package/dist/scripts/agent-real-codex-in-zellij-worker-pane-check.js +8 -2
- package/dist/scripts/agent-slot-pane-binding-proof-check.js +4 -4
- package/dist/scripts/codex-sdk-release-review-pipeline-check.js +2 -1
- package/dist/scripts/codex-sdk-zellij-pane-binding-check.js +2 -2
- package/dist/scripts/git-worktree-checkpoint-check.js +20 -0
- package/dist/scripts/git-worktree-cross-rebase-check.js +39 -0
- package/dist/scripts/git-worktree-merge-queue-check.js +1 -0
- package/dist/scripts/local-collab-worktree-gpt-final-apply-policy-check.js +63 -0
- package/dist/scripts/naruto-actual-worker-control-plane-check.js +56 -0
- package/dist/scripts/naruto-allocation-policy-check.js +33 -0
- package/dist/scripts/naruto-allocation-runtime-wiring-check.js +92 -0
- package/dist/scripts/naruto-extreme-parallelism-real-check.js +5 -4
- package/dist/scripts/naruto-orchestrator-runtime-source-check.js +70 -0
- package/dist/scripts/naruto-real-active-pool-runtime-check.js +4 -2
- package/dist/scripts/naruto-rebalance-policy-check.js +41 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +8 -4
- package/dist/scripts/release-dag-full-coverage-check.js +19 -1
- package/dist/scripts/release-readiness-report.js +1 -1
- package/dist/scripts/release-real-check.js +258 -77
- package/dist/scripts/zellij-first-slot-down-stack-check.js +20 -0
- package/dist/scripts/zellij-first-slot-down-stack-real-check.js +356 -0
- package/dist/scripts/zellij-right-column-manager-check.js +7 -2
- package/dist/scripts/zellij-slot-column-anchor-check.js +45 -0
- package/dist/scripts/zellij-slot-only-ui-check.js +6 -2
- package/dist/scripts/zellij-slot-renderer-proof-semantics-check.js +59 -0
- package/dist/scripts/zellij-worker-pane-manager-check.js +23 -1
- package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +11 -4
- package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +21 -4
- 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
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '2.0.
|
|
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
|
});
|