sneakoscope 2.0.6 → 2.0.8
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 +6 -1
- 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 +56 -8
- package/dist/core/agents/agent-command-surface.js +4 -2
- package/dist/core/agents/agent-orchestrator.js +140 -4
- package/dist/core/agents/agent-patch-schema.js +20 -4
- package/dist/core/agents/agent-proof-evidence.js +3 -0
- package/dist/core/agents/native-cli-session-swarm.js +31 -5
- package/dist/core/agents/native-cli-worker.js +28 -1
- package/dist/core/codex-control/python-codex-sdk-adapter.js +28 -4
- package/dist/core/commands/mad-sks-command.js +25 -0
- package/dist/core/commands/naruto-command.js +68 -10
- package/dist/core/feature-registry.js +2 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/git/git-integration-worktree.js +15 -0
- package/dist/core/git/git-repo-detection.js +79 -0
- package/dist/core/git/git-worktree-cache-policy.js +36 -0
- package/dist/core/git/git-worktree-capability.js +54 -0
- package/dist/core/git/git-worktree-cleanup.js +62 -0
- package/dist/core/git/git-worktree-conflict-resolver.js +13 -0
- package/dist/core/git/git-worktree-diff.js +55 -0
- package/dist/core/git/git-worktree-manager.js +93 -0
- package/dist/core/git/git-worktree-merge-queue.js +55 -0
- package/dist/core/git/git-worktree-patch-envelope.js +35 -0
- package/dist/core/git/git-worktree-pool.js +23 -0
- package/dist/core/git/git-worktree-root.js +52 -0
- package/dist/core/git/git-worktree-runner.js +40 -0
- package/dist/core/naruto/naruto-active-pool.js +35 -0
- package/dist/core/naruto/naruto-gpt-final-pack.js +2 -0
- package/dist/core/naruto/naruto-work-graph.js +16 -1
- package/dist/core/release/release-gate-cache-v2.js +63 -0
- package/dist/core/release/release-gate-dag.js +179 -0
- package/dist/core/release/release-gate-hermetic-env.js +32 -0
- package/dist/core/release/release-gate-node.js +62 -0
- package/dist/core/release/release-gate-report.js +11 -0
- package/dist/core/release/release-gate-resource-governor.js +54 -0
- package/dist/core/release/release-gate-scheduler.js +15 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-dashboard-pane.js +71 -0
- package/dist/core/zellij/zellij-dashboard-renderer.js +42 -0
- package/dist/core/zellij/zellij-naruto-dashboard.js +10 -1
- package/dist/core/zellij/zellij-worker-pane-manager.js +68 -6
- package/dist/scripts/git-worktree-cache-performance-check.js +25 -0
- package/dist/scripts/git-worktree-capability-check.js +27 -0
- package/dist/scripts/git-worktree-cleanup-check.js +27 -0
- package/dist/scripts/git-worktree-diff-envelope-check.js +17 -0
- package/dist/scripts/git-worktree-diff-export-check.js +43 -0
- package/dist/scripts/git-worktree-dirty-lock-check.js +17 -0
- package/dist/scripts/git-worktree-dirty-main-detection-check.js +14 -0
- package/dist/scripts/git-worktree-integration-primary-check.js +22 -0
- package/dist/scripts/git-worktree-manager-check.js +37 -0
- package/dist/scripts/git-worktree-manifest-append-check.js +18 -0
- package/dist/scripts/git-worktree-merge-queue-check.js +30 -0
- package/dist/scripts/git-worktree-pool-performance-check.js +20 -0
- package/dist/scripts/git-worktree-untracked-diff-check.js +18 -0
- package/dist/scripts/lib/git-worktree-fixture.js +33 -0
- package/dist/scripts/naruto-shadow-clone-swarm-check.js +9 -5
- package/dist/scripts/naruto-worktree-coding-blackbox.js +29 -0
- package/dist/scripts/naruto-worktree-coding-check.js +44 -0
- package/dist/scripts/naruto-worktree-gpt-final-check.js +45 -0
- package/dist/scripts/naruto-worktree-zellij-ui-check.js +28 -0
- package/dist/scripts/release-gate-dag-runner-check.js +17 -0
- package/dist/scripts/release-gate-dag-runner.js +32 -0
- package/dist/scripts/release-gate-worker.js +10 -0
- package/dist/scripts/release-metadata-1-19-check.js +8 -2
- package/dist/scripts/release-parallel-check.js +1 -1
- package/dist/scripts/release-parallel-speed-budget-check.js +25 -0
- package/dist/scripts/release-stability-report-check.js +99 -0
- package/dist/scripts/zellij-dashboard-pane-check.js +68 -0
- package/dist/scripts/zellij-dashboard-watch.js +41 -0
- package/dist/scripts/zellij-worker-pane-real-ui-blackbox.js +185 -0
- package/package.json +33 -5
- package/schemas/git/git-worktree-capability.schema.json +19 -0
- package/schemas/git/git-worktree-manifest.schema.json +36 -0
- package/schemas/release/release-gate-node.schema.json +52 -0
|
@@ -26,6 +26,8 @@ class NativeCliSessionSwarmRecorder {
|
|
|
26
26
|
await this.persist();
|
|
27
27
|
}
|
|
28
28
|
async launchWorker(ctx) {
|
|
29
|
+
const worktree = normalizeWorkerWorktree(ctx.agent?.worktree || ctx.slice?.worktree || ctx.opts?.worktree || null);
|
|
30
|
+
const workerCwd = worktree?.path || ctx.opts.cwd || packageRoot();
|
|
29
31
|
const workerDirRel = path.join(ctx.agent.session_artifact_dir || path.join('sessions', ctx.agent.id), 'worker');
|
|
30
32
|
const workerDir = path.join(this.root, workerDirRel);
|
|
31
33
|
await ensureDir(workerDir);
|
|
@@ -45,6 +47,9 @@ class NativeCliSessionSwarmRecorder {
|
|
|
45
47
|
backend_explicit: this.input.backendExplicit === true,
|
|
46
48
|
no_ollama: this.input.noOllama === true || ctx.opts.noOllama === true,
|
|
47
49
|
agent_root: this.root,
|
|
50
|
+
main_repo_root: worktree?.main_repo_root || ctx.opts.cwd || packageRoot(),
|
|
51
|
+
cwd: workerCwd,
|
|
52
|
+
worktree,
|
|
48
53
|
agent: ctx.agent,
|
|
49
54
|
slice: ctx.slice,
|
|
50
55
|
worker_artifact_dir: workerDirRel,
|
|
@@ -89,13 +94,18 @@ class NativeCliSessionSwarmRecorder {
|
|
|
89
94
|
patch_envelope_path: patchRel,
|
|
90
95
|
fast_mode: this.input.fastModePolicy.fast_mode,
|
|
91
96
|
service_tier: this.input.fastModePolicy.service_tier,
|
|
97
|
+
cwd: workerCwd,
|
|
92
98
|
status: 'launching',
|
|
93
99
|
exit_code: null,
|
|
94
100
|
blockers: []
|
|
95
101
|
};
|
|
96
102
|
const stdout = fs.createWriteStream(path.join(this.root, stdoutRel), { flags: 'a' });
|
|
97
103
|
const stderr = fs.createWriteStream(path.join(this.root, stderrRel), { flags: 'a' });
|
|
98
|
-
|
|
104
|
+
const placement = String(ctx.opts.workerPlacement || this.input.workerPlacement || (this.input.backend === 'zellij' ? 'zellij-pane' : 'process'));
|
|
105
|
+
const useZellijPane = placement === 'zellij-pane'
|
|
106
|
+
&& ctx.opts.zellijPaneWorker !== false
|
|
107
|
+
&& (ctx.opts.zellijSessionName || this.input.missionId);
|
|
108
|
+
if (useZellijPane) {
|
|
99
109
|
stdout.end();
|
|
100
110
|
stderr.end();
|
|
101
111
|
return this.launchWorkerInZellijPane({
|
|
@@ -110,7 +120,7 @@ class NativeCliSessionSwarmRecorder {
|
|
|
110
120
|
});
|
|
111
121
|
}
|
|
112
122
|
const child = spawn(process.execPath, args, {
|
|
113
|
-
cwd:
|
|
123
|
+
cwd: workerCwd,
|
|
114
124
|
env: {
|
|
115
125
|
...process.env,
|
|
116
126
|
...(ctx.opts.env || {}),
|
|
@@ -179,6 +189,8 @@ class NativeCliSessionSwarmRecorder {
|
|
|
179
189
|
async launchWorkerInZellijPane(input) {
|
|
180
190
|
const sessionName = String(input.ctx.opts.zellijSessionName || (this.input.missionId ? `sks-${this.input.missionId}` : 'sks-agent-runtime'));
|
|
181
191
|
const slotId = String(input.ctx.agent.slot_id || input.ctx.agent.id || 'slot-001');
|
|
192
|
+
const worktree = normalizeWorkerWorktree(input.ctx.agent?.worktree || input.ctx.slice?.worktree || input.ctx.opts?.worktree || null);
|
|
193
|
+
const workerCwd = worktree?.path || input.ctx.opts.cwd || packageRoot();
|
|
182
194
|
const activeToken = this.nextPaneToken--;
|
|
183
195
|
this.active.add(activeToken);
|
|
184
196
|
this.maxObserved = Math.max(this.maxObserved, this.active.size);
|
|
@@ -222,9 +234,11 @@ class NativeCliSessionSwarmRecorder {
|
|
|
222
234
|
patchEnvelopePath: input.record.patch_envelope_path,
|
|
223
235
|
stdoutLog: input.stdoutRel,
|
|
224
236
|
stderrLog: input.stderrRel,
|
|
225
|
-
cwd:
|
|
237
|
+
cwd: workerCwd,
|
|
226
238
|
providerContext,
|
|
227
|
-
serviceTier: this.input.fastModePolicy.service_tier
|
|
239
|
+
serviceTier: this.input.fastModePolicy.service_tier,
|
|
240
|
+
worktree: worktree ? { id: worktree.id, path: worktree.path, branch: worktree.branch } : null,
|
|
241
|
+
backend: this.input.backend
|
|
228
242
|
});
|
|
229
243
|
const launchBlockers = paneRecord.blockers || [];
|
|
230
244
|
input.record.command_line = ['zellij', '--session', sessionName, 'action', 'new-pane', '--direction', paneRecord.direction_applied, '--name', paneRecord.pane_name, '--', 'sh', '-lc', '<native-cli-worker-command>'];
|
|
@@ -239,6 +253,7 @@ class NativeCliSessionSwarmRecorder {
|
|
|
239
253
|
input.record.provider = paneRecord.provider;
|
|
240
254
|
input.record.service_tier = paneRecord.service_tier;
|
|
241
255
|
input.record.provider_context = paneRecord.provider_context;
|
|
256
|
+
input.record.worktree = worktree;
|
|
242
257
|
input.record.status = launchBlockers.length ? 'failed' : 'running';
|
|
243
258
|
input.record.blockers = launchBlockers;
|
|
244
259
|
await this.record(input.record);
|
|
@@ -323,7 +338,7 @@ class NativeCliSessionSwarmRecorder {
|
|
|
323
338
|
paneRecord = await closeWorkerPane({
|
|
324
339
|
root: this.root,
|
|
325
340
|
paneRecord,
|
|
326
|
-
cwd:
|
|
341
|
+
cwd: workerCwd,
|
|
327
342
|
status: input.record.status === 'closed' ? 'closed' : 'failed',
|
|
328
343
|
blockers: input.record.blockers,
|
|
329
344
|
sdkThreadId,
|
|
@@ -462,4 +477,15 @@ function redactWorkerArgs(args) {
|
|
|
462
477
|
function shellQuote(value) {
|
|
463
478
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
464
479
|
}
|
|
480
|
+
function normalizeWorkerWorktree(value) {
|
|
481
|
+
const pathValue = value?.path || value?.worktree_path;
|
|
482
|
+
if (!pathValue)
|
|
483
|
+
return null;
|
|
484
|
+
return {
|
|
485
|
+
id: String(value?.id || value?.worktree_id || value?.slot_id || 'worktree'),
|
|
486
|
+
path: String(pathValue),
|
|
487
|
+
branch: String(value?.branch || 'unknown'),
|
|
488
|
+
main_repo_root: value?.main_repo_root == null ? null : String(value.main_repo_root)
|
|
489
|
+
};
|
|
490
|
+
}
|
|
465
491
|
//# sourceMappingURL=native-cli-session-swarm.js.map
|
|
@@ -27,6 +27,8 @@ export async function runNativeCliWorker(input = {}) {
|
|
|
27
27
|
};
|
|
28
28
|
const slice = intake.slice || {};
|
|
29
29
|
const backend = String(input.backend || intake.backend || 'fake');
|
|
30
|
+
const workerCwd = path.resolve(String(input.cwd || intake.cwd || process.cwd()));
|
|
31
|
+
const worktree = normalizeWorkerWorktree(input.worktree || intake.worktree || null);
|
|
30
32
|
const policy = resolveFastModePolicy({
|
|
31
33
|
fastMode: intake.fast_mode ?? input.fastMode,
|
|
32
34
|
serviceTier: intake.service_tier ?? input.serviceTier
|
|
@@ -37,6 +39,10 @@ export async function runNativeCliWorker(input = {}) {
|
|
|
37
39
|
const heartbeatRel = String(input.heartbeatPath || intake.heartbeat_path || path.join(workerDirRel, 'worker-heartbeat.jsonl'));
|
|
38
40
|
const patchRel = String(input.patchEnvelopePath || intake.patch_envelope_path || path.join(workerDirRel, 'worker-patch-envelope.json'));
|
|
39
41
|
await ensureDir(workerDir);
|
|
42
|
+
try {
|
|
43
|
+
process.chdir(workerCwd);
|
|
44
|
+
}
|
|
45
|
+
catch { }
|
|
40
46
|
const recursion = scanAgentTextForRecursion(JSON.stringify({ agent, slice, backend }));
|
|
41
47
|
const guard = {
|
|
42
48
|
schema: 'sks.native-cli-worker-recursion-guard.v1',
|
|
@@ -56,6 +62,9 @@ export async function runNativeCliWorker(input = {}) {
|
|
|
56
62
|
persona_id: String(agent.persona_id || input.personaId || ''),
|
|
57
63
|
lease_id: String(intake.lease_id || input.leaseId || ''),
|
|
58
64
|
agent_root: agentRoot,
|
|
65
|
+
main_repo_root: String(input.mainRepoRoot || intake.main_repo_root || worktree?.main_repo_root || agentRoot),
|
|
66
|
+
cwd: workerCwd,
|
|
67
|
+
worktree,
|
|
59
68
|
worker_artifact_dir: workerDirRel,
|
|
60
69
|
result_path: resultRel,
|
|
61
70
|
heartbeat_path: heartbeatRel,
|
|
@@ -65,7 +74,9 @@ export async function runNativeCliWorker(input = {}) {
|
|
|
65
74
|
backend: backend === 'zellij' ? 'codex-sdk' : backend,
|
|
66
75
|
output_schema_id: 'sks.agent-worker-result.v1',
|
|
67
76
|
sandbox_policy: Array.isArray(slice.write_paths) && slice.write_paths.length > 0 ? 'workspace-write' : 'read-only',
|
|
68
|
-
thread_policy: 'new_thread_per_generation'
|
|
77
|
+
thread_policy: 'new_thread_per_generation',
|
|
78
|
+
worktree,
|
|
79
|
+
cwd: workerCwd
|
|
69
80
|
},
|
|
70
81
|
service_tier: policy.service_tier,
|
|
71
82
|
fast_mode: policy.fast_mode,
|
|
@@ -143,6 +154,8 @@ export async function runNativeCliWorker(input = {}) {
|
|
|
143
154
|
generated_at: nowIso(),
|
|
144
155
|
ok: guard.ok,
|
|
145
156
|
backend,
|
|
157
|
+
cwd: workerCwd,
|
|
158
|
+
worktree,
|
|
146
159
|
agent_id: agent.id,
|
|
147
160
|
session_id: agent.session_id,
|
|
148
161
|
slot_id: agent.slot_id || null,
|
|
@@ -221,6 +234,9 @@ export async function runNativeCliWorker(input = {}) {
|
|
|
221
234
|
slot_id: agent.slot_id || null,
|
|
222
235
|
generation_index: agent.generation_index || null,
|
|
223
236
|
process_id: process.pid,
|
|
237
|
+
main_repo_root: String(input.mainRepoRoot || intake.main_repo_root || worktree?.main_repo_root || agentRoot),
|
|
238
|
+
cwd: workerCwd,
|
|
239
|
+
worktree,
|
|
224
240
|
artifact_dir: workerDirRel,
|
|
225
241
|
patch_envelope: patchEnvelopes.length ? patchRel : null,
|
|
226
242
|
no_patch_reason: patchEnvelopes.length ? null : path.join(workerDirRel, 'worker-no-patch-reason.json'),
|
|
@@ -290,4 +306,15 @@ function redactCommandLine(argv) {
|
|
|
290
306
|
return part;
|
|
291
307
|
});
|
|
292
308
|
}
|
|
309
|
+
function normalizeWorkerWorktree(value) {
|
|
310
|
+
const pathValue = value?.path || value?.worktree_path;
|
|
311
|
+
if (!pathValue)
|
|
312
|
+
return null;
|
|
313
|
+
return {
|
|
314
|
+
id: String(value?.id || value?.worktree_id || value?.slot_id || 'worktree'),
|
|
315
|
+
path: String(pathValue),
|
|
316
|
+
branch: String(value?.branch || 'unknown'),
|
|
317
|
+
main_repo_root: value?.main_repo_root == null ? null : String(value.main_repo_root)
|
|
318
|
+
};
|
|
319
|
+
}
|
|
293
320
|
//# sourceMappingURL=native-cli-worker.js.map
|
|
@@ -9,18 +9,24 @@ export async function detectPythonCodexSdkCapability() {
|
|
|
9
9
|
const pyOk = parsePythonVersion(python.versionText) >= 3.10;
|
|
10
10
|
const probes = [];
|
|
11
11
|
let detected = null;
|
|
12
|
+
let detectedModulePath = '';
|
|
13
|
+
let detectedModuleParentPath = '';
|
|
12
14
|
if (pyOk) {
|
|
13
15
|
for (const candidate of PYTHON_CODEX_SDK_CANDIDATES) {
|
|
14
|
-
const importProbe = await runProcess(python.path, ['-c', `import ${candidate.importName}; print("
|
|
16
|
+
const importProbe = await runProcess(python.path, ['-c', `import ${candidate.importName} as m, os; print(os.path.dirname(os.path.abspath(getattr(m, "__file__", ""))))`], { timeoutMs: 5000, maxOutputBytes: 4096 })
|
|
15
17
|
.catch((err) => ({ code: 1, stdout: '', stderr: err.message || String(err) }));
|
|
18
|
+
const modulePath = String(importProbe.stdout || '').trim().split(/\r?\n/).filter(Boolean).at(-1) || '';
|
|
16
19
|
probes.push({
|
|
17
20
|
package_name: candidate.packageName,
|
|
18
21
|
import_name: candidate.importName,
|
|
19
22
|
ok: importProbe.code === 0,
|
|
23
|
+
module_path: modulePath || null,
|
|
20
24
|
stderr: String(importProbe.stderr || '').slice(-500)
|
|
21
25
|
});
|
|
22
26
|
if (importProbe.code === 0) {
|
|
23
27
|
detected = candidate;
|
|
28
|
+
detectedModulePath = modulePath;
|
|
29
|
+
detectedModuleParentPath = modulePath ? path.dirname(modulePath) : '';
|
|
24
30
|
break;
|
|
25
31
|
}
|
|
26
32
|
}
|
|
@@ -29,7 +35,7 @@ export async function detectPythonCodexSdkCapability() {
|
|
|
29
35
|
...(pyOk ? [] : ['python_version_below_3_10']),
|
|
30
36
|
...(detected ? [] : ['python_codex_sdk_unavailable'])
|
|
31
37
|
];
|
|
32
|
-
return capability(blockers.length === 0, python.path, python.versionText, blockers, detected, blockers.length ? setupAction(python.path) : null, probes);
|
|
38
|
+
return capability(blockers.length === 0, python.path, python.versionText, blockers, detected, blockers.length ? setupAction(python.path) : null, probes, detectedModulePath, detectedModuleParentPath);
|
|
33
39
|
}
|
|
34
40
|
export async function runPythonCodexSdkTask(input, opts = {}) {
|
|
35
41
|
const cap = await detectPythonCodexSdkCapability();
|
|
@@ -48,7 +54,7 @@ export async function runPythonCodexSdkTask(input, opts = {}) {
|
|
|
48
54
|
prompt: input.prompt,
|
|
49
55
|
output_schema: input.outputSchema || {}
|
|
50
56
|
};
|
|
51
|
-
const events = await runPythonRunner(python, request, opts.env);
|
|
57
|
+
const events = await runPythonRunner(python, request, pythonRunnerEnv(opts.env, cap.module_parent_path));
|
|
52
58
|
const translatedEvents = translatePythonCodexSdkEvents(events);
|
|
53
59
|
const last = [...events].reverse().find((event) => event?.event === 'turn_completed');
|
|
54
60
|
const errors = events.filter((event) => event?.event === 'error').map((event) => String(event.message || 'python_codex_sdk_error'));
|
|
@@ -63,6 +69,22 @@ export async function runPythonCodexSdkTask(input, opts = {}) {
|
|
|
63
69
|
capability: cap
|
|
64
70
|
};
|
|
65
71
|
}
|
|
72
|
+
function pythonRunnerEnv(envOverride, moduleParentPath) {
|
|
73
|
+
const env = {};
|
|
74
|
+
for (const [key, value] of Object.entries(envOverride || process.env)) {
|
|
75
|
+
if (value !== undefined)
|
|
76
|
+
env[key] = String(value);
|
|
77
|
+
}
|
|
78
|
+
const parent = String(moduleParentPath || '').trim();
|
|
79
|
+
if (!parent)
|
|
80
|
+
return env;
|
|
81
|
+
env.PYTHONPATH = prependPath(env.PYTHONPATH, parent);
|
|
82
|
+
return env;
|
|
83
|
+
}
|
|
84
|
+
function prependPath(value, entry) {
|
|
85
|
+
const parts = String(value || '').split(path.delimiter).filter(Boolean);
|
|
86
|
+
return [entry, ...parts.filter((part) => part !== entry)].join(path.delimiter);
|
|
87
|
+
}
|
|
66
88
|
function runPythonRunner(python, request, envOverride) {
|
|
67
89
|
const runner = path.join(packageRoot(), 'pytools', 'codex_sdk_runner.py');
|
|
68
90
|
return new Promise((resolve, reject) => {
|
|
@@ -177,7 +199,7 @@ function setupAction(pythonBin) {
|
|
|
177
199
|
`If your environment provides the directive package, run \`${pythonBin} -m pip install openai-codex\`.`
|
|
178
200
|
].join(' ');
|
|
179
201
|
}
|
|
180
|
-
function capability(ok, pythonBin, versionText, blockers, detected, setupActionValue, probes = []) {
|
|
202
|
+
function capability(ok, pythonBin, versionText, blockers, detected, setupActionValue, probes = [], modulePath = '', moduleParentPath = '') {
|
|
181
203
|
const selected = detected || PYTHON_CODEX_SDK_CANDIDATES[0] || { packageName: 'codex-app-server', importName: 'codex_app_server', source: 'developers.openai.com/codex/sdk' };
|
|
182
204
|
return {
|
|
183
205
|
schema: 'sks.python-codex-sdk-capability.v1',
|
|
@@ -189,6 +211,8 @@ function capability(ok, pythonBin, versionText, blockers, detected, setupActionV
|
|
|
189
211
|
source: selected.source,
|
|
190
212
|
supported_packages: PYTHON_CODEX_SDK_CANDIDATES.map((candidate) => candidate.packageName),
|
|
191
213
|
supported_imports: PYTHON_CODEX_SDK_CANDIDATES.map((candidate) => candidate.importName),
|
|
214
|
+
module_path: modulePath || null,
|
|
215
|
+
module_parent_path: moduleParentPath || null,
|
|
192
216
|
import_probes: probes,
|
|
193
217
|
setup_action: setupActionValue,
|
|
194
218
|
blockers
|
|
@@ -7,6 +7,7 @@ import { createMission, setCurrent } from '../mission.js';
|
|
|
7
7
|
import { buildMadHighLaunchProfileNoWrite, madHighProfileName } from '../auto-review.js';
|
|
8
8
|
import { permissionGateSummary } from '../permission-gates.js';
|
|
9
9
|
import { attachZellijSessionInteractive, launchMadZellijUi, sanitizeZellijSessionName } from '../zellij/zellij-launcher.js';
|
|
10
|
+
import { openZellijDashboardPane } from '../zellij/zellij-dashboard-pane.js';
|
|
10
11
|
import { createMadSksAuthorizationManifest, validateMadSksAuthorizationManifest } from '../mad-sks/authorization-manifest.js';
|
|
11
12
|
import { createMadSksAuditLedger, madSksAuditAction, writeMadSksAuditLedger } from '../mad-sks/audit-ledger.js';
|
|
12
13
|
import { compareProtectedCoreSnapshots, evaluateMadSksWrite, resolveProtectedCore, snapshotProtectedCore } from '../mad-sks/immutable-harness-guard.js';
|
|
@@ -113,6 +114,30 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
113
114
|
console.log(`MAD Zellij action: ${formatMadZellijAction(launch)}`);
|
|
114
115
|
return launch;
|
|
115
116
|
}
|
|
117
|
+
launch.dashboard_pane = await openZellijDashboardPane({
|
|
118
|
+
root: madLaunch.root,
|
|
119
|
+
missionId: madLaunch.mission_id,
|
|
120
|
+
sessionName: launch.session_name,
|
|
121
|
+
cwd: process.cwd(),
|
|
122
|
+
snapshot: {
|
|
123
|
+
mode: 'mad-sks',
|
|
124
|
+
backend_counts: { zellij: 1 },
|
|
125
|
+
placement_counts: { 'zellij-pane': 1, headless: 0 },
|
|
126
|
+
active_workers: 1,
|
|
127
|
+
visible_panes: 1,
|
|
128
|
+
headless_workers: 0,
|
|
129
|
+
queue_depth: 0,
|
|
130
|
+
worktrees: { active: 0, completed: 0, retained: 0 },
|
|
131
|
+
local_llm: { tps: 0, queue: 0 },
|
|
132
|
+
gpt_final_status: 'pending',
|
|
133
|
+
gate_progress: 'mad-sks:session-open'
|
|
134
|
+
}
|
|
135
|
+
}).catch((err) => ({
|
|
136
|
+
ok: false,
|
|
137
|
+
pane_kind: 'dashboard',
|
|
138
|
+
worker_pane: false,
|
|
139
|
+
blockers: [`zellij_dashboard_exception:${err?.message || String(err)}`]
|
|
140
|
+
}));
|
|
116
141
|
const madNativeSwarm = await startMadNativeSwarm(madLaunch.root, madLaunch, args, profile, {
|
|
117
142
|
env: {
|
|
118
143
|
...madSksEnv,
|
|
@@ -10,11 +10,13 @@ import { attachZellijSessionInteractive, launchZellijLayout } from '../zellij/ze
|
|
|
10
10
|
import { buildNarutoWorkGraph } from '../naruto/naruto-work-graph.js';
|
|
11
11
|
import { buildNarutoRoleDistribution } from '../naruto/naruto-role-policy.js';
|
|
12
12
|
import { decideNarutoConcurrency } from '../naruto/naruto-concurrency-governor.js';
|
|
13
|
-
import {
|
|
13
|
+
import { runNarutoActivePool } from '../naruto/naruto-active-pool.js';
|
|
14
14
|
import { buildNarutoVerificationDag } from '../naruto/naruto-verification-dag.js';
|
|
15
15
|
import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
|
|
16
16
|
import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
|
|
17
|
+
import { openZellijDashboardPane } from '../zellij/zellij-dashboard-pane.js';
|
|
17
18
|
import { checkPromptPlaceholders } from '../prompt/prompt-placeholder-guard.js';
|
|
19
|
+
import { evaluateGitWorktreeCapability } from '../git/git-worktree-capability.js';
|
|
18
20
|
const NARUTO_RESULT_SCHEMA = 'sks.naruto-command-result.v1';
|
|
19
21
|
const NARUTO_ROUTE = '$Naruto';
|
|
20
22
|
// $Naruto — Shadow Clone Swarm (影分身 / Kage Bunshin no Jutsu).
|
|
@@ -61,6 +63,25 @@ async function narutoRun(parsed) {
|
|
|
61
63
|
readonly: parsed.readonly,
|
|
62
64
|
maxAgentCount: MAX_NARUTO_AGENT_COUNT
|
|
63
65
|
});
|
|
66
|
+
const mission = await createMission(root, { mode: 'naruto', prompt: parsed.prompt });
|
|
67
|
+
const gitWorktreeCapability = writeCapable
|
|
68
|
+
? await evaluateGitWorktreeCapability({ root, missionId: mission.id })
|
|
69
|
+
: null;
|
|
70
|
+
const worktreePolicy = gitWorktreeCapability?.mode === 'git-worktree'
|
|
71
|
+
? {
|
|
72
|
+
mode: 'git-worktree',
|
|
73
|
+
required: true,
|
|
74
|
+
main_repo_root: gitWorktreeCapability.detection.root,
|
|
75
|
+
worktree_root: gitWorktreeCapability.root_resolution?.root || null,
|
|
76
|
+
fallback_reason: null
|
|
77
|
+
}
|
|
78
|
+
: {
|
|
79
|
+
mode: 'patch-envelope-only',
|
|
80
|
+
required: false,
|
|
81
|
+
main_repo_root: gitWorktreeCapability?.detection.root || null,
|
|
82
|
+
worktree_root: null,
|
|
83
|
+
fallback_reason: writeCapable ? (gitWorktreeCapability?.blockers.join(';') || 'not_git_repo_or_worktree_unavailable') : 'readonly_or_write_disabled'
|
|
84
|
+
};
|
|
64
85
|
// The clone roster is the full work fan-out; live concurrency is throttled to a
|
|
65
86
|
// system-safe number so naruto never spawns the whole count at once unless an
|
|
66
87
|
// explicit operator override asks for a higher target.
|
|
@@ -74,7 +95,8 @@ async function narutoRun(parsed) {
|
|
|
74
95
|
readonly: parsed.readonly,
|
|
75
96
|
writeCapable,
|
|
76
97
|
leaseBasePath: patchEnvelopeBasePath,
|
|
77
|
-
maxActiveWorkers: parsed.concurrency || safe.cap
|
|
98
|
+
maxActiveWorkers: parsed.concurrency || safe.cap,
|
|
99
|
+
worktreePolicy
|
|
78
100
|
});
|
|
79
101
|
const roleDistribution = buildNarutoRoleDistribution(workGraph.work_items, { readonly: parsed.readonly });
|
|
80
102
|
const governor = decideNarutoConcurrency({
|
|
@@ -86,22 +108,24 @@ async function narutoRun(parsed) {
|
|
|
86
108
|
const backendMinimum = schedulerBackend === 'fake' ? roster.agent_count : Math.min(roster.agent_count, 2);
|
|
87
109
|
const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
|
|
88
110
|
const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
|
|
89
|
-
const activePool =
|
|
111
|
+
const activePool = await runNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
|
|
90
112
|
const verificationDag = buildNarutoVerificationDag(workGraph, { cwd: root });
|
|
91
113
|
const gptFinalPack = buildNarutoGptFinalPack({
|
|
92
|
-
missionId:
|
|
114
|
+
missionId: mission.id,
|
|
93
115
|
graph: workGraph,
|
|
94
116
|
roleDistribution,
|
|
95
|
-
localLlmMetrics: localWorker
|
|
117
|
+
localLlmMetrics: localWorker,
|
|
118
|
+
worktreePolicy,
|
|
119
|
+
worktreeDiffs: []
|
|
96
120
|
});
|
|
97
121
|
const zellijDashboard = planNarutoZellijDashboard({
|
|
98
122
|
targetActiveWorkers: activeSlots,
|
|
99
123
|
visiblePaneCap: governor.safe_zellij_visible_panes,
|
|
100
124
|
backpressure: governor.backpressure,
|
|
101
125
|
roles: roleDistribution.work_item_roles.map((row) => row.role),
|
|
102
|
-
backend: schedulerBackend
|
|
126
|
+
backend: schedulerBackend,
|
|
127
|
+
worktreePolicy
|
|
103
128
|
});
|
|
104
|
-
const mission = await createMission(root, { mode: 'naruto', prompt: parsed.prompt });
|
|
105
129
|
const ledgerRoot = path.join(mission.dir, 'agents');
|
|
106
130
|
await writeNarutoArtifacts(ledgerRoot, {
|
|
107
131
|
workGraph,
|
|
@@ -109,9 +133,10 @@ async function narutoRun(parsed) {
|
|
|
109
133
|
governor,
|
|
110
134
|
activePool,
|
|
111
135
|
verificationDag,
|
|
112
|
-
gptFinalPack
|
|
136
|
+
gptFinalPack,
|
|
113
137
|
zellijDashboard,
|
|
114
|
-
placeholderGuard
|
|
138
|
+
placeholderGuard,
|
|
139
|
+
gitWorktreeCapability
|
|
115
140
|
});
|
|
116
141
|
let liveZellij = null;
|
|
117
142
|
if (!parsed.json && !parsed.mock && !parsed.noOpenZellij) {
|
|
@@ -125,6 +150,34 @@ async function narutoRun(parsed) {
|
|
|
125
150
|
attach: false
|
|
126
151
|
});
|
|
127
152
|
if (liveZellij?.ok && liveZellij.capability?.status === 'ok') {
|
|
153
|
+
liveZellij.dashboard_pane = await openZellijDashboardPane({
|
|
154
|
+
root,
|
|
155
|
+
missionId: mission.id,
|
|
156
|
+
sessionName: liveZellij.session_name,
|
|
157
|
+
cwd: root,
|
|
158
|
+
snapshot: {
|
|
159
|
+
mode: 'naruto',
|
|
160
|
+
backend_counts: { [schedulerBackend]: activeSlots },
|
|
161
|
+
placement_counts: { 'zellij-pane': zellijVisiblePanes, headless: Math.max(0, activeSlots - zellijVisiblePanes) },
|
|
162
|
+
active_workers: activeSlots,
|
|
163
|
+
visible_panes: zellijVisiblePanes,
|
|
164
|
+
headless_workers: Math.max(0, activeSlots - zellijVisiblePanes),
|
|
165
|
+
queue_depth: Math.max(0, workGraph.total_work_items - activeSlots),
|
|
166
|
+
worktrees: {
|
|
167
|
+
active: worktreePolicy.mode === 'git-worktree' ? activeSlots : 0,
|
|
168
|
+
completed: 0,
|
|
169
|
+
retained: 0
|
|
170
|
+
},
|
|
171
|
+
local_llm: { tps: 0, queue: localWorker.auto_select_eligible ? Math.max(0, activeSlots - zellijVisiblePanes) : 0 },
|
|
172
|
+
gpt_final_status: 'pending',
|
|
173
|
+
gate_progress: 'naruto:pre-orchestrator'
|
|
174
|
+
}
|
|
175
|
+
}).catch((err) => ({
|
|
176
|
+
ok: false,
|
|
177
|
+
pane_kind: 'dashboard',
|
|
178
|
+
worker_pane: false,
|
|
179
|
+
blockers: [`zellij_dashboard_exception:${err?.message || String(err)}`]
|
|
180
|
+
}));
|
|
128
181
|
console.log('Zellij: prepared ' + zellijVisiblePanes + ' visible active clone lane(s) in ' + liveZellij.session_name + ' with ' + Math.max(0, activeSlots - zellijVisiblePanes) + ' headless active worker(s). Attach with: ' + (liveZellij.attach_command_with_env || liveZellij.attach_command));
|
|
129
182
|
if (parsed.attach)
|
|
130
183
|
attachZellijSessionInteractive(liveZellij.session_name, { cwd: process.cwd(), configPath: liveZellij.clipboard_config_path });
|
|
@@ -165,6 +218,7 @@ async function narutoRun(parsed) {
|
|
|
165
218
|
serviceTier: 'fast',
|
|
166
219
|
noFast: false,
|
|
167
220
|
writeMode: writeCapable ? parsed.writeMode || 'parallel' : 'off',
|
|
221
|
+
gitWorktreePolicy: worktreePolicy,
|
|
168
222
|
json: parsed.json
|
|
169
223
|
});
|
|
170
224
|
const clones = result.roster?.agent_count ?? roster.agent_count;
|
|
@@ -188,8 +242,10 @@ async function narutoRun(parsed) {
|
|
|
188
242
|
write_allowed_count: workGraph.write_allowed_count,
|
|
189
243
|
active_wave_count: workGraph.active_waves.length,
|
|
190
244
|
parallel_write_wave_count: workGraph.active_waves.filter((wave) => wave.write_paths.length > 1).length,
|
|
191
|
-
ok: workGraph.ok
|
|
245
|
+
ok: workGraph.ok,
|
|
246
|
+
worktree_policy: workGraph.worktree_policy
|
|
192
247
|
},
|
|
248
|
+
git_worktree: gitWorktreeCapability,
|
|
193
249
|
role_distribution: roleDistribution,
|
|
194
250
|
concurrency_governor: governor,
|
|
195
251
|
active_pool: {
|
|
@@ -328,6 +384,8 @@ async function writeNarutoArtifacts(ledgerRoot, artifacts) {
|
|
|
328
384
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-gpt-final-pack.json'), artifacts.gptFinalPack);
|
|
329
385
|
await writeJsonAtomic(path.join(ledgerRoot, 'naruto-zellij-dashboard.json'), artifacts.zellijDashboard);
|
|
330
386
|
await writeJsonAtomic(path.join(ledgerRoot, 'prompt-placeholder-guard.json'), artifacts.placeholderGuard);
|
|
387
|
+
if (artifacts.gitWorktreeCapability)
|
|
388
|
+
await writeJsonAtomic(path.join(ledgerRoot, 'git-worktree-capability.json'), artifacts.gitWorktreeCapability);
|
|
331
389
|
}
|
|
332
390
|
function clampClones(value) {
|
|
333
391
|
if (!Number.isFinite(value) || value < 1)
|
|
@@ -786,6 +786,8 @@ function isExternalPromptCommandMention(mention) {
|
|
|
786
786
|
'$SKS_CODEX_APP_IMAGEGEN_OUTPUT',
|
|
787
787
|
'$SKS_CODEX_APP_IMAGEGEN_OUTPUT_ID',
|
|
788
788
|
'$SKS_CODEX_APP_IMAGEGEN_CREATED_AT',
|
|
789
|
+
'$SKS_WORKTREE_ROOT',
|
|
790
|
+
'$XDG_CACHE_HOME',
|
|
789
791
|
'$IMAGEGEN'
|
|
790
792
|
].includes(normalized);
|
|
791
793
|
}
|
package/dist/core/fsx.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '2.0.
|
|
8
|
+
export const PACKAGE_VERSION = '2.0.8';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { allocateWorkerWorktree } from './git-worktree-manager.js';
|
|
2
|
+
export async function createGitIntegrationWorktree(input) {
|
|
3
|
+
const allocationInput = {
|
|
4
|
+
repoRoot: input.repoRoot,
|
|
5
|
+
missionId: input.missionId,
|
|
6
|
+
workerId: 'integration',
|
|
7
|
+
slotId: 'integration',
|
|
8
|
+
generationIndex: 1,
|
|
9
|
+
branchPrefix: 'sks-integration'
|
|
10
|
+
};
|
|
11
|
+
if (input.baseRef !== undefined)
|
|
12
|
+
allocationInput.baseRef = input.baseRef;
|
|
13
|
+
return allocateWorkerWorktree(allocationInput);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=git-integration-worktree.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { which } from '../fsx.js';
|
|
3
|
+
import { gitBlocker, gitOutputLine, runGitCommand } from './git-worktree-runner.js';
|
|
4
|
+
export async function detectGitRepo(root = process.cwd()) {
|
|
5
|
+
const cwd = path.resolve(root);
|
|
6
|
+
const gitBinary = await which('git');
|
|
7
|
+
const blockers = [];
|
|
8
|
+
if (!gitBinary) {
|
|
9
|
+
return baseDetection(cwd, null, false, ['git_binary_missing']);
|
|
10
|
+
}
|
|
11
|
+
const inside = await runGitCommand(cwd, ['rev-parse', '--is-inside-work-tree']);
|
|
12
|
+
if (!inside.ok || gitOutputLine(inside) !== 'true') {
|
|
13
|
+
return baseDetection(cwd, gitBinary, true, []);
|
|
14
|
+
}
|
|
15
|
+
const top = await runGitCommand(cwd, ['rev-parse', '--show-toplevel']);
|
|
16
|
+
const gitDir = await runGitCommand(cwd, ['rev-parse', '--git-dir']);
|
|
17
|
+
const commonDir = await runGitCommand(cwd, ['rev-parse', '--git-common-dir']);
|
|
18
|
+
const bare = await runGitCommand(cwd, ['rev-parse', '--is-bare-repository']);
|
|
19
|
+
const branch = await runGitCommand(cwd, ['branch', '--show-current']);
|
|
20
|
+
const head = await runGitCommand(cwd, ['rev-parse', 'HEAD']);
|
|
21
|
+
const status = await runGitCommand(cwd, ['status', '--porcelain=v1', '--untracked-files=all']);
|
|
22
|
+
if (!top.ok)
|
|
23
|
+
blockers.push(gitBlocker('git_root_unresolved', top));
|
|
24
|
+
if (!gitDir.ok)
|
|
25
|
+
blockers.push(gitBlocker('git_dir_unresolved', gitDir));
|
|
26
|
+
if (!commonDir.ok)
|
|
27
|
+
blockers.push(gitBlocker('git_common_dir_unresolved', commonDir));
|
|
28
|
+
if (!head.ok)
|
|
29
|
+
blockers.push(gitBlocker('git_head_unresolved', head));
|
|
30
|
+
if (!status.ok)
|
|
31
|
+
blockers.push(gitBlocker('git_status_unresolved', status));
|
|
32
|
+
const repoRoot = top.ok ? path.resolve(gitOutputLine(top)) : null;
|
|
33
|
+
const resolvedGitDir = gitDir.ok ? absolutizeGitPath(cwd, gitOutputLine(gitDir)) : null;
|
|
34
|
+
const resolvedCommonDir = commonDir.ok ? absolutizeGitPath(cwd, gitOutputLine(commonDir)) : null;
|
|
35
|
+
return {
|
|
36
|
+
schema: 'sks.git-repo-detection.v1',
|
|
37
|
+
ok: blockers.length === 0,
|
|
38
|
+
cwd,
|
|
39
|
+
git_binary: gitBinary,
|
|
40
|
+
is_git_repo: true,
|
|
41
|
+
inside_work_tree: true,
|
|
42
|
+
bare: gitOutputLine(bare) === 'true',
|
|
43
|
+
root: repoRoot,
|
|
44
|
+
git_dir: resolvedGitDir,
|
|
45
|
+
common_dir: resolvedCommonDir,
|
|
46
|
+
worktree_git_dir: resolvedGitDir && resolvedCommonDir && resolvedGitDir !== resolvedCommonDir ? resolvedGitDir : null,
|
|
47
|
+
branch: gitOutputLine(branch) || null,
|
|
48
|
+
head: gitOutputLine(head) || null,
|
|
49
|
+
main_worktree_dirty: status.ok && status.stdout.trim().length > 0,
|
|
50
|
+
status_porcelain: status.stdout || '',
|
|
51
|
+
blockers
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function baseDetection(cwd, gitBinary, gitAvailable, blockers) {
|
|
55
|
+
return {
|
|
56
|
+
schema: 'sks.git-repo-detection.v1',
|
|
57
|
+
ok: gitAvailable || blockers.length === 0,
|
|
58
|
+
cwd,
|
|
59
|
+
git_binary: gitBinary,
|
|
60
|
+
is_git_repo: false,
|
|
61
|
+
inside_work_tree: false,
|
|
62
|
+
bare: false,
|
|
63
|
+
root: null,
|
|
64
|
+
git_dir: null,
|
|
65
|
+
common_dir: null,
|
|
66
|
+
worktree_git_dir: null,
|
|
67
|
+
branch: null,
|
|
68
|
+
head: null,
|
|
69
|
+
main_worktree_dirty: false,
|
|
70
|
+
status_porcelain: '',
|
|
71
|
+
blockers
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function absolutizeGitPath(cwd, value) {
|
|
75
|
+
if (!value)
|
|
76
|
+
return null;
|
|
77
|
+
return path.isAbsolute(value) ? path.resolve(value) : path.resolve(cwd, value);
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=git-repo-detection.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { nowIso } from '../fsx.js';
|
|
2
|
+
export function planGitWorktreeCachePolicy(input) {
|
|
3
|
+
const nowMs = Math.max(0, Math.floor(Number(input.nowMs ?? Date.now())));
|
|
4
|
+
const maxEntries = Math.max(1, Math.floor(Number(input.maxEntries ?? 50)));
|
|
5
|
+
const maxBytes = Math.max(1024 * 1024, Math.floor(Number(input.maxBytes ?? 8 * 1024 * 1024 * 1024)));
|
|
6
|
+
const ttlMs = Math.max(60000, Math.floor(Number(input.ttlMs ?? 7 * 24 * 60 * 60 * 1000)));
|
|
7
|
+
const sorted = [...input.entries].sort((a, b) => a.updated_at_ms - b.updated_at_ms);
|
|
8
|
+
const prune = new Set();
|
|
9
|
+
let totalBytes = sorted.reduce((sum, entry) => sum + Math.max(0, entry.bytes), 0);
|
|
10
|
+
for (const entry of sorted) {
|
|
11
|
+
if (entry.dirty)
|
|
12
|
+
continue;
|
|
13
|
+
if (nowMs - entry.updated_at_ms > ttlMs)
|
|
14
|
+
prune.add(entry.path);
|
|
15
|
+
}
|
|
16
|
+
for (const entry of sorted) {
|
|
17
|
+
if (sorted.length - prune.size <= maxEntries && totalBytes <= maxBytes)
|
|
18
|
+
break;
|
|
19
|
+
if (entry.dirty || prune.has(entry.path))
|
|
20
|
+
continue;
|
|
21
|
+
prune.add(entry.path);
|
|
22
|
+
totalBytes -= Math.max(0, entry.bytes);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
schema: 'sks.git-worktree-cache-policy.v1',
|
|
26
|
+
ok: true,
|
|
27
|
+
generated_at: nowIso(),
|
|
28
|
+
max_entries: maxEntries,
|
|
29
|
+
max_bytes: maxBytes,
|
|
30
|
+
ttl_ms: ttlMs,
|
|
31
|
+
keep: sorted.filter((entry) => !prune.has(entry.path)).map((entry) => entry.path),
|
|
32
|
+
prune: sorted.filter((entry) => prune.has(entry.path)).map((entry) => entry.path),
|
|
33
|
+
dirty_retained: sorted.filter((entry) => entry.dirty === true).map((entry) => entry.path)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=git-worktree-cache-policy.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ensureDir } from '../fsx.js';
|
|
2
|
+
import { detectGitRepo } from './git-repo-detection.js';
|
|
3
|
+
import { gitBlocker, runGitCommand } from './git-worktree-runner.js';
|
|
4
|
+
import { resolveGitWorktreeRoot } from './git-worktree-root.js';
|
|
5
|
+
export async function evaluateGitWorktreeCapability(input = {}) {
|
|
6
|
+
const requireGitWorktree = input.requireGitWorktree === true || process.env.SKS_REQUIRE_GIT_WORKTREE === '1';
|
|
7
|
+
const detection = await detectGitRepo(input.root || process.cwd());
|
|
8
|
+
const blockers = [...detection.blockers];
|
|
9
|
+
const gitAvailable = Boolean(detection.git_binary);
|
|
10
|
+
if (!detection.is_git_repo || !detection.root) {
|
|
11
|
+
if (requireGitWorktree)
|
|
12
|
+
blockers.push('git_worktree_required_but_not_git_repo');
|
|
13
|
+
return {
|
|
14
|
+
schema: 'sks.git-worktree-capability.v1',
|
|
15
|
+
ok: blockers.length === 0,
|
|
16
|
+
mode: 'patch-envelope-only',
|
|
17
|
+
require_git_worktree: requireGitWorktree,
|
|
18
|
+
git_available: gitAvailable,
|
|
19
|
+
is_git_repo: false,
|
|
20
|
+
worktree_supported: false,
|
|
21
|
+
worktree_probe_attempted: false,
|
|
22
|
+
detection,
|
|
23
|
+
root_resolution: null,
|
|
24
|
+
blockers
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const rootResolution = resolveGitWorktreeRoot({
|
|
28
|
+
repoRoot: detection.root,
|
|
29
|
+
missionId: input.missionId || 'capability'
|
|
30
|
+
});
|
|
31
|
+
blockers.push(...rootResolution.blockers);
|
|
32
|
+
if (rootResolution.ok)
|
|
33
|
+
await ensureDir(rootResolution.root);
|
|
34
|
+
const list = await runGitCommand(detection.root, ['worktree', 'list', '--porcelain']);
|
|
35
|
+
const worktreeSupported = list.ok;
|
|
36
|
+
if (!worktreeSupported)
|
|
37
|
+
blockers.push(gitBlocker('git_worktree_list_failed', list));
|
|
38
|
+
if (requireGitWorktree && !worktreeSupported)
|
|
39
|
+
blockers.push('git_worktree_required_but_unsupported');
|
|
40
|
+
return {
|
|
41
|
+
schema: 'sks.git-worktree-capability.v1',
|
|
42
|
+
ok: blockers.length === 0,
|
|
43
|
+
mode: blockers.length === 0 && worktreeSupported ? 'git-worktree' : 'patch-envelope-only',
|
|
44
|
+
require_git_worktree: requireGitWorktree,
|
|
45
|
+
git_available: gitAvailable,
|
|
46
|
+
is_git_repo: true,
|
|
47
|
+
worktree_supported: worktreeSupported,
|
|
48
|
+
worktree_probe_attempted: true,
|
|
49
|
+
detection,
|
|
50
|
+
root_resolution: rootResolution,
|
|
51
|
+
blockers: [...new Set(blockers)]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=git-worktree-capability.js.map
|