sneakoscope 2.0.15 → 2.0.17
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 +5 -3
- 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/cli/command-registry.js +1 -1
- package/dist/commands/proof.js +21 -0
- package/dist/commands/zellij-slot-pane.js +7 -1
- package/dist/core/agents/agent-orchestrator.js +68 -3
- package/dist/core/agents/agent-scheduler.js +217 -86
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/agents/native-cli-session-swarm.js +97 -27
- package/dist/core/agents/native-cli-worker.js +56 -7
- package/dist/core/agents/parallel-runtime-proof.js +276 -0
- package/dist/core/agents/runtime-proof-summary.js +75 -0
- package/dist/core/codex-control/codex-task-runner.js +32 -4
- package/dist/core/codex-control/model-call-concurrency.js +106 -0
- package/dist/core/commands/naruto-command.js +65 -8
- package/dist/core/commands/team-command.js +6 -487
- package/dist/core/commands/team-legacy-observe-command.js +182 -0
- package/dist/core/db-safety.js +49 -6
- package/dist/core/feature-registry.js +4 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/git/git-worktree-capability.js +18 -0
- package/dist/core/git/git-worktree-manager.js +80 -0
- package/dist/core/git/git-worktree-pool.js +4 -0
- package/dist/core/hooks-runtime.js +41 -4
- package/dist/core/init.js +1 -0
- package/dist/core/mad-db/mad-db-capability.js +42 -2
- package/dist/core/mad-db/mad-db-ledger.js +14 -0
- package/dist/core/mad-db/mad-db-policy-resolver.js +2 -0
- package/dist/core/mad-db/mad-db-result-lifecycle.js +136 -0
- package/dist/core/naruto/naruto-concurrency-governor.js +14 -1
- package/dist/core/release/release-gate-affected-selector.js +47 -5
- package/dist/core/release/release-gate-dag.js +5 -1
- package/dist/core/release/release-gate-scheduler.js +2 -1
- package/dist/core/routes.js +3 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-slot-pane-renderer.js +74 -1
- package/dist/core/zellij/zellij-slot-telemetry.js +81 -3
- package/dist/core/zellij/zellij-ui-mode.js +12 -2
- package/dist/scripts/prepublish-release-check-or-fast.js +3 -3
- package/dist/scripts/release-speed-summary.js +23 -1
- package/package.json +38 -3
- package/schemas/agents/parallel-runtime-proof.schema.json +79 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { appendJsonlBounded, nowIso, readJson, readText, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
import { missionDir } from '../mission.js';
|
|
5
|
+
import { appendMadDbOperationLifecycle } from './mad-db-ledger.js';
|
|
6
|
+
const PENDING_FILE = 'mad-db-lifecycle-pending.jsonl';
|
|
7
|
+
const PENDING_LATEST_FILE = 'mad-db-lifecycle-pending.latest.json';
|
|
8
|
+
export async function recordPendingMadDbLifecycleHook(root, missionId, hook) {
|
|
9
|
+
const dir = missionDir(root, missionId);
|
|
10
|
+
const row = {
|
|
11
|
+
schema: 'sks.mad-db-lifecycle-pending.v1',
|
|
12
|
+
ts: nowIso(),
|
|
13
|
+
mission_id: missionId,
|
|
14
|
+
hook
|
|
15
|
+
};
|
|
16
|
+
await appendJsonlBounded(path.join(dir, PENDING_FILE), row);
|
|
17
|
+
await writeJsonAtomic(path.join(dir, PENDING_LATEST_FILE), row).catch(() => undefined);
|
|
18
|
+
return row;
|
|
19
|
+
}
|
|
20
|
+
export async function readLatestPendingMadDbLifecycleHook(root, missionId, payload = {}) {
|
|
21
|
+
const dir = missionDir(root, missionId);
|
|
22
|
+
const embedded = lifecycleHookFromUnknown(payload);
|
|
23
|
+
if (embedded)
|
|
24
|
+
return embedded;
|
|
25
|
+
const latest = await readJson(path.join(dir, PENDING_LATEST_FILE), null).catch(() => null);
|
|
26
|
+
const latestHook = lifecycleHookFromUnknown(latest?.hook);
|
|
27
|
+
if (latestHook && hookMatchesPayload(latestHook, payload))
|
|
28
|
+
return latestHook;
|
|
29
|
+
const text = await readText(path.join(dir, PENDING_FILE), '').catch(() => '');
|
|
30
|
+
const rows = String(text).split(/\r?\n/).map((line) => line.trim()).filter(Boolean).reverse();
|
|
31
|
+
for (const line of rows.slice(0, 50)) {
|
|
32
|
+
try {
|
|
33
|
+
const row = JSON.parse(line);
|
|
34
|
+
const hook = lifecycleHookFromUnknown(row?.hook);
|
|
35
|
+
if (hook && hookMatchesPayload(hook, payload))
|
|
36
|
+
return hook;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Ignore malformed pending rows.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
export async function recordMadDbToolResult(input) {
|
|
45
|
+
const terminalType = input.ok ? 'db_operation.succeeded' : 'db_operation.failed';
|
|
46
|
+
if (await hasTerminalLifecycleEvent(input.root, input.missionId, input.hook.operation_id)) {
|
|
47
|
+
return {
|
|
48
|
+
schema: 'sks.mad-db-tool-result-lifecycle.v1',
|
|
49
|
+
ok: true,
|
|
50
|
+
skipped: true,
|
|
51
|
+
reason: 'mad_db_operation_terminal_event_already_recorded',
|
|
52
|
+
operation_id: input.hook.operation_id
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const event = await appendMadDbOperationLifecycle(input.root, input.missionId, {
|
|
56
|
+
type: terminalType,
|
|
57
|
+
operationId: input.hook.operation_id,
|
|
58
|
+
cycleId: input.hook.cycle_id || null,
|
|
59
|
+
mcpServer: input.hook.mcp_server || null,
|
|
60
|
+
toolName: input.hook.tool_name || null,
|
|
61
|
+
sqlHash: input.hook.sql_hash || null,
|
|
62
|
+
destructive: input.hook.destructive === true,
|
|
63
|
+
resultStatus: input.ok ? 'succeeded' : 'failed',
|
|
64
|
+
rowCount: input.rowCount ?? null,
|
|
65
|
+
error: input.error || null
|
|
66
|
+
});
|
|
67
|
+
await markPendingHookResolved(input.root, input.missionId, input.hook, input.ok);
|
|
68
|
+
return {
|
|
69
|
+
schema: 'sks.mad-db-tool-result-lifecycle.v1',
|
|
70
|
+
ok: true,
|
|
71
|
+
skipped: false,
|
|
72
|
+
operation_id: input.hook.operation_id,
|
|
73
|
+
result_status: input.ok ? 'succeeded' : 'failed',
|
|
74
|
+
event
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function lifecycleHookFromUnknown(value) {
|
|
78
|
+
const candidate = value?.ledger_result_hook || value?.mad_db?.ledger_result_hook || value;
|
|
79
|
+
const missionId = stringOrNull(candidate?.mission_id || candidate?.missionId);
|
|
80
|
+
const operationId = stringOrNull(candidate?.operation_id || candidate?.operationId);
|
|
81
|
+
if (!missionId || !operationId)
|
|
82
|
+
return null;
|
|
83
|
+
return {
|
|
84
|
+
mission_id: missionId,
|
|
85
|
+
operation_id: operationId,
|
|
86
|
+
cycle_id: stringOrNull(candidate?.cycle_id || candidate?.cycleId),
|
|
87
|
+
tool_name: stringOrNull(candidate?.tool_name || candidate?.toolName),
|
|
88
|
+
sql_hash: stringOrNull(candidate?.sql_hash || candidate?.sqlHash),
|
|
89
|
+
mcp_server: stringOrNull(candidate?.mcp_server || candidate?.mcpServer),
|
|
90
|
+
destructive: candidate?.destructive === true
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function hookMatchesPayload(hook, payload) {
|
|
94
|
+
if (!hook.tool_name)
|
|
95
|
+
return true;
|
|
96
|
+
const toolText = [
|
|
97
|
+
payload.tool_name,
|
|
98
|
+
payload.toolName,
|
|
99
|
+
payload.name,
|
|
100
|
+
payload.tool?.name,
|
|
101
|
+
payload.server,
|
|
102
|
+
payload.mcp_tool,
|
|
103
|
+
payload.tool,
|
|
104
|
+
payload.type
|
|
105
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
106
|
+
if (!toolText)
|
|
107
|
+
return true;
|
|
108
|
+
return toolText.includes(String(hook.tool_name).toLowerCase()) || String(hook.tool_name).toLowerCase().includes(toolText);
|
|
109
|
+
}
|
|
110
|
+
async function hasTerminalLifecycleEvent(root, missionId, operationId) {
|
|
111
|
+
const ledger = path.join(missionDir(root, missionId), 'mad-db-ledger.jsonl');
|
|
112
|
+
const text = await readText(ledger, '').catch(() => '');
|
|
113
|
+
return String(text).split(/\r?\n/).some((line) => {
|
|
114
|
+
if (!line.includes(operationId))
|
|
115
|
+
return false;
|
|
116
|
+
return line.includes('db_operation.succeeded') || line.includes('db_operation.failed');
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
async function markPendingHookResolved(root, missionId, hook, ok) {
|
|
120
|
+
const dir = missionDir(root, missionId);
|
|
121
|
+
const row = {
|
|
122
|
+
schema: 'sks.mad-db-lifecycle-pending-resolution.v1',
|
|
123
|
+
ts: nowIso(),
|
|
124
|
+
mission_id: missionId,
|
|
125
|
+
operation_id: hook.operation_id,
|
|
126
|
+
cycle_id: hook.cycle_id || null,
|
|
127
|
+
result_status: ok ? 'succeeded' : 'failed'
|
|
128
|
+
};
|
|
129
|
+
await appendJsonlBounded(path.join(dir, 'mad-db-lifecycle-resolved.jsonl'), row).catch(() => undefined);
|
|
130
|
+
await fs.rm(path.join(dir, PENDING_LATEST_FILE), { force: true }).catch(() => undefined);
|
|
131
|
+
}
|
|
132
|
+
function stringOrNull(value) {
|
|
133
|
+
const text = String(value || '').trim();
|
|
134
|
+
return text ? text : null;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=mad-db-result-lifecycle.js.map
|
|
@@ -9,6 +9,7 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
9
9
|
const hardware = probeHardwareCapacity(input.hardware || {});
|
|
10
10
|
const zellijVisiblePaneCap = normalizePositiveInt(input.zellijVisiblePaneCap, Math.min(8, Math.max(4, Math.floor(hardware.terminal_rows / 5))));
|
|
11
11
|
const backend = String(input.backend || 'codex-sdk');
|
|
12
|
+
const parallelismMode = normalizeParallelismMode(input.parallelismMode);
|
|
12
13
|
const freeGb = hardware.free_memory_bytes / (1024 * 1024 * 1024);
|
|
13
14
|
const totalGb = hardware.total_memory_bytes / (1024 * 1024 * 1024);
|
|
14
15
|
const reclaimableFloorGb = totalGb >= 32 ? 16 : totalGb >= 16 ? 8 : totalGb >= 8 ? 4 : Math.max(1, freeGb);
|
|
@@ -33,7 +34,12 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
33
34
|
const rawSafe = Math.max(1, Math.min(requestedClones, totalWorkItems, memoryCap, fdCap, cpuCap + ioCap, gitWorktreeCap + processCap, backendBudget, queueCap, leaseCap, 100));
|
|
34
35
|
const pressure = monitorNarutoResourcePressure(hardware, { activeWorkers: rawSafe, zellijVisiblePaneCap });
|
|
35
36
|
const backpressure = applyNarutoBackpressure(rawSafe, pressure);
|
|
36
|
-
const
|
|
37
|
+
const currentSafeActiveWorkers = Math.max(1, Math.min(rawSafe, backpressure.adjusted_active_workers));
|
|
38
|
+
const safeActiveWorkers = parallelismMode === 'extreme'
|
|
39
|
+
? rawSafe
|
|
40
|
+
: parallelismMode === 'balanced'
|
|
41
|
+
? Math.max(1, Math.min(rawSafe, Math.max(16, Math.floor(rawSafe * 0.75))))
|
|
42
|
+
: currentSafeActiveWorkers;
|
|
37
43
|
const safeVisible = Math.min(safeActiveWorkers, zellijVisiblePaneCap);
|
|
38
44
|
const reasons = [
|
|
39
45
|
...(memoryCap < requestedClones ? ['memory_cap'] : []),
|
|
@@ -60,11 +66,18 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
60
66
|
git_worktree_parallel: gitWorktreeCap,
|
|
61
67
|
cpu_io_parallel: cpuCap + ioCap,
|
|
62
68
|
verification_parallel: Math.max(1, Math.min(hardware.cpu_core_count * 2, safeActiveWorkers, 16)),
|
|
69
|
+
parallelism_mode: parallelismMode,
|
|
63
70
|
reasons: [...new Set(reasons)],
|
|
64
71
|
backpressure: backpressure.backpressure,
|
|
65
72
|
hardware
|
|
66
73
|
};
|
|
67
74
|
}
|
|
75
|
+
function normalizeParallelismMode(value) {
|
|
76
|
+
const text = String(value || process.env.SKS_NARUTO_PARALLELISM || 'extreme').toLowerCase();
|
|
77
|
+
if (text === 'safe' || text === 'balanced' || text === 'extreme')
|
|
78
|
+
return text;
|
|
79
|
+
return 'extreme';
|
|
80
|
+
}
|
|
68
81
|
function normalizePositiveInt(value, fallback) {
|
|
69
82
|
const parsed = Number(value);
|
|
70
83
|
if (!Number.isFinite(parsed) || parsed < 1)
|
|
@@ -10,7 +10,7 @@ export function selectAffectedReleaseGates(root, manifest, gates, input = {}) {
|
|
|
10
10
|
if (input.full) {
|
|
11
11
|
return selectionResult(gates, gates, [], 'full', {}, []);
|
|
12
12
|
}
|
|
13
|
-
const changedFiles = resolveChangedFiles(root, input.changedSince || 'auto');
|
|
13
|
+
const changedFiles = input.changedFiles ? [...new Set(input.changedFiles)].sort() : resolveChangedFiles(root, input.changedSince || 'auto');
|
|
14
14
|
const selected = [];
|
|
15
15
|
const reasons = {};
|
|
16
16
|
for (const gate of gates) {
|
|
@@ -28,7 +28,9 @@ export function selectAffectedReleaseGates(root, manifest, gates, input = {}) {
|
|
|
28
28
|
reasons[gate.id] = 'always_keep_core_release_safety';
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
-
const expanded =
|
|
31
|
+
const expanded = input.preset === 'affected' || input.preset === 'fast'
|
|
32
|
+
? selected
|
|
33
|
+
: expandWithDependencies(selected, manifest);
|
|
32
34
|
const ordered = manifest.gates.filter((gate) => expanded.some((row) => row.id === gate.id));
|
|
33
35
|
return selectionResult(gates, ordered, changedFiles, 'affected', reasons, gates.filter((gate) => !ordered.some((row) => row.id === gate.id)).map((gate) => gate.id));
|
|
34
36
|
}
|
|
@@ -53,19 +55,32 @@ function gateSelectionReason(gate, changedFiles, preset) {
|
|
|
53
55
|
return 'always_keep_core_release_safety';
|
|
54
56
|
if (!changedFiles.length)
|
|
55
57
|
return preset === 'fast' ? 'fast_no_diff_core_only_skip' : 'no_changed_files';
|
|
58
|
+
const releaseGate = /^(release:|publish:|prepublish)/.test(gate.id);
|
|
56
59
|
if (changedFiles.some((file) => file === 'package.json' || file === 'package-lock.json')) {
|
|
57
60
|
if (/^(release:|publish:|prepublish|runtime:|typecheck|schema:check)/.test(gate.id))
|
|
58
61
|
return 'package_metadata_changed';
|
|
59
62
|
}
|
|
60
|
-
if (changedFiles.some((file) => file === 'release-gates.v2.json' || file.startsWith('src/core/release/')
|
|
61
|
-
|
|
63
|
+
if (changedFiles.some((file) => file === 'release-gates.v2.json' || file.startsWith('src/core/release/'))) {
|
|
64
|
+
if (releaseGate)
|
|
65
|
+
return 'release_gate_system_changed';
|
|
66
|
+
}
|
|
67
|
+
const matchingReleaseScript = changedFiles.some((file) => releaseScriptGateCandidates(file).includes(gate.id));
|
|
68
|
+
if (matchingReleaseScript)
|
|
69
|
+
return 'release_script_changed';
|
|
70
|
+
if (changedFiles.some((file) => file.startsWith('src/scripts/prepublish-') || file.startsWith('src/scripts/publish-'))) {
|
|
71
|
+
if (releaseGate && gate.id === 'release:version-truth')
|
|
72
|
+
return 'publish_or_prepublish_script_changed';
|
|
73
|
+
}
|
|
74
|
+
if (changedFiles.some((file) => file.startsWith('src/scripts/scheduler-') || file.startsWith('src/core/scheduler/'))) {
|
|
75
|
+
return gate.id.startsWith('scheduler:') ? 'scheduler_source_changed' : null;
|
|
76
|
+
}
|
|
62
77
|
if (changedFiles.some((file) => file.startsWith('src/core/research/')))
|
|
63
78
|
return gate.id.startsWith('research:') ? 'research_source_changed' : null;
|
|
64
79
|
if (changedFiles.some((file) => file.startsWith('src/core/zellij/') || file.startsWith('src/commands/zellij')))
|
|
65
80
|
return gate.id.startsWith('zellij:') || gate.id.startsWith('agent:zellij') || gate.id.startsWith('naruto:zellij') ? 'zellij_source_changed' : null;
|
|
66
81
|
if (changedFiles.some((file) => file.includes('/db') || file.includes('mad-db') || file.includes('mcp')))
|
|
67
82
|
return /db|mcp|mad-db|mad-sks/.test(gate.id) ? 'db_mcp_or_mad_db_changed' : null;
|
|
68
|
-
const inputs = gate.cache?.inputs || [];
|
|
83
|
+
const inputs = (gate.cache?.inputs || []).filter((pattern) => !isBroadAffectedInput(pattern));
|
|
69
84
|
if (inputs.some((pattern) => changedFiles.some((file) => matchesGlobish(file, pattern))))
|
|
70
85
|
return 'cache_input_changed';
|
|
71
86
|
return null;
|
|
@@ -110,4 +125,31 @@ function matchesGlobish(file, pattern) {
|
|
|
110
125
|
return file.startsWith(normalized.slice(0, -1));
|
|
111
126
|
return false;
|
|
112
127
|
}
|
|
128
|
+
function isBroadAffectedInput(pattern) {
|
|
129
|
+
const normalized = pattern.replace(/\\/g, '/');
|
|
130
|
+
return new Set([
|
|
131
|
+
'**',
|
|
132
|
+
'**/*',
|
|
133
|
+
'src/**',
|
|
134
|
+
'src/**/*',
|
|
135
|
+
'schemas/**',
|
|
136
|
+
'schemas/**/*',
|
|
137
|
+
'package.json',
|
|
138
|
+
'package-lock.json',
|
|
139
|
+
'release-gates.v2.json'
|
|
140
|
+
]).has(normalized);
|
|
141
|
+
}
|
|
142
|
+
function releaseScriptGateCandidates(file) {
|
|
143
|
+
const normalized = file.replace(/\\/g, '/');
|
|
144
|
+
const base = normalized.split('/').pop()?.replace(/\.(ts|js|mjs|cjs)$/, '') || '';
|
|
145
|
+
if (!base.startsWith('release-'))
|
|
146
|
+
return [];
|
|
147
|
+
const rest = base.slice('release-'.length);
|
|
148
|
+
const withoutCheck = rest.replace(/-check$/, '');
|
|
149
|
+
return [
|
|
150
|
+
`release:${rest}`,
|
|
151
|
+
`release:${withoutCheck}`,
|
|
152
|
+
`release:${withoutCheck}:check`
|
|
153
|
+
];
|
|
154
|
+
}
|
|
113
155
|
//# sourceMappingURL=release-gate-affected-selector.js.map
|
|
@@ -26,6 +26,10 @@ export async function runReleaseGateDag(input) {
|
|
|
26
26
|
? selectAffectedReleaseGates(root, manifest, presetGates, { changedSince: input.changedSince || 'auto', preset })
|
|
27
27
|
: selectAffectedReleaseGates(root, manifest, presetGates, { full: true, preset });
|
|
28
28
|
const selected = affected.gates;
|
|
29
|
+
const selectedIds = new Set(selected.map((gate) => gate.id));
|
|
30
|
+
const affectedExternalSatisfiedDeps = affected.selection.mode === 'affected'
|
|
31
|
+
? new Set(selected.flatMap((gate) => gate.deps || []).filter((dep) => !selectedIds.has(dep)))
|
|
32
|
+
: new Set();
|
|
29
33
|
const runId = `rg-${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`;
|
|
30
34
|
const reportDir = path.join(root, '.sneakoscope', 'reports', 'release-gates', runId);
|
|
31
35
|
fs.mkdirSync(reportDir, { recursive: true });
|
|
@@ -88,7 +92,7 @@ export async function runReleaseGateDag(input) {
|
|
|
88
92
|
writeReleaseGateJson(path.join(reportDir, 'explain.json'), { schema: RELEASE_GATE_NODE_SCHEMA, preset, budget, gates: selected.map((gate) => ({ id: gate.id, deps: gate.deps, resource: gate.resource, command: gate.command })) });
|
|
89
93
|
}
|
|
90
94
|
while (pending.size || running.size) {
|
|
91
|
-
const ready = findReadyReleaseGateNodes({ pending, completed, failed });
|
|
95
|
+
const ready = findReadyReleaseGateNodes({ pending, completed, failed, satisfiedDeps: affectedExternalSatisfiedDeps });
|
|
92
96
|
const launchable = pickReadyLaunchableReleaseGates({ ready, running: [...running.values()].map((row) => row.gate) });
|
|
93
97
|
let progressed = false;
|
|
94
98
|
for (const gate of launchable) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defaultReleaseGateBudget, pickLaunchableReleaseGates } from './release-gate-resource-governor.js';
|
|
2
2
|
export function findReadyReleaseGateNodes(input) {
|
|
3
|
-
|
|
3
|
+
const satisfiedDeps = input.satisfiedDeps || new Set();
|
|
4
|
+
return [...input.pending.values()].filter((gate) => gate.deps.every((dep) => input.completed.has(dep) || satisfiedDeps.has(dep)) && !gate.deps.some((dep) => input.failed.has(dep)));
|
|
4
5
|
}
|
|
5
6
|
export function findReleaseGatesBlockedByFailedDeps(input) {
|
|
6
7
|
return [...input.pending.values()].filter((gate) => gate.deps.some((dep) => input.failed.has(dep)));
|
package/dist/core/routes.js
CHANGED
|
@@ -552,12 +552,14 @@ export const ROUTES = [
|
|
|
552
552
|
route: 'explicit scoped permission-widening modifier',
|
|
553
553
|
description: 'Explicit high-risk authorization modifier that can be combined with other $ commands to temporarily open approved target-project scopes such as files, shell, package installs, services, network, Computer Use/browser workflows, generated assets, file permissions, migrations, Supabase MCP DB writes, direct execute SQL, schema cleanup, and normal targeted DB writes for the active invocation, while preserving catastrophic wipe/all-row/project-management, credential-exfiltration, persistent security-weakening, and unrequested fallback safeguards.',
|
|
554
554
|
requiredSkills: ['mad-sks', 'db-safety-guard', 'pipeline-runner', 'context7-docs', REFLECTION_SKILL_NAME, 'honest-mode'],
|
|
555
|
+
dollarAliases: ['$MAD-DB'],
|
|
556
|
+
appSkillAliases: ['mad-db'],
|
|
555
557
|
lifecycle: ['explicit_invocation', 'auto_sealed_permission_scope', 'scoped_permission_override', 'catastrophic_guard', 'permission_deactivation', 'post_route_reflection', 'honest_mode'],
|
|
556
558
|
context7Policy: 'required',
|
|
557
559
|
reasoningPolicy: 'xhigh',
|
|
558
560
|
stopGate: 'mad-sks-gate.json',
|
|
559
561
|
cliEntrypoint: 'Codex App prompt route only: $MAD-SKS <task>',
|
|
560
|
-
examples: ['$MAD-SKS $Team target project maintenance with package/service/file and DB scopes', '$DB Supabase 점검 $MAD-SKS']
|
|
562
|
+
examples: ['$MAD-SKS $Team target project maintenance with package/service/file and DB scopes', '$DB Supabase 점검 $MAD-SKS', '$MAD-DB enable one-cycle DB break-glass only after explicit ack']
|
|
561
563
|
},
|
|
562
564
|
{
|
|
563
565
|
id: 'GX',
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '2.0.
|
|
1
|
+
export const PACKAGE_VERSION = '2.0.17';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -31,21 +31,41 @@ export function renderZellijSlotPane(input) {
|
|
|
31
31
|
return frameSlotPane(`LIVE SLOT ${input.slotId}`, rows.slice(0, Math.max(1, maxLines - 2)));
|
|
32
32
|
}
|
|
33
33
|
export async function renderZellijSlotPaneFromArtifacts(input) {
|
|
34
|
+
const artifactRender = await renderZellijSlotPaneFromArtifactDir(input).catch(() => null);
|
|
34
35
|
if (input.missionId && input.missionId !== 'latest') {
|
|
35
36
|
const telemetry = await tryRenderTelemetrySlotPane({
|
|
36
37
|
artifactRoot: input.artifactRoot || input.artifactDir,
|
|
37
38
|
missionId: input.missionId,
|
|
38
39
|
slotId: input.slotId,
|
|
39
|
-
generationIndex: input.generationIndex
|
|
40
|
+
generationIndex: input.generationIndex,
|
|
41
|
+
artifactRender
|
|
40
42
|
});
|
|
41
43
|
if (telemetry)
|
|
42
44
|
return telemetry;
|
|
45
|
+
if (artifactRender)
|
|
46
|
+
return artifactRender;
|
|
43
47
|
return [
|
|
44
48
|
`${input.slotId} gen-${Math.max(1, Math.floor(Number(input.generationIndex) || 1))}`,
|
|
45
49
|
'waiting for telemetry...',
|
|
46
50
|
`mission ${input.missionId}`
|
|
47
51
|
].join('\n');
|
|
48
52
|
}
|
|
53
|
+
if (artifactRender)
|
|
54
|
+
return artifactRender;
|
|
55
|
+
const fallbackInput = {
|
|
56
|
+
slotId: input.slotId,
|
|
57
|
+
generationIndex: input.generationIndex,
|
|
58
|
+
status: 'launching',
|
|
59
|
+
currentTask: 'waiting for worker intake',
|
|
60
|
+
mode: input.mode || 'compact-slots'
|
|
61
|
+
};
|
|
62
|
+
if (input.role !== undefined)
|
|
63
|
+
fallbackInput.role = input.role;
|
|
64
|
+
if (input.backend !== undefined)
|
|
65
|
+
fallbackInput.backend = input.backend;
|
|
66
|
+
return renderZellijSlotPane(fallbackInput);
|
|
67
|
+
}
|
|
68
|
+
async function renderZellijSlotPaneFromArtifactDir(input) {
|
|
49
69
|
const artifactDir = path.resolve(input.artifactDir);
|
|
50
70
|
const result = await readJson(path.join(artifactDir, 'worker-result.json'));
|
|
51
71
|
const intake = await readJson(path.join(artifactDir, 'worker-intake.json'));
|
|
@@ -77,6 +97,8 @@ export async function renderZellijSlotPaneFromArtifacts(input) {
|
|
|
77
97
|
...(Array.isArray(intake?.input_files) ? intake.input_files : [])
|
|
78
98
|
]);
|
|
79
99
|
const now = Date.now();
|
|
100
|
+
if (!result && !intake && !backendReport && !fastReport && !paneReport && !codexProof && !localProof && !heartbeatMtime && !eventRows.length)
|
|
101
|
+
return null;
|
|
80
102
|
return renderZellijSlotPane({
|
|
81
103
|
slotId: input.slotId,
|
|
82
104
|
generationIndex: input.generationIndex,
|
|
@@ -136,6 +158,20 @@ export async function renderZellijSlotPaneFromArtifacts(input) {
|
|
|
136
158
|
mode: input.mode || 'compact-slots'
|
|
137
159
|
});
|
|
138
160
|
}
|
|
161
|
+
export async function renderZellijSlotPaneStatusFromArtifacts(input) {
|
|
162
|
+
const snapshot = input.missionId && input.missionId !== 'latest'
|
|
163
|
+
? await readZellijSlotTelemetrySnapshot(path.resolve(input.artifactRoot || input.artifactDir), input.missionId).catch(() => null)
|
|
164
|
+
: null;
|
|
165
|
+
const status = telemetryStatus(snapshot);
|
|
166
|
+
return {
|
|
167
|
+
schema: 'sks.zellij-slot-pane-status.v1',
|
|
168
|
+
mission_id: input.missionId || null,
|
|
169
|
+
slot_id: input.slotId,
|
|
170
|
+
generation_index: Math.max(1, Math.floor(Number(input.generationIndex) || 1)),
|
|
171
|
+
telemetry_stale: status.telemetry_stale,
|
|
172
|
+
telemetry_age_ms: status.telemetry_age_ms
|
|
173
|
+
};
|
|
174
|
+
}
|
|
139
175
|
export function buildZellijSlotPaneCommand(input) {
|
|
140
176
|
const args = [
|
|
141
177
|
input.cliPath,
|
|
@@ -159,9 +195,14 @@ async function tryRenderTelemetrySlotPane(input) {
|
|
|
159
195
|
const slot = findTelemetrySlot(snapshot, input.slotId, input.generationIndex);
|
|
160
196
|
if (!slot)
|
|
161
197
|
return null;
|
|
198
|
+
const staleRows = staleTelemetryRows(telemetryStatus(snapshot).telemetry_age_ms);
|
|
199
|
+
const fallbackRows = artifactFallbackRows(input.artifactRender);
|
|
200
|
+
const liveRows = staleRows.length || !slot.progress ? fallbackRows : [];
|
|
162
201
|
if (slot.status === 'failed') {
|
|
163
202
|
return [
|
|
164
203
|
`${slot.slot_id} gen-${slot.generation_index} · FAILED`,
|
|
204
|
+
...staleRows,
|
|
205
|
+
...liveRows,
|
|
165
206
|
`blocker: ${trimInline(slot.blockers[0] || 'worker_failed', 78)}`,
|
|
166
207
|
`artifact: ${trimInline(slot.artifact_paths[slot.artifact_paths.length - 1] || '-', 78)}`
|
|
167
208
|
].join('\n');
|
|
@@ -169,6 +210,8 @@ async function tryRenderTelemetrySlotPane(input) {
|
|
|
169
210
|
if (slot.status === 'completed' || slot.status === 'drained') {
|
|
170
211
|
return [
|
|
171
212
|
`${slot.slot_id} gen-${slot.generation_index} · done`,
|
|
213
|
+
...staleRows,
|
|
214
|
+
...liveRows,
|
|
172
215
|
`artifacts ${slot.artifact_paths.length} · ${slot.latest_event_type === 'verification_passed' ? 'verify passed' : 'verify queued'}`,
|
|
173
216
|
'closing in 3s'
|
|
174
217
|
].join('\n');
|
|
@@ -177,12 +220,25 @@ async function tryRenderTelemetrySlotPane(input) {
|
|
|
177
220
|
const heartbeat = slot.latest_ts ? `${Math.max(0, Math.round((Date.now() - Date.parse(slot.latest_ts)) / 1000))}s` : '?';
|
|
178
221
|
return [
|
|
179
222
|
`${slot.slot_id} gen-${slot.generation_index} · ${trimInline(slot.role || 'worker', 28)}`,
|
|
223
|
+
...staleRows,
|
|
224
|
+
...liveRows,
|
|
180
225
|
trimInline(backend, 78),
|
|
181
226
|
`${slot.status}: ${trimInline(slot.task_title || 'worker task', 68)}`,
|
|
182
227
|
`${formatTelemetryProgress(slot.progress)} · latest ${slot.latest_event_type} ${heartbeat}`,
|
|
183
228
|
`${slot.latest_event_type === 'patch_candidate' ? 'patch candidate' : 'patch'}: ${slot.latest_event_type === 'patch_candidate' ? 'queued' : trimInline(slot.current_file || '-', 42)}`
|
|
184
229
|
].join('\n');
|
|
185
230
|
}
|
|
231
|
+
function artifactFallbackRows(text) {
|
|
232
|
+
if (!text)
|
|
233
|
+
return [];
|
|
234
|
+
return String(text)
|
|
235
|
+
.split(/\r?\n/)
|
|
236
|
+
.map((line) => line.replace(/^\|\s?/, '').replace(/\s?\|$/, '').trim())
|
|
237
|
+
.filter((line) => /^(heartbeat|doing|files|event|out|err):\s+/i.test(line))
|
|
238
|
+
.filter((line) => !/unknown|waiting for worker intake|no changed file yet/i.test(line))
|
|
239
|
+
.slice(-4)
|
|
240
|
+
.map((line) => `live: ${trimInline(line, 72)}`);
|
|
241
|
+
}
|
|
186
242
|
function findTelemetrySlot(snapshot, slotId, generationIndex) {
|
|
187
243
|
const generation = Math.max(1, Math.floor(Number(generationIndex) || 1));
|
|
188
244
|
return Object.values(snapshot.slots || {}).find((row) => row.slot_id === slotId && Number(row.generation_index) === generation) || null;
|
|
@@ -192,6 +248,23 @@ function formatTelemetryProgress(progress) {
|
|
|
192
248
|
return 'progress ?';
|
|
193
249
|
return `progress ${progress.done}/${progress.total}${progress.label ? ` ${trimInline(progress.label, 24)}` : ''}`;
|
|
194
250
|
}
|
|
251
|
+
function telemetryStatus(snapshot) {
|
|
252
|
+
const parsed = snapshot?.updated_at ? Date.parse(snapshot.updated_at) : NaN;
|
|
253
|
+
const telemetryAgeMs = Number.isFinite(parsed) ? Math.max(0, Date.now() - parsed) : Number.MAX_SAFE_INTEGER;
|
|
254
|
+
return {
|
|
255
|
+
telemetry_stale: telemetryAgeMs > 3000,
|
|
256
|
+
telemetry_age_ms: telemetryAgeMs
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
function staleTelemetryRows(ageMs) {
|
|
260
|
+
if (!Number.isFinite(ageMs))
|
|
261
|
+
return ['telemetry stale; worker may still be running'];
|
|
262
|
+
if (ageMs > 10000)
|
|
263
|
+
return ['telemetry stale; worker may still be running'];
|
|
264
|
+
if (ageMs > 3000)
|
|
265
|
+
return [`telemetry stale ${(ageMs / 1000).toFixed(1)}s`];
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
195
268
|
async function readJson(file) {
|
|
196
269
|
try {
|
|
197
270
|
return JSON.parse(await fs.promises.readFile(file, 'utf8'));
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import { appendJsonlBounded, ensureDir, nowIso, readJson, readText } from '../fsx.js';
|
|
3
4
|
export const ZELLIJ_SLOT_TELEMETRY_EVENT_SCHEMA = 'sks.zellij-slot-telemetry-event.v1';
|
|
4
5
|
export const ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA = 'sks.zellij-slot-telemetry-snapshot.v1';
|
|
6
|
+
const telemetrySnapshotCache = new Map();
|
|
7
|
+
const telemetrySnapshotWriteCounts = new Map();
|
|
8
|
+
const telemetrySnapshotFlushCounts = new Map();
|
|
9
|
+
const telemetrySnapshotLastFlushMs = new Map();
|
|
5
10
|
export function slotTelemetryEventPath(root, missionId) {
|
|
6
11
|
return path.join(inferMissionDir(root, missionId), 'zellij', 'slot-telemetry.events.jsonl');
|
|
7
12
|
}
|
|
@@ -16,14 +21,52 @@ export async function appendZellijSlotTelemetry(root, event) {
|
|
|
16
21
|
const file = slotTelemetryEventPath(root, missionId);
|
|
17
22
|
await ensureDir(path.dirname(file));
|
|
18
23
|
await appendJsonlBounded(file, normalized);
|
|
24
|
+
const previous = await readZellijSlotTelemetrySnapshotNoRebuild(root, missionId);
|
|
25
|
+
if (previous) {
|
|
26
|
+
const snapshotPath = slotTelemetrySnapshotPath(root, missionId);
|
|
27
|
+
const next = applyTelemetryEventToSnapshot(previous, normalized);
|
|
28
|
+
telemetrySnapshotCache.set(snapshotPath, next);
|
|
29
|
+
if (shouldFlushTelemetrySnapshot(snapshotPath, normalized))
|
|
30
|
+
await writeTelemetrySnapshotFast(snapshotPath, next);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
19
33
|
await rebuildZellijSlotTelemetrySnapshot(root, missionId);
|
|
20
34
|
}
|
|
21
35
|
export async function readZellijSlotTelemetrySnapshot(root, missionId) {
|
|
22
|
-
const
|
|
36
|
+
const snapshotPath = slotTelemetrySnapshotPath(root, missionId);
|
|
37
|
+
const cached = telemetrySnapshotCache.get(snapshotPath);
|
|
38
|
+
if (cached?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA)
|
|
39
|
+
return cached;
|
|
40
|
+
const existing = await readJson(snapshotPath, null);
|
|
23
41
|
if (existing?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA)
|
|
24
42
|
return existing;
|
|
25
43
|
return rebuildZellijSlotTelemetrySnapshot(root, missionId);
|
|
26
44
|
}
|
|
45
|
+
export async function readZellijSlotTelemetrySnapshotNoRebuild(root, missionId) {
|
|
46
|
+
const snapshotPath = slotTelemetrySnapshotPath(root, missionId);
|
|
47
|
+
const cached = telemetrySnapshotCache.get(snapshotPath);
|
|
48
|
+
if (cached?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA)
|
|
49
|
+
return cached;
|
|
50
|
+
const existing = await readJson(snapshotPath, null);
|
|
51
|
+
if (existing?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA)
|
|
52
|
+
telemetrySnapshotCache.set(snapshotPath, existing);
|
|
53
|
+
return existing?.schema === ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA ? existing : null;
|
|
54
|
+
}
|
|
55
|
+
export function applyTelemetryEventToSnapshot(snapshot, event) {
|
|
56
|
+
const key = slotTelemetryKey(event.slot_id || event.worker_id, event.generation_index);
|
|
57
|
+
const slots = {
|
|
58
|
+
...(snapshot.slots || {}),
|
|
59
|
+
[key]: mergeSlotTelemetry(snapshot.slots?.[key], event)
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
schema: ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA,
|
|
63
|
+
mission_id: event.mission_id || snapshot.mission_id,
|
|
64
|
+
updated_at: nowIso(),
|
|
65
|
+
flush_count: snapshot.flush_count || 0,
|
|
66
|
+
slots,
|
|
67
|
+
counts: countSlotTelemetry(slots)
|
|
68
|
+
};
|
|
69
|
+
}
|
|
27
70
|
export async function rebuildZellijSlotTelemetrySnapshot(root, missionId) {
|
|
28
71
|
const eventsPath = slotTelemetryEventPath(root, missionId);
|
|
29
72
|
const rows = await readTelemetryEvents(eventsPath);
|
|
@@ -39,10 +82,11 @@ export async function rebuildZellijSlotTelemetrySnapshot(root, missionId) {
|
|
|
39
82
|
schema: ZELLIJ_SLOT_TELEMETRY_SNAPSHOT_SCHEMA,
|
|
40
83
|
mission_id: missionId,
|
|
41
84
|
updated_at: nowIso(),
|
|
85
|
+
flush_count: 0,
|
|
42
86
|
slots,
|
|
43
87
|
counts: countSlotTelemetry(slots)
|
|
44
88
|
};
|
|
45
|
-
await
|
|
89
|
+
await writeTelemetrySnapshotFast(slotTelemetrySnapshotPath(root, missionId), snapshot);
|
|
46
90
|
return snapshot;
|
|
47
91
|
}
|
|
48
92
|
async function readTelemetryEvents(file) {
|
|
@@ -167,6 +211,40 @@ function tail(value, max) {
|
|
|
167
211
|
const text = String(value || '').replace(/\s+$/g, '');
|
|
168
212
|
return text.length > max ? text.slice(-max) : text;
|
|
169
213
|
}
|
|
214
|
+
async function writeTelemetrySnapshotFast(file, snapshot) {
|
|
215
|
+
await ensureDir(path.dirname(file));
|
|
216
|
+
const flushCount = Number(telemetrySnapshotFlushCounts.get(file) || 0) + 1;
|
|
217
|
+
telemetrySnapshotFlushCounts.set(file, flushCount);
|
|
218
|
+
telemetrySnapshotLastFlushMs.set(file, Date.now());
|
|
219
|
+
const next = { ...snapshot, flush_count: flushCount };
|
|
220
|
+
telemetrySnapshotCache.set(file, next);
|
|
221
|
+
await fsp.writeFile(file, `${JSON.stringify(next)}\n`, 'utf8');
|
|
222
|
+
}
|
|
223
|
+
function shouldFlushTelemetrySnapshot(file, event) {
|
|
224
|
+
const next = (telemetrySnapshotWriteCounts.get(file) || 0) + 1;
|
|
225
|
+
telemetrySnapshotWriteCounts.set(file, next);
|
|
226
|
+
const now = Date.now();
|
|
227
|
+
const last = telemetrySnapshotLastFlushMs.get(file) || 0;
|
|
228
|
+
const parsedFlushMs = Number(process.env.SKS_ZELLIJ_SLOT_TELEMETRY_FLUSH_MS || 1000);
|
|
229
|
+
const parsedFlushEvery = Number(process.env.SKS_ZELLIJ_SLOT_TELEMETRY_FLUSH_EVERY_N || 100);
|
|
230
|
+
const flushMs = Math.max(250, Number.isFinite(parsedFlushMs) ? parsedFlushMs : 1000);
|
|
231
|
+
const flushEvery = Math.max(1, Number.isFinite(parsedFlushEvery) ? Math.floor(parsedFlushEvery) : 100);
|
|
232
|
+
const important = event.event_type === 'task_started'
|
|
233
|
+
|| event.event_type === 'task_progress'
|
|
234
|
+
|| event.event_type === 'artifact_written'
|
|
235
|
+
|| event.event_type === 'patch_candidate'
|
|
236
|
+
|| event.event_type === 'worker_completed'
|
|
237
|
+
|| event.event_type === 'worker_failed'
|
|
238
|
+
|| event.status === 'completed'
|
|
239
|
+
|| event.status === 'failed';
|
|
240
|
+
const should = next === 1
|
|
241
|
+
|| important
|
|
242
|
+
|| now - last >= flushMs
|
|
243
|
+
|| next % flushEvery === 0;
|
|
244
|
+
if (should)
|
|
245
|
+
telemetrySnapshotLastFlushMs.set(file, now);
|
|
246
|
+
return should;
|
|
247
|
+
}
|
|
170
248
|
function inferMissionDir(root, missionId) {
|
|
171
249
|
const resolved = path.resolve(root);
|
|
172
250
|
if (path.basename(resolved) === 'agents' && path.basename(path.dirname(resolved)) === missionId)
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
export function resolveZellijUiMode(args = [], env = process.env) {
|
|
2
|
+
return resolveExplicitZellijUiMode(args, env) || 'compact-slots';
|
|
3
|
+
}
|
|
4
|
+
export function resolveZellijWorkerPaneUiMode(args = [], env = process.env) {
|
|
5
|
+
return resolveExplicitZellijUiMode(args, env) || 'full-debug';
|
|
6
|
+
}
|
|
7
|
+
function resolveExplicitZellijUiMode(args = [], env = process.env) {
|
|
2
8
|
const fromEnv = String(env.SKS_ZELLIJ_UI_MODE || '').trim();
|
|
9
|
+
if (fromEnv === 'compact-slots')
|
|
10
|
+
return 'compact-slots';
|
|
3
11
|
if (fromEnv === 'full-debug')
|
|
4
12
|
return 'full-debug';
|
|
5
13
|
if (fromEnv === 'dashboard-plus-slots')
|
|
6
14
|
return 'dashboard-plus-slots';
|
|
15
|
+
if (args.includes('--zellij-compact-slots'))
|
|
16
|
+
return 'compact-slots';
|
|
7
17
|
if (args.includes('--zellij-dashboard'))
|
|
8
18
|
return 'dashboard-plus-slots';
|
|
9
19
|
if (args.includes('--zellij-full-debug'))
|
|
10
20
|
return 'full-debug';
|
|
11
|
-
return
|
|
21
|
+
return null;
|
|
12
22
|
}
|
|
13
23
|
export function zellijUiModeCreatesDashboard(mode) {
|
|
14
|
-
return mode
|
|
24
|
+
return mode === 'dashboard-plus-slots';
|
|
15
25
|
}
|
|
16
26
|
//# sourceMappingURL=zellij-ui-mode.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// Fast path: accept a current release-check stamp.
|
|
6
6
|
// Repair path: if the stamp is missing/stale, run the authoritative full
|
|
7
|
-
// `release:check` once, then require the fast check to pass.
|
|
7
|
+
// `release:check:full` once, then require the fast check to pass.
|
|
8
8
|
//
|
|
9
9
|
// This keeps direct `npm publish` usable without weakening the publish gate:
|
|
10
10
|
// stale stamp repair is the full release gate, not a synthetic stamp write.
|
|
@@ -37,7 +37,7 @@ function runReleaseCheck() {
|
|
|
37
37
|
stdio: 'inherit'
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
|
-
return spawnSync(npmCmd, ['run', 'release:check'], {
|
|
40
|
+
return spawnSync(npmCmd, ['run', 'release:check:full'], {
|
|
41
41
|
cwd: process.cwd(),
|
|
42
42
|
encoding: 'utf8',
|
|
43
43
|
env: process.env,
|
|
@@ -82,7 +82,7 @@ function main() {
|
|
|
82
82
|
console.error('Prepublish release-check auto-repair disabled by SKS_PREPUBLISH_RUN_RELEASE_CHECK_ON_STALE=0.');
|
|
83
83
|
process.exit(first.status || 1);
|
|
84
84
|
}
|
|
85
|
-
console.error('Prepublish release stamp is stale or missing; running full `npm run release:check` before publish.');
|
|
85
|
+
console.error('Prepublish release stamp is stale or missing; running full `npm run release:check:full` before publish.');
|
|
86
86
|
const releaseCheck = runReleaseCheck();
|
|
87
87
|
if (releaseCheck.status !== 0)
|
|
88
88
|
process.exit(releaseCheck.status || 1);
|