sneakoscope 2.0.6 → 2.0.7

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 (51) hide show
  1. package/README.md +6 -1
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +32 -8
  8. package/dist/core/agents/agent-orchestrator.js +138 -3
  9. package/dist/core/agents/agent-patch-schema.js +16 -2
  10. package/dist/core/agents/agent-proof-evidence.js +3 -0
  11. package/dist/core/agents/native-cli-session-swarm.js +25 -4
  12. package/dist/core/agents/native-cli-worker.js +28 -1
  13. package/dist/core/codex-control/python-codex-sdk-adapter.js +28 -4
  14. package/dist/core/commands/naruto-command.js +39 -10
  15. package/dist/core/feature-registry.js +2 -0
  16. package/dist/core/fsx.js +1 -1
  17. package/dist/core/git/git-integration-worktree.js +15 -0
  18. package/dist/core/git/git-repo-detection.js +72 -0
  19. package/dist/core/git/git-worktree-cache-policy.js +36 -0
  20. package/dist/core/git/git-worktree-capability.js +54 -0
  21. package/dist/core/git/git-worktree-cleanup.js +51 -0
  22. package/dist/core/git/git-worktree-conflict-resolver.js +13 -0
  23. package/dist/core/git/git-worktree-diff.js +50 -0
  24. package/dist/core/git/git-worktree-manager.js +86 -0
  25. package/dist/core/git/git-worktree-merge-queue.js +55 -0
  26. package/dist/core/git/git-worktree-patch-envelope.js +35 -0
  27. package/dist/core/git/git-worktree-pool.js +23 -0
  28. package/dist/core/git/git-worktree-root.js +52 -0
  29. package/dist/core/git/git-worktree-runner.js +40 -0
  30. package/dist/core/naruto/naruto-active-pool.js +35 -0
  31. package/dist/core/naruto/naruto-gpt-final-pack.js +2 -0
  32. package/dist/core/naruto/naruto-work-graph.js +16 -1
  33. package/dist/core/version.js +1 -1
  34. package/dist/core/zellij/zellij-naruto-dashboard.js +10 -1
  35. package/dist/core/zellij/zellij-worker-pane-manager.js +6 -4
  36. package/dist/scripts/git-worktree-cache-performance-check.js +25 -0
  37. package/dist/scripts/git-worktree-capability-check.js +27 -0
  38. package/dist/scripts/git-worktree-cleanup-check.js +27 -0
  39. package/dist/scripts/git-worktree-diff-export-check.js +43 -0
  40. package/dist/scripts/git-worktree-manager-check.js +37 -0
  41. package/dist/scripts/git-worktree-merge-queue-check.js +30 -0
  42. package/dist/scripts/git-worktree-pool-performance-check.js +20 -0
  43. package/dist/scripts/lib/git-worktree-fixture.js +33 -0
  44. package/dist/scripts/naruto-shadow-clone-swarm-check.js +9 -5
  45. package/dist/scripts/naruto-worktree-coding-check.js +44 -0
  46. package/dist/scripts/naruto-worktree-gpt-final-check.js +45 -0
  47. package/dist/scripts/naruto-worktree-zellij-ui-check.js +28 -0
  48. package/dist/scripts/release-parallel-check.js +1 -1
  49. package/package.json +14 -3
  50. package/schemas/git/git-worktree-capability.schema.json +19 -0
  51. package/schemas/git/git-worktree-manifest.schema.json +36 -0
@@ -0,0 +1,35 @@
1
+ export function buildGitWorktreePatchEnvelope(input) {
2
+ const changedFiles = input.diff.changed_files.length ? input.diff.changed_files : ['git-worktree.diff'];
3
+ return {
4
+ schema: 'sks.agent-patch-envelope.v1',
5
+ source: 'git-worktree-diff',
6
+ mission_id: input.diff.mission_id,
7
+ route: '$Naruto',
8
+ agent_id: input.agentId,
9
+ session_id: input.sessionId,
10
+ slot_id: input.slotId,
11
+ generation_index: input.generationIndex,
12
+ lease_id: `git-worktree:${input.diff.worker_id}`,
13
+ allowed_paths: changedFiles,
14
+ git_worktree: {
15
+ main_repo_root: input.diff.main_repo_root,
16
+ worktree_path: input.diff.worktree_path,
17
+ branch: input.diff.branch,
18
+ base_head: input.diff.base_head,
19
+ worktree_head: input.diff.worktree_head,
20
+ changed_files: changedFiles,
21
+ diff_bytes: input.diff.diff_bytes
22
+ },
23
+ operations: changedFiles.map((file) => ({
24
+ op: 'unified_diff',
25
+ path: file,
26
+ diff: input.diff.diff
27
+ })),
28
+ rationale: 'Process-generated patch envelope exported from an isolated Git worktree diff.',
29
+ verification_hint: {
30
+ command: 'git apply --3way --check <diff>',
31
+ notes: 'Apply inside an integration worktree based on the recorded base head.'
32
+ }
33
+ };
34
+ }
35
+ //# sourceMappingURL=git-worktree-patch-envelope.js.map
@@ -0,0 +1,23 @@
1
+ import { nowIso } from '../fsx.js';
2
+ export function planGitWorktreePool(input) {
3
+ const reusable = [...(input.reusableWorktrees || [])];
4
+ const assignments = input.workerIds.map((workerId) => {
5
+ const worktree = reusable.shift() || null;
6
+ return {
7
+ worker_id: workerId,
8
+ action: worktree ? 'reuse' : 'allocate',
9
+ worktree_path: worktree
10
+ };
11
+ });
12
+ return {
13
+ schema: 'sks.git-worktree-pool.v1',
14
+ ok: true,
15
+ generated_at: nowIso(),
16
+ requested_workers: input.workerIds.length,
17
+ reusable_count: (input.reusableWorktrees || []).length,
18
+ allocate_count: assignments.filter((row) => row.action === 'allocate').length,
19
+ assignments,
20
+ blockers: []
21
+ };
22
+ }
23
+ //# sourceMappingURL=git-worktree-pool.js.map
@@ -0,0 +1,52 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { sha256 } from '../fsx.js';
5
+ export function resolveGitWorktreeRoot(input) {
6
+ const env = input.env || process.env;
7
+ const repoRoot = path.resolve(input.repoRoot);
8
+ const missionId = sanitizePathPart(input.missionId || 'mission');
9
+ const repoHash = sha256(repoRoot).slice(0, 16);
10
+ const explicitRoot = env.SKS_WORKTREE_ROOT ? path.resolve(env.SKS_WORKTREE_ROOT) : null;
11
+ const source = explicitRoot ? 'SKS_WORKTREE_ROOT' : env.XDG_CACHE_HOME ? 'XDG_CACHE_HOME' : 'HOME_CACHE';
12
+ const cacheBase = explicitRoot || path.join(env.XDG_CACHE_HOME || path.join(env.HOME || os.homedir(), '.cache'), 'sks', 'worktrees');
13
+ const root = explicitRoot ? path.join(cacheBase, repoHash, missionId) : path.join(cacheBase, repoHash, missionId);
14
+ const inRepo = isPathInside(root, repoRoot);
15
+ const allowInRepo = env.SKS_ALLOW_IN_REPO_WORKTREES === '1';
16
+ const blockers = inRepo && !allowInRepo ? ['git_worktree_root_inside_repo_blocked'] : [];
17
+ return {
18
+ schema: 'sks.git-worktree-root.v1',
19
+ ok: blockers.length === 0,
20
+ repo_root: repoRoot,
21
+ mission_id: missionId,
22
+ root,
23
+ source,
24
+ repo_hash: repoHash,
25
+ in_repo: inRepo,
26
+ allow_in_repo: allowInRepo,
27
+ blockers
28
+ };
29
+ }
30
+ export function sanitizePathPart(value) {
31
+ return String(value || 'item').replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'item';
32
+ }
33
+ export function isPathInside(candidate, parent) {
34
+ const canonicalParent = canonicalPath(parent);
35
+ const canonicalCandidate = canonicalPath(candidate);
36
+ const rel = path.relative(canonicalParent, canonicalCandidate);
37
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
38
+ }
39
+ function canonicalPath(value) {
40
+ const resolved = path.resolve(value);
41
+ try {
42
+ return fs.realpathSync.native(resolved);
43
+ }
44
+ catch {
45
+ const parent = path.dirname(resolved);
46
+ if (parent === resolved)
47
+ return resolved;
48
+ const base = canonicalPath(parent);
49
+ return path.join(base, path.basename(resolved));
50
+ }
51
+ }
52
+ //# sourceMappingURL=git-worktree-root.js.map
@@ -0,0 +1,40 @@
1
+ import { runProcess } from '../fsx.js';
2
+ export async function runGitCommand(cwd, args, opts = {}) {
3
+ const processOptions = {
4
+ cwd,
5
+ timeoutMs: opts.timeoutMs ?? 30000,
6
+ maxOutputBytes: 512 * 1024
7
+ };
8
+ if (opts.input !== undefined)
9
+ processOptions.input = opts.input;
10
+ const result = await runProcess('git', args, processOptions);
11
+ return normalizeGitResult(cwd, args, result);
12
+ }
13
+ export function normalizeGitResult(cwd, args, result) {
14
+ const stdout = result.stdout || '';
15
+ const stderr = result.stderr || '';
16
+ return {
17
+ ok: result.code === 0,
18
+ code: result.code,
19
+ args,
20
+ cwd,
21
+ stdout,
22
+ stderr,
23
+ stdout_tail: stdout.slice(-4000),
24
+ stderr_tail: stderr.slice(-4000),
25
+ timed_out: result.timedOut
26
+ };
27
+ }
28
+ export function gitOutputLine(result) {
29
+ return String(result.stdout || '').split(/\r?\n/).find((line) => line.trim())?.trim() || '';
30
+ }
31
+ export function gitBlocker(prefix, result) {
32
+ const combined = [result.stderr_tail || result.stderr, result.stdout_tail || result.stdout]
33
+ .map((value) => String(value || '').trim())
34
+ .filter(Boolean)
35
+ .join('\n');
36
+ const detail = combined.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-3).join(' | ');
37
+ const meta = `code=${result.code ?? 'null'} timed_out=${result.timed_out ? '1' : '0'} args=${result.args.join(' ')}`;
38
+ return detail ? `${prefix}:${meta}:${detail.slice(0, 320)}` : `${prefix}:${meta}`;
39
+ }
40
+ //# sourceMappingURL=git-worktree-runner.js.map
@@ -1,4 +1,39 @@
1
1
  import { createNarutoGeneration, completeNarutoGeneration } from './naruto-generation-scheduler.js';
2
+ export async function runNarutoActivePool(input) {
3
+ const base = simulateNarutoActivePool(input);
4
+ const allocations = [];
5
+ for (const item of input.graph.work_items) {
6
+ if (!item.write_allowed)
7
+ continue;
8
+ const mode = item.worktree?.mode || input.graph.worktree_policy.mode;
9
+ if (mode !== 'git-worktree') {
10
+ allocations.push({ work_item_id: item.id, mode, allocation_status: 'skipped', blockers: [] });
11
+ continue;
12
+ }
13
+ if (!input.allocateWorktree) {
14
+ allocations.push({ work_item_id: item.id, mode, allocation_status: 'planned', blockers: [] });
15
+ continue;
16
+ }
17
+ const allocated = await input.allocateWorktree(item);
18
+ allocations.push({
19
+ work_item_id: item.id,
20
+ mode,
21
+ allocation_status: allocated.ok ? 'allocated' : 'planned',
22
+ ...(allocated.worktree_path ? { worktree_path: allocated.worktree_path } : {}),
23
+ ...(allocated.branch ? { branch: allocated.branch } : {}),
24
+ blockers: allocated.blockers || []
25
+ });
26
+ }
27
+ const allocationBlockers = allocations.flatMap((row) => row.blockers);
28
+ return {
29
+ ...base,
30
+ ok: base.ok && allocationBlockers.length === 0,
31
+ worktree_mode: input.graph.worktree_policy.mode,
32
+ worktree_allocation_required_count: allocations.filter((row) => row.mode === 'git-worktree').length,
33
+ worktree_allocations: allocations,
34
+ blockers: [...base.blockers, ...allocationBlockers]
35
+ };
36
+ }
2
37
  export function simulateNarutoActivePool(input) {
3
38
  const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
4
39
  const retryLimit = Math.max(0, Math.floor(Number(input.retryLimit ?? 1)));
@@ -12,6 +12,8 @@ export function buildNarutoGptFinalPack(input) {
12
12
  },
13
13
  role_distribution: input.roleDistribution,
14
14
  changed_files: [...new Set((input.changedFiles || []).map(String))],
15
+ worktree_policy: input.worktreePolicy || input.graph.worktree_policy,
16
+ worktree_diffs: (input.worktreeDiffs || []).slice(0, maxPatchEnvelopes).map(redactSecrets),
15
17
  patch_envelopes: (input.patchEnvelopes || []).slice(0, maxPatchEnvelopes).map(redactSecrets),
16
18
  verification_results: (input.verificationResults || []).slice(0, 200).map(redactSecrets),
17
19
  failed_shards: (input.failedShards || []).slice(0, 100).map(redactSecrets),
@@ -33,6 +33,13 @@ export function buildNarutoWorkGraph(input = {}) {
33
33
  const basePath = normalizeNarutoPath(input.leaseBasePath || '.sneakoscope/naruto/patch-envelopes');
34
34
  const targetPaths = normalizePaths(input.targetPaths || []);
35
35
  const readonlyPaths = normalizePaths(input.readonlyPaths || []);
36
+ const worktreePolicy = input.worktreePolicy || {
37
+ mode: 'patch-envelope-only',
38
+ required: false,
39
+ main_repo_root: null,
40
+ worktree_root: null,
41
+ fallback_reason: writeCapable ? 'git_capability_not_evaluated' : 'readonly_or_write_disabled'
42
+ };
36
43
  const workItems = [];
37
44
  for (let index = 0; index < totalWorkItems; index += 1) {
38
45
  const id = `NW-${String(index + 1).padStart(6, '0')}`;
@@ -64,7 +71,14 @@ export function buildNarutoWorkGraph(input = {}) {
64
71
  requires_patch_envelope: writePaths.length > 0,
65
72
  requires_verification: kind !== 'research' && kind !== 'final_review_input_pack',
66
73
  requires_gpt_final: writePaths.length > 0 || kind === 'final_review_input_pack'
67
- }
74
+ },
75
+ ...(writePaths.length > 0 ? {
76
+ worktree: {
77
+ mode: worktreePolicy.mode,
78
+ required: worktreePolicy.required,
79
+ allocation_required: worktreePolicy.mode === 'git-worktree'
80
+ }
81
+ } : {})
68
82
  });
69
83
  }
70
84
  const activeWaves = planNarutoWorkWaves(workItems, Math.max(1, normalizePositiveInt(input.maxActiveWorkers, requestedClones)));
@@ -88,6 +102,7 @@ export function buildNarutoWorkGraph(input = {}) {
88
102
  active_waves: activeWaves,
89
103
  mixed_work_kinds: mixedWorkKinds,
90
104
  write_allowed_count: writeAllowedCount,
105
+ worktree_policy: worktreePolicy,
91
106
  ok: blockers.length === 0,
92
107
  blockers
93
108
  };
@@ -1,2 +1,2 @@
1
- export const PACKAGE_VERSION = '2.0.6';
1
+ export const PACKAGE_VERSION = '2.0.7';
2
2
  //# sourceMappingURL=version.js.map
@@ -4,11 +4,18 @@ export function planNarutoZellijDashboard(input) {
4
4
  const visibleWorkerPanes = Math.min(targetActiveWorkers, visiblePaneCap);
5
5
  const headlessWorkers = Math.max(0, targetActiveWorkers - visibleWorkerPanes);
6
6
  const backend = input.backend || 'codex-sdk';
7
+ const worktreeMode = input.worktreePolicy?.mode || 'patch-envelope-only';
7
8
  const roles = input.roles && input.roles.length ? input.roles : ['implementer', 'modifier', 'verifier', 'test_writer'];
9
+ const worktreeLabels = [];
8
10
  const paneTitles = Array.from({ length: visibleWorkerPanes }, (_, index) => {
9
11
  const slot = `slot-${String(index + 1).padStart(3, '0')}`;
10
12
  const role = roles[index % roles.length] || 'worker';
11
- return `${slot}/gen-1 · ${role} · ${backend} · active`;
13
+ if (worktreeMode !== 'git-worktree')
14
+ return `${slot}/gen-1 · ${role} · ${backend} · active`;
15
+ const wt = `WT:${slot}-gen-1`;
16
+ const branch = `branch:${slot}/gen-1`;
17
+ worktreeLabels.push(`${wt} ${branch}`);
18
+ return `${slot}/gen-1 · ${wt} · ${branch} · ${role} · ${backend} · active`;
12
19
  });
13
20
  const blockers = [
14
21
  ...(visibleWorkerPanes > visiblePaneCap ? ['naruto_zellij_visible_panes_exceed_cap'] : []),
@@ -29,6 +36,8 @@ export function planNarutoZellijDashboard(input) {
29
36
  backpressure: input.backpressure || 'normal'
30
37
  },
31
38
  pane_titles: paneTitles,
39
+ worktree_mode: worktreeMode,
40
+ worktree_labels: worktreeLabels,
32
41
  ok: blockers.length === 0,
33
42
  blockers
34
43
  };
@@ -9,10 +9,11 @@ export const ZELLIJ_WORKER_PANE_EVENT_SCHEMA = 'sks.zellij-worker-pane-event.v1'
9
9
  export function buildWorkerPaneName(slotId, generationIndex) {
10
10
  return `${slotId}/gen-${Math.max(1, Math.floor(Number(generationIndex) || 1))}`;
11
11
  }
12
- export function buildWorkerPaneTitle(slotId, generationIndex, context, serviceTier, backend, status) {
12
+ export function buildWorkerPaneTitle(slotId, generationIndex, context, serviceTier, backend, status, worktree) {
13
13
  const base = buildWorkerPaneName(slotId, generationIndex);
14
14
  const normalized = normalizePaneProviderContext(context, serviceTier);
15
- return `${base} · ${backend || 'codex-sdk'} · ${providerPaneLabel(normalized)} · ${status || 'launching'}`;
15
+ const wt = worktree ? ` · WT:${worktree.id} · branch:${worktree.branch}` : '';
16
+ return `${base}${wt} · ${backend || 'codex-sdk'} · ${providerPaneLabel(normalized)} · ${status || 'launching'}`;
16
17
  }
17
18
  export function isRealZellijWorkerPaneIdSource(value) {
18
19
  return value === 'zellij_worker_new_pane_stdout' || value === 'zellij_worker_list_panes';
@@ -22,7 +23,7 @@ export function buildWorkerPaneArtifact(input) {
22
23
  const paneIdSource = input.paneIdSource || 'zellij_worker_pane_launch_failed';
23
24
  const blockers = input.blockers || [];
24
25
  const providerContext = normalizePaneProviderContext(input.providerContext, input.serviceTier);
25
- const paneTitle = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.status || input.statusLabel);
26
+ const paneTitle = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.status || input.statusLabel, input.worktree || null);
26
27
  return {
27
28
  schema: ZELLIJ_WORKER_PANE_SCHEMA,
28
29
  generated_at: now,
@@ -42,6 +43,7 @@ export function buildWorkerPaneArtifact(input) {
42
43
  provider: providerContext.provider,
43
44
  service_tier: providerContext.service_tier,
44
45
  provider_context: providerContext,
46
+ worktree: input.worktree || null,
45
47
  worker_artifact_dir: input.workerArtifactDir,
46
48
  worker_result_path: input.resultPath,
47
49
  heartbeat_path: input.heartbeatPath,
@@ -82,7 +84,7 @@ export async function openWorkerPane(input) {
82
84
  timeoutMs: 5000,
83
85
  optional: false
84
86
  });
85
- const paneName = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.statusLabel || 'running');
87
+ const paneName = buildWorkerPaneTitle(input.slotId, input.generationIndex, providerContext, input.serviceTier, input.backend, input.statusLabel || 'running', input.worktree || null);
86
88
  let launch = createSession.ok
87
89
  ? await runZellij(['--session', input.sessionName, 'action', 'new-pane', '--direction', 'right', '--name', paneName, '--', 'sh', '-lc', input.workerCommand], {
88
90
  cwd,
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
4
+ const policyMod = await importDist('core/git/git-worktree-cache-policy.js');
5
+ const now = Date.now();
6
+ const entries = Array.from({ length: 1000 }, (_, index) => ({
7
+ path: `/tmp/wt-${index}`,
8
+ updated_at_ms: now - index * 60000,
9
+ bytes: 1024 * (index + 1),
10
+ dirty: index % 137 === 0
11
+ }));
12
+ const start = Date.now();
13
+ const plan = policyMod.planGitWorktreeCachePolicy({ entries, nowMs: now, maxEntries: 120, maxBytes: 40 * 1024 * 1024, ttlMs: 2 * 60 * 60 * 1000 });
14
+ const elapsed = Date.now() - start;
15
+ assertGate(plan.ok === true, 'cache policy plan must pass', plan);
16
+ assertGate(plan.keep.length <= 120 || plan.dirty_retained.length > 0, 'cache policy must bound retained clean entries', plan);
17
+ assertGate(plan.dirty_retained.length > 0, 'dirty entries must be retained by cache policy', plan);
18
+ assertGate(elapsed < 250, 'cache policy must stay fast on 1000 entries', { elapsed });
19
+ emitGate('git:worktree-cache-performance', {
20
+ elapsed_ms: elapsed,
21
+ keep: plan.keep.length,
22
+ prune: plan.prune.length,
23
+ dirty_retained: plan.dirty_retained.length
24
+ });
25
+ //# sourceMappingURL=git-worktree-cache-performance-check.js.map
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ import { makeGitFixture, makeNonGitFixture } from './lib/git-worktree-fixture.js';
8
+ const capabilityMod = await importDist('core/git/git-worktree-capability.js');
9
+ const nonGit = makeNonGitFixture('worktree-capability-non-git');
10
+ process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-capability-'));
11
+ delete process.env.SKS_ALLOW_IN_REPO_WORKTREES;
12
+ const nonGitCapability = await capabilityMod.evaluateGitWorktreeCapability({ root: nonGit, missionId: 'M-non-git' });
13
+ assertGate(nonGitCapability.mode === 'patch-envelope-only', 'non-Git projects must degrade to patch-envelope-only', nonGitCapability);
14
+ assertGate(nonGitCapability.worktree_probe_attempted === false, 'non-Git projects must not probe git worktree', nonGitCapability);
15
+ const repo = makeGitFixture('worktree-capability-git');
16
+ const gitCapability = await capabilityMod.evaluateGitWorktreeCapability({ root: repo, missionId: 'M-git' });
17
+ assertGate(gitCapability.ok === true && gitCapability.mode === 'git-worktree', 'Git fixture must support worktree mode', gitCapability);
18
+ assertGate(gitCapability.root_resolution?.in_repo === false, 'default worktree root must be outside main repo', gitCapability.root_resolution);
19
+ process.env.SKS_WORKTREE_ROOT = path.join(repo, '.sneakoscope', 'worktrees');
20
+ const blocked = await capabilityMod.evaluateGitWorktreeCapability({ root: repo, missionId: 'M-in-repo' });
21
+ assertGate(blocked.ok === false && blocked.blockers.includes('git_worktree_root_inside_repo_blocked'), 'in-repo worktree root must be blocked by default', blocked);
22
+ emitGate('git:worktree-capability', {
23
+ non_git_mode: nonGitCapability.mode,
24
+ git_mode: gitCapability.mode,
25
+ in_repo_blocked: true
26
+ });
27
+ //# sourceMappingURL=git-worktree-capability-check.js.map
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ import { makeGitFixture } from './lib/git-worktree-fixture.js';
8
+ const managerMod = await importDist('core/git/git-worktree-manager.js');
9
+ const cleanupMod = await importDist('core/git/git-worktree-cleanup.js');
10
+ const repo = makeGitFixture('worktree-cleanup');
11
+ process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-cleanup-'));
12
+ const cleanAllocation = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-cleanup', workerId: 'clean', slotId: 'slot-001' });
13
+ const clean = await cleanupMod.cleanupGitWorktree({ repoRoot: repo, worktreePath: cleanAllocation.worktree_path, branch: cleanAllocation.branch, deleteBranch: true });
14
+ assertGate(clean.ok === true && clean.action === 'removed', 'clean worktree must be removed by cleanup manager', clean);
15
+ assertGate(!fs.existsSync(cleanAllocation.worktree_path), 'removed clean worktree path should be gone');
16
+ const dirtyAllocation = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-cleanup', workerId: 'dirty', slotId: 'slot-002' });
17
+ fs.writeFileSync(path.join(dirtyAllocation.worktree_path, 'a.txt'), 'dirty\n');
18
+ const dirty = await cleanupMod.cleanupGitWorktree({ repoRoot: repo, worktreePath: dirtyAllocation.worktree_path, branch: dirtyAllocation.branch, deleteBranch: true });
19
+ assertGate(dirty.ok === true && dirty.action === 'retained_dirty', 'dirty worktree must be retained', dirty);
20
+ assertGate(fs.existsSync(dirtyAllocation.worktree_path), 'dirty worktree path must still exist');
21
+ assertGate(dirty.retention_lock_path && fs.existsSync(dirty.retention_lock_path), 'dirty retention lock must be written outside the retained worktree', dirty);
22
+ emitGate('git:worktree-cleanup', {
23
+ clean_action: clean.action,
24
+ dirty_action: dirty.action,
25
+ retention_lock_path: dirty.retention_lock_path
26
+ });
27
+ //# sourceMappingURL=git-worktree-cleanup-check.js.map
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ import { makeGitFixture, run } from './lib/git-worktree-fixture.js';
8
+ const managerMod = await importDist('core/git/git-worktree-manager.js');
9
+ const diffMod = await importDist('core/git/git-worktree-diff.js');
10
+ const envelopeMod = await importDist('core/git/git-worktree-patch-envelope.js');
11
+ const schemaMod = await importDist('core/agents/agent-patch-schema.js');
12
+ const cleanupMod = await importDist('core/git/git-worktree-cleanup.js');
13
+ const repo = makeGitFixture('worktree-diff');
14
+ process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-diff-'));
15
+ const allocation = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-diff', workerId: 'worker-1', slotId: 'slot-001' });
16
+ fs.writeFileSync(path.join(allocation.worktree_path, 'a.txt'), 'alpha\nchanged\n');
17
+ const diff = await diffMod.exportGitWorktreeDiff({
18
+ mainRepoRoot: repo,
19
+ worktreePath: allocation.worktree_path,
20
+ missionId: 'M-diff',
21
+ workerId: 'worker-1'
22
+ });
23
+ const envelope = envelopeMod.buildGitWorktreePatchEnvelope({
24
+ diff,
25
+ agentId: 'agent-1',
26
+ sessionId: 'session-1',
27
+ slotId: 'slot-001',
28
+ generationIndex: 1
29
+ });
30
+ const validation = schemaMod.validateAgentPatchEnvelope(schemaMod.normalizeAgentPatchEnvelope(envelope));
31
+ assertGate(diff.ok === true && diff.clean === false, 'diff export must detect changed worktree', diff);
32
+ assertGate(diff.changed_files.includes('a.txt'), 'diff export must include changed file', diff);
33
+ assertGate(/index [0-9a-f]{40}\.\.[0-9a-f]{40}/.test(diff.diff), 'diff export must use full-index diff hashes', { diff: diff.diff });
34
+ assertGate(envelope.source === 'git-worktree-diff' && validation.ok === true, 'git worktree diff patch envelope must validate', { envelope, validation });
35
+ assertGate(run('git', ['status', '--porcelain=v1'], repo).trim() === '', 'main repo must stay clean after diff export');
36
+ run('git', ['checkout', '--', 'a.txt'], allocation.worktree_path);
37
+ await cleanupMod.cleanupGitWorktree({ repoRoot: repo, worktreePath: allocation.worktree_path, branch: allocation.branch, deleteBranch: true });
38
+ emitGate('git:worktree-diff-export', {
39
+ changed_files: diff.changed_files,
40
+ diff_bytes: diff.diff_bytes,
41
+ envelope_source: envelope.source
42
+ });
43
+ //# sourceMappingURL=git-worktree-diff-export-check.js.map
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ import { makeGitFixture, run } from './lib/git-worktree-fixture.js';
8
+ const managerMod = await importDist('core/git/git-worktree-manager.js');
9
+ const cleanupMod = await importDist('core/git/git-worktree-cleanup.js');
10
+ const repo = makeGitFixture('worktree-manager');
11
+ process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-manager-'));
12
+ delete process.env.SKS_ALLOW_IN_REPO_WORKTREES;
13
+ const allocation = await managerMod.allocateWorkerWorktree({
14
+ repoRoot: repo,
15
+ missionId: 'M-manager',
16
+ workerId: 'worker-1',
17
+ slotId: 'slot-001',
18
+ generationIndex: 1
19
+ });
20
+ assertGate(allocation.ok === true, 'worker worktree allocation must pass', allocation);
21
+ assertGate(fs.existsSync(allocation.worktree_path), 'allocated worktree path must exist', allocation);
22
+ assertGate(!path.resolve(allocation.worktree_path).startsWith(path.resolve(repo) + path.sep), 'worktree must be outside main repo by default', allocation);
23
+ assertGate(fs.existsSync(allocation.manifest_path), 'worktree manifest must be written', allocation);
24
+ assertGate(run('git', ['status', '--porcelain=v1'], repo).trim() === '', 'main repo must stay clean after allocation');
25
+ const cleanup = await cleanupMod.cleanupGitWorktree({
26
+ repoRoot: repo,
27
+ worktreePath: allocation.worktree_path,
28
+ branch: allocation.branch,
29
+ deleteBranch: true
30
+ });
31
+ assertGate(cleanup.ok === true && cleanup.action === 'removed', 'clean worktree cleanup must remove allocation', cleanup);
32
+ emitGate('git:worktree-manager', {
33
+ worktree_outside_repo: true,
34
+ branch: allocation.branch,
35
+ cleanup: cleanup.action
36
+ });
37
+ //# sourceMappingURL=git-worktree-manager-check.js.map
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
7
+ import { makeGitFixture } from './lib/git-worktree-fixture.js';
8
+ const managerMod = await importDist('core/git/git-worktree-manager.js');
9
+ const diffMod = await importDist('core/git/git-worktree-diff.js');
10
+ const integrationMod = await importDist('core/git/git-integration-worktree.js');
11
+ const queueMod = await importDist('core/git/git-worktree-merge-queue.js');
12
+ const repo = makeGitFixture('worktree-merge');
13
+ process.env.SKS_WORKTREE_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-wt-merge-'));
14
+ const a = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-merge', workerId: 'a', slotId: 'slot-001' });
15
+ const b = await managerMod.allocateWorkerWorktree({ repoRoot: repo, missionId: 'M-merge', workerId: 'b', slotId: 'slot-002' });
16
+ fs.writeFileSync(path.join(a.worktree_path, 'a.txt'), 'alpha\nfrom-a\n');
17
+ fs.writeFileSync(path.join(b.worktree_path, 'b.txt'), 'bravo\nfrom-b\n');
18
+ const diffA = await diffMod.exportGitWorktreeDiff({ mainRepoRoot: repo, worktreePath: a.worktree_path, missionId: 'M-merge', workerId: 'a' });
19
+ const diffB = await diffMod.exportGitWorktreeDiff({ mainRepoRoot: repo, worktreePath: b.worktree_path, missionId: 'M-merge', workerId: 'b' });
20
+ const integration = await integrationMod.createGitIntegrationWorktree({ repoRoot: repo, missionId: 'M-merge' });
21
+ const report = await queueMod.applyGitWorktreeMergeQueue({ integrationWorktreePath: integration.worktree_path, diffs: [diffA, diffB] });
22
+ assertGate(report.ok === true && report.applied_count === 2, 'merge queue must apply non-conflicting worktree diffs', report);
23
+ assertGate(fs.readFileSync(path.join(integration.worktree_path, 'a.txt'), 'utf8').includes('from-a'), 'integration worktree must include first diff');
24
+ assertGate(fs.readFileSync(path.join(integration.worktree_path, 'b.txt'), 'utf8').includes('from-b'), 'integration worktree must include second diff');
25
+ assertGate(fs.readFileSync(path.join(repo, 'a.txt'), 'utf8') === 'alpha\n', 'main worktree must remain unchanged before integration commit');
26
+ emitGate('git:worktree-merge-queue', {
27
+ applied_count: report.applied_count,
28
+ changed_files: report.changed_files
29
+ });
30
+ //# sourceMappingURL=git-worktree-merge-queue-check.js.map
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ // @ts-nocheck
3
+ import { assertGate, emitGate, importDist } from './sks-1-18-gate-lib.js';
4
+ const poolMod = await importDist('core/git/git-worktree-pool.js');
5
+ const workerIds = Array.from({ length: 100 }, (_, index) => `worker-${index + 1}`);
6
+ const reusable = Array.from({ length: 32 }, (_, index) => `/tmp/reuse-${index + 1}`);
7
+ const start = Date.now();
8
+ const plan = poolMod.planGitWorktreePool({ workerIds, reusableWorktrees: reusable });
9
+ const elapsed = Date.now() - start;
10
+ assertGate(plan.ok === true, 'pool plan must pass', plan);
11
+ assertGate(plan.assignments.length === 100, 'pool must assign every requested worker', plan);
12
+ assertGate(plan.assignments.filter((row) => row.action === 'reuse').length === 32, 'pool must reuse available worktrees first', plan);
13
+ assertGate(plan.allocate_count === 68, 'pool must allocate only the remaining workers', plan);
14
+ assertGate(elapsed < 100, 'pool plan must stay fast for 100 workers', { elapsed });
15
+ emitGate('git:worktree-pool-performance', {
16
+ elapsed_ms: elapsed,
17
+ reusable: 32,
18
+ allocate_count: plan.allocate_count
19
+ });
20
+ //# sourceMappingURL=git-worktree-pool-performance-check.js.map
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { spawnSync } from 'node:child_process';
5
+ export function makeGitFixture(name) {
6
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), `sks-${name}-`));
7
+ run('git', ['init'], root);
8
+ run('git', ['config', 'user.email', 'sks@example.invalid'], root);
9
+ run('git', ['config', 'user.name', 'SKS Test'], root);
10
+ fs.writeFileSync(path.join(root, 'a.txt'), 'alpha\n');
11
+ fs.writeFileSync(path.join(root, 'b.txt'), 'bravo\n');
12
+ run('git', ['add', 'a.txt', 'b.txt'], root);
13
+ run('git', ['commit', '-m', 'fixture'], root);
14
+ return root;
15
+ }
16
+ export function makeNonGitFixture(name) {
17
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), `sks-${name}-`));
18
+ fs.writeFileSync(path.join(root, 'plain.txt'), 'not git\n');
19
+ return root;
20
+ }
21
+ export function run(command, args, cwd, input) {
22
+ const result = spawnSync(command, args, {
23
+ cwd,
24
+ input,
25
+ encoding: 'utf8',
26
+ stdio: input === undefined ? ['ignore', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe']
27
+ });
28
+ if (result.status !== 0) {
29
+ throw new Error(`${command} ${args.join(' ')} failed in ${cwd}: ${result.stderr || result.stdout}`);
30
+ }
31
+ return result.stdout || '';
32
+ }
33
+ //# sourceMappingURL=git-worktree-fixture.js.map
@@ -6,6 +6,7 @@
6
6
  // schedules >20 concurrent clone sessions to completion with proof.
7
7
  import { spawnSync } from 'node:child_process';
8
8
  import fs from 'node:fs';
9
+ import os from 'node:os';
9
10
  import path from 'node:path';
10
11
  import { assertGate, emitGate, importDist, root, exists } from './sks-1-18-gate-lib.js';
11
12
  const schema = await importDist('core/agents/agent-schema.js');
@@ -71,8 +72,10 @@ assertGate(bigMemoryHost.cap >= 64, 'a 64 GB host must allow >= 64 parallel code
71
72
  // concurrency is throttled to the host-safe cap (never the full 24 unless the host allows).
72
73
  const proofClones = 24;
73
74
  const cli = path.join(root, 'dist', 'bin', 'sks.js');
75
+ const isolatedWorktreeRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sks-naruto-shadow-wt-'));
76
+ const childEnv = { ...process.env, SKS_WORKTREE_ROOT: isolatedWorktreeRoot };
74
77
  assertGate(exists('dist/bin/sks.js'), 'dist/bin/sks.js missing (build first)');
75
- const helpRun = spawnSync(process.execPath, [cli, 'naruto', '--help', '--json'], { cwd: root, encoding: 'utf8', timeout: 30000, maxBuffer: 1024 * 1024 });
78
+ const helpRun = spawnSync(process.execPath, [cli, 'naruto', '--help', '--json'], { cwd: root, env: childEnv, encoding: 'utf8', timeout: 30000, maxBuffer: 1024 * 1024 });
76
79
  const helpParsed = parseJson(helpRun.stdout);
77
80
  assertGate(helpRun.status === 0 && helpParsed?.action === 'help', 'sks naruto --help must emit help instead of launching a run', { status: helpRun.status, stdout: tail(helpRun.stdout), stderr: tail(helpRun.stderr) });
78
81
  const run = spawnSync(process.execPath, [
@@ -81,7 +84,7 @@ const run = spawnSync(process.execPath, [
81
84
  '--backend', 'fake',
82
85
  '--work-items', String(proofClones),
83
86
  '--json'
84
- ], { cwd: root, encoding: 'utf8', timeout: 240000, maxBuffer: 8 * 1024 * 1024 });
87
+ ], { cwd: root, env: childEnv, encoding: 'utf8', timeout: 240000, maxBuffer: 8 * 1024 * 1024 });
85
88
  assertGate(run.status === 0, 'sks naruto run must exit 0', { status: run.status, stderr: tail(run.stderr) });
86
89
  const parsed = parseJson(run.stdout);
87
90
  assertGate(parsed !== null, 'sks naruto run must emit JSON', { stdout: tail(run.stdout) });
@@ -109,11 +112,11 @@ assertGate(new Set(writePaths).size > 1, 'naruto command default patch-envelope
109
112
  assertGate(commandGraph.active_waves.some((wave) => wave.write_paths.length > 1), 'naruto command graph must contain a parallel write wave when route-local patch envelopes do not overlap', { waves: commandGraph.active_waves.slice(0, 3) });
110
113
  const state = parsed.run?.scheduler?.state || parsed.run?.scheduler || {};
111
114
  assertGate(Number(state.completed_count) === proofClones, 'all clone work items must complete despite throttling', { completed_count: state.completed_count });
112
- const explicitConcurrency = spawnSync(process.execPath, [cli, 'naruto', 'run', 'explicit concurrency', '--clones', '6', '--backend', 'fake', '--work-items', '6', '--concurrency', '6', '--json'], { cwd: root, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 });
115
+ const explicitConcurrency = spawnSync(process.execPath, [cli, 'naruto', 'run', 'explicit concurrency', '--clones', '6', '--backend', 'fake', '--work-items', '6', '--concurrency', '6', '--json'], { cwd: root, env: childEnv, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 });
113
116
  const explicitParsed = parseJson(explicitConcurrency.stdout);
114
117
  assertGate(explicitConcurrency.status === 0 && explicitParsed?.target_active_slots === 6, 'explicit --concurrency must let Naruto use the requested parallel slot count', { status: explicitConcurrency.status, target_active_slots: explicitParsed?.target_active_slots });
115
118
  // 7) A small request is NOT throttled below what was asked (cap only ever reduces, never inflates).
116
- const small = spawnSync(process.execPath, [cli, 'naruto', 'run', 'tiny', '--clones', '2', '--backend', 'fake', '--work-items', '2', '--json'], { cwd: root, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 });
119
+ const small = spawnSync(process.execPath, [cli, 'naruto', 'run', 'tiny', '--clones', '2', '--backend', 'fake', '--work-items', '2', '--json'], { cwd: root, env: childEnv, encoding: 'utf8', timeout: 120000, maxBuffer: 4 * 1024 * 1024 });
117
120
  const smallParsed = parseJson(small.stdout);
118
121
  assertGate(small.status === 0 && smallParsed?.target_active_slots === 2, 'a 2-clone run must run 2 concurrently (no over-throttle)', { status: small.status, target_active_slots: smallParsed?.target_active_slots });
119
122
  emitGate('naruto:shadow-clone-swarm', {
@@ -128,7 +131,8 @@ emitGate('naruto:shadow-clone-swarm', {
128
131
  low_free_capable_cap: lowFreeButCapable.cap,
129
132
  cores: heavySafe.cores,
130
133
  completed_count: state.completed_count,
131
- mission_id: parsed.mission_id
134
+ mission_id: parsed.mission_id,
135
+ isolated_worktree_root: isolatedWorktreeRoot
132
136
  });
133
137
  function parseJson(text) {
134
138
  try {