sneakoscope 3.1.2 → 3.1.4
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 +1 -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/commands/codex-app.js +45 -1
- package/dist/commands/doctor.js +44 -1
- package/dist/core/codex-app/codex-agent-role-sync.js +67 -0
- package/dist/core/codex-app/codex-app-execution-profile.js +32 -0
- package/dist/core/codex-app/codex-app-harness-matrix.js +75 -0
- package/dist/core/codex-app/codex-hook-lifecycle.js +40 -0
- package/dist/core/codex-app/codex-init-deep.js +87 -0
- package/dist/core/codex-app/codex-skill-sync.js +92 -0
- package/dist/core/codex-app/lazycodex-analysis.js +51 -0
- package/dist/core/codex-app/lazycodex-interop-policy.js +49 -0
- package/dist/core/commands/loop-command.js +11 -0
- package/dist/core/commands/mad-sks-command.js +77 -16
- package/dist/core/commands/naruto-command.js +20 -3
- package/dist/core/doctor/doctor-readiness-matrix.js +7 -0
- package/dist/core/doctor/doctor-zellij-repair.js +36 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +13 -0
- package/dist/core/loops/loop-continuation-enforcer.js +32 -0
- package/dist/core/loops/loop-planner.js +9 -0
- package/dist/core/loops/loop-worker-runtime.js +5 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/homebrew-policy.js +45 -0
- package/dist/core/zellij/zellij-capability.js +32 -3
- package/dist/core/zellij/zellij-self-heal.js +353 -0
- package/dist/core/zellij/zellij-update.js +31 -6
- package/dist/scripts/sks-3-1-4-directive-check-lib.js +241 -0
- package/package.json +28 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
export function buildLazyCodexPatternAnalysis() {
|
|
5
|
+
return {
|
|
6
|
+
schema: 'sks.lazycodex-pattern-analysis.v1',
|
|
7
|
+
source_repo: 'code-yeongyu/lazycodex',
|
|
8
|
+
analyzed_at: nowIso(),
|
|
9
|
+
patterns: [
|
|
10
|
+
pattern('npx-no-global-install', 'npx no-global install alias', ['Directive: npx lazycodex-ai install aliases npx --yes --package oh-my-openagent omo install --platform=codex.'], 'adapt', 'Keep optional tooling no-global by default and record repair transactions.', ['src/cli/install-helpers.ts', 'src/core/zellij/zellij-self-heal.ts']),
|
|
11
|
+
pattern('codex-marketplace-plugin', 'Codex marketplace plugin add/upgrade', ['Directive: codex plugin marketplace add and codex plugin add omo@sisyphuslabs.'], 'adapt', 'Track marketplace/plugin inventory without assuming hooks are approved.', ['src/core/codex-app/codex-app-harness-matrix.ts']),
|
|
12
|
+
pattern('startup-review-hooks', 'Startup review hook approval', ['Directive: hooks require Codex startup review approval and re-approval after modifications.'], 'adopt', 'Separate installed hook files from approval state; unknown remains unknown.', ['src/core/codex-app/codex-hook-lifecycle.ts']),
|
|
13
|
+
pattern('background-bootstrap', 'Background bootstrap and restart notice', ['Directive: first approved session performs background bootstrap and upgrade may require restart.'], 'adapt', 'Report bootstrap proof as warning/blocker instead of silently assuming completion.', ['src/core/codex-app/codex-app-harness-matrix.ts']),
|
|
14
|
+
pattern('doctor-health-report', 'Doctor health report for plugin cache/hooks/MCP/agents/config', ['Directive: LazyCodex doctor reports plugin cache, hooks, MCP servers, agents, config state.'], 'adopt', 'Add Codex App Harness section to SKS doctor.', ['src/commands/doctor.ts']),
|
|
15
|
+
pattern('dollar-skill-picker', '$ skill picker and $command invocation', ['Directive: Codex composer $ browses installed skills.'], 'adapt', 'Keep SKS route skills synced without clobbering user or LazyCodex skills.', ['src/core/codex-app/codex-skill-sync.ts']),
|
|
16
|
+
pattern('init-deep-agents', '$init-deep hierarchical AGENTS.md', ['Directive: init-deep creates hierarchical AGENTS.md context.'], 'adapt', 'Generate SKS memory under .sneakoscope/context by default and preserve user AGENTS.md.', ['src/core/codex-app/codex-init-deep.ts']),
|
|
17
|
+
pattern('plan-start-loop', '$ulw-plan, $start-work, $ulw-loop command pillars', ['Directive: separate planning, durable work, and evidence loop.'], 'adapt', 'Map onto sks loop plan/run/proof without replacing existing Loop Mesh.', ['src/core/commands/loop-command.ts']),
|
|
18
|
+
pattern('specialist-skills', 'Specialist skills', ['Directive: specialist skills include review-work, LSP, AST-grep, programming, frontend UI/UX.'], 'watch', 'Keep checker profile selection explicit and evidence-backed.', ['src/core/loops/loop-gate-selector.ts']),
|
|
19
|
+
pattern('native-agent-type', 'Native spawn_agent agent_type with message fallback', ['Directive: LazyCodex probes agent_type and falls back to role in message.'], 'adopt', 'Expose native agent_type capability in execution profile.', ['src/core/codex-app/codex-agent-role-sync.ts', 'src/core/codex-app/codex-app-execution-profile.ts']),
|
|
20
|
+
pattern('multi-model-routing', 'Multi-model routing', ['Directive: LazyCodex/OmO route multiple models.'], 'watch', 'SKS keeps provider/profile policy separate from harness matrix.', ['src/core/provider/provider-context.ts']),
|
|
21
|
+
pattern('hook-continuation', 'Hook lifecycle and continuation enforcer', ['Directive: UserPromptSubmit, PreToolUse, PostToolUse, Stop, Notification map to pipeline actions.'], 'adopt', 'Map lifecycle and add Loop continuation proof adapter.', ['src/core/codex-app/codex-hook-lifecycle.ts', 'src/core/loops/loop-continuation-enforcer.ts']),
|
|
22
|
+
pattern('skill-mcp-slashcommand', 'Skill MCP and slashcommand tool', ['Directive: OmO exposes skill MCP and slashcommand tools.'], 'adapt', 'Report MCP candidates and SKS route skill availability without assuming external plugin behavior.', ['src/core/codex-app/codex-app-harness-matrix.ts']),
|
|
23
|
+
pattern('lsp-ast-grep', 'LSP/AST-grep optional tooling', ['Directive: LSP/AST-grep are optional-but-first-class loop gates/tools.'], 'watch', 'Use as future specialist gates; do not add unrequested fallback tooling now.', ['src/core/loops/loop-gate-selector.ts'])
|
|
24
|
+
],
|
|
25
|
+
blockers: []
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export async function writeLazyCodexPatternAnalysis(root) {
|
|
29
|
+
const report = buildLazyCodexPatternAnalysis();
|
|
30
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'lazycodex-analysis.json'), report);
|
|
31
|
+
return report;
|
|
32
|
+
}
|
|
33
|
+
export function renderLazyCodexAnalysisMarkdown(report) {
|
|
34
|
+
const rows = report.patterns.map((p) => `| ${p.id} | ${p.sks_adoption} | ${p.rationale.replace(/\|/g, '\\|')} |`).join('\n');
|
|
35
|
+
return [
|
|
36
|
+
'# LazyCodex / OmO Pattern Analysis',
|
|
37
|
+
'',
|
|
38
|
+
`Source repo: \`${report.source_repo}\``,
|
|
39
|
+
`Analyzed at: \`${report.analyzed_at}\``,
|
|
40
|
+
'',
|
|
41
|
+
'| Pattern | Adoption | Rationale |',
|
|
42
|
+
'|---|---|---|',
|
|
43
|
+
rows,
|
|
44
|
+
'',
|
|
45
|
+
'This artifact is deterministic and based on the SKS 3.1.4 directive plus current SKS repository surfaces. Live LazyCodex runtime behavior remains a separate verification concern.'
|
|
46
|
+
].join('\n');
|
|
47
|
+
}
|
|
48
|
+
function pattern(id, title, evidence, sks_adoption, rationale, target_modules) {
|
|
49
|
+
return { id, title, evidence, sks_adoption, rationale, target_modules };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=lazycodex-analysis.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
6
|
+
import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
|
|
7
|
+
export async function buildLazyCodexInteropPolicy(input) {
|
|
8
|
+
const root = path.resolve(input.root);
|
|
9
|
+
const inventory = input.inventory || await buildCodexPluginInventory().catch((err) => ({ plugins: [], blockers: [err?.message || String(err)] }));
|
|
10
|
+
const codexHome = input.codexHome || process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
11
|
+
const skillNames = await discoverSkillNames([path.join(root, '.agents', 'skills'), path.join(codexHome, 'skills')]);
|
|
12
|
+
const pluginIds = (inventory.plugins || []).map((plugin) => `${plugin.id || ''} ${plugin.name || ''}`.toLowerCase());
|
|
13
|
+
const lazycodexInstalled = pluginIds.some((id) => id.includes('omo') || id.includes('lazycodex'))
|
|
14
|
+
|| ['ulw-loop', 'ulw-plan', 'start-work'].some((name) => skillNames.includes(name));
|
|
15
|
+
const collisions = ['ulw-loop', 'ulw-plan', 'start-work'].filter((name) => skillNames.includes(name));
|
|
16
|
+
const report = {
|
|
17
|
+
schema: 'sks.lazycodex-interop-policy.v1',
|
|
18
|
+
generated_at: nowIso(),
|
|
19
|
+
ok: true,
|
|
20
|
+
mode: input.mode || 'coexist',
|
|
21
|
+
lazycodex_detected: lazycodexInstalled,
|
|
22
|
+
detection: {
|
|
23
|
+
plugin_inventory_ids: pluginIds,
|
|
24
|
+
skill_names: skillNames,
|
|
25
|
+
collisions
|
|
26
|
+
},
|
|
27
|
+
policy: {
|
|
28
|
+
clobber_lazycodex_skills: false,
|
|
29
|
+
clobber_user_skills: false,
|
|
30
|
+
default_mode: 'coexist',
|
|
31
|
+
explicit_handoff_required: true
|
|
32
|
+
},
|
|
33
|
+
actions: collisions.map((name) => `preserve_existing_skill:${name}`),
|
|
34
|
+
blockers: []
|
|
35
|
+
};
|
|
36
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'lazycodex-interop-policy.json'), report).catch(() => undefined);
|
|
37
|
+
return report;
|
|
38
|
+
}
|
|
39
|
+
async function discoverSkillNames(roots) {
|
|
40
|
+
const names = new Set();
|
|
41
|
+
for (const root of roots) {
|
|
42
|
+
const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
43
|
+
for (const entry of entries)
|
|
44
|
+
if (entry.isDirectory())
|
|
45
|
+
names.add(entry.name);
|
|
46
|
+
}
|
|
47
|
+
return [...names].sort();
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=lazycodex-interop-policy.js.map
|
|
@@ -11,6 +11,7 @@ import { runLoopNode, runLoopPlan } from '../loops/loop-runtime.js';
|
|
|
11
11
|
import { scheduleLoopGraph } from '../loops/loop-scheduler.js';
|
|
12
12
|
import { writeLoopKillRequest } from '../loops/loop-runtime-control.js';
|
|
13
13
|
import { flag, promptOf, readFlagValue } from './command-utils.js';
|
|
14
|
+
import { runCodexInitDeep } from '../codex-app/codex-init-deep.js';
|
|
14
15
|
export async function loopCommand(subcommand = 'help', args = []) {
|
|
15
16
|
const action = subcommand || 'help';
|
|
16
17
|
if (action === 'plan')
|
|
@@ -27,6 +28,8 @@ export async function loopCommand(subcommand = 'help', args = []) {
|
|
|
27
28
|
return loopResume(args);
|
|
28
29
|
if (action === 'graph')
|
|
29
30
|
return loopGraph(args);
|
|
31
|
+
if (action === 'init-deep')
|
|
32
|
+
return loopInitDeep(args);
|
|
30
33
|
console.log(`SKS Loop
|
|
31
34
|
|
|
32
35
|
Usage:
|
|
@@ -37,8 +40,16 @@ Usage:
|
|
|
37
40
|
sks loop kill <loop-id|all>
|
|
38
41
|
sks loop resume latest [--rerun-completed]
|
|
39
42
|
sks loop graph latest
|
|
43
|
+
sks loop init-deep [--json]
|
|
40
44
|
`);
|
|
41
45
|
}
|
|
46
|
+
async function loopInitDeep(args) {
|
|
47
|
+
const root = await sksRoot();
|
|
48
|
+
const result = await runCodexInitDeep({ root, apply: !flag(args, '--check-only') && !flag(args, '--dry-run') });
|
|
49
|
+
if (flag(args, '--json'))
|
|
50
|
+
return printJson(result);
|
|
51
|
+
console.log(`Loop init-deep: ${result.ok ? 'ok' : 'blocked'} ${result.generated_path || ''}`);
|
|
52
|
+
}
|
|
42
53
|
async function loopPlan(args) {
|
|
43
54
|
const root = await sksRoot();
|
|
44
55
|
const request = promptOf(args);
|
|
@@ -22,6 +22,7 @@ import { checkSksUpdateNotice } from '../update/update-notice.js';
|
|
|
22
22
|
import { createMadDbCapability, MAD_DB_ACK } from '../mad-db/mad-db-capability.js';
|
|
23
23
|
import { writeCodex0138CapabilityArtifacts } from '../codex-control/codex-0138-capability.js';
|
|
24
24
|
import { writeCodex0139CapabilityArtifacts } from '../codex-control/codex-0139-capability.js';
|
|
25
|
+
import { repairZellijForSks } from '../zellij/zellij-self-heal.js';
|
|
25
26
|
export async function madHighCommand(args = [], deps = {}) {
|
|
26
27
|
const subcommand = firstSubcommand(args);
|
|
27
28
|
if (subcommand)
|
|
@@ -32,19 +33,52 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
32
33
|
return console.log(JSON.stringify(profile, null, 2));
|
|
33
34
|
}
|
|
34
35
|
const update = { status: 'notice_only', non_blocking: true };
|
|
36
|
+
const rawArgs = (args || []).map((arg) => String(arg));
|
|
37
|
+
const headlessZellij = rawArgs.includes('--headless') || process.env.SKS_MAD_ALLOW_HEADLESS === '1';
|
|
38
|
+
const skipZellijRepair = rawArgs.includes('--skip-zellij-repair') || rawArgs.includes('--no-auto-install-zellij');
|
|
39
|
+
const launchRoot = process.cwd();
|
|
40
|
+
if (!(await exists(path.join(launchRoot, '.sneakoscope'))))
|
|
41
|
+
await initProject(launchRoot, {});
|
|
35
42
|
const codexUpdate = deps.maybePromptCodexUpdateForLaunch ? await deps.maybePromptCodexUpdateForLaunch(args, { label: 'MAD launch' }) : { status: 'skipped' };
|
|
36
43
|
if (codexUpdate.status === 'failed' || codexUpdate.status === 'updated_not_reflected') {
|
|
37
44
|
console.error(`Codex CLI update failed: ${codexUpdate.error || 'updated version was not visible on PATH'}`);
|
|
38
45
|
process.exitCode = 1;
|
|
39
46
|
return;
|
|
40
47
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
const zellijUpdate = skipZellijRepair
|
|
49
|
+
? { status: 'skipped', command: 'sks doctor --fix --yes' }
|
|
50
|
+
: deps.maybePromptZellijUpdateForLaunch
|
|
51
|
+
? await deps.maybePromptZellijUpdateForLaunch(args, {
|
|
52
|
+
label: 'MAD launch',
|
|
53
|
+
root: launchRoot,
|
|
54
|
+
selfHealOnMissing: true,
|
|
55
|
+
autoApprove: rawArgs.includes('--yes') || rawArgs.includes('-y'),
|
|
56
|
+
installHomebrew: rawArgs.includes('--install-homebrew'),
|
|
57
|
+
allowHeadlessFallback: headlessZellij
|
|
58
|
+
}).catch(() => ({ status: 'error', command: 'sks doctor --fix --yes' }))
|
|
59
|
+
: await repairZellijForSks({
|
|
60
|
+
root: launchRoot,
|
|
61
|
+
requestedBy: 'sks --mad',
|
|
62
|
+
fixRequested: true,
|
|
63
|
+
autoApprove: rawArgs.includes('--yes') || rawArgs.includes('-y'),
|
|
64
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY && process.env.SKS_NO_QUESTION !== '1'),
|
|
65
|
+
installHomebrew: rawArgs.includes('--install-homebrew'),
|
|
66
|
+
allowHeadlessFallback: headlessZellij
|
|
67
|
+
});
|
|
68
|
+
const zellijRepairBlocked = !headlessZellij && (zellijUpdate.status === 'manual_required'
|
|
69
|
+
|| zellijUpdate.strategy === 'manual-required'
|
|
70
|
+
|| zellijUpdate.ok === false);
|
|
71
|
+
if (zellijRepairBlocked) {
|
|
72
|
+
console.error('SKS MAD launch blocked by Zellij repair_required.');
|
|
73
|
+
console.error(`Run: ${zellijUpdate.command || 'sks doctor --fix --yes'}`);
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
return { ok: false, status: 'repair_required', command: zellijUpdate.command || 'sks doctor --fix --yes', zellij_repair: zellijUpdate };
|
|
76
|
+
}
|
|
77
|
+
const depStatus = skipZellijRepair && deps.ensureMadLaunchDependencies
|
|
78
|
+
? await deps.ensureMadLaunchDependencies(args)
|
|
79
|
+
: { ready: true, actions: [] };
|
|
46
80
|
if (!depStatus.ready) {
|
|
47
|
-
console.error('SKS MAD launch blocked by
|
|
81
|
+
console.error('SKS MAD launch blocked by required Zellij dependency.');
|
|
48
82
|
for (const action of depStatus.actions)
|
|
49
83
|
deps.printDepsInstallAction?.(action);
|
|
50
84
|
process.exitCode = 1;
|
|
@@ -56,9 +90,6 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
56
90
|
return;
|
|
57
91
|
}
|
|
58
92
|
const profile = buildMadHighLaunchProfileNoWrite();
|
|
59
|
-
const launchRoot = process.cwd();
|
|
60
|
-
if (!(await exists(path.join(launchRoot, '.sneakoscope'))))
|
|
61
|
-
await initProject(launchRoot, {});
|
|
62
93
|
const uiSnapshotId = Date.now().toString(36);
|
|
63
94
|
const beforeUi = await writeCodexAppUiSnapshot(launchRoot, `mad-before-${uiSnapshotId}`).catch(() => null);
|
|
64
95
|
// launchFast skips the redundant live-`codex exec` config probe (up to ~20s, run
|
|
@@ -66,7 +97,6 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
66
97
|
// later when the Zellij session opens. All filesystem/permission/EPERM/symlink/ACL
|
|
67
98
|
// readability + repair checks still run. SKS_LAUNCH_FULL_CODEX_PROBE=1 restores the
|
|
68
99
|
// old behavior.
|
|
69
|
-
const rawArgs = (args || []).map((arg) => String(arg));
|
|
70
100
|
const madDbRequested = rawArgs.includes('--mad-db');
|
|
71
101
|
const madDbAck = readOption(rawArgs, '--ack', '');
|
|
72
102
|
if (madDbRequested && madDbAck !== MAD_DB_ACK) {
|
|
@@ -138,7 +168,9 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
138
168
|
};
|
|
139
169
|
const launchOpts = codexLbImmediateLaunchOpts(cleanArgs, launchLb, { codexArgs: profile.launch_args, conciseBlockers: true, madSksEnv, launchEnv: madSksEnv });
|
|
140
170
|
const workspace = readOption(cleanArgs, '--workspace', readOption(cleanArgs, '--session', launchOpts.session || `sks-mad-${sanitizeZellijSessionName(process.cwd())}`));
|
|
141
|
-
const launch =
|
|
171
|
+
const launch = headlessZellij
|
|
172
|
+
? await writeMadHeadlessZellijFallback(madLaunch, workspace)
|
|
173
|
+
: await launchMadZellijUi([...cleanArgs, '--workspace', workspace], { ...launchOpts, missionId: madLaunch.mission_id, root: madLaunch.root, cwd: process.cwd(), ledgerRoot: path.join(madLaunch.dir, 'agents'), slotCount: 0, requireZellij: process.env.SKS_REQUIRE_ZELLIJ === '1' });
|
|
142
174
|
const afterLaunchUi = beforeUi ? await writeCodexAppUiSnapshot(launchRoot, `mad-after-launch-${uiSnapshotId}`).catch(() => null) : null;
|
|
143
175
|
const launchUiDiff = beforeUi && afterLaunchUi ? diffCodexAppUiSnapshots(beforeUi, afterLaunchUi) : null;
|
|
144
176
|
if (launchUiDiff) {
|
|
@@ -157,7 +189,8 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
157
189
|
schema: 'sks.zellij-initial-ui.v1',
|
|
158
190
|
ok: true,
|
|
159
191
|
mission_id: madLaunch.mission_id,
|
|
160
|
-
session_name: launch.session_name,
|
|
192
|
+
session_name: launch.session_name || null,
|
|
193
|
+
live_panes: !headlessZellij,
|
|
161
194
|
initial_panes: 'main-only',
|
|
162
195
|
dashboard_created: false,
|
|
163
196
|
worker_panes_created: 0,
|
|
@@ -166,16 +199,16 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
166
199
|
const madNativeSwarm = await startMadNativeSwarm(madLaunch.root, madLaunch, args, profile, {
|
|
167
200
|
env: {
|
|
168
201
|
...madSksEnv,
|
|
169
|
-
SKS_ZELLIJ_SESSION_NAME: launch.session_name
|
|
202
|
+
...(launch.session_name ? { SKS_ZELLIJ_SESSION_NAME: launch.session_name } : {})
|
|
170
203
|
},
|
|
171
|
-
zellijSessionName: launch.session_name,
|
|
172
|
-
workerPlacement: shouldAutoAttachZellij(args) ? 'zellij-pane' : 'process',
|
|
204
|
+
zellijSessionName: launch.session_name || null,
|
|
205
|
+
workerPlacement: headlessZellij ? 'process' : shouldAutoAttachZellij(args) ? 'zellij-pane' : 'process',
|
|
173
206
|
zellijVisiblePaneCap: Number(process.env.SKS_ZELLIJ_VISIBLE_PANE_CAP || 8)
|
|
174
207
|
});
|
|
175
208
|
// The launcher only creates a detached background session. In an interactive
|
|
176
209
|
// terminal, immediately attach so the session actually opens for the user
|
|
177
210
|
// instead of leaving them to copy/paste the attach command by hand.
|
|
178
|
-
if (shouldAutoAttachZellij(args)) {
|
|
211
|
+
if (!headlessZellij && shouldAutoAttachZellij(args)) {
|
|
179
212
|
console.log(`Opening Zellij session: ${launch.session_name} (detach with Ctrl+q, re-attach later with: ${launch.attach_command_with_env})`);
|
|
180
213
|
const attached = attachZellijSessionInteractive(launch.session_name, { cwd: process.cwd(), configPath: launch.clipboard_config_path });
|
|
181
214
|
if (!attached.ok) {
|
|
@@ -187,6 +220,8 @@ export async function madHighCommand(args = [], deps = {}) {
|
|
|
187
220
|
}
|
|
188
221
|
if (launch.attach_command_with_env)
|
|
189
222
|
console.log(`Attach with: ${launch.attach_command_with_env}`);
|
|
223
|
+
if (headlessZellij)
|
|
224
|
+
console.log('MAD launch running headless: live_panes=false.');
|
|
190
225
|
return launch;
|
|
191
226
|
}
|
|
192
227
|
export async function startMadNativeSwarm(root, madLaunch, args = [], profile = {}, opts = {}) {
|
|
@@ -375,6 +410,27 @@ function formatMadZellijAction(launch) {
|
|
|
375
410
|
const report = launch.report_path ? ` | report: ${launch.report_path}` : '';
|
|
376
411
|
return `${blockers}${detail}${report}`;
|
|
377
412
|
}
|
|
413
|
+
async function writeMadHeadlessZellijFallback(madLaunch, workspace) {
|
|
414
|
+
const report = {
|
|
415
|
+
schema: 'sks.zellij-session.v1',
|
|
416
|
+
generated_at: nowIso(),
|
|
417
|
+
ok: true,
|
|
418
|
+
kind: 'mad',
|
|
419
|
+
status: 'headless-fallback',
|
|
420
|
+
live_panes: false,
|
|
421
|
+
mission_id: madLaunch.mission_id,
|
|
422
|
+
session_name: null,
|
|
423
|
+
workspace,
|
|
424
|
+
root: madLaunch.root,
|
|
425
|
+
cwd: path.resolve(process.cwd()),
|
|
426
|
+
attach_command_with_env: null,
|
|
427
|
+
blockers: [],
|
|
428
|
+
warnings: ['zellij_headless_fallback_live_panes_false']
|
|
429
|
+
};
|
|
430
|
+
await writeJsonAtomic(path.join(madLaunch.dir, 'zellij-session.json'), report);
|
|
431
|
+
await appendJsonlBounded(path.join(madLaunch.dir, 'events.jsonl'), { ts: nowIso(), type: 'mad_sks.zellij_headless_fallback', live_panes: false });
|
|
432
|
+
return report;
|
|
433
|
+
}
|
|
378
434
|
async function activateMadZellijPermissionState(cwd = process.cwd(), args = []) {
|
|
379
435
|
const root = await sksRoot();
|
|
380
436
|
if (!(await exists(path.join(root, '.sneakoscope'))))
|
|
@@ -473,6 +529,9 @@ function madLaunchOnlyFlags() {
|
|
|
473
529
|
'--attach',
|
|
474
530
|
'--no-attach',
|
|
475
531
|
'--no-auto-install-zellij',
|
|
532
|
+
'--skip-zellij-repair',
|
|
533
|
+
'--install-homebrew',
|
|
534
|
+
'--headless',
|
|
476
535
|
'--allow-system',
|
|
477
536
|
'--allow-db-write',
|
|
478
537
|
'--allow-package-install',
|
|
@@ -528,6 +587,8 @@ export function defaultMadSwarmBackend(args = [], opts = {}) {
|
|
|
528
587
|
return String(opts.backend);
|
|
529
588
|
if (list.includes('--json') || list.includes('--no-attach') || opts.nonInteractive === true)
|
|
530
589
|
return 'codex-sdk';
|
|
590
|
+
if (list.includes('--headless') || process.env.SKS_MAD_ALLOW_HEADLESS === '1')
|
|
591
|
+
return 'codex-sdk';
|
|
531
592
|
return 'zellij';
|
|
532
593
|
}
|
|
533
594
|
function stripMadLaunchOnlyArgs(args = []) {
|
|
@@ -508,6 +508,14 @@ async function narutoRun(parsed) {
|
|
|
508
508
|
} : null,
|
|
509
509
|
parallel_write_policy: result.parallel_write_policy || null,
|
|
510
510
|
local_worker: localWorkerSummary,
|
|
511
|
+
fast_mode_policy: result.fast_mode_policy || null,
|
|
512
|
+
fast_mode_propagation: result.fast_mode_propagation ? {
|
|
513
|
+
ok: result.fast_mode_propagation.ok === true,
|
|
514
|
+
fast_mode: result.fast_mode_propagation.fast_mode,
|
|
515
|
+
service_tier: result.fast_mode_propagation.service_tier,
|
|
516
|
+
worker_process_report_count: result.fast_mode_propagation.worker_process_report_count || 0,
|
|
517
|
+
blockers: result.fast_mode_propagation.blockers || []
|
|
518
|
+
} : null,
|
|
511
519
|
finalizer,
|
|
512
520
|
proof: result.proof?.status || 'missing',
|
|
513
521
|
run: compactNarutoRunResult(result),
|
|
@@ -798,9 +806,18 @@ function parseNarutoArgs(args = []) {
|
|
|
798
806
|
const dryRunPatches = hasFlag(args, '--dry-run-patches') || hasFlag(args, '--dry-run-patch');
|
|
799
807
|
const maxWriteAgents = Math.max(0, Math.floor(Number(readOption(args, '--max-write-agents', '0')) || 0));
|
|
800
808
|
const explicitServiceTier = String(readOption(args, '--service-tier', '') || '');
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
809
|
+
const requestedServiceTier = normalizeServiceTier(explicitServiceTier, null) || undefined;
|
|
810
|
+
// Naruto clones always run in the fast service tier. The route-level skill and
|
|
811
|
+
// release docs explicitly treat --no-fast / standard as non-honored for clones.
|
|
812
|
+
const serviceTier = action === 'run' ? 'fast' : requestedServiceTier;
|
|
813
|
+
const fastMode = action === 'run'
|
|
814
|
+
? true
|
|
815
|
+
: hasFlag(args, '--no-fast') || requestedServiceTier === 'standard'
|
|
816
|
+
? false
|
|
817
|
+
: hasFlag(args, '--fast')
|
|
818
|
+
? true
|
|
819
|
+
: undefined;
|
|
820
|
+
const noFast = action === 'run' ? false : hasFlag(args, '--no-fast');
|
|
804
821
|
const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status' || action === 'proof'
|
|
805
822
|
? positionalArgs(rest, new Set()).find((arg) => /^latest$|^M-/.test(arg))
|
|
806
823
|
: null;
|
|
@@ -65,6 +65,12 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
65
65
|
warnings.add('codex_0139_real_probes_not_run');
|
|
66
66
|
for (const warning of normalizeList(input.codex_plugin_app_template_policy?.doctor_warnings))
|
|
67
67
|
warnings.add(warning);
|
|
68
|
+
const codexAppHarness = input.codex_app_harness_matrix || null;
|
|
69
|
+
for (const warning of normalizeList(codexAppHarness?.warnings))
|
|
70
|
+
warnings.add(warning);
|
|
71
|
+
if (codexAppHarness?.ok === false)
|
|
72
|
+
for (const blocker of normalizeList(codexAppHarness.blockers))
|
|
73
|
+
warnings.add(`codex_app_harness:${blocker}`);
|
|
68
74
|
if (input.codex_lb?.ok === false)
|
|
69
75
|
warnings.add(`codex_lb_${input.codex_lb?.circuit?.state || 'blocked'}`);
|
|
70
76
|
const localModel = input.local_model || {};
|
|
@@ -124,6 +130,7 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
124
130
|
codex_0139_real_probes: codex0139RealProbes,
|
|
125
131
|
codex_plugin_inventory: input.codex_plugin_inventory || null,
|
|
126
132
|
codex_plugin_app_template_policy: input.codex_plugin_app_template_policy || null,
|
|
133
|
+
codex_app_harness_matrix: codexAppHarness,
|
|
127
134
|
fast_mode_ready: input.fast_mode_ready !== false,
|
|
128
135
|
codex_app_ui: input.codex_app_ui || null,
|
|
129
136
|
hooks_ready: input.hooks_ready !== false,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { repairZellijForSks } from '../zellij/zellij-self-heal.js';
|
|
3
|
+
export async function runDoctorZellijRepair(input) {
|
|
4
|
+
const args = (input.args || []).map(String);
|
|
5
|
+
if (input.doctorFix !== true)
|
|
6
|
+
return null;
|
|
7
|
+
return repairZellijForSks({
|
|
8
|
+
root: input.root,
|
|
9
|
+
requestedBy: 'doctor --fix',
|
|
10
|
+
fixRequested: true,
|
|
11
|
+
autoApprove: args.includes('--yes') || args.includes('-y'),
|
|
12
|
+
installHomebrew: args.includes('--install-homebrew') || process.env.SKS_ALLOW_HOMEBREW_INSTALL === '1',
|
|
13
|
+
interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY && process.env.SKS_NO_QUESTION !== '1'),
|
|
14
|
+
allowHeadlessFallback: false,
|
|
15
|
+
env: process.env
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function doctorZellijRepairConsoleLine(result) {
|
|
19
|
+
if (!result)
|
|
20
|
+
return null;
|
|
21
|
+
if (result.strategy === 'none-current')
|
|
22
|
+
return `Zellij repair: current ${result.after.version || ''}`.trim();
|
|
23
|
+
if (result.ok && (result.strategy === 'brew-install-zellij' || result.strategy === 'brew-install-homebrew-then-zellij')) {
|
|
24
|
+
return `Zellij repair: installed ${result.after.version || 'latest'} via ${result.command || 'brew install zellij'}`;
|
|
25
|
+
}
|
|
26
|
+
if (result.ok && result.strategy === 'brew-upgrade-zellij') {
|
|
27
|
+
return `Zellij repair: upgraded ${result.before.version || 'unknown'} -> ${result.after.version || 'latest'} via ${result.command || 'brew upgrade zellij'}`;
|
|
28
|
+
}
|
|
29
|
+
if (result.strategy === 'manual-required') {
|
|
30
|
+
return `Zellij repair: manual_required\nRun: ${result.command || 'sks doctor --fix --install-homebrew --yes'}`;
|
|
31
|
+
}
|
|
32
|
+
if (result.strategy === 'headless-fallback')
|
|
33
|
+
return 'Zellij repair: headless_fallback live_panes=false';
|
|
34
|
+
return `Zellij repair: failed\nRun: ${result.command || 'sks doctor --fix --yes'}`;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=doctor-zellij-repair.js.map
|
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 = '3.1.
|
|
8
|
+
export const PACKAGE_VERSION = '3.1.4';
|
|
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() {
|
|
@@ -14,6 +14,7 @@ import { REQUIRED_CODEX_MODEL, isForbiddenCodexModel } from './codex-model-guard
|
|
|
14
14
|
import { dollarCommand, routeRequiresSubagents, stripVisibleDecisionAnswerBlocks } from './routes.js';
|
|
15
15
|
import { appendMissionStatus } from './recallpulse.js';
|
|
16
16
|
import { scanAgentTextForRecursion } from './agents/agent-recursion-guard.js';
|
|
17
|
+
import { evaluateLoopContinuation } from './loops/loop-continuation-enforcer.js';
|
|
17
18
|
import { buildCompactContinue, buildPermissionRequestAllow, buildPermissionRequestDeny, buildPostToolUseBlock, buildPostToolUseContinue, buildPreToolUseContinue, buildPreToolUseDeny, buildSessionStartContinue, buildStopBlock, buildStopContinue, buildSubagentStartContinue, buildSubagentStopBlock, buildSubagentStopContinue, buildUserPromptSubmitBlock, buildUserPromptSubmitContinue } from './codex-compat/codex-hook-output-builders.js';
|
|
18
19
|
const TEAM_DIGEST_MAX_EVENTS = 4;
|
|
19
20
|
const TEAM_DIGEST_MESSAGE_CHARS = 180;
|
|
@@ -606,6 +607,18 @@ function clarificationPauseBlockReason(state = {}) {
|
|
|
606
607
|
}
|
|
607
608
|
async function hookStop(root, state, payload, noQuestion) {
|
|
608
609
|
const last = extractLastMessage(payload);
|
|
610
|
+
if (state?.mode === 'LOOP' || state?.route === 'Loop' || state?.route_command === '$Loop') {
|
|
611
|
+
const missionId = state?.mission_id;
|
|
612
|
+
if (missionId) {
|
|
613
|
+
const continuation = await evaluateLoopContinuation({ root, missionId }).catch(() => null);
|
|
614
|
+
if (continuation?.should_continue) {
|
|
615
|
+
return {
|
|
616
|
+
decision: 'block',
|
|
617
|
+
reason: `SKS Loop continuation required. Resume with: ${continuation.resume_instruction}`
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
609
622
|
if (await consumeCodexGitActionStopBypass(root, payload)) {
|
|
610
623
|
return {
|
|
611
624
|
continue: true,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { loopPlanPath } from './loop-artifacts.js';
|
|
5
|
+
export async function evaluateLoopContinuation(input) {
|
|
6
|
+
const root = path.resolve(input.root || process.cwd());
|
|
7
|
+
const plan = await readJson(loopPlanPath(root, input.missionId), null);
|
|
8
|
+
const blockers = [];
|
|
9
|
+
if (!plan)
|
|
10
|
+
blockers.push('loop_plan_missing');
|
|
11
|
+
const nodes = plan?.graph?.nodes || [];
|
|
12
|
+
const proofs = await Promise.all(nodes.map((node) => readJson(path.join(root, '.sneakoscope', 'missions', input.missionId, 'loops', node.loop_id, 'loop-proof.json'), null)));
|
|
13
|
+
const completed = proofs.filter((proof) => proof?.status === 'completed').length;
|
|
14
|
+
const incomplete = Math.max(0, nodes.length - completed);
|
|
15
|
+
const shouldContinue = Boolean(plan && incomplete > 0 && blockers.length === 0);
|
|
16
|
+
const report = {
|
|
17
|
+
schema: 'sks.loop-continuation-enforcer.v1',
|
|
18
|
+
generated_at: nowIso(),
|
|
19
|
+
ok: blockers.length === 0,
|
|
20
|
+
mission_id: input.missionId,
|
|
21
|
+
nodes: nodes.length,
|
|
22
|
+
completed,
|
|
23
|
+
incomplete,
|
|
24
|
+
max_continuation_turns: input.maxContinuationTurns || 3,
|
|
25
|
+
should_continue: shouldContinue,
|
|
26
|
+
resume_instruction: shouldContinue ? `sks loop resume ${input.missionId}` : null,
|
|
27
|
+
blockers
|
|
28
|
+
};
|
|
29
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'missions', input.missionId, 'loop-continuation-enforcer.json'), report).catch(() => undefined);
|
|
30
|
+
return report;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=loop-continuation-enforcer.js.map
|
|
@@ -5,6 +5,7 @@ import { selectLoopGates } from './loop-gate-selector.js';
|
|
|
5
5
|
import { inferLoopOwnerScope } from './loop-owner-inference.js';
|
|
6
6
|
import { classifyLoopRisk } from './loop-risk-classifier.js';
|
|
7
7
|
import { defaultLoopBudget, validateLoopPlan } from './loop-schema.js';
|
|
8
|
+
import { readInitDeepMemory } from '../codex-app/codex-init-deep.js';
|
|
8
9
|
export async function planLoopsFromRequest(input) {
|
|
9
10
|
const parallelism = input.parallelism || 'balanced';
|
|
10
11
|
const maxLoops = Math.max(1, Math.min(32, input.maxLoops || 8));
|
|
@@ -83,6 +84,14 @@ export async function planLoopsFromRequest(input) {
|
|
|
83
84
|
},
|
|
84
85
|
blockers: []
|
|
85
86
|
};
|
|
87
|
+
const projectMemory = await readInitDeepMemory(input.root).catch(() => null);
|
|
88
|
+
if (projectMemory) {
|
|
89
|
+
plan.project_memory = {
|
|
90
|
+
source: projectMemory.path,
|
|
91
|
+
injected: true,
|
|
92
|
+
summary: projectMemory.text.split(/\r?\n/).filter((line) => /^##\s+/.test(line)).slice(0, 8)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
86
95
|
const validation = validateLoopPlan(plan);
|
|
87
96
|
plan.blockers = validation.blockers;
|
|
88
97
|
await writeJsonAtomic(loopPlanPath(input.root, input.missionId), plan);
|
|
@@ -6,6 +6,7 @@ import { loopNodeRoot } from './loop-artifacts.js';
|
|
|
6
6
|
import { computeLoopConcurrencyBudget, loopWorkerBudgetFor } from './loop-concurrency-budget.js';
|
|
7
7
|
import { decideLoopFixturePolicy, writeLoopFixturePolicyDecision } from './loop-fixture-policy.js';
|
|
8
8
|
import { buildLoopCheckerPrompt, buildLoopMakerPrompt } from './loop-worker-prompts.js';
|
|
9
|
+
import { resolveCodexAppExecutionProfile } from '../codex-app/codex-app-execution-profile.js';
|
|
9
10
|
export async function runLoopMakerWorkers(input) {
|
|
10
11
|
return runLoopWorkers({ ...input, phase: 'maker' });
|
|
11
12
|
}
|
|
@@ -51,6 +52,7 @@ async function runLoopWorkerNative(input) {
|
|
|
51
52
|
// The loop worktree is still where workers cwd + write: it is threaded through the per-worker
|
|
52
53
|
// `worktree` opt below, which launchWorker reads as ctx.opts.worktree -> workerCwd.
|
|
53
54
|
const insideZellij = Boolean(process.env.SKS_ZELLIJ_SESSION_NAME || process.env.ZELLIJ);
|
|
55
|
+
const executionProfile = await resolveCodexAppExecutionProfile({ root: input.root }).catch(() => null);
|
|
54
56
|
const visiblePaneCap = Math.min(resolveLoopVisiblePaneCap(workerCount), Math.max(1, workerCount));
|
|
55
57
|
const zellijPlacementOpts = insideZellij ? {
|
|
56
58
|
workerPlacement: 'zellij-pane',
|
|
@@ -77,7 +79,9 @@ async function runLoopWorkerNative(input) {
|
|
|
77
79
|
SKS_LOOP_ID: input.node.loop_id,
|
|
78
80
|
SKS_LOOP_PHASE: input.phase,
|
|
79
81
|
SKS_LOOP_MAIN_ROOT: input.root,
|
|
80
|
-
SKS_LOOP_WORKER_BUDGET: String(workerCount)
|
|
82
|
+
SKS_LOOP_WORKER_BUDGET: String(workerCount),
|
|
83
|
+
SKS_CODEX_APP_EXECUTION_PROFILE: executionProfile?.mode || 'unknown',
|
|
84
|
+
SKS_CODEX_AGENT_ROLE_STRATEGY: executionProfile?.agent_role_strategy || 'message-role'
|
|
81
85
|
},
|
|
82
86
|
...(input.worktree?.path ? {
|
|
83
87
|
worktree: {
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '3.1.
|
|
1
|
+
export const PACKAGE_VERSION = '3.1.4';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import readline from 'node:readline';
|
|
3
|
+
export const HOMEBREW_INSTALL_COMMAND = '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"';
|
|
4
|
+
export function resolveHomebrewInstallPolicy(input = {}) {
|
|
5
|
+
const args = (input.args || []).map(String);
|
|
6
|
+
const env = input.env || process.env;
|
|
7
|
+
const hasFlag = input.installHomebrew === true || args.includes('--install-homebrew');
|
|
8
|
+
const hasYes = input.autoApprove === true || args.includes('--yes') || args.includes('-y');
|
|
9
|
+
const envAllowed = env.SKS_ALLOW_HOMEBREW_INSTALL === '1';
|
|
10
|
+
const interactiveAccepted = input.interactiveAccepted === true;
|
|
11
|
+
const allowed = envAllowed || interactiveAccepted || (hasFlag && hasYes);
|
|
12
|
+
const source = envAllowed ? 'env'
|
|
13
|
+
: interactiveAccepted ? 'interactive_confirmed'
|
|
14
|
+
: hasFlag && hasYes ? 'yes_install_homebrew'
|
|
15
|
+
: hasFlag ? 'flag'
|
|
16
|
+
: 'not_allowed';
|
|
17
|
+
return {
|
|
18
|
+
schema: 'sks.homebrew-policy.v1',
|
|
19
|
+
allowed,
|
|
20
|
+
source,
|
|
21
|
+
install_command: HOMEBREW_INSTALL_COMMAND,
|
|
22
|
+
blockers: allowed ? [] : ['homebrew_install_requires_explicit_approval']
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function homebrewMissingDoctorMessage() {
|
|
26
|
+
return [
|
|
27
|
+
'Zellij repair: Homebrew missing. Run:',
|
|
28
|
+
' sks doctor --fix --install-homebrew --yes',
|
|
29
|
+
'or install Homebrew manually, then:',
|
|
30
|
+
' sks doctor --fix --yes'
|
|
31
|
+
].join('\n');
|
|
32
|
+
}
|
|
33
|
+
export async function askHomebrewInstallAllowed(question = 'Homebrew is missing. Install Homebrew now? [y/N] ') {
|
|
34
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY))
|
|
35
|
+
return false;
|
|
36
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
37
|
+
try {
|
|
38
|
+
const answer = await new Promise((resolve) => rl.question(question, resolve));
|
|
39
|
+
return /^(y|yes|예|네|응)$/i.test(String(answer || '').trim());
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
rl.close();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=homebrew-policy.js.map
|
|
@@ -30,7 +30,10 @@ export function resolveZellijStackedPaneCapability(input = {}) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
export async function checkZellijStackedPaneCapability(opts = {}) {
|
|
33
|
-
const
|
|
33
|
+
const runOpts = { optional: true, timeoutMs: 5000 };
|
|
34
|
+
if (opts.env !== undefined)
|
|
35
|
+
runOpts.env = opts.env;
|
|
36
|
+
const versionRun = await runZellij(['--version'], runOpts);
|
|
34
37
|
const versionText = `${versionRun.stdout_tail}\n${versionRun.stderr_tail}`.trim();
|
|
35
38
|
const report = resolveZellijStackedPaneCapability({
|
|
36
39
|
ok: versionRun.ok,
|
|
@@ -45,8 +48,34 @@ export async function checkZellijStackedPaneCapability(opts = {}) {
|
|
|
45
48
|
return report;
|
|
46
49
|
}
|
|
47
50
|
export async function checkZellijCapability(opts = {}) {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
51
|
+
const env = opts.env || process.env;
|
|
52
|
+
const requireZellij = opts.require === true || env.SKS_REQUIRE_ZELLIJ === '1';
|
|
53
|
+
if (env.SKS_ZELLIJ_CAPABILITY_FAKE_STATUS) {
|
|
54
|
+
const fakeStatus = String(env.SKS_ZELLIJ_CAPABILITY_FAKE_STATUS);
|
|
55
|
+
const status = fakeStatus === 'ok' || fakeStatus === 'missing' || fakeStatus === 'too_old' || fakeStatus === 'blocked' ? fakeStatus : 'blocked';
|
|
56
|
+
const report = {
|
|
57
|
+
schema: ZELLIJ_CAPABILITY_SCHEMA,
|
|
58
|
+
generated_at: nowIso(),
|
|
59
|
+
ok: status === 'ok',
|
|
60
|
+
status,
|
|
61
|
+
integration_optional: !requireZellij,
|
|
62
|
+
require_zellij: requireZellij,
|
|
63
|
+
min_version: ZELLIJ_MIN_VERSION,
|
|
64
|
+
version: status === 'missing' ? null : String(env.SKS_ZELLIJ_CAPABILITY_FAKE_VERSION || '0.40.0'),
|
|
65
|
+
bin: 'zellij',
|
|
66
|
+
command: ['zellij', '--version'],
|
|
67
|
+
docs_evidence: [],
|
|
68
|
+
blockers: requireZellij && status !== 'ok' ? [`zellij_${status}_required`] : [],
|
|
69
|
+
warnings: !requireZellij && status !== 'ok' ? [`zellij_${status}_optional_integration`] : [],
|
|
70
|
+
operator_actions: status === 'ok' ? [] : ['Install Zellij. On macOS: `brew install zellij`.']
|
|
71
|
+
};
|
|
72
|
+
if (opts.writeReport !== false) {
|
|
73
|
+
const root = opts.root || process.cwd();
|
|
74
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'zellij-capability.json'), report);
|
|
75
|
+
}
|
|
76
|
+
return report;
|
|
77
|
+
}
|
|
78
|
+
const versionRun = await runZellij(['--version'], { optional: !requireZellij, timeoutMs: 5000, env });
|
|
50
79
|
const versionText = `${versionRun.stdout_tail}\n${versionRun.stderr_tail}`.trim();
|
|
51
80
|
const version = parseZellijVersionText(versionText);
|
|
52
81
|
const missing = !versionRun.ok && versionRun.blockers.includes('zellij_missing');
|