litmus-cli 1.4.33 → 1.4.35
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/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +59 -6
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +42 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +15 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +70 -6
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +72 -5
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/submit.d.ts.map +1 -1
- package/dist/commands/submit.js +12 -17
- package/dist/commands/submit.js.map +1 -1
- package/dist/lib/api.d.ts +9 -0
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +2 -0
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/cursor-restart.d.ts +260 -0
- package/dist/lib/cursor-restart.d.ts.map +1 -0
- package/dist/lib/cursor-restart.js +308 -0
- package/dist/lib/cursor-restart.js.map +1 -0
- package/dist/lib/editor-autosave.d.ts +30 -0
- package/dist/lib/editor-autosave.d.ts.map +1 -0
- package/dist/lib/editor-autosave.js +63 -0
- package/dist/lib/editor-autosave.js.map +1 -0
- package/dist/lib/schemas.d.ts +1 -0
- package/dist/lib/schemas.d.ts.map +1 -1
- package/dist/lib/schemas.js +5 -0
- package/dist/lib/schemas.js.map +1 -1
- package/dist/lib/submit-route.d.ts +32 -41
- package/dist/lib/submit-route.d.ts.map +1 -1
- package/dist/lib/submit-route.js +33 -42
- package/dist/lib/submit-route.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quit-and-relaunch Cursor so a hook config it hasn't read yet gets loaded
|
|
3
|
+
* before the candidate's clock starts (ENG-2491).
|
|
4
|
+
*
|
|
5
|
+
* THE PROBLEM THIS CLOSES. Cursor's hooks service reads its configuration
|
|
6
|
+
* once, at application startup, and never again — not on a window reload,
|
|
7
|
+
* not on a fresh Remote-SSH connection (measured, ENG-1728). `litmus init`
|
|
8
|
+
* and `litmus connect` have said so in increasingly loud text for months
|
|
9
|
+
* (`restartWarningLines` in commands/init.ts, `announceCursorFullQuit` in
|
|
10
|
+
* commands/connect.ts), and ENG-2481 measured what depending on a candidate
|
|
11
|
+
* to act on that text costs: 121 of 151 dark sessions in 30 days carry
|
|
12
|
+
* exactly this signature. A warning read in a timed interview is not a fix.
|
|
13
|
+
* This module is the fix: quit Cursor for the candidate, confirm it is
|
|
14
|
+
* gone, reopen it, confirm it is back.
|
|
15
|
+
*
|
|
16
|
+
* THE HAZARD THIS IS WRITTEN AROUND. Quitting an editor out from under
|
|
17
|
+
* someone can destroy unsaved work, and a full-app quit takes every window
|
|
18
|
+
* with it — including ones that have nothing to do with this assessment.
|
|
19
|
+
* The design answer is NOT "trust Cursor's hot-exit". Hot exit is VS Code's
|
|
20
|
+
* documented behaviour and Cursor, a VS Code fork, inherits it unmodified —
|
|
21
|
+
* but a candidate can have it turned off, and nothing in this module can
|
|
22
|
+
* ask Cursor whether they have. The design answer is narrower and does not
|
|
23
|
+
* depend on that fact being true: we never send a kill signal, only the
|
|
24
|
+
* same Apple Event Cmd+Q sends (`tell application "Cursor" to quit`), and
|
|
25
|
+
* we give Cursor a real window to actually finish quitting before calling
|
|
26
|
+
* it done. A dirty buffer that would raise a "Save changes?" dialog, and a
|
|
27
|
+
* first-run automation-permission prompt, look identical from here: the
|
|
28
|
+
* process is still running once our wait is over. Either way we STOP — we
|
|
29
|
+
* never relaunch a process that never actually quit (see `attemptQuit`) —
|
|
30
|
+
* so the worst thing this module can do to a candidate's own work is
|
|
31
|
+
* nothing at all, and their answer to whatever dialog Cursor itself put up
|
|
32
|
+
* is never touched, let alone overridden.
|
|
33
|
+
*
|
|
34
|
+
* THE OTHER FAILURE DIRECTION. A quit that succeeds and a relaunch that
|
|
35
|
+
* does not is strictly worse than doing nothing: it leaves a candidate with
|
|
36
|
+
* no editor and a running clock. So a relaunch is only ever attempted after
|
|
37
|
+
* a quit is CONFIRMED — the process is actually gone, not merely "the
|
|
38
|
+
* request was sent" — and a relaunch that does not reappear within its own
|
|
39
|
+
* budget is reported with the exact command a candidate can run to finish
|
|
40
|
+
* the job themselves. See `restartCursorForCapture`.
|
|
41
|
+
*
|
|
42
|
+
* PLATFORM. `tell application "..." to quit` is AppleScript, so this whole
|
|
43
|
+
* module is macOS-only by construction (`platformSupportsGracefulCursorRestart`).
|
|
44
|
+
* That is not an oversight to fill in later: Windows' nearest equivalent is
|
|
45
|
+
* a close-main-window request, which is not the same guarantee a graceful
|
|
46
|
+
* quit is and degrades to `taskkill /F` — a hard kill — the moment a window
|
|
47
|
+
* does not respond, and Linux's depends on the window manager and has no
|
|
48
|
+
* reliable answer at all under Wayland, which deliberately restricts this
|
|
49
|
+
* exact kind of cross-application control. A platform that cannot CONFIRM a
|
|
50
|
+
* graceful quit falls back to the existing warning, unchanged, rather than
|
|
51
|
+
* shipping a weaker, unconfirmable version of this feature — this ticket
|
|
52
|
+
* ruled that out rather than leaving it unconsidered. If the mechanism ever
|
|
53
|
+
* generalises to those platforms, it needs its own confirmed-quit primitive
|
|
54
|
+
* first; see this PR's description for the same note.
|
|
55
|
+
*
|
|
56
|
+
* WHY `osascript`/`open` AND NOT `System Events`. Sending an Apple Event
|
|
57
|
+
* directly to an application you name — `tell application "Cursor" to
|
|
58
|
+
* quit` — is a different permission from UI-scripting an app through
|
|
59
|
+
* `System Events` (synthetic keystrokes/clicks): the former is what Cmd+Q
|
|
60
|
+
* itself amounts to, and it is what stays available on a machine that has
|
|
61
|
+
* never granted the latter. `System Events` was confirmed BLOCKED on the
|
|
62
|
+
* machine this ticket was built on — `-1743, not authorized to send Apple
|
|
63
|
+
* events` — which is the exact wall a manual Cursor probe hit earlier the
|
|
64
|
+
* same day (ENG-1728). Needing `System Events` for this feature would have
|
|
65
|
+
* been a design mistake, not a permission gap to work around: it is also
|
|
66
|
+
* how a candidate's own keystrokes would be synthesised, which this module
|
|
67
|
+
* has no business doing.
|
|
68
|
+
*
|
|
69
|
+
* A SCAN FAILURE IS NOT AN ANSWER (Greptile on #2613). `isRunning` reads a
|
|
70
|
+
* process list, and that read itself can fail or time out — `ps` wedged,
|
|
71
|
+
* killed, whatever. Collapsing that into "not running" is the exact bug
|
|
72
|
+
* this ticket exists to remove, reappearing one layer down: a failed scan
|
|
73
|
+
* between the initial check and `attemptQuit` would read as "Cursor already
|
|
74
|
+
* quit", skip the quit request entirely, relaunch on top of the still-live
|
|
75
|
+
* original process, and report `restarted` once a later scan happens to
|
|
76
|
+
* succeed — Cursor never actually restarted, the hook it just got is still
|
|
77
|
+
* unloaded, and the candidate is told capture is live. So presence is
|
|
78
|
+
* three-valued (`ProcessPresence`), not two, and only an EXPLICIT "gone" or
|
|
79
|
+
* "present" reading can ever satisfy `attemptQuit`/`attemptRelaunch`'s wait
|
|
80
|
+
* loops — "unknown" never does, on either side, so a persistently failing
|
|
81
|
+
* scan times out to the honest "could not confirm" outcome instead of
|
|
82
|
+
* quietly resolving to success.
|
|
83
|
+
*
|
|
84
|
+
* A QUIT MUST NOT HAPPEN ON A PATH THAT CANNOT USEFULLY RELAUNCH (Greptile
|
|
85
|
+
* on #2613, and the brief's own "conditional on being able to reopen, not
|
|
86
|
+
* the other way round"). `restartCursorForCapture` (used by `init`/`doctor`,
|
|
87
|
+
* whose relaunch is a bare `open -a Cursor`) checks, BEFORE sending any quit
|
|
88
|
+
* request, that Cursor is resolvable as a launchable application at all
|
|
89
|
+
* (`canRelaunch`) — a read-only Launch Services query, `path to application
|
|
90
|
+
* "Cursor"`, which neither launches nor addresses the app and so needs no
|
|
91
|
+
* automation permission of any kind. If that cannot be confirmed, nothing is
|
|
92
|
+
* quit. `connect` does not need this check: it already resolves and
|
|
93
|
+
* verifies the exact command it is about to launch Cursor with (elsewhere in
|
|
94
|
+
* that file) before it ever reaches the quit — a stronger guarantee than a
|
|
95
|
+
* generic "is Cursor launchable" probe — so it reuses that existing
|
|
96
|
+
* verification rather than adding a second one. See connect.ts for how the
|
|
97
|
+
* ORDER matters there too: a quit sequenced before a check that can still
|
|
98
|
+
* refuse the launch is the same hazard from a different direction.
|
|
99
|
+
*/
|
|
100
|
+
import { spawnSync } from "child_process";
|
|
101
|
+
import chalk from "chalk";
|
|
102
|
+
import { detectRunningHookTools } from "./capture-selftest.js";
|
|
103
|
+
import { getProcessList } from "./platform.js";
|
|
104
|
+
export function platformSupportsGracefulCursorRestart(platform) {
|
|
105
|
+
return platform === "darwin";
|
|
106
|
+
}
|
|
107
|
+
export const QUIT_CONFIRM_TIMEOUT_MS = 10000;
|
|
108
|
+
export const RELAUNCH_CONFIRM_TIMEOUT_MS = 20000;
|
|
109
|
+
const POLL_INTERVAL_MS = 500;
|
|
110
|
+
async function waitUntil(predicate, timeoutMs, sleep) {
|
|
111
|
+
const deadline = Date.now() + timeoutMs;
|
|
112
|
+
while (!predicate() && Date.now() < deadline) {
|
|
113
|
+
await sleep(Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
|
|
114
|
+
}
|
|
115
|
+
return predicate();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Ask Cursor to quit, then WAIT to see whether it actually did — never trust
|
|
119
|
+
* the request alone. `requestQuit` returning true means only that the Apple
|
|
120
|
+
* Event was delivered, the same as `exit 0` from `osascript`; Cursor can
|
|
121
|
+
* still be sitting on a "Save changes?" dialog, or a first-run automation
|
|
122
|
+
* permission prompt can be blocking it, and neither is distinguishable from
|
|
123
|
+
* out here except by the one thing they have in common: the process is
|
|
124
|
+
* still there a few seconds later. Timing out is therefore not a special
|
|
125
|
+
* case to handle — it IS the safety mechanism. A candidate's dirty buffer
|
|
126
|
+
* is never at risk from this function, because once the request is sent it
|
|
127
|
+
* never does anything BUT wait; whatever is protecting that buffer (a
|
|
128
|
+
* dialog Cursor itself put up) is left completely alone, and its answer is
|
|
129
|
+
* always the candidate's, never ours.
|
|
130
|
+
*
|
|
131
|
+
* Already-CONFIRMED-not-running short-circuits to `true`: there is nothing
|
|
132
|
+
* to confirm, and a caller asking "is it safe to relaunch" should get the
|
|
133
|
+
* same answer whether Cursor quit a moment ago or was never open at all. An
|
|
134
|
+
* "unknown" reading does NOT short-circuit — see this file's header on why
|
|
135
|
+
* that would be the same bug as the wait loop below collapsing "unknown"
|
|
136
|
+
* into "gone".
|
|
137
|
+
*/
|
|
138
|
+
export async function attemptQuit(io, timeoutMs = QUIT_CONFIRM_TIMEOUT_MS) {
|
|
139
|
+
if (io.isRunning() === "not-running")
|
|
140
|
+
return true;
|
|
141
|
+
if (!io.requestQuit())
|
|
142
|
+
return false;
|
|
143
|
+
return waitUntil(() => io.isRunning() === "not-running", timeoutMs, io.sleep);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Ask Cursor to relaunch, then wait to see whether it actually came back.
|
|
147
|
+
* Callers must only reach this after `attemptQuit` has CONFIRMED Cursor is
|
|
148
|
+
* gone (see `restartCursorForCapture`) — relaunching a process that never
|
|
149
|
+
* quit would, at best, just refocus the singleton app and do nothing, and
|
|
150
|
+
* at worst race whatever dialog it was showing. Only an EXPLICIT "running"
|
|
151
|
+
* reading counts as confirmed; "unknown" keeps waiting, same as `attemptQuit`.
|
|
152
|
+
*/
|
|
153
|
+
export async function attemptRelaunch(io, timeoutMs = RELAUNCH_CONFIRM_TIMEOUT_MS) {
|
|
154
|
+
if (!io.requestRelaunch())
|
|
155
|
+
return false;
|
|
156
|
+
return waitUntil(() => io.isRunning() === "running", timeoutMs, io.sleep);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* The whole sequence for `init` and `doctor`, which both need Cursor closed
|
|
160
|
+
* AND reopened (unlike `connect`, which is about to open it anyway — see
|
|
161
|
+
* that command for how it reuses just `attemptQuit`). Never reaches
|
|
162
|
+
* `attemptRelaunch` on an unconfirmed quit: that ordering is the one rule
|
|
163
|
+
* this function exists to enforce, not an incidental sequence of steps.
|
|
164
|
+
*
|
|
165
|
+
* Requires an EXPLICIT "running" reading before doing anything at all — an
|
|
166
|
+
* "unknown" presence at the very start is treated the same as "not running"
|
|
167
|
+
* here (nothing attempted, existing warning still covers it), because the
|
|
168
|
+
* question this function answers is "should we act", and "we can't tell" is
|
|
169
|
+
* always answered no. `canRelaunch` runs only once that gate is passed, and
|
|
170
|
+
* BEFORE any quit request — refusing to quit on a Cursor this module cannot
|
|
171
|
+
* confirm it can reopen (see this file's header).
|
|
172
|
+
*
|
|
173
|
+
* `onCommitted`, if given, fires exactly once — right after both gates pass
|
|
174
|
+
* and right before the quit request is sent — never before. That is the
|
|
175
|
+
* one moment a caller can announce "restarting Cursor now" and have it stay
|
|
176
|
+
* true: announcing any earlier could be followed by a silent `not-running`
|
|
177
|
+
* or `relaunch-not-verifiable` (said something was about to happen, then
|
|
178
|
+
* nothing did), and announcing later would tell a candidate about a quit
|
|
179
|
+
* that had already happened to them, on their own screen, unannounced.
|
|
180
|
+
*/
|
|
181
|
+
export async function restartCursorForCapture(platform, io, opts = {}) {
|
|
182
|
+
if (!platformSupportsGracefulCursorRestart(platform))
|
|
183
|
+
return { kind: "unsupported" };
|
|
184
|
+
if (io.isRunning() !== "running")
|
|
185
|
+
return { kind: "not-running" };
|
|
186
|
+
if (!io.canRelaunch())
|
|
187
|
+
return { kind: "relaunch-not-verifiable" };
|
|
188
|
+
opts.onCommitted?.();
|
|
189
|
+
const quit = await attemptQuit(io, opts.quitMs);
|
|
190
|
+
if (!quit)
|
|
191
|
+
return { kind: "quit-unconfirmed" };
|
|
192
|
+
const relaunched = await attemptRelaunch(io, opts.relaunchMs);
|
|
193
|
+
return relaunched ? { kind: "restarted" } : { kind: "relaunch-unconfirmed" };
|
|
194
|
+
}
|
|
195
|
+
// ── Real IO (macOS) ──────────────────────────────────────────────
|
|
196
|
+
/** What a candidate can run themselves to finish the job if we could not. */
|
|
197
|
+
export const CURSOR_RELAUNCH_COMMAND = "open -a Cursor";
|
|
198
|
+
function runOsascript(script) {
|
|
199
|
+
try {
|
|
200
|
+
const r = spawnSync("osascript", ["-e", script], { timeout: 10000, encoding: "utf8" });
|
|
201
|
+
return { ok: !r.error && r.status === 0, stdout: r.stdout ?? "" };
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return { ok: false, stdout: "" };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Real process control, macOS only. `ps` is injectable (defaults to the
|
|
209
|
+
* real live scan, which can throw — see below) purely so a caller that has
|
|
210
|
+
* already paid for one process-list read this tick can hand it in rather
|
|
211
|
+
* than triggering a second `ps`.
|
|
212
|
+
*
|
|
213
|
+
* `isRunning` is the ONLY place a scan failure is caught, and it is caught
|
|
214
|
+
* into `"unknown"`, never into `"not-running"` — see this file's header for
|
|
215
|
+
* why folding those together is the exact bug this ticket exists to fix,
|
|
216
|
+
* one layer down.
|
|
217
|
+
*
|
|
218
|
+
* `canRelaunch` asks Launch Services where the application named "Cursor"
|
|
219
|
+
* lives (`path to application`, a Standard Additions query, not an event
|
|
220
|
+
* sent to Cursor itself) and succeeds only if it resolves to something —
|
|
221
|
+
* this neither launches nor addresses the app, so it needs no automation
|
|
222
|
+
* permission at all, unlike `requestQuit` below.
|
|
223
|
+
*
|
|
224
|
+
* `requestQuit` targets Cursor BY NAME, never `System Events` — see this
|
|
225
|
+
* file's header for why that distinction is load-bearing, not stylistic.
|
|
226
|
+
* `requestRelaunch` is plain Launch Services (`open -a`), which needs no
|
|
227
|
+
* automation permission either: it is exactly what double-clicking the Dock
|
|
228
|
+
* icon does.
|
|
229
|
+
*/
|
|
230
|
+
export function realCursorProcessIo(ps = getProcessList) {
|
|
231
|
+
return {
|
|
232
|
+
isRunning: () => {
|
|
233
|
+
let list;
|
|
234
|
+
try {
|
|
235
|
+
list = ps();
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return "unknown";
|
|
239
|
+
}
|
|
240
|
+
return detectRunningHookTools(list).includes("cursor") ? "running" : "not-running";
|
|
241
|
+
},
|
|
242
|
+
canRelaunch: () => {
|
|
243
|
+
const result = runOsascript('POSIX path of (path to application "Cursor")');
|
|
244
|
+
return result.ok && result.stdout.trim().length > 0;
|
|
245
|
+
},
|
|
246
|
+
requestQuit: () => runOsascript('tell application "Cursor" to quit').ok,
|
|
247
|
+
requestRelaunch: () => {
|
|
248
|
+
try {
|
|
249
|
+
const r = spawnSync("open", ["-a", "Cursor"], { timeout: 10000, stdio: "ignore" });
|
|
250
|
+
return !r.error && r.status === 0;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
// ── Candidate-facing copy ────────────────────────────────────────
|
|
260
|
+
/**
|
|
261
|
+
* Said BEFORE anything happens (Greptile's own rule on #2223, applied here):
|
|
262
|
+
* a candidate whose editor vanishes without warning will reasonably think
|
|
263
|
+
* something broke, and in a timed interview that costs real minutes and
|
|
264
|
+
* composure. Also the one place the "other windows reopen the same way"
|
|
265
|
+
* caveat lives — a full quit cannot spare them, and saying so up front is
|
|
266
|
+
* more honest than a relaunch that quietly does not restore everything they
|
|
267
|
+
* had open.
|
|
268
|
+
*/
|
|
269
|
+
export function announceCursorAutoRestart() {
|
|
270
|
+
return [
|
|
271
|
+
"",
|
|
272
|
+
chalk.yellow.bold(" Cursor is open right now, and it only reads hook config at launch — so it"),
|
|
273
|
+
chalk.yellow.bold(" can't see the tracking hooks just installed until it restarts."),
|
|
274
|
+
"",
|
|
275
|
+
" Restarting it for you now. If it asks to save anything, go ahead — Cursor",
|
|
276
|
+
" will carry on once you answer, and this will pick up from there once you do.",
|
|
277
|
+
" Any other Cursor windows you have open will reopen the same way.",
|
|
278
|
+
];
|
|
279
|
+
}
|
|
280
|
+
/** What to say once the attempt is over — pass or one of the two ways it fails. */
|
|
281
|
+
export function renderCursorRestartOutcome(outcome) {
|
|
282
|
+
switch (outcome.kind) {
|
|
283
|
+
case "restarted":
|
|
284
|
+
return ["", chalk.green(" ✓ Cursor restarted — it will use the hooks installed above.")];
|
|
285
|
+
case "quit-unconfirmed":
|
|
286
|
+
return [
|
|
287
|
+
"",
|
|
288
|
+
chalk.yellow(" Cursor didn't quit. It may be waiting on you — a \"Save changes?\" dialog, or a"),
|
|
289
|
+
chalk.yellow(" one-time permission prompt — or this machine hasn't allowed Litmus to control"),
|
|
290
|
+
chalk.yellow(" it yet. Nothing was changed: Cursor is exactly as you left it."),
|
|
291
|
+
];
|
|
292
|
+
case "relaunch-unconfirmed":
|
|
293
|
+
return [
|
|
294
|
+
"",
|
|
295
|
+
chalk.red.bold(" Cursor quit, but didn't reopen. Nothing is running right now — reopen it"),
|
|
296
|
+
chalk.red.bold(` yourself: ${CURSOR_RELAUNCH_COMMAND}`),
|
|
297
|
+
chalk.red(" (or launch it from Spotlight or the Dock), then run \"litmus doctor\" to confirm"),
|
|
298
|
+
chalk.red(" capture is working."),
|
|
299
|
+
];
|
|
300
|
+
case "not-running":
|
|
301
|
+
case "unsupported":
|
|
302
|
+
// Silent, exactly like "not-running": nothing was touched, so nothing
|
|
303
|
+
// new needs saying, and the existing static warning still applies.
|
|
304
|
+
case "relaunch-not-verifiable":
|
|
305
|
+
return [];
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=cursor-restart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor-restart.js","sourceRoot":"","sources":["../../src/lib/cursor-restart.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ENG-2543: turn on autosave for VS Code and Cursor in the assessment folder.
|
|
3
|
+
*
|
|
4
|
+
* Every capture path reads the disk: the watcher's change events, the periodic
|
|
5
|
+
* snapshot, and the zip `submit` builds. An unsaved editor buffer is on none of
|
|
6
|
+
* them. Both editors ship with autosave off, and the notebook editor lets a
|
|
7
|
+
* candidate write and run cells, outputs and all, without ever writing the
|
|
8
|
+
* .ipynb. At the deadline the auto-submit zips what is on disk and deletes the
|
|
9
|
+
* folder, so a candidate who never pressed Cmd+S is graded on their last save.
|
|
10
|
+
* On the 9/22 Mercor Benchmark cohort that was 53 minutes of work.
|
|
11
|
+
*
|
|
12
|
+
* A workspace `.vscode/settings.json` is read by both editors and overrides the
|
|
13
|
+
* candidate's user setting for this folder only.
|
|
14
|
+
*
|
|
15
|
+
* ADDS, NEVER REPLACES. A starter that ships its own settings keeps every key,
|
|
16
|
+
* and a key the author set on purpose (`files.autoSave` included) wins. A file
|
|
17
|
+
* that does not parse as plain JSON (VS Code allows comments) is left alone:
|
|
18
|
+
* rewriting it would drop the comments, and autosave is not worth that.
|
|
19
|
+
*
|
|
20
|
+
* Runs before `init` commits the baseline, so the file is part of the starting
|
|
21
|
+
* state and never shows up in the candidate's diff. The watcher and snapshot
|
|
22
|
+
* already ignore dotfiles, so it is never a signal either.
|
|
23
|
+
*/
|
|
24
|
+
export declare const AUTOSAVE_SETTINGS: {
|
|
25
|
+
readonly "files.autoSave": "afterDelay";
|
|
26
|
+
readonly "files.autoSaveDelay": 1000;
|
|
27
|
+
};
|
|
28
|
+
export type AutosaveResult = "created" | "merged" | "already-set" | "skipped";
|
|
29
|
+
export declare function ensureEditorAutosave(targetDir: string): AutosaveResult;
|
|
30
|
+
//# sourceMappingURL=editor-autosave.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor-autosave.d.ts","sourceRoot":"","sources":["../../src/lib/editor-autosave.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,eAAO,MAAM,iBAAiB;;;CAGpB,CAAA;AAEV,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,aAAa,GAAG,SAAS,CAAA;AAE7E,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAiCtE"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* ENG-2543: turn on autosave for VS Code and Cursor in the assessment folder.
|
|
5
|
+
*
|
|
6
|
+
* Every capture path reads the disk: the watcher's change events, the periodic
|
|
7
|
+
* snapshot, and the zip `submit` builds. An unsaved editor buffer is on none of
|
|
8
|
+
* them. Both editors ship with autosave off, and the notebook editor lets a
|
|
9
|
+
* candidate write and run cells, outputs and all, without ever writing the
|
|
10
|
+
* .ipynb. At the deadline the auto-submit zips what is on disk and deletes the
|
|
11
|
+
* folder, so a candidate who never pressed Cmd+S is graded on their last save.
|
|
12
|
+
* On the 9/22 Mercor Benchmark cohort that was 53 minutes of work.
|
|
13
|
+
*
|
|
14
|
+
* A workspace `.vscode/settings.json` is read by both editors and overrides the
|
|
15
|
+
* candidate's user setting for this folder only.
|
|
16
|
+
*
|
|
17
|
+
* ADDS, NEVER REPLACES. A starter that ships its own settings keeps every key,
|
|
18
|
+
* and a key the author set on purpose (`files.autoSave` included) wins. A file
|
|
19
|
+
* that does not parse as plain JSON (VS Code allows comments) is left alone:
|
|
20
|
+
* rewriting it would drop the comments, and autosave is not worth that.
|
|
21
|
+
*
|
|
22
|
+
* Runs before `init` commits the baseline, so the file is part of the starting
|
|
23
|
+
* state and never shows up in the candidate's diff. The watcher and snapshot
|
|
24
|
+
* already ignore dotfiles, so it is never a signal either.
|
|
25
|
+
*/
|
|
26
|
+
export const AUTOSAVE_SETTINGS = {
|
|
27
|
+
"files.autoSave": "afterDelay",
|
|
28
|
+
"files.autoSaveDelay": 1000,
|
|
29
|
+
};
|
|
30
|
+
export function ensureEditorAutosave(targetDir) {
|
|
31
|
+
const dir = path.join(targetDir, ".vscode");
|
|
32
|
+
const file = path.join(dir, "settings.json");
|
|
33
|
+
try {
|
|
34
|
+
if (!existsSync(file)) {
|
|
35
|
+
mkdirSync(dir, { recursive: true });
|
|
36
|
+
writeFileSync(file, JSON.stringify(AUTOSAVE_SETTINGS, null, 2) + "\n");
|
|
37
|
+
return "created";
|
|
38
|
+
}
|
|
39
|
+
let existing;
|
|
40
|
+
try {
|
|
41
|
+
existing = JSON.parse(readFileSync(file, "utf8"));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return "skipped";
|
|
45
|
+
}
|
|
46
|
+
if (typeof existing !== "object" || existing === null || Array.isArray(existing)) {
|
|
47
|
+
return "skipped";
|
|
48
|
+
}
|
|
49
|
+
const settings = existing;
|
|
50
|
+
if ("files.autoSave" in settings)
|
|
51
|
+
return "already-set";
|
|
52
|
+
// The author's keys spread last, so a delay they already chose survives.
|
|
53
|
+
const merged = { ...AUTOSAVE_SETTINGS, ...settings };
|
|
54
|
+
writeFileSync(file, JSON.stringify(merged, null, 2) + "\n");
|
|
55
|
+
return "merged";
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Best-effort, like the kernel binding: an editor setting is never a
|
|
59
|
+
// reason to fail a start.
|
|
60
|
+
return "skipped";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=editor-autosave.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor-autosave.js","sourceRoot":"","sources":["../../src/lib/editor-autosave.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAA;AACvE,OAAO,IAAI,MAAM,MAAM,CAAA;AAEvB;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,gBAAgB,EAAE,YAAY;IAC9B,qBAAqB,EAAE,IAAI;CACnB,CAAA;AAIV,MAAM,UAAU,oBAAoB,CAAC,SAAiB;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;IAC3C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAA;IAE5C,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACnC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;YACtE,OAAO,SAAS,CAAA;QAClB,CAAC;QAED,IAAI,QAAiB,CAAA;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjF,OAAO,SAAS,CAAA;QAClB,CAAC;QAED,MAAM,QAAQ,GAAG,QAAmC,CAAA;QACpD,IAAI,gBAAgB,IAAI,QAAQ;YAAE,OAAO,aAAa,CAAA;QAEtD,yEAAyE;QACzE,MAAM,MAAM,GAAG,EAAE,GAAG,iBAAiB,EAAE,GAAG,QAAQ,EAAE,CAAA;QACpD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;QAC3D,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,qEAAqE;QACrE,0BAA0B;QAC1B,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC"}
|
package/dist/lib/schemas.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare const InitMetadataSchema: z.ZodObject<{
|
|
|
10
10
|
timeLimit: z.ZodNullable<z.ZodNumber>;
|
|
11
11
|
walkthroughWindowMinutes: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
12
12
|
startedAt: z.ZodISODateTime;
|
|
13
|
+
workTrial: z.ZodOptional<z.ZodBoolean>;
|
|
13
14
|
}, z.core.$strip>;
|
|
14
15
|
export type InitMetadata = z.infer<typeof InitMetadataSchema>;
|
|
15
16
|
export declare const SubmitResultSchema: z.ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/lib/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../../src/lib/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,kBAAkB;;;;;;;;;;;;iBAqB7B,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE7D,eAAO,MAAM,kBAAkB;;;;;iBAQ7B,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAK7D,eAAO,MAAM,iBAAiB;;;;iBAI5B,CAAA;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAE3D,eAAO,MAAM,kBAAkB;;iBAE7B,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAI7D,eAAO,MAAM,qBAAqB;;iBAEhC,CAAA;AAEF,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAA;AAMnE,eAAO,MAAM,yBAAyB;;;;;;iBAQpC,CAAA;AAEF,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAU3E,eAAO,MAAM,sBAAsB;;;;;iBAKjC,CAAA;AAEF,MAAM,MAAM,wBAAwB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAA"}
|
package/dist/lib/schemas.js
CHANGED
|
@@ -12,6 +12,11 @@ export const InitMetadataSchema = z.object({
|
|
|
12
12
|
// services/walkthrough_shared.py) — the fallback when the server omits it.
|
|
13
13
|
walkthroughWindowMinutes: z.number().positive().optional().default(10),
|
|
14
14
|
startedAt: z.iso.datetime(),
|
|
15
|
+
// ENG-2523: true for a work trial, whose submit is a checkpoint (ENG-2378).
|
|
16
|
+
// THREE-VALUED on purpose: `undefined` means an older frontend did not say,
|
|
17
|
+
// which is not the same as "a take-home". Wording that is only true of a
|
|
18
|
+
// take-home is gated on `=== false`; the checkpoint copy on `=== true`.
|
|
19
|
+
workTrial: z.boolean().optional(),
|
|
15
20
|
});
|
|
16
21
|
export const SubmitResultSchema = z.object({
|
|
17
22
|
submissionId: z.uuid(),
|
package/dist/lib/schemas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/lib/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,YAAY,EAAE,CAAC,CAAC,IAAI,EAAE;IACtB,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE;IAE1B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,UAAU,EAAE,CAAC,CAAC,GAAG,EAAE;IAEnB,cAAc,EAAE,CAAC,CAAC,KAAK,EAAE;IACzB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IAEzB,QAAQ,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACrC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,4DAA4D;IAC5D,2EAA2E;IAC3E,wBAAwB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACtE,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/lib/schemas.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,42 +1,39 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Which command
|
|
3
|
-
* (ENG-2130).
|
|
2
|
+
* Which command hands work in, for the install this CLI is running from.
|
|
4
3
|
*
|
|
5
|
-
* There are two flows and
|
|
4
|
+
* There are two flows, and inside a Codespaces v2 workspace both names reach the
|
|
5
|
+
* same place:
|
|
6
6
|
*
|
|
7
|
-
* * ON A LAPTOP (the CLI flow)
|
|
8
|
-
*
|
|
9
|
-
* * INSIDE A
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* * ON A LAPTOP (the CLI flow) `litmus submit` zips the local checkout and
|
|
8
|
+
* uploads it to `/cli/submit`.
|
|
9
|
+
* * INSIDE A WORKSPACE `litmus push` POSTs `/workspace/submit` — the same
|
|
10
|
+
* endpoint the assessment page's Submit button calls, capturing the live
|
|
11
|
+
* container filesystem host-side (ENG-621). There is nothing local to zip.
|
|
12
|
+
* Since ENG-2523 `litmus submit` there runs `push` rather than refusing
|
|
13
|
+
* (ENG-2130), because candidates reach for `submit` and a refusal sent them
|
|
14
|
+
* back to the browser to press the button.
|
|
13
15
|
*
|
|
14
|
-
* WHY
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* therefore went out as a literal `Authorization: Bearer undefined` and was
|
|
23
|
-
* refused — AFTER the archive had been built, so a candidate read it as an
|
|
24
|
-
* upload failure rather than as the wrong command. Every surface that names a
|
|
25
|
-
* submit command was naming the laptop one to both audiences, so the answer is
|
|
26
|
-
* derived in one place and the sites ask.
|
|
16
|
+
* WHY `submit` DELEGATES RATHER THAN READING THE INJECTED TOKEN ITSELF. Giving it
|
|
17
|
+
* the env fallback `push` has would route a v2 candidate down `/cli/submit`,
|
|
18
|
+
* which grades an UPLOADED zip, while v2's canonical submit grades the live
|
|
19
|
+
* filesystem — the two flows would then disagree about what a submission is.
|
|
20
|
+
* Delegating keeps one definition. The token must also never be written into
|
|
21
|
+
* `.litmus/config.json` (what `submit` reads on a laptop): hostd stages that file
|
|
22
|
+
* with deliberately no `token` because it rides the submission zip and the R2
|
|
23
|
+
* durability tar.
|
|
27
24
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
25
|
+
* WHY THE COMMAND WE NAME IS STILL `push`. `submitCommandFor` answers `push`
|
|
26
|
+
* inside a workspace even though `submit` now works there, because the delegation
|
|
27
|
+
* is keyed on the INSTALL, not the assessment: a v2 candidate who learns
|
|
28
|
+
* `litmus submit` and runs it on their own laptop uploads whatever local folder
|
|
29
|
+
* they are in, and `/cli/submit` does not check the provider. `push` does the
|
|
30
|
+
* right thing from either place, so it is the one to teach.
|
|
34
31
|
*
|
|
35
32
|
* The predicate is `isWorkspaceInstall`, read from our own module path for the
|
|
36
33
|
* reasons its own docstring gives (an env marker is absent in an sshd session,
|
|
37
34
|
* and a marker injected alongside the token disappears in exactly the case we
|
|
38
35
|
* need to describe). Nothing security-relevant hangs off any of this: it only
|
|
39
|
-
* decides which command a candidate is told to run.
|
|
36
|
+
* decides which command runs and which one a candidate is told to run.
|
|
40
37
|
*/
|
|
41
38
|
/** The submit command inside a Codespaces v2 workspace. */
|
|
42
39
|
export declare const WORKSPACE_SUBMIT_COMMAND = "litmus push";
|
|
@@ -50,19 +47,13 @@ export declare const LOCAL_SUBMIT_COMMAND = "litmus submit";
|
|
|
50
47
|
* the laptop one.
|
|
51
48
|
*/
|
|
52
49
|
export declare function submitCommandFor(moduleUrl: string): string;
|
|
53
|
-
/** A refusal to run, as `fatal(message, hint)` takes it. */
|
|
54
|
-
export type SubmitRefusal = {
|
|
55
|
-
message: string;
|
|
56
|
-
hint: string;
|
|
57
|
-
};
|
|
58
50
|
/**
|
|
59
|
-
*
|
|
51
|
+
* Whether `litmus submit` should submit the workspace (run `push`) instead of
|
|
52
|
+
* zipping a local checkout.
|
|
60
53
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* problem. Both routes named in the hint reach `/workspace/submit`, so this
|
|
65
|
-
* points at the two things that work and sends the candidate nowhere new.
|
|
54
|
+
* True only for the in-workspace install. The caller must ask BEFORE doing any
|
|
55
|
+
* work: the laptop path walks the cwd, reads `.litmus/config.json` and builds an
|
|
56
|
+
* archive, none of which means anything in a workspace (ENG-2130).
|
|
66
57
|
*/
|
|
67
|
-
export declare function
|
|
58
|
+
export declare function submitsWorkspace(moduleUrl: string): boolean;
|
|
68
59
|
//# sourceMappingURL=submit-route.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submit-route.d.ts","sourceRoot":"","sources":["../../src/lib/submit-route.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"submit-route.d.ts","sourceRoot":"","sources":["../../src/lib/submit-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAIH,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,gBAAgB,CAAA;AAErD,yDAAyD;AACzD,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AAEnD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE3D"}
|