sneakoscope 3.1.3 → 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/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,353 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { ensureDir, nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
|
|
6
|
+
import { guardContextForRoute, guardedPackageInstall } from '../safety/mutation-guard.js';
|
|
7
|
+
import { mutationLedgerPath } from '../safety/mutation-ledger.js';
|
|
8
|
+
import { checkZellijCapability, ZELLIJ_MIN_VERSION } from './zellij-capability.js';
|
|
9
|
+
import { compareVersionLike } from './zellij-command.js';
|
|
10
|
+
import { askHomebrewInstallAllowed, HOMEBREW_INSTALL_COMMAND, resolveHomebrewInstallPolicy } from './homebrew-policy.js';
|
|
11
|
+
export async function repairZellijForSks(input) {
|
|
12
|
+
const root = path.resolve(input.root || process.cwd());
|
|
13
|
+
const env = input.env || process.env;
|
|
14
|
+
const autoApproved = input.autoApprove === true;
|
|
15
|
+
const beforeReport = await capabilitySnapshot(root, env, 'before');
|
|
16
|
+
const before = compactCapability(beforeReport);
|
|
17
|
+
const latest = await latestZellijVersion(env);
|
|
18
|
+
const brew = await findBrew(env);
|
|
19
|
+
const stale = Boolean(before.version && latest && compareVersionLike(latest, before.version) > 0);
|
|
20
|
+
const needsRepair = before.status === 'missing' || before.status === 'too_old' || stale;
|
|
21
|
+
const mutationArtifact = mutationLedgerPath(root);
|
|
22
|
+
if (!needsRepair) {
|
|
23
|
+
return persistSelfHeal(root, input.missionDir, {
|
|
24
|
+
schema: 'sks.zellij-self-heal.v1',
|
|
25
|
+
ok: true,
|
|
26
|
+
requested_by: input.requestedBy,
|
|
27
|
+
fix_requested: input.fixRequested === true,
|
|
28
|
+
auto_approved: autoApproved,
|
|
29
|
+
install_homebrew_allowed: false,
|
|
30
|
+
before,
|
|
31
|
+
latest_version: latest,
|
|
32
|
+
strategy: 'none-current',
|
|
33
|
+
command: null,
|
|
34
|
+
after: before,
|
|
35
|
+
mutation_guard_artifact: null,
|
|
36
|
+
homebrew: { present: brew.present, bin: brew.bin, install_attempted: false, install_allowed: false },
|
|
37
|
+
blockers: [],
|
|
38
|
+
warnings: beforeReport.warnings || []
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (input.fixRequested !== true) {
|
|
42
|
+
return manualResult(root, input, env, before, latest, brew, 'fix_not_requested');
|
|
43
|
+
}
|
|
44
|
+
if (!autoApproved && input.interactive !== true) {
|
|
45
|
+
return input.allowHeadlessFallback === true
|
|
46
|
+
? headlessResult(root, input, before, latest, brew, 'noninteractive_without_auto_approval')
|
|
47
|
+
: manualResult(root, input, env, before, latest, brew, 'noninteractive_without_auto_approval');
|
|
48
|
+
}
|
|
49
|
+
if (!autoApproved && input.interactive === true) {
|
|
50
|
+
const accepted = await askZellijRepairAllowed(before.status === 'missing'
|
|
51
|
+
? 'Zellij is missing. Install it with Homebrew now? [Y/n] '
|
|
52
|
+
: `Zellij ${before.version || 'unknown'} needs repair. Upgrade with Homebrew now? [Y/n] `);
|
|
53
|
+
if (!accepted)
|
|
54
|
+
return manualResult(root, input, env, before, latest, brew, 'operator_declined_zellij_repair');
|
|
55
|
+
}
|
|
56
|
+
let brewBin = brew.bin;
|
|
57
|
+
let homebrewInstallAttempted = false;
|
|
58
|
+
let homebrewInstallAllowed = false;
|
|
59
|
+
if (!brew.present) {
|
|
60
|
+
const interactiveAccepted = input.interactive === true && !autoApproved
|
|
61
|
+
? await askHomebrewInstallAllowed()
|
|
62
|
+
: false;
|
|
63
|
+
const policy = resolveHomebrewInstallPolicy({
|
|
64
|
+
env,
|
|
65
|
+
installHomebrew: input.installHomebrew === true,
|
|
66
|
+
autoApprove: autoApproved,
|
|
67
|
+
interactiveAccepted
|
|
68
|
+
});
|
|
69
|
+
homebrewInstallAllowed = policy.allowed;
|
|
70
|
+
if (!policy.allowed) {
|
|
71
|
+
return input.allowHeadlessFallback === true
|
|
72
|
+
? headlessResult(root, input, before, latest, brew, policy.blockers[0] || 'homebrew_missing')
|
|
73
|
+
: manualResult(root, input, env, before, latest, brew, policy.blockers[0] || 'homebrew_missing');
|
|
74
|
+
}
|
|
75
|
+
homebrewInstallAttempted = true;
|
|
76
|
+
const homebrewRun = await runHomebrewInstall(root, env);
|
|
77
|
+
if (homebrewRun.code !== 0) {
|
|
78
|
+
return persistSelfHeal(root, input.missionDir, {
|
|
79
|
+
schema: 'sks.zellij-self-heal.v1',
|
|
80
|
+
ok: false,
|
|
81
|
+
requested_by: input.requestedBy,
|
|
82
|
+
fix_requested: true,
|
|
83
|
+
auto_approved: autoApproved,
|
|
84
|
+
install_homebrew_allowed: true,
|
|
85
|
+
before,
|
|
86
|
+
latest_version: latest,
|
|
87
|
+
strategy: 'failed',
|
|
88
|
+
command: HOMEBREW_INSTALL_COMMAND,
|
|
89
|
+
after: before,
|
|
90
|
+
mutation_guard_artifact: mutationArtifact,
|
|
91
|
+
homebrew: { present: false, bin: null, install_attempted: true, install_allowed: true },
|
|
92
|
+
blockers: [`homebrew_install_failed:${tail(homebrewRun.stderr || homebrewRun.stdout || 'unknown')}`],
|
|
93
|
+
warnings: []
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const afterBrew = await findBrew(env);
|
|
97
|
+
brewBin = afterBrew.bin || brew.bin || 'brew';
|
|
98
|
+
}
|
|
99
|
+
if (!brewBin) {
|
|
100
|
+
return manualResult(root, input, env, before, latest, brew, 'homebrew_missing');
|
|
101
|
+
}
|
|
102
|
+
const install = before.status === 'missing';
|
|
103
|
+
const brewArgs = install ? ['install', 'zellij'] : ['upgrade', 'zellij'];
|
|
104
|
+
const strategy = !brew.present && homebrewInstallAttempted ? 'brew-install-homebrew-then-zellij'
|
|
105
|
+
: install ? 'brew-install-zellij'
|
|
106
|
+
: 'brew-upgrade-zellij';
|
|
107
|
+
const command = `brew ${brewArgs.join(' ')}`;
|
|
108
|
+
const run = await runZellijBrew(root, env, brewBin, brewArgs, command);
|
|
109
|
+
if (run.code !== 0 && /already installed|already up-to-date/i.test(`${run.stdout}\n${run.stderr}`)) {
|
|
110
|
+
const after = await capabilitySnapshot(root, env, 'after-noop');
|
|
111
|
+
return persistSelfHeal(root, input.missionDir, {
|
|
112
|
+
schema: 'sks.zellij-self-heal.v1',
|
|
113
|
+
ok: true,
|
|
114
|
+
requested_by: input.requestedBy,
|
|
115
|
+
fix_requested: true,
|
|
116
|
+
auto_approved: autoApproved,
|
|
117
|
+
install_homebrew_allowed: homebrewInstallAllowed,
|
|
118
|
+
before,
|
|
119
|
+
latest_version: latest,
|
|
120
|
+
strategy: 'none-current',
|
|
121
|
+
command,
|
|
122
|
+
after: compactCapability(after, latest || before.version),
|
|
123
|
+
mutation_guard_artifact: mutationArtifact,
|
|
124
|
+
homebrew: { present: true, bin: brewBin, install_attempted: homebrewInstallAttempted, install_allowed: homebrewInstallAllowed },
|
|
125
|
+
blockers: [],
|
|
126
|
+
warnings: ['brew_reported_already_current']
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (run.code !== 0) {
|
|
130
|
+
return persistSelfHeal(root, input.missionDir, {
|
|
131
|
+
schema: 'sks.zellij-self-heal.v1',
|
|
132
|
+
ok: false,
|
|
133
|
+
requested_by: input.requestedBy,
|
|
134
|
+
fix_requested: true,
|
|
135
|
+
auto_approved: autoApproved,
|
|
136
|
+
install_homebrew_allowed: homebrewInstallAllowed,
|
|
137
|
+
before,
|
|
138
|
+
latest_version: latest,
|
|
139
|
+
strategy: 'failed',
|
|
140
|
+
command,
|
|
141
|
+
after: before,
|
|
142
|
+
mutation_guard_artifact: mutationArtifact,
|
|
143
|
+
homebrew: { present: true, bin: brewBin, install_attempted: homebrewInstallAttempted, install_allowed: homebrewInstallAllowed },
|
|
144
|
+
blockers: [`zellij_brew_repair_failed:${tail(run.stderr || run.stdout || 'unknown')}`],
|
|
145
|
+
warnings: []
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const afterReport = await capabilitySnapshot(root, env, 'after', latest || env.SKS_ZELLIJ_SELF_HEAL_AFTER_VERSION || '0.44.0');
|
|
149
|
+
const after = compactCapability(afterReport, latest || before.version || '0.44.0');
|
|
150
|
+
const ok = after.status === 'ok';
|
|
151
|
+
return persistSelfHeal(root, input.missionDir, {
|
|
152
|
+
schema: 'sks.zellij-self-heal.v1',
|
|
153
|
+
ok,
|
|
154
|
+
requested_by: input.requestedBy,
|
|
155
|
+
fix_requested: true,
|
|
156
|
+
auto_approved: autoApproved,
|
|
157
|
+
install_homebrew_allowed: homebrewInstallAllowed,
|
|
158
|
+
before,
|
|
159
|
+
latest_version: latest,
|
|
160
|
+
strategy,
|
|
161
|
+
command,
|
|
162
|
+
after,
|
|
163
|
+
mutation_guard_artifact: mutationArtifact,
|
|
164
|
+
homebrew: { present: true, bin: brewBin, install_attempted: homebrewInstallAttempted, install_allowed: homebrewInstallAllowed },
|
|
165
|
+
blockers: ok ? [] : ['zellij_repair_completed_but_capability_not_ok'],
|
|
166
|
+
warnings: []
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async function capabilitySnapshot(root, env, phase, fallbackVersion) {
|
|
170
|
+
const fakeStatus = phase === 'before' ? env.SKS_ZELLIJ_SELF_HEAL_BEFORE_STATUS : env.SKS_ZELLIJ_SELF_HEAL_AFTER_STATUS;
|
|
171
|
+
if (fakeStatus) {
|
|
172
|
+
const version = phase === 'before'
|
|
173
|
+
? (env.SKS_ZELLIJ_SELF_HEAL_BEFORE_VERSION || (fakeStatus === 'missing' ? null : '0.40.0'))
|
|
174
|
+
: (env.SKS_ZELLIJ_SELF_HEAL_AFTER_VERSION || fallbackVersion || '0.44.0');
|
|
175
|
+
return fakeCapability(String(fakeStatus), version);
|
|
176
|
+
}
|
|
177
|
+
return checkZellijCapability({ root, require: false, writeReport: false, env }).catch((err) => ({
|
|
178
|
+
schema: 'sks.zellij-capability.v1',
|
|
179
|
+
generated_at: nowIso(),
|
|
180
|
+
ok: false,
|
|
181
|
+
status: 'blocked',
|
|
182
|
+
integration_optional: true,
|
|
183
|
+
require_zellij: false,
|
|
184
|
+
min_version: ZELLIJ_MIN_VERSION,
|
|
185
|
+
version: null,
|
|
186
|
+
bin: null,
|
|
187
|
+
command: ['zellij', '--version'],
|
|
188
|
+
docs_evidence: [],
|
|
189
|
+
blockers: [`zellij_capability_check_failed:${tail(err?.message || String(err))}`],
|
|
190
|
+
warnings: [],
|
|
191
|
+
operator_actions: ['Resolve the Zellij capability check failure, then rerun `sks doctor --fix --yes`.']
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
function fakeCapability(status, version) {
|
|
195
|
+
const normalized = status === 'ok' || status === 'missing' || status === 'too_old' || status === 'blocked' ? status : 'blocked';
|
|
196
|
+
return {
|
|
197
|
+
schema: 'sks.zellij-capability.v1',
|
|
198
|
+
generated_at: nowIso(),
|
|
199
|
+
ok: normalized === 'ok',
|
|
200
|
+
status: normalized,
|
|
201
|
+
integration_optional: true,
|
|
202
|
+
require_zellij: false,
|
|
203
|
+
min_version: ZELLIJ_MIN_VERSION,
|
|
204
|
+
version,
|
|
205
|
+
bin: 'zellij',
|
|
206
|
+
command: ['zellij', '--version'],
|
|
207
|
+
docs_evidence: [],
|
|
208
|
+
blockers: normalized === 'ok' ? [] : [`zellij_${normalized}`],
|
|
209
|
+
warnings: normalized === 'ok' ? [] : [`zellij_${normalized}_fixture`],
|
|
210
|
+
operator_actions: normalized === 'ok' ? [] : ['Install Zellij. On macOS: `brew install zellij`.']
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
function compactCapability(report, fallbackVersion) {
|
|
214
|
+
return {
|
|
215
|
+
status: report.status,
|
|
216
|
+
version: report.version || fallbackVersion || null,
|
|
217
|
+
bin: report.status === 'missing' ? null : report.bin || 'zellij'
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function latestZellijVersion(env) {
|
|
221
|
+
if (env.SKS_ZELLIJ_LATEST_VERSION)
|
|
222
|
+
return String(env.SKS_ZELLIJ_LATEST_VERSION).replace(/^v(?=\d)/, '');
|
|
223
|
+
const mod = await import('./zellij-update.js').catch(() => null);
|
|
224
|
+
if (!mod?.fetchLatestZellijVersion)
|
|
225
|
+
return null;
|
|
226
|
+
const result = await mod.fetchLatestZellijVersion({ env, timeoutMs: 1500 }).catch(() => null);
|
|
227
|
+
return result?.version || null;
|
|
228
|
+
}
|
|
229
|
+
async function findBrew(env) {
|
|
230
|
+
if (env.SKS_ZELLIJ_SELF_HEAL_BREW_PRESENT === '0')
|
|
231
|
+
return { present: false, bin: null };
|
|
232
|
+
if (env.SKS_FAKE_BREW_BIN)
|
|
233
|
+
return { present: true, bin: String(env.SKS_FAKE_BREW_BIN) };
|
|
234
|
+
for (const dir of String(env.PATH || process.env.PATH || '').split(path.delimiter).filter(Boolean)) {
|
|
235
|
+
const candidate = path.join(dir, process.platform === 'win32' ? 'brew.cmd' : 'brew');
|
|
236
|
+
try {
|
|
237
|
+
await fs.access(candidate);
|
|
238
|
+
return { present: true, bin: candidate };
|
|
239
|
+
}
|
|
240
|
+
catch { }
|
|
241
|
+
}
|
|
242
|
+
if (env.SKS_ZELLIJ_SELF_HEAL_BREW_PRESENT === '1')
|
|
243
|
+
return { present: true, bin: 'brew' };
|
|
244
|
+
return { present: false, bin: null };
|
|
245
|
+
}
|
|
246
|
+
async function runZellijBrew(root, env, brewBin, args, command) {
|
|
247
|
+
if (env.SKS_ZELLIJ_SELF_HEAL_FAKE_RUN === '1') {
|
|
248
|
+
await appendFakeBrewLog(env, args);
|
|
249
|
+
return { code: Number(env.SKS_ZELLIJ_SELF_HEAL_FAKE_RUN_CODE || 0), stdout: 'fake brew ok', stderr: '' };
|
|
250
|
+
}
|
|
251
|
+
const contract = createRequestedScopeContract({
|
|
252
|
+
route: 'zellij-self-heal',
|
|
253
|
+
userRequest: command,
|
|
254
|
+
projectRoot: root,
|
|
255
|
+
overrides: { package_install: true, zellij_install: true }
|
|
256
|
+
});
|
|
257
|
+
return guardedPackageInstall(guardContextForRoute(root, contract, command), 'zellij', {
|
|
258
|
+
confirmed: true,
|
|
259
|
+
command: brewBin,
|
|
260
|
+
args,
|
|
261
|
+
env,
|
|
262
|
+
timeoutMs: 180000,
|
|
263
|
+
maxOutputBytes: 256 * 1024
|
|
264
|
+
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
265
|
+
}
|
|
266
|
+
async function runHomebrewInstall(root, env) {
|
|
267
|
+
if (env.SKS_ZELLIJ_SELF_HEAL_FAKE_RUN === '1') {
|
|
268
|
+
await appendFakeBrewLog(env, ['install-homebrew']);
|
|
269
|
+
return { code: Number(env.SKS_ZELLIJ_SELF_HEAL_FAKE_HOMEBREW_CODE || 0), stdout: 'fake homebrew install ok', stderr: '' };
|
|
270
|
+
}
|
|
271
|
+
const contract = createRequestedScopeContract({
|
|
272
|
+
route: 'zellij-self-heal',
|
|
273
|
+
userRequest: HOMEBREW_INSTALL_COMMAND,
|
|
274
|
+
projectRoot: root,
|
|
275
|
+
overrides: { package_install: true, zellij_install: true }
|
|
276
|
+
});
|
|
277
|
+
return guardedPackageInstall(guardContextForRoute(root, contract, HOMEBREW_INSTALL_COMMAND), 'homebrew', {
|
|
278
|
+
confirmed: true,
|
|
279
|
+
command: '/bin/bash',
|
|
280
|
+
args: ['-c', 'curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh | /bin/bash'],
|
|
281
|
+
env,
|
|
282
|
+
timeoutMs: 600000,
|
|
283
|
+
maxOutputBytes: 256 * 1024
|
|
284
|
+
}).catch((err) => ({ code: 1, stdout: '', stderr: err?.message || String(err) }));
|
|
285
|
+
}
|
|
286
|
+
async function appendFakeBrewLog(env, args) {
|
|
287
|
+
if (!env.SKS_FAKE_BREW_LOG)
|
|
288
|
+
return;
|
|
289
|
+
await ensureDir(path.dirname(env.SKS_FAKE_BREW_LOG));
|
|
290
|
+
await fs.appendFile(env.SKS_FAKE_BREW_LOG, `${args.join(' ')}\n`, 'utf8');
|
|
291
|
+
}
|
|
292
|
+
async function manualResult(root, input, env, before, latest, brew, reason) {
|
|
293
|
+
const command = brew.present ? 'sks doctor --fix --yes' : 'sks doctor --fix --install-homebrew --yes';
|
|
294
|
+
return persistSelfHeal(root, input.missionDir, {
|
|
295
|
+
schema: 'sks.zellij-self-heal.v1',
|
|
296
|
+
ok: false,
|
|
297
|
+
requested_by: input.requestedBy,
|
|
298
|
+
fix_requested: input.fixRequested === true,
|
|
299
|
+
auto_approved: input.autoApprove === true,
|
|
300
|
+
install_homebrew_allowed: false,
|
|
301
|
+
before,
|
|
302
|
+
latest_version: latest,
|
|
303
|
+
strategy: 'manual-required',
|
|
304
|
+
command,
|
|
305
|
+
after: before,
|
|
306
|
+
mutation_guard_artifact: null,
|
|
307
|
+
homebrew: { present: brew.present, bin: brew.bin, install_attempted: false, install_allowed: false },
|
|
308
|
+
blockers: [reason],
|
|
309
|
+
warnings: []
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
async function headlessResult(root, input, before, latest, brew, reason) {
|
|
313
|
+
return persistSelfHeal(root, input.missionDir, {
|
|
314
|
+
schema: 'sks.zellij-self-heal.v1',
|
|
315
|
+
ok: true,
|
|
316
|
+
requested_by: input.requestedBy,
|
|
317
|
+
fix_requested: input.fixRequested === true,
|
|
318
|
+
auto_approved: input.autoApprove === true,
|
|
319
|
+
install_homebrew_allowed: false,
|
|
320
|
+
before,
|
|
321
|
+
latest_version: latest,
|
|
322
|
+
strategy: 'headless-fallback',
|
|
323
|
+
command: 'sks --mad --headless',
|
|
324
|
+
after: before,
|
|
325
|
+
mutation_guard_artifact: null,
|
|
326
|
+
homebrew: { present: brew.present, bin: brew.bin, install_attempted: false, install_allowed: false },
|
|
327
|
+
blockers: [],
|
|
328
|
+
warnings: [reason, 'live_panes=false']
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
async function persistSelfHeal(root, missionDir, result) {
|
|
332
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'zellij-self-heal.json'), result).catch(() => undefined);
|
|
333
|
+
if (missionDir)
|
|
334
|
+
await writeJsonAtomic(path.join(missionDir, 'zellij-self-heal.json'), result).catch(() => undefined);
|
|
335
|
+
return result;
|
|
336
|
+
}
|
|
337
|
+
async function askZellijRepairAllowed(question) {
|
|
338
|
+
if (!(process.stdin.isTTY && process.stdout.isTTY))
|
|
339
|
+
return false;
|
|
340
|
+
const rl = (await import('node:readline')).createInterface({ input: process.stdin, output: process.stdout });
|
|
341
|
+
try {
|
|
342
|
+
const answer = await new Promise((resolve) => rl.question(question, resolve));
|
|
343
|
+
const trimmed = String(answer || '').trim();
|
|
344
|
+
return trimmed === '' || /^(y|yes|예|네|응)$/i.test(trimmed);
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
rl.close();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function tail(value, limit = 1000) {
|
|
351
|
+
return String(value || '').replace(/\s+/g, ' ').trim().slice(-limit);
|
|
352
|
+
}
|
|
353
|
+
//# sourceMappingURL=zellij-self-heal.js.map
|
|
@@ -80,7 +80,7 @@ export async function checkZellijUpdateNotice(input = {}) {
|
|
|
80
80
|
message: 'Zellij update notice disabled by environment.'
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
|
-
const capability = await checkZellijCapability({ require: false, writeReport: false }).catch(() => null);
|
|
83
|
+
const capability = await checkZellijCapability({ require: false, writeReport: false, env }).catch(() => null);
|
|
84
84
|
const current = capability?.version || null;
|
|
85
85
|
const missing = capability?.status === 'missing' || !capability;
|
|
86
86
|
const fetchInput = { env };
|
|
@@ -117,7 +117,7 @@ export async function checkZellijUpdateNotice(input = {}) {
|
|
|
117
117
|
* the exact operator command, mirroring how the Codex CLI update flow behaves.
|
|
118
118
|
*/
|
|
119
119
|
export async function upgradeZellijToLatest(input = {}) {
|
|
120
|
-
const before = await checkZellijCapability({ require: false, writeReport: false }).catch(() => null);
|
|
120
|
+
const before = await checkZellijCapability({ require: false, writeReport: false, env: input.env || process.env }).catch(() => null);
|
|
121
121
|
const beforeVersion = before?.version || null;
|
|
122
122
|
const missing = before?.status === 'missing' || !before;
|
|
123
123
|
const latest = await fetchLatestZellijVersion({ env: input.env || process.env });
|
|
@@ -185,7 +185,7 @@ export async function upgradeZellijToLatest(input = {}) {
|
|
|
185
185
|
error: `${run.stderr || run.stdout || 'brew upgrade failed'}`.trim().slice(-1000)
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
|
-
const after = await checkZellijCapability({ require: false, writeReport: false }).catch(() => null);
|
|
188
|
+
const after = await checkZellijCapability({ require: false, writeReport: false, env: input.env || process.env }).catch(() => null);
|
|
189
189
|
return {
|
|
190
190
|
status: missing ? 'installed' : 'upgraded',
|
|
191
191
|
before_version: beforeVersion,
|
|
@@ -222,9 +222,34 @@ export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
|
|
|
222
222
|
if (!notice)
|
|
223
223
|
return { status: 'skipped', current: null, latest: null, command: null };
|
|
224
224
|
if (notice.zellij_missing) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
225
|
+
if (opts.selfHealOnMissing === true) {
|
|
226
|
+
const { repairZellijForSks } = await import('./zellij-self-heal.js');
|
|
227
|
+
const repaired = await repairZellijForSks({
|
|
228
|
+
root: opts.root || process.cwd(),
|
|
229
|
+
requestedBy: opts.label === 'MAD launch' ? 'sks --mad' : 'sks zellij update',
|
|
230
|
+
fixRequested: true,
|
|
231
|
+
autoApprove: opts.autoApprove === true || list.includes('--yes') || list.includes('-y'),
|
|
232
|
+
interactive: mode === 'interactive-prompt',
|
|
233
|
+
installHomebrew: opts.installHomebrew === true || list.includes('--install-homebrew'),
|
|
234
|
+
allowHeadlessFallback: opts.allowHeadlessFallback === true,
|
|
235
|
+
missionDir: opts.missionDir || null,
|
|
236
|
+
env
|
|
237
|
+
});
|
|
238
|
+
if (repaired.strategy === 'headless-fallback')
|
|
239
|
+
console.log('Zellij repair: headless fallback selected (live_panes=false).');
|
|
240
|
+
else if (repaired.ok && repaired.command)
|
|
241
|
+
console.log(`Zellij repair: ${repaired.strategy} via ${repaired.command}`);
|
|
242
|
+
else if (!repaired.ok)
|
|
243
|
+
console.log(`Zellij repair required. Run: ${repaired.command || notice.upgrade_command}`);
|
|
244
|
+
return {
|
|
245
|
+
status: repaired.ok ? (repaired.strategy === 'headless-fallback' ? 'missing' : 'installed') : 'manual_required',
|
|
246
|
+
current: repaired.after.version || repaired.before.version,
|
|
247
|
+
latest: repaired.latest_version,
|
|
248
|
+
command: repaired.command,
|
|
249
|
+
error: repaired.blockers[0] || null
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
console.log(`Zellij missing, required for sks --mad. Repairable with: sks doctor --fix --yes`);
|
|
228
253
|
return { status: 'missing', current: null, latest: notice.latest_version, command: notice.upgrade_command };
|
|
229
254
|
}
|
|
230
255
|
if (!notice.update_available) {
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import fsp from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { spawnSync } from 'node:child_process';
|
|
8
|
+
import { assertGate, emitGate, importDist, root } from './sks-1-18-gate-lib.js';
|
|
9
|
+
export async function runDirective314Gate(id) {
|
|
10
|
+
if (id.startsWith('zellij:'))
|
|
11
|
+
return zellijGate(id);
|
|
12
|
+
if (id.startsWith('doctor:zellij'))
|
|
13
|
+
return doctorZellijGate(id);
|
|
14
|
+
if (id.startsWith('mad:zellij'))
|
|
15
|
+
return madZellijGate(id);
|
|
16
|
+
if (id === 'lazycodex:analysis')
|
|
17
|
+
return lazycodexAnalysisGate(id);
|
|
18
|
+
if (id.startsWith('codex-app:') || id === 'doctor:codex-app-harness')
|
|
19
|
+
return codexAppGate(id);
|
|
20
|
+
if (id.startsWith('loop:'))
|
|
21
|
+
return loopGate(id);
|
|
22
|
+
if (id === 'lazycodex:interop-policy' || id === 'lazycodex:pattern-adoption-blackbox')
|
|
23
|
+
return lazycodexInteropGate(id);
|
|
24
|
+
throw new Error(`unknown_gate:${id}`);
|
|
25
|
+
}
|
|
26
|
+
async function zellijGate(id) {
|
|
27
|
+
const rootDir = await tempRoot(`sks-${id.replace(/[:/]/g, '-')}-`);
|
|
28
|
+
const selfHeal = await importDist('core/zellij/zellij-self-heal.js');
|
|
29
|
+
const policy = await importDist('core/zellij/homebrew-policy.js');
|
|
30
|
+
if (id === 'zellij:homebrew-policy') {
|
|
31
|
+
assertGate(policy.resolveHomebrewInstallPolicy({ env: {} }).allowed === false, 'Homebrew install must not be silent by default');
|
|
32
|
+
assertGate(policy.resolveHomebrewInstallPolicy({ installHomebrew: true }).allowed === false, 'Homebrew install flag alone must still require --yes or interactive/env approval');
|
|
33
|
+
assertGate(policy.resolveHomebrewInstallPolicy({ installHomebrew: true, autoApprove: true }).allowed === true, 'Homebrew install should be allowed with explicit flag+yes');
|
|
34
|
+
assertGate(policy.homebrewMissingDoctorMessage().includes('sks doctor --fix --install-homebrew --yes'), 'Homebrew policy must expose one-shot doctor command');
|
|
35
|
+
return emitGate(id, { fixtures: 3 });
|
|
36
|
+
}
|
|
37
|
+
if (id === 'zellij:update-missing-self-heal') {
|
|
38
|
+
const update = await importDist('core/zellij/zellij-update.js');
|
|
39
|
+
const result = await update.maybePromptZellijUpdateForLaunch(['--yes'], {
|
|
40
|
+
label: 'MAD launch',
|
|
41
|
+
root: rootDir,
|
|
42
|
+
selfHealOnMissing: true,
|
|
43
|
+
autoApprove: true,
|
|
44
|
+
env: fakeZellijEnv('missing', { brew: true })
|
|
45
|
+
});
|
|
46
|
+
assertGate(result.status === 'installed', 'missing update path must self-heal when requested', result);
|
|
47
|
+
return emitGate(id, { status: result.status });
|
|
48
|
+
}
|
|
49
|
+
const result = await selfHeal.repairZellijForSks({
|
|
50
|
+
root: rootDir,
|
|
51
|
+
requestedBy: 'doctor --fix',
|
|
52
|
+
fixRequested: true,
|
|
53
|
+
autoApprove: true,
|
|
54
|
+
installHomebrew: false,
|
|
55
|
+
env: fakeZellijEnv('missing', { brew: true })
|
|
56
|
+
});
|
|
57
|
+
assertGate(result.ok === true, 'zellij self-heal must succeed with fake brew present', result);
|
|
58
|
+
assertGate(result.strategy === 'brew-install-zellij', 'missing zellij must select brew-install-zellij', result);
|
|
59
|
+
assertGate(fs.existsSync(path.join(rootDir, '.sneakoscope', 'reports', 'zellij-self-heal.json')), 'self-heal artifact missing');
|
|
60
|
+
emitGate(id, { strategy: result.strategy });
|
|
61
|
+
}
|
|
62
|
+
async function doctorZellijGate(id) {
|
|
63
|
+
const rootDir = await tempRoot(`sks-${id.replace(/[:/]/g, '-')}-`);
|
|
64
|
+
const mod = await importDist('core/doctor/doctor-zellij-repair.js');
|
|
65
|
+
const env = fakeZellijEnv(id.includes('upgrade') ? 'too_old' : 'missing', { brew: !id.includes('no-homebrew') });
|
|
66
|
+
const previous = swapEnv(env);
|
|
67
|
+
try {
|
|
68
|
+
const result = await mod.runDoctorZellijRepair({ root: rootDir, args: ['--fix', '--yes'], doctorFix: true });
|
|
69
|
+
if (id.includes('no-homebrew')) {
|
|
70
|
+
assertGate(result.strategy === 'manual-required', 'no-homebrew doctor repair must be manual-required', result);
|
|
71
|
+
assertGate(String(result.command).includes('--install-homebrew'), 'manual no-homebrew path must show install-homebrew command', result);
|
|
72
|
+
}
|
|
73
|
+
else if (id.includes('upgrade')) {
|
|
74
|
+
assertGate(result.strategy === 'brew-upgrade-zellij', 'stale zellij must upgrade', result);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
assertGate(result.strategy === 'brew-install-zellij', 'missing zellij must install', result);
|
|
78
|
+
}
|
|
79
|
+
if (id === 'doctor:zellij-fix-output') {
|
|
80
|
+
const line = mod.doctorZellijRepairConsoleLine(result);
|
|
81
|
+
assertGate(!/optional live panes disabled/i.test(line), 'doctor repair output must not use optional/blocking wording', { line });
|
|
82
|
+
}
|
|
83
|
+
emitGate(id, { strategy: result.strategy });
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
restoreEnv(previous);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function madZellijGate(id) {
|
|
90
|
+
const source = fs.readFileSync(path.join(root, 'src/core/commands/mad-sks-command.ts'), 'utf8');
|
|
91
|
+
assertGate(source.includes("requestedBy: 'sks --mad'"), 'MAD must request zellij self-heal as sks --mad');
|
|
92
|
+
assertGate(source.includes('--headless') && source.includes('live_panes: false'), 'MAD must support headless live_panes=false');
|
|
93
|
+
assertGate(!/optional live panes disabled/.test(source), 'MAD source must not print optional live panes disabled');
|
|
94
|
+
if (id === 'mad:zellij-no-contradictory-output') {
|
|
95
|
+
const update = fs.readFileSync(path.join(root, 'src/core/zellij/zellij-update.ts'), 'utf8');
|
|
96
|
+
assertGate(!/Zellij not found \(optional live panes disabled\)/.test(update), 'Zellij missing output must not be contradictory optional wording');
|
|
97
|
+
}
|
|
98
|
+
emitGate(id, { source_checked: true });
|
|
99
|
+
}
|
|
100
|
+
async function lazycodexAnalysisGate(id) {
|
|
101
|
+
const mod = await importDist('core/codex-app/lazycodex-analysis.js');
|
|
102
|
+
const report = await mod.writeLazyCodexPatternAnalysis(root);
|
|
103
|
+
assertGate(report.patterns.length >= 14, 'LazyCodex analysis must include required patterns', report);
|
|
104
|
+
const docs = mod.renderLazyCodexAnalysisMarkdown(report);
|
|
105
|
+
await fsp.writeFile(path.join(root, 'docs', 'lazycodex-analysis.md'), `${docs}\n`, 'utf8');
|
|
106
|
+
emitGate(id, { patterns: report.patterns.length });
|
|
107
|
+
}
|
|
108
|
+
async function codexAppGate(id) {
|
|
109
|
+
const rootDir = await tempRoot(`sks-${id.replace(/[:/]/g, '-')}-`);
|
|
110
|
+
const previous = swapEnv({
|
|
111
|
+
SKS_CODEX_0138_FAKE: '1',
|
|
112
|
+
SKS_CODEX_0139_FAKE: '1',
|
|
113
|
+
SKS_CODEX_PLUGIN_JSON_FAKE: '1',
|
|
114
|
+
SKS_CODEX_AGENT_TYPE_SUPPORTED: id.includes('blackbox') ? '1' : ''
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
if (id === 'codex-app:harness-matrix' || id === 'doctor:codex-app-harness' || id === 'codex-app:harness-blackbox') {
|
|
118
|
+
const mod = await importDist('core/codex-app/codex-app-harness-matrix.js');
|
|
119
|
+
const matrix = await mod.buildCodexAppHarnessMatrix({ root: rootDir });
|
|
120
|
+
assertGate(matrix.schema === 'sks.codex-app-harness-matrix.v1', 'harness matrix schema mismatch', matrix);
|
|
121
|
+
assertGate(matrix.app_features.plugin_json === true, 'fixture should expose plugin_json', matrix);
|
|
122
|
+
if (id === 'doctor:codex-app-harness') {
|
|
123
|
+
const doctor = fs.readFileSync(path.join(root, 'src/commands/doctor.ts'), 'utf8');
|
|
124
|
+
assertGate(doctor.includes('Codex App Harness:'), 'doctor output must include Codex App Harness section');
|
|
125
|
+
assertGate(doctor.includes('codex_app_harness_matrix'), 'doctor JSON must include codex_app_harness_matrix');
|
|
126
|
+
}
|
|
127
|
+
return emitGate(id, { ok: matrix.ok, warnings: matrix.warnings.length });
|
|
128
|
+
}
|
|
129
|
+
if (id === 'codex-app:skill-sync' || id === 'codex-app:skill-agent-blackbox') {
|
|
130
|
+
const mod = await importDist('core/codex-app/codex-skill-sync.js');
|
|
131
|
+
const skillsRoot = path.join(rootDir, 'skills');
|
|
132
|
+
await fsp.mkdir(path.join(skillsRoot, 'ulw-loop'), { recursive: true });
|
|
133
|
+
const report = await mod.syncCodexSksSkills({ root: rootDir, skillsRoot, apply: true });
|
|
134
|
+
assertGate(report.interop.clobbered_lazycodex === false && report.lazycodex_reserved_present.includes('ulw-loop'), 'skill sync must preserve LazyCodex skills', report);
|
|
135
|
+
return emitGate(id, { desired: report.desired_skills.length });
|
|
136
|
+
}
|
|
137
|
+
if (id === 'codex-app:agent-role-sync') {
|
|
138
|
+
const mod = await importDist('core/codex-app/codex-agent-role-sync.js');
|
|
139
|
+
const report = await mod.syncCodexAgentRoles({ root: rootDir, codexHome: path.join(rootDir, 'codex-home'), apply: true, agentTypeSupported: true });
|
|
140
|
+
assertGate(report.fallback === 'agent_type', 'agent role sync should use agent_type when supported', report);
|
|
141
|
+
return emitGate(id, { roles: report.directive_roles.length });
|
|
142
|
+
}
|
|
143
|
+
if (id === 'codex-app:init-deep') {
|
|
144
|
+
const mod = await importDist('core/codex-app/codex-init-deep.js');
|
|
145
|
+
await fsp.mkdir(path.join(rootDir, 'src/core/zellij'), { recursive: true });
|
|
146
|
+
await fsp.writeFile(path.join(rootDir, 'src/core/zellij/a.ts'), 'export {}\n');
|
|
147
|
+
const report = await mod.runCodexInitDeep({ root: rootDir, apply: true });
|
|
148
|
+
assertGate(report.root_agents_preserved === true, 'init-deep must preserve user AGENTS.md', report);
|
|
149
|
+
return emitGate(id, { guidance: report.directory_guidance.length });
|
|
150
|
+
}
|
|
151
|
+
if (id === 'codex-app:hook-lifecycle') {
|
|
152
|
+
const mod = await importDist('core/codex-app/codex-hook-lifecycle.js');
|
|
153
|
+
const report = await mod.buildCodexHookLifecycle({ root: rootDir });
|
|
154
|
+
assertGate(report.approval_state === 'unknown', 'hook lifecycle must report unknown approval when not detectable', report);
|
|
155
|
+
return emitGate(id, { lifecycle: Object.keys(report.lifecycle).length });
|
|
156
|
+
}
|
|
157
|
+
if (id === 'codex-app:execution-profile') {
|
|
158
|
+
const mod = await importDist('core/codex-app/codex-app-execution-profile.js');
|
|
159
|
+
const profile = await mod.resolveCodexAppExecutionProfile({ root: rootDir });
|
|
160
|
+
assertGate(['codex-app-native', 'codex-cli-headless', 'sks-loop-headless', 'degraded-no-app'].includes(profile.mode), 'execution profile mode invalid', profile);
|
|
161
|
+
return emitGate(id, { mode: profile.mode });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
restoreEnv(previous);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function loopGate(id) {
|
|
169
|
+
const rootDir = await tempRoot(`sks-${id.replace(/[:/]/g, '-')}-`);
|
|
170
|
+
if (id === 'loop:planner-project-memory') {
|
|
171
|
+
const init = await importDist('core/codex-app/codex-init-deep.js');
|
|
172
|
+
const planner = await importDist('core/loops/loop-planner.js');
|
|
173
|
+
await fsp.mkdir(path.join(rootDir, 'src/core/loops'), { recursive: true });
|
|
174
|
+
await fsp.writeFile(path.join(rootDir, 'src/core/loops/a.ts'), 'export {}\n');
|
|
175
|
+
await init.runCodexInitDeep({ root: rootDir, apply: true });
|
|
176
|
+
const plan = await planner.planLoopsFromRequest({ root: rootDir, missionId: 'M-loop-memory', request: 'update loop planner project memory', sourceCommand: 'loop' });
|
|
177
|
+
assertGate(plan.project_memory?.injected === true, 'loop planner must consume init-deep memory hints', plan);
|
|
178
|
+
return emitGate(id, { injected: true });
|
|
179
|
+
}
|
|
180
|
+
const planDir = path.join(rootDir, '.sneakoscope', 'missions', 'M-loop-cont', 'loops');
|
|
181
|
+
await fsp.mkdir(planDir, { recursive: true });
|
|
182
|
+
await fsp.writeFile(path.join(rootDir, '.sneakoscope', 'missions', 'M-loop-cont', 'loops', 'loop-plan.json'), JSON.stringify({ graph: { nodes: [{ loop_id: 'loop-a' }] } }));
|
|
183
|
+
const mod = await importDist('core/loops/loop-continuation-enforcer.js');
|
|
184
|
+
const report = await mod.evaluateLoopContinuation({ root: rootDir, missionId: 'M-loop-cont' });
|
|
185
|
+
assertGate(report.should_continue === true, 'loop continuation should request resume when proof missing', report);
|
|
186
|
+
emitGate(id, { should_continue: report.should_continue });
|
|
187
|
+
}
|
|
188
|
+
async function lazycodexInteropGate(id) {
|
|
189
|
+
const rootDir = await tempRoot(`sks-${id.replace(/[:/]/g, '-')}-`);
|
|
190
|
+
const previous = swapEnv({ SKS_CODEX_PLUGIN_JSON_FAKE: '1' });
|
|
191
|
+
try {
|
|
192
|
+
const mod = await importDist('core/codex-app/lazycodex-interop-policy.js');
|
|
193
|
+
const skillsRoot = path.join(rootDir, '.codex', 'skills');
|
|
194
|
+
await fsp.mkdir(path.join(skillsRoot, 'start-work'), { recursive: true });
|
|
195
|
+
const report = await mod.buildLazyCodexInteropPolicy({ root: rootDir, codexHome: path.join(rootDir, '.codex') });
|
|
196
|
+
assertGate(report.policy.clobber_lazycodex_skills === false, 'interop policy must not clobber LazyCodex skills', report);
|
|
197
|
+
emitGate(id, { detected: report.lazycodex_detected, collisions: report.detection.collisions.length });
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
restoreEnv(previous);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function fakeZellijEnv(status, opts = {}) {
|
|
204
|
+
return {
|
|
205
|
+
...process.env,
|
|
206
|
+
SKS_ZELLIJ_CAPABILITY_FAKE_STATUS: status,
|
|
207
|
+
SKS_ZELLIJ_CAPABILITY_FAKE_VERSION: status === 'too_old' ? '0.40.0' : '0.44.0',
|
|
208
|
+
SKS_ZELLIJ_SELF_HEAL_BEFORE_STATUS: status,
|
|
209
|
+
SKS_ZELLIJ_SELF_HEAL_BEFORE_VERSION: status === 'too_old' ? '0.40.0' : '',
|
|
210
|
+
SKS_ZELLIJ_SELF_HEAL_AFTER_STATUS: 'ok',
|
|
211
|
+
SKS_ZELLIJ_SELF_HEAL_AFTER_VERSION: '0.44.3',
|
|
212
|
+
SKS_ZELLIJ_LATEST_VERSION: '0.44.3',
|
|
213
|
+
SKS_ZELLIJ_SELF_HEAL_FAKE_RUN: '1',
|
|
214
|
+
SKS_ZELLIJ_SELF_HEAL_BREW_PRESENT: opts.brew ? '1' : '0'
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async function tempRoot(prefix) {
|
|
218
|
+
const dir = await fsp.mkdtemp(path.join(os.tmpdir(), prefix));
|
|
219
|
+
await fsp.mkdir(path.join(dir, '.sneakoscope', 'reports'), { recursive: true });
|
|
220
|
+
return dir;
|
|
221
|
+
}
|
|
222
|
+
function swapEnv(next) {
|
|
223
|
+
const previous = {};
|
|
224
|
+
for (const [key, value] of Object.entries(next)) {
|
|
225
|
+
previous[key] = process.env[key];
|
|
226
|
+
if (value === '')
|
|
227
|
+
delete process.env[key];
|
|
228
|
+
else
|
|
229
|
+
process.env[key] = value;
|
|
230
|
+
}
|
|
231
|
+
return previous;
|
|
232
|
+
}
|
|
233
|
+
function restoreEnv(previous) {
|
|
234
|
+
for (const [key, value] of Object.entries(previous)) {
|
|
235
|
+
if (value === undefined)
|
|
236
|
+
delete process.env[key];
|
|
237
|
+
else
|
|
238
|
+
process.env[key] = value;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=sks-3-1-4-directive-check-lib.js.map
|