@uinaf/skillcheck 0.5.1 → 0.5.3
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/cursor-process.js +71 -0
- package/dist/cursor-provider.js +95 -33
- package/docs/releasing.md +13 -4
- package/docs/usage.md +7 -1
- package/package.json +1 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
//#region src/cursor-process.ts
|
|
3
|
+
let child;
|
|
4
|
+
let terminal = false;
|
|
5
|
+
let cleaning = false;
|
|
6
|
+
let watchdog = setTimeout(cleanup, 5e3);
|
|
7
|
+
function cleanup() {
|
|
8
|
+
if (cleaning) return;
|
|
9
|
+
cleaning = true;
|
|
10
|
+
clearTimeout(watchdog);
|
|
11
|
+
if (process.platform !== "win32") process.kill(-process.pid, "SIGKILL");
|
|
12
|
+
child?.kill("SIGKILL");
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
function fail(error) {
|
|
16
|
+
if (terminal) return;
|
|
17
|
+
terminal = true;
|
|
18
|
+
process.send?.({
|
|
19
|
+
type: "failure",
|
|
20
|
+
error
|
|
21
|
+
}, cleanup);
|
|
22
|
+
clearTimeout(watchdog);
|
|
23
|
+
watchdog = setTimeout(cleanup, 1e3);
|
|
24
|
+
}
|
|
25
|
+
if (!process.send) process.exit(1);
|
|
26
|
+
process.on("disconnect", cleanup);
|
|
27
|
+
process.on("message", (message) => {
|
|
28
|
+
if (message === "stop") return cleanup();
|
|
29
|
+
if (message === "cleanup" && terminal) {
|
|
30
|
+
process.send?.({ type: "cleanup" }, cleanup);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (child !== void 0 || terminal) return;
|
|
34
|
+
if (typeof message !== "object" || message === null || !("command" in message) || typeof message.command !== "string" || !("cwd" in message) || typeof message.cwd !== "string" || !("args" in message) || !Array.isArray(message.args) || !message.args.every((arg) => typeof arg === "string") || !("timeoutMs" in message) || typeof message.timeoutMs !== "number" || !Number.isFinite(message.timeoutMs) || message.timeoutMs <= 0) return fail("cursor-agent supervisor received invalid configuration");
|
|
35
|
+
clearTimeout(watchdog);
|
|
36
|
+
watchdog = setTimeout(() => fail("cursor-agent supervisor watchdog timed out"), message.timeoutMs + 1e3);
|
|
37
|
+
const command = message.command;
|
|
38
|
+
try {
|
|
39
|
+
child = spawn(command, message.args, {
|
|
40
|
+
cwd: message.cwd,
|
|
41
|
+
stdio: [
|
|
42
|
+
"pipe",
|
|
43
|
+
"inherit",
|
|
44
|
+
"inherit"
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return fail(`failed to spawn ${command}: ${error instanceof Error ? error.message : String(error)}`);
|
|
49
|
+
}
|
|
50
|
+
child.on("error", (error) => fail(`failed to spawn ${command}: ${error.message}`));
|
|
51
|
+
child.stdin?.on("error", (error) => fail(`cursor-agent stdin failed: ${error.message}`));
|
|
52
|
+
process.stdin.on("error", (error) => fail(`cursor-agent prompt stream failed: ${error.message}`));
|
|
53
|
+
if (child.stdin) process.stdin.pipe(child.stdin);
|
|
54
|
+
child.on("exit", (code, signal) => {
|
|
55
|
+
if (terminal) return;
|
|
56
|
+
if (!child?.stdin?.writableFinished) return fail("cursor-agent stdin closed before prompt delivery");
|
|
57
|
+
terminal = true;
|
|
58
|
+
process.stdin.unpipe();
|
|
59
|
+
clearTimeout(watchdog);
|
|
60
|
+
watchdog = setTimeout(cleanup, 1e3);
|
|
61
|
+
process.send?.({
|
|
62
|
+
type: "terminal",
|
|
63
|
+
code,
|
|
64
|
+
signal
|
|
65
|
+
}, (error) => {
|
|
66
|
+
if (error) cleanup();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//#endregion
|
|
71
|
+
export {};
|
package/dist/cursor-provider.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fork } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
//#region src/cursor-provider.ts
|
|
3
4
|
function newStreamState() {
|
|
4
5
|
return {
|
|
@@ -65,53 +66,114 @@ var CursorAgentProvider = class {
|
|
|
65
66
|
const stderr = [];
|
|
66
67
|
let stdoutBuf = "";
|
|
67
68
|
return new Promise((resolve) => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const timeoutMs = this.config.timeout_ms ?? 9e5;
|
|
70
|
+
const supervisor = fork(new URL(`./cursor-process${path.extname(import.meta.url)}`, import.meta.url), {
|
|
71
|
+
execArgv: [],
|
|
72
|
+
detached: process.platform !== "win32",
|
|
71
73
|
stdio: [
|
|
72
74
|
"pipe",
|
|
73
75
|
"pipe",
|
|
74
|
-
"pipe"
|
|
76
|
+
"pipe",
|
|
77
|
+
"ipc"
|
|
75
78
|
]
|
|
76
79
|
});
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
});
|
|
86
|
-
child.stdout.on("data", (chunk) => {
|
|
87
|
-
stdoutBuf += chunk.toString("utf8");
|
|
88
|
-
const lines = stdoutBuf.split("\n");
|
|
89
|
-
stdoutBuf = lines.pop() ?? "";
|
|
90
|
-
for (const line of lines) foldLine(state, line);
|
|
91
|
-
});
|
|
92
|
-
child.stderr.on("data", (chunk) => {
|
|
93
|
-
stderr.push(chunk.toString("utf8"));
|
|
94
|
-
});
|
|
95
|
-
child.on("close", (code) => {
|
|
80
|
+
let failure;
|
|
81
|
+
let terminal;
|
|
82
|
+
let cleanupConfirmed = false;
|
|
83
|
+
let settled = false;
|
|
84
|
+
let cleanupTimer;
|
|
85
|
+
const finish = (closed, signal = null) => {
|
|
86
|
+
if (settled) return;
|
|
87
|
+
settled = true;
|
|
96
88
|
clearTimeout(timer);
|
|
89
|
+
clearTimeout(cleanupTimer);
|
|
90
|
+
supervisor.stdin?.destroy();
|
|
91
|
+
supervisor.stdout?.destroy();
|
|
92
|
+
supervisor.stderr?.destroy();
|
|
93
|
+
if (!closed) {
|
|
94
|
+
supervisor.kill("SIGKILL");
|
|
95
|
+
supervisor.unref();
|
|
96
|
+
if (supervisor.connected) supervisor.disconnect();
|
|
97
|
+
}
|
|
97
98
|
if (stdoutBuf !== "") foldLine(state, stdoutBuf);
|
|
98
|
-
|
|
99
|
+
const metadata = { skillCalls: state.skillCalls };
|
|
100
|
+
const fail = (error) => resolve({
|
|
101
|
+
error,
|
|
102
|
+
tokenUsage: state.tokenUsage,
|
|
103
|
+
metadata
|
|
104
|
+
});
|
|
105
|
+
if (failure !== void 0) {
|
|
99
106
|
const tail = stderr.join("").trim().slice(-2e3);
|
|
100
|
-
|
|
101
|
-
return;
|
|
107
|
+
const missingResult = terminal !== void 0 && state.result === void 0 ? " without a result event" : "";
|
|
108
|
+
return fail(`${failure}${missingResult}${tail === "" ? "" : `: ${tail}`}`);
|
|
102
109
|
}
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
if (!closed || !cleanupConfirmed || terminal === void 0 || process.platform !== "win32" && signal !== "SIGKILL") return fail("cursor-agent supervisor ended without confirmed cleanup");
|
|
111
|
+
if (terminal.code !== 0 || terminal.signal !== null || state.result === void 0) {
|
|
112
|
+
const tail = stderr.join("").trim().slice(-2e3);
|
|
113
|
+
return fail(`cursor-agent exited ${terminal.signal ?? terminal.code ?? "without status"}${state.result === void 0 ? " without a result event" : ""}${tail === "" ? "" : `: ${tail}`}`);
|
|
106
114
|
}
|
|
115
|
+
if (state.isError) return fail(state.result || "cursor-agent reported an error result");
|
|
107
116
|
resolve({
|
|
108
117
|
output: state.result,
|
|
109
118
|
tokenUsage: state.tokenUsage,
|
|
110
|
-
metadata
|
|
119
|
+
metadata
|
|
111
120
|
});
|
|
121
|
+
};
|
|
122
|
+
const boundCleanup = () => {
|
|
123
|
+
cleanupTimer ??= setTimeout(() => {
|
|
124
|
+
failure = failure === void 0 ? "cursor-agent cleanup or pipe draining timed out" : `${failure}; cleanup or pipe draining timed out`;
|
|
125
|
+
finish(false);
|
|
126
|
+
}, 2e3);
|
|
127
|
+
};
|
|
128
|
+
const stop = (error) => {
|
|
129
|
+
if (settled) return;
|
|
130
|
+
failure ??= error;
|
|
131
|
+
boundCleanup();
|
|
132
|
+
if (supervisor.connected) supervisor.send("stop", () => {});
|
|
133
|
+
};
|
|
134
|
+
const timer = setTimeout(() => stop(`cursor-agent timed out after ${timeoutMs}ms`), timeoutMs);
|
|
135
|
+
supervisor.on("error", (err) => stop(`cursor-agent supervisor failed: ${err.message}`));
|
|
136
|
+
supervisor.on("disconnect", () => {
|
|
137
|
+
if (!cleanupConfirmed && !settled) stop("cursor-agent supervisor disconnected before cleanup");
|
|
138
|
+
});
|
|
139
|
+
supervisor.on("message", (message) => {
|
|
140
|
+
if (settled) return;
|
|
141
|
+
if (typeof message !== "object" || message === null) return;
|
|
142
|
+
if (!("type" in message)) return;
|
|
143
|
+
if (message.type === "terminal" && "code" in message && "signal" in message && (message.code === null || typeof message.code === "number") && (message.signal === null || typeof message.signal === "string")) {
|
|
144
|
+
terminal = {
|
|
145
|
+
code: message.code,
|
|
146
|
+
signal: message.signal
|
|
147
|
+
};
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
if (terminal.code !== 0 || terminal.signal !== null) failure ??= `cursor-agent exited ${terminal.signal ?? terminal.code ?? "without status"}`;
|
|
150
|
+
boundCleanup();
|
|
151
|
+
supervisor.send("cleanup", (err) => {
|
|
152
|
+
if (err) stop(`cursor-agent cleanup request failed: ${err.message}`);
|
|
153
|
+
});
|
|
154
|
+
} else if (message.type === "failure" && "error" in message && typeof message.error === "string") stop(message.error);
|
|
155
|
+
else if (message.type === "cleanup" && terminal !== void 0) cleanupConfirmed = true;
|
|
156
|
+
});
|
|
157
|
+
supervisor.stdout?.on("data", (chunk) => {
|
|
158
|
+
stdoutBuf += chunk.toString("utf8");
|
|
159
|
+
const lines = stdoutBuf.split("\n");
|
|
160
|
+
stdoutBuf = lines.pop() ?? "";
|
|
161
|
+
for (const line of lines) foldLine(state, line);
|
|
162
|
+
});
|
|
163
|
+
supervisor.stderr?.on("data", (chunk) => stderr.push(chunk.toString("utf8")));
|
|
164
|
+
supervisor.stdout?.on("error", (err) => stop(`cursor-agent stdout failed: ${err.message}`));
|
|
165
|
+
supervisor.stderr?.on("error", (err) => stop(`cursor-agent stderr failed: ${err.message}`));
|
|
166
|
+
supervisor.stdin?.on("error", (err) => stop(`cursor-agent stdin failed: ${err.message}`));
|
|
167
|
+
supervisor.on("close", (_code, signal) => finish(true, signal));
|
|
168
|
+
supervisor.send({
|
|
169
|
+
command,
|
|
170
|
+
args,
|
|
171
|
+
cwd: this.config.working_dir,
|
|
172
|
+
timeoutMs
|
|
173
|
+
}, (err) => {
|
|
174
|
+
if (err) stop(`cursor-agent supervisor initialization failed: ${err.message}`);
|
|
112
175
|
});
|
|
113
|
-
|
|
114
|
-
child.stdin.end();
|
|
176
|
+
supervisor.stdin?.end(prompt);
|
|
115
177
|
});
|
|
116
178
|
}
|
|
117
179
|
};
|
package/docs/releasing.md
CHANGED
|
@@ -48,11 +48,20 @@ history continues from `v0.1.3`; `v0.1.0`–`v0.1.3` are the legacy git-install
|
|
|
48
48
|
tags and are never deleted or moved.
|
|
49
49
|
|
|
50
50
|
During preparation, `@semantic-release/npm` stages the released `package.json`
|
|
51
|
-
version and
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
version and `scripts/release-commit.ts` commits it through GitHub's signed
|
|
52
|
+
`createCommitOnBranch` API as the authenticated App. The checkout and expected
|
|
53
|
+
branch head are the verified workflow event SHA. GitHub atomically rejects the
|
|
54
|
+
write if `main` has advanced; a successful response is fetched by its immutable
|
|
55
|
+
commit SHA. The plugin checks its parent, unchanged source, and exact prepared
|
|
56
|
+
manifest before semantic-release tags it. Only the package version may change. The `[skip ci]` marker on that commit is what stops a release
|
|
54
57
|
from releasing itself.
|
|
55
58
|
|
|
59
|
+
If GitHub accepts the commit but fetching or validating it fails, preparation
|
|
60
|
+
stops before tagging or publishing. Inspect that commit's parent, tree, version
|
|
61
|
+
and verified signature, plus existing tags, npm versions and GitHub Releases,
|
|
62
|
+
before recovery. Reconcile only missing publication steps from the validated
|
|
63
|
+
commit; do not create another version or move an existing tag to hide failure.
|
|
64
|
+
|
|
56
65
|
Check what the next version would be without publishing anything:
|
|
57
66
|
|
|
58
67
|
```sh
|
|
@@ -61,7 +70,7 @@ pnpm dlx semantic-release --dry-run --no-ci
|
|
|
61
70
|
|
|
62
71
|
## The artifact
|
|
63
72
|
|
|
64
|
-
`dist/` is generated and untracked. `prepublishOnly` runs `pnpm run verify`,
|
|
73
|
+
`dist/` is generated and untracked. `prepublishOnly` runs `pnpm run verify:full`,
|
|
65
74
|
which builds it, so the tarball is always packed from a tree that just passed
|
|
66
75
|
the gate. `files` is `dist`, `docs`, `README.md`, `LICENSE`.
|
|
67
76
|
|
package/docs/usage.md
CHANGED
|
@@ -56,7 +56,13 @@ the model to that CLI's own default.
|
|
|
56
56
|
cursor provider, so the run uses this package's own provider module, which
|
|
57
57
|
replays the CLI's `stream-json` output: the `result` event becomes the graded
|
|
58
58
|
output and `SKILL.md` reads under `.cursor/skills/` become the `skill-used`
|
|
59
|
-
evidence.
|
|
59
|
+
evidence. Grading requires both a successful result event and harness exit code
|
|
60
|
+
zero. On macOS and Linux, a supervisor owns the process group and kills remaining
|
|
61
|
+
helpers when the harness exits, times out, or the provider disconnects. Output
|
|
62
|
+
pipes have a separate two-second cleanup/drain deadline; incomplete cleanup is
|
|
63
|
+
an error. Helpers that detach into another process group are outside this cleanup
|
|
64
|
+
boundary; retained output pipes still cause a bounded error. Windows retains
|
|
65
|
+
only direct-child cleanup. The judge leg is unchanged.
|
|
60
66
|
|
|
61
67
|
`--judge` takes either a bare Claude model (graded through the Anthropic
|
|
62
68
|
selection in [auth](#auth)) or a provider-qualified promptfoo id, passed
|