sneakoscope 2.0.9 → 2.0.11
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 +8 -4
- 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 +37 -8
- package/dist/cli/command-registry.js +2 -0
- package/dist/cli/install-helpers.js +8 -20
- package/dist/commands/doctor.js +23 -10
- package/dist/commands/zellij-slot-column-anchor.js +23 -0
- package/dist/commands/zellij-slot-pane.js +26 -0
- package/dist/core/agents/agent-orchestrator.js +255 -16
- package/dist/core/agents/agent-patch-schema.js +8 -1
- package/dist/core/agents/agent-role-config.js +92 -0
- package/dist/core/agents/native-cli-session-swarm.js +186 -71
- package/dist/core/commands/naruto-command.js +165 -11
- package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
- 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 +69 -0
- package/dist/core/git/git-worktree-patch-envelope.js +8 -1
- package/dist/core/hooks-runtime.js +4 -0
- package/dist/core/init.js +3 -2
- package/dist/core/naruto/naruto-active-pool.js +35 -2
- package/dist/core/naruto/naruto-allocation-policy.js +99 -0
- package/dist/core/naruto/naruto-concurrency-governor.js +1 -1
- package/dist/core/naruto/naruto-real-worker-child.js +134 -0
- package/dist/core/naruto/naruto-real-worker-runtime.js +121 -0
- package/dist/core/naruto/naruto-rebalance-policy.js +36 -0
- package/dist/core/naruto/naruto-task-hints.js +71 -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 +111 -9
- package/dist/core/zellij/zellij-slot-column-anchor.js +59 -0
- package/dist/core/zellij/zellij-slot-pane-renderer.js +82 -0
- package/dist/core/zellij/zellij-ui-mode.js +16 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +104 -13
- package/dist/scripts/agent-role-config-repair-check.js +33 -0
- package/dist/scripts/git-worktree-checkpoint-check.js +20 -0
- package/dist/scripts/git-worktree-cross-rebase-check.js +27 -0
- package/dist/scripts/git-worktree-integration-primary-check.js +4 -2
- package/dist/scripts/git-worktree-integration-primary-runtime-check.js +20 -0
- package/dist/scripts/mutation-callsite-coverage-check.js +2 -1
- package/dist/scripts/naruto-actual-worker-control-plane-check.js +29 -0
- package/dist/scripts/naruto-allocation-policy-check.js +33 -0
- package/dist/scripts/naruto-extreme-parallelism-check.js +1 -1
- package/dist/scripts/naruto-extreme-parallelism-real-check.js +43 -0
- package/dist/scripts/naruto-orchestrator-runtime-source-check.js +11 -0
- package/dist/scripts/naruto-real-active-pool-check.js +3 -2
- package/dist/scripts/naruto-real-active-pool-runtime-check.js +55 -0
- package/dist/scripts/naruto-rebalance-policy-check.js +28 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +7 -3
- package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +29 -2
- package/dist/scripts/readme-architecture-imagegen-official-check.js +4 -3
- package/dist/scripts/release-check-dynamic-execute.js +27 -1
- package/dist/scripts/release-check-dynamic.js +38 -11
- package/dist/scripts/release-check-stamp.js +7 -2
- package/dist/scripts/release-dag-full-coverage-check.js +15 -1
- package/dist/scripts/release-dynamic-performance-check.js +31 -1
- package/dist/scripts/release-gate-existence-audit.js +29 -33
- package/dist/scripts/release-readiness-report.js +14 -3
- package/dist/scripts/zellij-first-slot-down-stack-check.js +20 -0
- package/dist/scripts/zellij-first-slot-down-stack-real-check.js +16 -0
- package/dist/scripts/zellij-right-column-geometry-proof.js +155 -22
- package/dist/scripts/zellij-right-column-headless-overflow-check.js +22 -0
- package/dist/scripts/zellij-right-column-manager-check.js +9 -4
- package/dist/scripts/zellij-slot-column-anchor-check.js +24 -0
- package/dist/scripts/zellij-slot-only-ui-check.js +24 -0
- package/dist/scripts/zellij-slot-pane-renderer-check.js +38 -0
- package/dist/scripts/zellij-worker-pane-manager-single-owner-check.js +11 -4
- package/package.json +22 -5
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { nowIso } from '../fsx.js';
|
|
2
|
+
import { gitBlocker, gitOutputLine, runGitCommand } from './git-worktree-runner.js';
|
|
3
|
+
export async function checkpointWorkerWorktree(input) {
|
|
4
|
+
const status = await runGitCommand(input.worktreePath, ['status', '--porcelain=v1', '--untracked-files=all']);
|
|
5
|
+
const names = await runGitCommand(input.worktreePath, ['diff', '--name-only', 'HEAD']);
|
|
6
|
+
const untracked = await runGitCommand(input.worktreePath, ['ls-files', '--others', '--exclude-standard']);
|
|
7
|
+
const changedFiles = [...new Set([...lines(names.stdout), ...lines(untracked.stdout), ...statusFiles(status.stdout)])];
|
|
8
|
+
const blockers = [...(status.ok ? [] : [gitBlocker('git_worktree_status_failed', status)])];
|
|
9
|
+
const requested = input.mode || 'auto';
|
|
10
|
+
const commitMode = requested === 'checkpoint-commit' || (requested === 'auto' && changedFiles.length > 1);
|
|
11
|
+
if (!changedFiles.length || blockers.length) {
|
|
12
|
+
return report(input, requested, 'noop', null, changedFiles, blockers);
|
|
13
|
+
}
|
|
14
|
+
if (!commitMode)
|
|
15
|
+
return report(input, requested, 'diff-envelope', null, changedFiles, blockers);
|
|
16
|
+
const add = await runGitCommand(input.worktreePath, ['add', '-A'], { timeoutMs: 30000 });
|
|
17
|
+
if (!add.ok)
|
|
18
|
+
blockers.push(gitBlocker('git_worktree_checkpoint_add_failed', add));
|
|
19
|
+
const commit = blockers.length ? null : await runGitCommand(input.worktreePath, ['commit', '--no-verify', '-m', `sks(worker): checkpoint ${input.workerId}/${input.taskId}`], { timeoutMs: 120000 });
|
|
20
|
+
if (commit && !commit.ok)
|
|
21
|
+
blockers.push(gitBlocker('git_worktree_checkpoint_commit_failed', commit));
|
|
22
|
+
const head = blockers.length ? null : await runGitCommand(input.worktreePath, ['rev-parse', 'HEAD']);
|
|
23
|
+
const hash = head?.ok ? gitOutputLine(head) : null;
|
|
24
|
+
return report(input, requested, blockers.length ? 'noop' : 'checkpoint-commit', hash, changedFiles, blockers);
|
|
25
|
+
}
|
|
26
|
+
function report(input, mode, applied, commitHash, changedFiles, blockers) {
|
|
27
|
+
return {
|
|
28
|
+
schema: 'sks.git-worktree-checkpoint.v1',
|
|
29
|
+
ok: blockers.length === 0,
|
|
30
|
+
generated_at: nowIso(),
|
|
31
|
+
worktree_path: input.worktreePath,
|
|
32
|
+
repo_root: input.repoRoot,
|
|
33
|
+
worker_id: input.workerId,
|
|
34
|
+
task_id: input.taskId,
|
|
35
|
+
mode_requested: mode,
|
|
36
|
+
mode_applied: applied,
|
|
37
|
+
commit_hash: commitHash,
|
|
38
|
+
changed_files: changedFiles,
|
|
39
|
+
blockers
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function lines(text) {
|
|
43
|
+
return String(text || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
44
|
+
}
|
|
45
|
+
function statusFiles(text) {
|
|
46
|
+
return lines(text).map((line) => {
|
|
47
|
+
const match = line.match(/^.{2}\s+(.*)$/) || line.match(/^\S+\s+(.*)$/);
|
|
48
|
+
const file = (match?.[1] || line).trim();
|
|
49
|
+
return file.includes(' -> ') ? file.split(' -> ').pop()?.trim() || file : file;
|
|
50
|
+
}).filter(Boolean);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=git-worktree-checkpoint.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { nowIso } from '../fsx.js';
|
|
2
|
+
import { gitBlocker, gitOutputLine, runGitCommand } from './git-worktree-runner.js';
|
|
3
|
+
export async function crossRebaseIdleWorktrees(input) {
|
|
4
|
+
const records = [];
|
|
5
|
+
for (const worker of input.workers) {
|
|
6
|
+
const before = await runGitCommand(worker.worktree_path, ['rev-parse', 'HEAD']);
|
|
7
|
+
const beforeHead = before.ok ? gitOutputLine(before) : null;
|
|
8
|
+
if (!['idle', 'done', 'failed', 'unknown'].includes(worker.state)) {
|
|
9
|
+
records.push(record(worker, 'skipped', 'worker_not_idle', beforeHead, beforeHead, []));
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
const status = await runGitCommand(worker.worktree_path, ['status', '--porcelain=v1', '--untracked-files=all']);
|
|
13
|
+
if (!status.ok) {
|
|
14
|
+
records.push(record(worker, 'failed', 'status_failed', beforeHead, beforeHead, [gitBlocker('git_worktree_cross_rebase_status_failed', status)]));
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
if (status.stdout.trim()) {
|
|
18
|
+
records.push(record(worker, 'skipped', 'dirty_worktree_skipped', beforeHead, beforeHead, []));
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const rebase = await runGitCommand(worker.worktree_path, ['rebase', input.integrationHead], { timeoutMs: 120000 });
|
|
22
|
+
if (!rebase.ok) {
|
|
23
|
+
await runGitCommand(worker.worktree_path, ['rebase', '--abort'], { timeoutMs: 30000 }).catch(() => null);
|
|
24
|
+
records.push(record(worker, 'failed', 'rebase_failed', beforeHead, beforeHead, [gitBlocker('git_worktree_cross_rebase_failed', rebase)]));
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const after = await runGitCommand(worker.worktree_path, ['rev-parse', 'HEAD']);
|
|
28
|
+
records.push(record(worker, 'applied', 'rebased_to_integration_head', beforeHead, after.ok ? gitOutputLine(after) : null, []));
|
|
29
|
+
}
|
|
30
|
+
const blockers = records.flatMap((row) => row.blockers);
|
|
31
|
+
return {
|
|
32
|
+
schema: 'sks.git-worktree-cross-rebase.v1',
|
|
33
|
+
ok: blockers.length === 0,
|
|
34
|
+
generated_at: nowIso(),
|
|
35
|
+
integration_head: input.integrationHead,
|
|
36
|
+
applied_count: records.filter((row) => row.status === 'applied').length,
|
|
37
|
+
skipped_count: records.filter((row) => row.status === 'skipped').length,
|
|
38
|
+
records,
|
|
39
|
+
blockers
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function record(worker, status, reason, beforeHead, afterHead, blockers) {
|
|
43
|
+
return {
|
|
44
|
+
worker_id: worker.worker_id,
|
|
45
|
+
worktree_path: worker.worktree_path,
|
|
46
|
+
state: worker.state,
|
|
47
|
+
status,
|
|
48
|
+
reason,
|
|
49
|
+
before_head: beforeHead,
|
|
50
|
+
after_head: afterHead,
|
|
51
|
+
blockers
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=git-worktree-cross-rebase.js.map
|
|
@@ -3,10 +3,35 @@ import { runGitCommand } from './git-worktree-runner.js';
|
|
|
3
3
|
import { summarizeGitWorktreeConflict } from './git-worktree-conflict-resolver.js';
|
|
4
4
|
export async function applyGitWorktreeMergeQueue(input) {
|
|
5
5
|
const conflicts = [];
|
|
6
|
+
const strategyResults = [];
|
|
6
7
|
const changedFiles = new Set();
|
|
7
8
|
let appliedCount = 0;
|
|
8
9
|
let skippedCleanCount = 0;
|
|
10
|
+
let checkpointCommitCount = 0;
|
|
11
|
+
for (const checkpoint of input.checkpoints || []) {
|
|
12
|
+
for (const file of checkpoint.changed_files || [])
|
|
13
|
+
changedFiles.add(file);
|
|
14
|
+
if (checkpoint.mode_applied !== 'checkpoint-commit' || !checkpoint.commit_hash)
|
|
15
|
+
continue;
|
|
16
|
+
checkpointCommitCount += 1;
|
|
17
|
+
const merged = await applyCheckpointCommit(input.integrationWorktreePath, checkpoint);
|
|
18
|
+
strategyResults.push(merged);
|
|
19
|
+
if (merged.ok)
|
|
20
|
+
appliedCount += 1;
|
|
21
|
+
else
|
|
22
|
+
conflicts.push({
|
|
23
|
+
worker_id: checkpoint.worker_id,
|
|
24
|
+
changed_files: checkpoint.changed_files,
|
|
25
|
+
strategy: 'checkpoint-commit',
|
|
26
|
+
blockers: merged.blockers,
|
|
27
|
+
conflict_files: merged.conflict_files
|
|
28
|
+
});
|
|
29
|
+
}
|
|
9
30
|
for (const diff of input.diffs) {
|
|
31
|
+
if ((input.checkpoints || []).some((checkpoint) => checkpoint.worker_id === diff.worker_id && checkpoint.mode_applied === 'checkpoint-commit' && checkpoint.commit_hash)) {
|
|
32
|
+
skippedCleanCount += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
10
35
|
for (const file of diff.changed_files)
|
|
11
36
|
changedFiles.add(file);
|
|
12
37
|
if (diff.clean || !diff.diff.trim()) {
|
|
@@ -46,10 +71,54 @@ export async function applyGitWorktreeMergeQueue(input) {
|
|
|
46
71
|
generated_at: nowIso(),
|
|
47
72
|
integration_worktree_path: input.integrationWorktreePath,
|
|
48
73
|
applied_count: appliedCount,
|
|
74
|
+
checkpoint_commit_count: checkpointCommitCount,
|
|
49
75
|
skipped_clean_count: skippedCleanCount,
|
|
50
76
|
conflicts,
|
|
77
|
+
strategy_results: strategyResults,
|
|
51
78
|
changed_files: [...changedFiles],
|
|
52
79
|
blockers
|
|
53
80
|
};
|
|
54
81
|
}
|
|
82
|
+
async function applyCheckpointCommit(integrationWorktreePath, checkpoint) {
|
|
83
|
+
const merge = await runGitCommand(integrationWorktreePath, ['merge', '--no-ff', '--no-edit', checkpoint.commit_hash || ''], {
|
|
84
|
+
timeoutMs: 120000
|
|
85
|
+
});
|
|
86
|
+
if (merge.ok) {
|
|
87
|
+
return {
|
|
88
|
+
ok: true,
|
|
89
|
+
worker_id: checkpoint.worker_id,
|
|
90
|
+
strategy: 'merge',
|
|
91
|
+
commit_hash: checkpoint.commit_hash,
|
|
92
|
+
conflict_files: [],
|
|
93
|
+
blockers: []
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
await runGitCommand(integrationWorktreePath, ['merge', '--abort'], { timeoutMs: 30000 }).catch(() => null);
|
|
97
|
+
const cherryPick = await runGitCommand(integrationWorktreePath, ['cherry-pick', checkpoint.commit_hash || ''], {
|
|
98
|
+
timeoutMs: 120000
|
|
99
|
+
});
|
|
100
|
+
if (cherryPick.ok) {
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
worker_id: checkpoint.worker_id,
|
|
104
|
+
strategy: 'cherry-pick',
|
|
105
|
+
commit_hash: checkpoint.commit_hash,
|
|
106
|
+
conflict_files: [],
|
|
107
|
+
blockers: []
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const conflictFiles = await runGitCommand(integrationWorktreePath, ['diff', '--name-only', '--diff-filter=U'], { timeoutMs: 30000 }).catch(() => null);
|
|
111
|
+
await runGitCommand(integrationWorktreePath, ['cherry-pick', '--abort'], { timeoutMs: 30000 }).catch(() => null);
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
worker_id: checkpoint.worker_id,
|
|
115
|
+
strategy: 'merge_then_cherry-pick',
|
|
116
|
+
commit_hash: checkpoint.commit_hash,
|
|
117
|
+
conflict_files: String(conflictFiles?.stdout || '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean),
|
|
118
|
+
blockers: [
|
|
119
|
+
`git_worktree_checkpoint_merge_failed:${merge.stderr_tail || merge.stdout_tail}`,
|
|
120
|
+
`git_worktree_checkpoint_cherry_pick_failed:${cherryPick.stderr_tail || cherryPick.stdout_tail}`
|
|
121
|
+
]
|
|
122
|
+
};
|
|
123
|
+
}
|
|
55
124
|
//# sourceMappingURL=git-worktree-merge-queue.js.map
|
|
@@ -18,7 +18,14 @@ export function buildGitWorktreePatchEnvelope(input) {
|
|
|
18
18
|
base_head: input.diff.base_head,
|
|
19
19
|
worktree_head: input.diff.worktree_head,
|
|
20
20
|
changed_files: changedFiles,
|
|
21
|
-
diff_bytes: input.diff.diff_bytes
|
|
21
|
+
diff_bytes: input.diff.diff_bytes,
|
|
22
|
+
checkpoint: input.checkpoint ? {
|
|
23
|
+
schema: input.checkpoint.schema,
|
|
24
|
+
mode_applied: input.checkpoint.mode_applied,
|
|
25
|
+
commit_hash: input.checkpoint.commit_hash,
|
|
26
|
+
changed_files: input.checkpoint.changed_files,
|
|
27
|
+
blockers: input.checkpoint.blockers
|
|
28
|
+
} : null
|
|
22
29
|
},
|
|
23
30
|
operations: [{
|
|
24
31
|
op: 'git_apply_patch',
|
|
@@ -25,6 +25,10 @@ const STOP_REPEAT_GUARD_WINDOW_MS = 10 * 60 * 1000;
|
|
|
25
25
|
const STOP_REPEAT_GUARD_MAX_ENTRIES = 25;
|
|
26
26
|
const DEFAULT_STOP_REPEAT_GUARD_LIMIT = 2;
|
|
27
27
|
const CODEX_GIT_ACTION_STOP_TTL_MS = 15 * 60 * 1000;
|
|
28
|
+
const UPDATE_CHECK_HOOK_INVOCATION_POLICY = 'function-only:no-runSksUpdateCheck-call-in-hooks';
|
|
29
|
+
// Update checks stay function-only in hooks: the policy marker above is checked
|
|
30
|
+
// by release readiness so ordinary Codex hook flow cannot grow a hidden update
|
|
31
|
+
// prompt path.
|
|
28
32
|
async function loadHookPayload() {
|
|
29
33
|
const raw = await readStdin();
|
|
30
34
|
try {
|
package/dist/core/init.js
CHANGED
|
@@ -237,7 +237,7 @@ function isSksManagedHook(hook) {
|
|
|
237
237
|
const command = String(hook.command || '');
|
|
238
238
|
return hook.type === 'command' && /\bhook\s+(?:session-start|user-prompt-submit|pre-tool|post-tool|permission-request|pre-compact|post-compact|subagent-start|subagent-stop|stop)\b/.test(command) && /\b(?:sks|sneakoscope|sks\.js)\b/.test(command);
|
|
239
239
|
}
|
|
240
|
-
const AGENTS_BLOCK = "\n# Sneakoscope Codex Managed Rules\n\nThis repository uses Sneakoscope Codex.\n\n## Core Rules\n\n- Codex native `/goal` workflows are the persisted continuation surface; Ralph is removed from the user-facing SKS surface.\n- Keep runtime state bounded: raw logs go to files, prompts get tails/summaries, and `sks gc` may prune stale artifacts.\n- Codex App hooks do not force SKS update prompts during ordinary work. CLI update surfaces (`sks update-check`, `sks update check`, and
|
|
240
|
+
const AGENTS_BLOCK = "\n# Sneakoscope Codex Managed Rules\n\nThis repository uses Sneakoscope Codex.\n\n## Core Rules\n\n- Codex native `/goal` workflows are the persisted continuation surface; Ralph is removed from the user-facing SKS surface.\n- Keep runtime state bounded: raw logs go to files, prompts get tails/summaries, and `sks gc` may prune stale artifacts.\n- Codex App hooks, launch paths, and `sks doctor --fix` do not force SKS update prompts during ordinary work. Manual CLI update surfaces (`sks update-check`, `sks update check`, and `sks update now`) remain available when the operator explicitly asks for them.\n- Versioning is explicit: use `sks versioning bump` when preparing release metadata. SKS must not install Git pre-commit hooks.\n- Installed harness files are immutable to LLM edits: `.codex/*`, `.agents/skills/`, `.codex/agents/`, `.sneakoscope/*policy*.json`, `AGENTS.md`, and `node_modules/sneakoscope`. The Sneakoscope engine source repo is the only automatic exception.\n- OMX/DCodex conflicts block setup/doctor. Show `sks conflicts prompt`; cleanup requires explicit human approval.\n- Do not stop at a plan when implementation was requested. Finish, verify, or report the hard blocker.\n- Do not create unrequested fallback implementation code. If the requested path is impossible, block with evidence instead of inventing substitute behavior.\n\n## Routes\n\n- General execution/code-changing prompts default to `$Team`: native agent intake agents, TriWiki refresh/validate, read-only debate, consensus, concrete runtime task graph/inboxes, fresh executor team, minimum five-lane Team review, integration, Honest Mode.\n- `$Computer-Use` / `$CU` is the maximum-speed Codex Computer Use lane for native macOS, desktop-app, OS-settings, and non-web visual tasks only. Web, browser, localhost, website, webapp, and web-based app verification must use the Codex Chrome Extension path first and halt rapidly if the extension is not installed/enabled.\n- `$Goal` is a fast bridge/overlay for Codex native `/goal` create/pause/resume/clear persistence controls; implementation continues through the selected SKS execution route.\n- TriWiki recall must stay bounded. Use `sks wiki sweep` to record demote, soft-forget, archive, delete, promote-to-skill, and promote-to-rule candidates instead of injecting every old claim.\n- Team missions must keep schema-backed evidence current: `work-order-ledger.json`, `effort-decision.json`, `team-dashboard-state.json`, and route-specific visual/dogfood artifacts where applicable. Team completion requires at least five independent reviewer/QA validation lanes before integration or final, even when a prompt requests fewer reviewers. Use `sks validate-artifacts latest` before claiming those artifacts pass.\n- `$DFix` is Direct Fix: only tiny copy/config/docs/labels/spacing/translation/simple mechanical edits, bypassing the main pipeline, Team, TriWiki/TriFix/reflection recording, and persistent route state; it still uses a one-line DFix-specific Honest check before final. Broad implementation stays on `$Team`, while UI design specifics follow the relevant design/UI route rules. `$PPT` is the restrained, information-first HTML/PDF presentation route and must seal delivery context, audience profile, STP, decision context, and 3+ pain-point/solution/aha mappings before design/render work. It must avoid over-designed visuals, carry detail through hierarchy, spacing, alignment, thin rules, source clarity, and subtle accents, preserve editable source HTML under `source-html/`, record `ppt-parallel-report.json`, and clean PPT-only temporary build files before completion. `$Image-UX-Review` / `$UX-Review` is the imagegen/gpt-image-2 UI/UX review route: source screenshots must become generated annotated review images, those generated images must be extracted into issue ledgers, and text-only critique cannot pass the route gate. `$Answer`, `$Help`, and `$Wiki` stay lightweight.\n- For code work, surface route/guard/write scopes first, split independent worker scopes when available, and keep parent-owned integration and verification.\n- Design work reads `design.md` as the only design decision SSOT. If missing, create it through `design-system-builder` from `docs/Design-Sys-Prompt.md`; getdesign.md, getdesign-reference, and curated DESIGN.md examples from https://github.com/VoltAgent/awesome-design-md are source inputs to fuse into that SSOT or route-local style tokens, not parallel design authorities. Image/logo/raster assets use `imagegen`, which must prefer official Codex App built-in image generation via `$imagegen` / `gpt-image-2`; for newest-model image requests prompt explicitly for ChatGPT Images 2.0 / GPT Image 2.0 with `gpt-image-2`. Do not replace required raster evidence with placeholder SVG/HTML/CSS, prose-only reviews, or fabricated files.\n- Research, AutoResearch, performance, token, accuracy, SEO/GEO, or workflow-improvement claims need experiment/eval evidence. Do not claim live model accuracy without a scored dataset.\n- Treat handwritten files above 3000 lines as split-review risks. Run `sks code-structure scan` and prefer extraction before adding substantial logic.\n- Skill dreaming stays lightweight: route use records JSON counters in `.sneakoscope/skills/dream-state.json`, and full skill inventory/recommendation runs only after the configured 10-route-event threshold and cooldown. Reports are recommendation-only; deleting or merging skills needs explicit user approval.\n\n## Evidence And Context\n\n- Context7 is required for external libraries, APIs, MCPs, package managers, SDKs, and generated docs: resolve-library-id then query-docs.\n- When tech stack, framework, package, runtime, or deployment-platform versions change, use Context7 or official vendor web docs, record current syntax/security/limit guidance as high-priority TriWiki claims, then refresh and validate before coding.\n- TriWiki is the context-tracking SSOT for long-running missions, Team handoffs, and context-pressure recovery. Read `.sneakoscope/wiki/context-pack.json` before each stage, use `attention.use_first` for compact high-trust recall, hydrate `attention.hydrate_first` from source before risky or lower-trust decisions, refresh after findings or artifact changes, and validate before handoffs/final claims.\n- Source priority: current code/tests/config, decision contract, vgraph, beta, GX render/snapshot metadata, LLM Wiki coordinate index, then model knowledge only if allowed.\n- Final response before stop: summarize what was done, what changed for the user/repo, what was verified, and what remains unverified or blocked; then run Honest Mode. Say what passed and what was not verified.\n- `$From-Chat-IMG` uses forensic visual effort, not ordinary Team effort. Completion is blocked until source inventory, visual mapping, work-order coverage, scoped dogfood/QA, and post-fix verification artifacts are present and valid.\n\n## Safety\n\n- Database access is high risk. Use read-only inspection by default; live data mutation is out of scope unless a sealed contract allows local or branch-only migration files.\n- MAD and MAD-SKS widen only explicit scoped permissions; they still do not authorize unrequested fallback implementation code.\n- Task completion requires relevant tests or justification, zero unsupported critical claims, accepted visual/wiki drift, and final evidence.\n\n## Codex App\n\nUse `.codex/SNEAKOSCOPE.md`, generated `.agents/skills`, `.codex/hooks.json`, and SKS dollar commands (`$sks`, `$team`, `$computer-use`, `$cu`, `$ppt`, `$image-ux-review`, `$ux-review`, `$goal`, `$dfix`, `$qa-loop`, etc.) as the app control surface.\n";
|
|
241
241
|
function agentsBlockText() {
|
|
242
242
|
return AGENTS_BLOCK;
|
|
243
243
|
}
|
|
@@ -1011,7 +1011,7 @@ function codexAppQuickReference(scope, commandPrefix) {
|
|
|
1011
1011
|
stackCurrentDocsPolicyText(commandPrefix),
|
|
1012
1012
|
`Team review: ${MIN_TEAM_REVIEW_POLICY_TEXT}`,
|
|
1013
1013
|
`Team Zellij view: ${commandPrefix} team "task" prepares live watch/lane commands and reconciles managed Team panes inside the current SKS-owned Zellij session when available; add --no-open-zellij for artifact-only creation. Existing hook-created Team missions can be opened later with ${commandPrefix} team open-zellij latest. The view keeps the main Codex pane alive, adds an overview watch pane plus color-coded split per-agent lanes, and closes only SKS-managed Team panes as agent lanes finish or cleanup is requested; ${commandPrefix} team lane latest --agent native_agent_1 --follow shows one agent's status, assigned runtime tasks, recent agent events, direct messages, and fallback global tail; ${commandPrefix} team message latest --from native_agent_1 --to executor_1 --message "handoff note" mirrors bounded agent communication into transcript/lane panes; ${commandPrefix} team cleanup-zellij latest marks the SKS session record complete and asks managed panes/follow loops to close or show a cleanup summary.`,
|
|
1014
|
-
`Runtime: open Codex App once, then run ${commandPrefix} bootstrap and ${commandPrefix} deps check. Zellij is the interactive lane runtime for ${commandPrefix} --mad and Team lane UI; ${commandPrefix} bootstrap --yes, ${commandPrefix} deps check --yes, and ${commandPrefix} --mad --yes can install or repair Codex CLI/Zellij on macOS/Homebrew. npm postinstall reports missing CLI tools but does not mutate Homebrew/npm globals unless SKS_POSTINSTALL_AUTO_INSTALL_CLI_TOOLS=1 is set.
|
|
1014
|
+
`Runtime: open Codex App once, then run ${commandPrefix} bootstrap and ${commandPrefix} deps check. Zellij is the interactive lane runtime for ${commandPrefix} --mad and Team lane UI; ${commandPrefix} bootstrap --yes, ${commandPrefix} deps check --yes, and ${commandPrefix} --mad --yes can install or repair Codex CLI/Zellij on macOS/Homebrew. npm postinstall reports missing CLI tools but does not mutate Homebrew/npm globals unless SKS_POSTINSTALL_AUTO_INSTALL_CLI_TOOLS=1 is set. Launch paths do not run sneakoscope npm update checks; use ${commandPrefix} update-check or ${commandPrefix} update now explicitly when you want that. Codex CLI latest checks remain dependency-readiness guidance and prompt Y/n only when the installed Codex CLI is missing or outdated. ${commandPrefix} doctor --fix repairs the local SKS/Codex setup without running a global SKS package update. ${commandPrefix} codex-app remote-control wraps the Codex CLI 0.130.0+ headless remote-control entrypoint. ${commandPrefix} team open-zellij latest is the explicit Team lane view command.`,
|
|
1015
1015
|
`Guard: generated harness files are immutable outside the engine source repo; check ${commandPrefix} guard check; conflicts use ${commandPrefix} conflicts prompt with human approval.`
|
|
1016
1016
|
].join('\n') + '\n';
|
|
1017
1017
|
}
|
|
@@ -1257,6 +1257,7 @@ async function removeDirIfEmpty(dir) {
|
|
|
1257
1257
|
}
|
|
1258
1258
|
async function installCodexAgents(root) {
|
|
1259
1259
|
const agents = {
|
|
1260
|
+
'analysis-scout.toml': `name = "analysis_scout"\ndescription = "Read-only SKS analysis scout retained for stale Codex agent-role config repair."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "medium"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are an SKS read-only analysis scout.\nDo not edit files.\nInspect only the assigned source paths and return concise source-backed findings.\n"""\n`,
|
|
1260
1261
|
'native-agent-intake.toml': `name = "native_agent"\ndescription = "Read-only Team native agent. Maps one independent repo/docs/tests/API/risk/user-friction slice and returns TriWiki-ready source-backed findings before debate starts."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "medium"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are an SKS Team native agent.\nDo not edit files.\nOwn exactly one investigation slice assigned by the parent orchestrator.\nUse the mission roster or worker inbox reasoning_effort when the host exposes it; simple bounded work can use low, tool-heavy runtime work medium, and knowledge/research/safety/release work high or xhigh. Default to medium only when no assignment is visible.\nMap relevant source files, docs, tests, APIs, DB or safety risks, UX friction, and likely implementation boundaries.\nReturn concise source-backed claims suitable for team-analysis.md and TriWiki ingestion: claim, source path, evidence hash or quoted anchor, risk, confidence, and recommended implementation slice.\nDo not debate the final plan and do not implement code.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
|
|
1261
1262
|
'team-consensus.toml': `name = "team_consensus"\ndescription = "Planning and debate specialist for SKS Team mode. Maps options, constraints, role-persona risks, and proposes the agreed objective before implementation starts."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are the SKS Team consensus specialist.\nDo not edit files.\nUse the mission roster reasoning_effort when the host exposes it; planning has a medium minimum, with high or xhigh for knowledge, safety, release, research, or recovery work.\nMap the affected code paths, viable approaches, constraints, risks, and acceptance criteria.\nRun the debate as role-persona synthesis: final users are low-context, self-interested, stubborn, and inconvenience-averse; executors are capable developers; reviewers are strict.\nArgue for the smallest coherent objective that can be handed to a fresh executor_N development team.\nPlan for at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes before integration or final.\nReturn: recommended objective, rejected alternatives, implementation slices, required reviewers, user-friction risks, and unresolved risks.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
|
|
1262
1263
|
'implementation-worker.toml': `name = "implementation_worker"\ndescription = "Implementation specialist for SKS Team mode. Owns one bounded write set and coordinates with other executor_N workers."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "medium"\nsandbox_mode = "workspace-write"\ndeveloper_instructions = """\nYou are an SKS Team executor/developer in the fresh development bundle.\nYou are not alone in the codebase. Other executor_N workers may be editing disjoint files.\nUse the mission roster or worker inbox reasoning_effort when the host exposes it; simple bounded changes can use low, tool-heavy implementation medium, and safety/release/DB work high.\nOnly edit the files or module slice assigned to you.\nDo not revert or overwrite edits made by others.\nRead local patterns first, make the smallest correct change, avoid adding user friction, run focused verification for your slice, and report changed paths plus evidence.\nDo not create fallback implementation code, substitute behavior, mock behavior, or compatibility shims unless the user or sealed decision contract explicitly requested them.\nRespect all SKS hooks, DB safety rules, no-question run rules, and H-Proof completion gates.\nAlso return concise LIVE_EVENT lines for started, blocked, changed files, verification, and final result so the parent can record them.\n"""\n`,
|
|
@@ -29,7 +29,7 @@ export async function runNarutoRealActivePool(input) {
|
|
|
29
29
|
visibleRunning += 1;
|
|
30
30
|
const handle = await input.spawnWorker(item, placement);
|
|
31
31
|
active.set(handle.id, handle);
|
|
32
|
-
lifecycle.push({ work_item_id: item.id, placement: placement.placement, started_at: handle.started_at, completed_at: null, ok: null });
|
|
32
|
+
lifecycle.push({ work_item_id: item.id, placement: placement.placement, pid: handle.pid || null, worker_artifact_dir: handle.worker_artifact_dir || null, started_at: handle.started_at, completed_at: null, ok: null });
|
|
33
33
|
launched += 1;
|
|
34
34
|
await input.updateDashboard({ event_type: 'worker_spawned', work_item_id: item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement });
|
|
35
35
|
}
|
|
@@ -40,7 +40,7 @@ export async function runNarutoRealActivePool(input) {
|
|
|
40
40
|
}
|
|
41
41
|
maxObserved = Math.max(maxObserved, active.size);
|
|
42
42
|
timeline.push({ tick, active: active.size, pending: pending.length, completed: completed.size, event: launched ? 'refill' : 'wait' });
|
|
43
|
-
const done =
|
|
43
|
+
const done = await nextCollectableWorkers(active);
|
|
44
44
|
if (!done.length)
|
|
45
45
|
break;
|
|
46
46
|
for (const handle of done) {
|
|
@@ -53,6 +53,8 @@ export async function runNarutoRealActivePool(input) {
|
|
|
53
53
|
if (row) {
|
|
54
54
|
row.completed_at = result.completed_at;
|
|
55
55
|
row.ok = result.ok;
|
|
56
|
+
row.pid = result.pid || row.pid || null;
|
|
57
|
+
row.worker_artifact_dir = result.worker_artifact_dir || row.worker_artifact_dir || null;
|
|
56
58
|
}
|
|
57
59
|
await input.updateDashboard({ event_type: 'worker_completed', work_item_id: result.item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement: result.placement });
|
|
58
60
|
if (result.item.verification_required) {
|
|
@@ -66,16 +68,32 @@ export async function runNarutoRealActivePool(input) {
|
|
|
66
68
|
}
|
|
67
69
|
await input.updateDashboard({ event_type: 'pool_drained', active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size });
|
|
68
70
|
const failedCount = [...completed.values()].filter((result) => !result.ok).length;
|
|
71
|
+
const activeSamples = timeline.map((row) => row.active);
|
|
72
|
+
const averageActiveWorkers = activeSamples.length
|
|
73
|
+
? activeSamples.reduce((sum, value) => sum + value, 0) / activeSamples.length
|
|
74
|
+
: 0;
|
|
75
|
+
const saturatedSamples = timeline
|
|
76
|
+
.filter((row) => row.pending + row.active >= safeActiveWorkers)
|
|
77
|
+
.map((row) => row.active);
|
|
78
|
+
const averageSaturatedActiveWorkers = saturatedSamples.length
|
|
79
|
+
? saturatedSamples.reduce((sum, value) => sum + value, 0) / saturatedSamples.length
|
|
80
|
+
: averageActiveWorkers;
|
|
81
|
+
const utilizationDenominator = Math.max(1, safeActiveWorkers);
|
|
82
|
+
const activePoolUtilization = Math.min(1, averageSaturatedActiveWorkers / utilizationDenominator);
|
|
83
|
+
const enoughWorkForUtilization = input.graph.total_work_items >= safeActiveWorkers * 2;
|
|
69
84
|
const blockers = [
|
|
70
85
|
...(pending.length ? ['naruto_real_active_pool_pending_not_drained'] : []),
|
|
71
86
|
...(active.size ? ['naruto_real_active_pool_active_not_drained'] : []),
|
|
72
87
|
...(maxObserved > safeActiveWorkers ? ['naruto_real_active_pool_exceeded_safe_workers'] : []),
|
|
88
|
+
...(enoughWorkForUtilization && maxObserved < Math.ceil(safeActiveWorkers * 0.8) ? ['naruto_real_active_pool_underutilized'] : []),
|
|
89
|
+
...(enoughWorkForUtilization && activePoolUtilization < 0.8 ? ['naruto_real_active_pool_low_sustained_utilization'] : []),
|
|
73
90
|
...[...completed.values()].flatMap((result) => result.blockers || [])
|
|
74
91
|
];
|
|
75
92
|
return {
|
|
76
93
|
schema: 'sks.naruto-active-pool.v1',
|
|
77
94
|
ok: blockers.length === 0,
|
|
78
95
|
runtime_mode: 'real-worker-lifecycle',
|
|
96
|
+
active_cap: safeActiveWorkers,
|
|
79
97
|
safe_active_workers: safeActiveWorkers,
|
|
80
98
|
total_work_items: input.graph.total_work_items,
|
|
81
99
|
completed_count: completed.size,
|
|
@@ -85,12 +103,27 @@ export async function runNarutoRealActivePool(input) {
|
|
|
85
103
|
duplicate_execution_count: 0,
|
|
86
104
|
conflict_items_enqueued: 0,
|
|
87
105
|
max_observed_write_lease_conflicts: 0,
|
|
106
|
+
average_active_workers: Number(averageSaturatedActiveWorkers.toFixed(4)),
|
|
107
|
+
active_pool_utilization: Number(activePoolUtilization.toFixed(4)),
|
|
108
|
+
visible_workers: lifecycle.filter((row) => row.placement === 'zellij-pane').length,
|
|
109
|
+
headless_workers: lifecycle.filter((row) => row.placement === 'headless').length,
|
|
88
110
|
refill_latency_ms_p95: percentile(refillLatencies, 0.95),
|
|
89
111
|
worker_lifecycle: lifecycle,
|
|
90
112
|
timeline,
|
|
91
113
|
blockers
|
|
92
114
|
};
|
|
93
115
|
}
|
|
116
|
+
async function nextCollectableWorkers(active) {
|
|
117
|
+
const handles = [...active.values()];
|
|
118
|
+
const handlesWithExit = handles.filter((handle) => typeof handle.exit?.then === 'function');
|
|
119
|
+
if (!handlesWithExit.length)
|
|
120
|
+
return handles.slice(0, Math.max(1, Math.ceil(active.size / 2)));
|
|
121
|
+
const completed = await Promise.race(handlesWithExit.map(async (handle) => {
|
|
122
|
+
await handle.exit.catch(() => undefined);
|
|
123
|
+
return handle;
|
|
124
|
+
}));
|
|
125
|
+
return completed ? [completed] : [];
|
|
126
|
+
}
|
|
94
127
|
export async function runNarutoActivePool(input) {
|
|
95
128
|
const base = simulateNarutoActivePool(input);
|
|
96
129
|
const allocations = [];
|
|
@@ -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
|
|
@@ -7,7 +7,7 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
7
7
|
const pending = normalizeNonNegativeInt(input.pendingWorkQueueSize, totalWorkItems);
|
|
8
8
|
const leaseConflicts = normalizeNonNegativeInt(input.activeLeaseConflicts, 0);
|
|
9
9
|
const hardware = probeHardwareCapacity(input.hardware || {});
|
|
10
|
-
const zellijVisiblePaneCap = normalizePositiveInt(input.zellijVisiblePaneCap, Math.min(
|
|
10
|
+
const zellijVisiblePaneCap = normalizePositiveInt(input.zellijVisiblePaneCap, Math.min(8, Math.max(4, Math.floor(hardware.terminal_rows / 5))));
|
|
11
11
|
const backend = String(input.backend || 'codex-sdk');
|
|
12
12
|
const freeGb = hardware.free_memory_bytes / (1024 * 1024 * 1024);
|
|
13
13
|
const totalGb = hardware.total_memory_bytes / (1024 * 1024 * 1024);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
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';
|
|
7
|
+
async function main() {
|
|
8
|
+
const intakePath = process.argv[2];
|
|
9
|
+
if (!intakePath)
|
|
10
|
+
throw new Error('naruto worker intake path is required');
|
|
11
|
+
const intake = await readJson(intakePath, null);
|
|
12
|
+
if (!intake?.result_path || !intake?.heartbeat_path || !intake?.item?.id) {
|
|
13
|
+
throw new Error('naruto worker intake is invalid');
|
|
14
|
+
}
|
|
15
|
+
await fs.appendFile(intake.heartbeat_path, `${JSON.stringify({
|
|
16
|
+
schema: 'sks.naruto-actual-worker-heartbeat.v1',
|
|
17
|
+
ts: nowIso(),
|
|
18
|
+
item_id: intake.item.id,
|
|
19
|
+
status: 'running'
|
|
20
|
+
})}\n`);
|
|
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');
|
|
127
|
+
}
|
|
128
|
+
main().then(() => {
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}).catch((err) => {
|
|
131
|
+
console.error(err?.message || String(err));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=naruto-real-worker-child.js.map
|