@holdpoint/engine-cursor 0.1.0-alpha.12 → 0.1.0-alpha.14
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/index.js +247 -33
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,6 +1,170 @@
|
|
|
1
1
|
// src/engine.ts
|
|
2
2
|
var HOLDPOINT_CURSOR_HOOK_MARKER = "HOLDPOINT_MANAGED=cursor";
|
|
3
3
|
var HOOK_COMMAND = `node .cursor/holdpoint-hook.mjs # ${HOLDPOINT_CURSOR_HOOK_MARKER}`;
|
|
4
|
+
var SECURITY_SCAN_PACKAGES = [
|
|
5
|
+
"@anthropic-ai/mcp-server-brave-search",
|
|
6
|
+
"@anthropic-ai/mcp-server-fetch",
|
|
7
|
+
"@modelcontextprotocol/server-filesystem",
|
|
8
|
+
"@modelcontextprotocol/server-github",
|
|
9
|
+
"@modelcontextprotocol/server-gitlab",
|
|
10
|
+
"@modelcontextprotocol/server-google-maps",
|
|
11
|
+
"@modelcontextprotocol/server-postgres",
|
|
12
|
+
"@modelcontextprotocol/server-slack",
|
|
13
|
+
"@modelcontextprotocol/server-memory",
|
|
14
|
+
"@modelcontextprotocol/server-puppeteer",
|
|
15
|
+
"@modelcontextprotocol/server-sequential-thinking",
|
|
16
|
+
"@modelcontextprotocol/server-everything"
|
|
17
|
+
];
|
|
18
|
+
function buildSecurityScanScript() {
|
|
19
|
+
return `
|
|
20
|
+
// Keep behavior in sync with packages/cli/src/lib/scan.ts.
|
|
21
|
+
const SECURITY_SCAN_VERIFIED = new Set(${JSON.stringify(SECURITY_SCAN_PACKAGES)});
|
|
22
|
+
const SECURITY_SCAN_SEVERITIES = new Set(["high", "critical"]);
|
|
23
|
+
function securityScanReadJson(path) {
|
|
24
|
+
try { return JSON.parse(readFileSync(path, "utf8")); } catch { return undefined; }
|
|
25
|
+
}
|
|
26
|
+
function securityScanStringArray(value) {
|
|
27
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
28
|
+
}
|
|
29
|
+
function securityScanPackageName(value) {
|
|
30
|
+
const normalized = String(value || "").replace(/\\\\/g, "/");
|
|
31
|
+
const idx = normalized.lastIndexOf("node_modules/");
|
|
32
|
+
if (idx >= 0) {
|
|
33
|
+
const parts = normalized.slice(idx + "node_modules/".length).split("/").filter(Boolean);
|
|
34
|
+
if (parts[0] && parts[0].startsWith("@") && parts[1]) return parts[0] + "/" + parts[1];
|
|
35
|
+
return parts[0];
|
|
36
|
+
}
|
|
37
|
+
if (normalized.startsWith("@")) {
|
|
38
|
+
const parts = normalized.split("/");
|
|
39
|
+
return parts[0] && parts[1] ? parts[0] + "/" + parts[1] : undefined;
|
|
40
|
+
}
|
|
41
|
+
if (!normalized.includes("/") && /^[a-z0-9@._-]+$/i.test(normalized)) return normalized;
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
function securityScanMcpEntries(config) {
|
|
45
|
+
if (!config || typeof config !== "object") return [];
|
|
46
|
+
const servers = config.mcpServers || config.servers;
|
|
47
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) return [];
|
|
48
|
+
return Object.entries(servers).map(([key, raw]) => {
|
|
49
|
+
const server = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
50
|
+
return {
|
|
51
|
+
key,
|
|
52
|
+
name: typeof server.name === "string" ? server.name : undefined,
|
|
53
|
+
command: typeof server.command === "string" ? server.command : undefined,
|
|
54
|
+
args: securityScanStringArray(server.args),
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function securityScanMcp(root) {
|
|
59
|
+
const results = [];
|
|
60
|
+
for (const file of [join(root, ".mcp.json"), join(root, ".claude/mcp.json")]) {
|
|
61
|
+
if (!existsSync(file)) continue;
|
|
62
|
+
for (const entry of securityScanMcpEntries(securityScanReadJson(file))) {
|
|
63
|
+
const values = [entry.name, entry.command, ...entry.args]
|
|
64
|
+
.filter((value) => typeof value === "string" && value.trim())
|
|
65
|
+
.map((value) => value.trim());
|
|
66
|
+
const packages = values.map(securityScanPackageName).filter(Boolean);
|
|
67
|
+
const verified = [...values, ...packages].some((candidate) => SECURITY_SCAN_VERIFIED.has(candidate));
|
|
68
|
+
results.push({ server: entry.name || entry.key, verified });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
function securityScanPackageManager(root) {
|
|
74
|
+
if (existsSync(join(root, "pnpm-lock.yaml"))) return "pnpm";
|
|
75
|
+
if (existsSync(join(root, "yarn.lock"))) return "yarn";
|
|
76
|
+
if (existsSync(join(root, "package-lock.json")) || existsSync(join(root, "npm-shrinkwrap.json"))) return "npm";
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function securityScanFirstTitle(value) {
|
|
80
|
+
if (typeof value === "string") return value;
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
for (const entry of value) {
|
|
83
|
+
const title = securityScanFirstTitle(entry);
|
|
84
|
+
if (title) return title;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (value && typeof value === "object" && typeof value.title === "string") return value.title;
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
function securityScanAddFinding(findings, seen, name, severity, title) {
|
|
91
|
+
if (!name || typeof severity !== "string") return;
|
|
92
|
+
const normalizedSeverity = severity.toLowerCase();
|
|
93
|
+
if (!SECURITY_SCAN_SEVERITIES.has(normalizedSeverity)) return;
|
|
94
|
+
const normalizedTitle = securityScanFirstTitle(title) || "Security advisory";
|
|
95
|
+
const key = name + "\\0" + normalizedSeverity + "\\0" + normalizedTitle;
|
|
96
|
+
if (seen.has(key)) return;
|
|
97
|
+
seen.add(key);
|
|
98
|
+
findings.push({ name, severity: normalizedSeverity, title: normalizedTitle });
|
|
99
|
+
}
|
|
100
|
+
function securityScanParseAudit(raw) {
|
|
101
|
+
const findings = [];
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
const parseOne = (data) => {
|
|
104
|
+
if (!data || typeof data !== "object") return;
|
|
105
|
+
if (data.vulnerabilities && typeof data.vulnerabilities === "object") {
|
|
106
|
+
for (const [name, vuln] of Object.entries(data.vulnerabilities)) {
|
|
107
|
+
if (!vuln || typeof vuln !== "object") continue;
|
|
108
|
+
securityScanAddFinding(findings, seen, name, vuln.severity, vuln.via || vuln.title);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (data.advisories && typeof data.advisories === "object") {
|
|
112
|
+
for (const advisory of Object.values(data.advisories)) {
|
|
113
|
+
if (!advisory || typeof advisory !== "object") continue;
|
|
114
|
+
securityScanAddFinding(findings, seen, advisory.module_name, advisory.severity, advisory.title);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (data.type === "auditAdvisory" && data.data && data.data.advisory) {
|
|
118
|
+
const advisory = data.data.advisory;
|
|
119
|
+
securityScanAddFinding(findings, seen, advisory.module_name, advisory.severity, advisory.title);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
try { parseOne(JSON.parse(raw)); }
|
|
123
|
+
catch {
|
|
124
|
+
for (const line of String(raw || "").split(/\\r?\\n/)) {
|
|
125
|
+
if (!line.trim()) continue;
|
|
126
|
+
try { parseOne(JSON.parse(line)); } catch {}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return findings.slice(0, 5);
|
|
130
|
+
}
|
|
131
|
+
function securityScanAudit(root) {
|
|
132
|
+
const pm = securityScanPackageManager(root);
|
|
133
|
+
if (!pm) return { pm: null, findings: [] };
|
|
134
|
+
try {
|
|
135
|
+
const stdout = execSync(pm + " audit --json", {
|
|
136
|
+
cwd: root,
|
|
137
|
+
encoding: "utf8",
|
|
138
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
139
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
140
|
+
timeout: 8000,
|
|
141
|
+
});
|
|
142
|
+
return { pm, findings: securityScanParseAudit(stdout) };
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const stdout = err && typeof err.stdout === "string" ? err.stdout : "";
|
|
145
|
+
return { pm, findings: stdout.trim() ? securityScanParseAudit(stdout) : [] };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function formatSecurityScan(root) {
|
|
149
|
+
const unverified = securityScanMcp(root).filter((entry) => !entry.verified);
|
|
150
|
+
const audit = securityScanAudit(root);
|
|
151
|
+
if (unverified.length === 0 && audit.findings.length === 0) return null;
|
|
152
|
+
const lines = ["\u26A0 Holdpoint Security Scan", ""];
|
|
153
|
+
if (unverified.length > 0) {
|
|
154
|
+
lines.push("MCP servers \u2014 unverified:");
|
|
155
|
+
for (const entry of unverified) lines.push(" \u2022 " + entry.server + " (source unknown \u2014 review before trusting)");
|
|
156
|
+
lines.push("");
|
|
157
|
+
}
|
|
158
|
+
if (audit.findings.length > 0) {
|
|
159
|
+
lines.push((audit.pm || "npm") + " audit \u2014 high/critical:");
|
|
160
|
+
for (const dep of audit.findings) lines.push(" \u2022 " + dep.name + " \xB7 " + dep.title + " (" + dep.severity + ")");
|
|
161
|
+
lines.push("");
|
|
162
|
+
}
|
|
163
|
+
lines.push("Review these before allowing the agent to install dependencies or invoke tools.");
|
|
164
|
+
return lines.join("\\n");
|
|
165
|
+
}
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
4
168
|
function hook(options = {}) {
|
|
5
169
|
return {
|
|
6
170
|
command: HOOK_COMMAND,
|
|
@@ -23,11 +187,10 @@ function buildHooksJson(config) {
|
|
|
23
187
|
subagentStop: [hook({ timeout: 600, loop_limit: 5 })],
|
|
24
188
|
preCompact: [hook({ timeout: 30 })],
|
|
25
189
|
afterAgentResponse: [hook({ timeout: 30 })],
|
|
26
|
-
stop: [hook({ timeout: 600, loop_limit: 5 })]
|
|
190
|
+
stop: [hook({ timeout: 600, loop_limit: 5 })],
|
|
191
|
+
sessionStart: [hook({ timeout: 30 })]
|
|
27
192
|
};
|
|
28
|
-
|
|
29
|
-
hooks.sessionStart = [hook({ timeout: 30 })];
|
|
30
|
-
}
|
|
193
|
+
void config;
|
|
31
194
|
return JSON.stringify({ version: 1, hooks }, null, 2) + "\n";
|
|
32
195
|
}
|
|
33
196
|
function buildCheckScript() {
|
|
@@ -36,7 +199,7 @@ function buildCheckScript() {
|
|
|
36
199
|
import { execSync } from "node:child_process";
|
|
37
200
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
38
201
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
39
|
-
|
|
202
|
+
${buildSecurityScanScript()}
|
|
40
203
|
const CHECK_COMMAND = "node_modules/.bin/holdpoint check --staged";
|
|
41
204
|
const LIVE_COMMAND = "node_modules/.bin/holdpoint event --engine cursor --from-hook";
|
|
42
205
|
const MAX_CONTEXT_CHARS = 100_000;
|
|
@@ -95,38 +258,76 @@ function sendLiveEvent(input) {
|
|
|
95
258
|
}
|
|
96
259
|
}
|
|
97
260
|
|
|
98
|
-
function
|
|
261
|
+
function readConfig(repoRoot) {
|
|
99
262
|
const configPath = join(repoRoot, ".github/holdpoint/generated/checks.immutable.json");
|
|
100
|
-
if (!existsSync(configPath)) return
|
|
101
|
-
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
263
|
+
if (!existsSync(configPath)) return {};
|
|
264
|
+
try { return JSON.parse(readFileSync(configPath, "utf8")); } catch { return {}; }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function readFileContext(repoRoot, file) {
|
|
268
|
+
if (typeof file !== "string" || !file.trim()) return null;
|
|
269
|
+
const abs = resolve(repoRoot, file);
|
|
270
|
+
if (!isPathInsideRoot(repoRoot, abs) || !existsSync(abs)) return null;
|
|
271
|
+
try { return "<!-- " + file + " -->\\n" + readFileSync(abs, "utf8"); } catch { return null; }
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const DATETIME_TEXT = () =>
|
|
275
|
+
"Current date and time: " + new Date().toISOString() + " (UTC)\\n" +
|
|
276
|
+
"Provided by Holdpoint \u2014 use this to avoid knowledge-cutoff confusion.";
|
|
277
|
+
|
|
278
|
+
// Gather agent context for a Holdpoint hook. NOTE: Cursor's beforeSubmitPrompt
|
|
279
|
+
// cannot inject context (it only gates submission), so the top-level datetime
|
|
280
|
+
// and message_submit checks are surfaced at sessionStart instead \u2014 the only
|
|
281
|
+
// Cursor stage that accepts additional_context per the hooks API.
|
|
282
|
+
function gatherHookContext(repoRoot, hook) {
|
|
283
|
+
const cfg = readConfig(repoRoot);
|
|
284
|
+
const checks = Array.isArray(cfg.checks) ? cfg.checks : [];
|
|
285
|
+
const parts = [];
|
|
286
|
+
let hasDatetime = false;
|
|
287
|
+
const addDatetime = () => { if (!hasDatetime) { hasDatetime = true; parts.push(DATETIME_TEXT()); } };
|
|
288
|
+
|
|
289
|
+
const includeHooks = hook === "session_start" ? ["session_start", "message_submit"] : [hook];
|
|
290
|
+
|
|
291
|
+
if (hook === "session_start") {
|
|
292
|
+
const scan = formatSecurityScan(repoRoot);
|
|
293
|
+
if (scan) parts.push(scan);
|
|
294
|
+
const files = Array.isArray(cfg.session_context_files) ? cfg.session_context_files : [];
|
|
295
|
+
for (const f of files) { const c = readFileContext(repoRoot, f); if (c) parts.push(c); }
|
|
296
|
+
}
|
|
297
|
+
for (const c of checks) {
|
|
298
|
+
const on = typeof c.on === "string" ? c.on : "before_done";
|
|
299
|
+
if (!includeHooks.includes(on)) continue;
|
|
300
|
+
if (c.inject && typeof c.inject === "object") {
|
|
301
|
+
if (c.inject.datetime === true) addDatetime();
|
|
302
|
+
if (typeof c.inject.text === "string" && c.inject.text.trim()) parts.push(c.inject.text);
|
|
303
|
+
if (Array.isArray(c.inject.files)) for (const f of c.inject.files) { const x = readFileContext(repoRoot, f); if (x) parts.push(x); }
|
|
304
|
+
} else if (typeof c.prompt === "string" && c.prompt.trim()) {
|
|
305
|
+
parts.push("Holdpoint reminder [" + (c.label || c.id || "check") + "]: " + c.prompt);
|
|
112
306
|
}
|
|
113
|
-
if (parts.length === 0) return undefined;
|
|
114
|
-
const context = truncateText(parts.join("\\n\\n"), MAX_CONTEXT_CHARS);
|
|
115
|
-
return {
|
|
116
|
-
additional_context: context.text,
|
|
117
|
-
truncated: context.truncated,
|
|
118
|
-
originalLength: context.originalLength,
|
|
119
|
-
emittedLength: context.text.length,
|
|
120
|
-
};
|
|
121
|
-
} catch {
|
|
122
|
-
return undefined;
|
|
123
307
|
}
|
|
308
|
+
// Cursor can only inject once (sessionStart), so fold the per-message datetime in here.
|
|
309
|
+
if (hook === "session_start" && cfg.inject_datetime !== false) addDatetime();
|
|
310
|
+
|
|
311
|
+
if (parts.length === 0) return undefined;
|
|
312
|
+
const context = truncateText(parts.join("\\n\\n"), MAX_CONTEXT_CHARS);
|
|
313
|
+
return {
|
|
314
|
+
additional_context: context.text,
|
|
315
|
+
truncated: context.truncated,
|
|
316
|
+
originalLength: context.originalLength,
|
|
317
|
+
emittedLength: context.text.length,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function hasCmdAt(repoRoot, hook) {
|
|
322
|
+
const cfg = readConfig(repoRoot);
|
|
323
|
+
const checks = Array.isArray(cfg.checks) ? cfg.checks : [];
|
|
324
|
+
return checks.some((c) => typeof c.cmd === "string" && (typeof c.on === "string" ? c.on : "before_done") === hook);
|
|
124
325
|
}
|
|
125
326
|
|
|
126
|
-
function runHoldpointChecks(repoRoot) {
|
|
327
|
+
function runHoldpointChecks(repoRoot, command = CHECK_COMMAND) {
|
|
127
328
|
const startedAt = Date.now();
|
|
128
329
|
try {
|
|
129
|
-
const output = execSync(
|
|
330
|
+
const output = execSync(command, {
|
|
130
331
|
cwd: repoRoot,
|
|
131
332
|
stdio: "pipe",
|
|
132
333
|
encoding: "utf8",
|
|
@@ -168,7 +369,7 @@ const repoRoot = resolveRepoRoot(cwd);
|
|
|
168
369
|
const name = eventName(input);
|
|
169
370
|
|
|
170
371
|
if (name === "sessionStart") {
|
|
171
|
-
const context =
|
|
372
|
+
const context = gatherHookContext(repoRoot, "session_start");
|
|
172
373
|
sendLiveEvent({
|
|
173
374
|
...input,
|
|
174
375
|
holdpoint_context: context
|
|
@@ -203,8 +404,19 @@ if (shouldRunCompletionChecks(input)) {
|
|
|
203
404
|
|
|
204
405
|
sendLiveEvent(input);
|
|
205
406
|
|
|
206
|
-
if (
|
|
207
|
-
|
|
407
|
+
if (name === "preToolUse") {
|
|
408
|
+
// Gate on before_tool cmd checks; deny the tool if they fail.
|
|
409
|
+
if (hasCmdAt(repoRoot, "before_tool")) {
|
|
410
|
+
const result = runHoldpointChecks(repoRoot, CHECK_COMMAND + " --hook before_tool");
|
|
411
|
+
if (!result.ok) {
|
|
412
|
+
process.stdout.write(
|
|
413
|
+
JSON.stringify({ permission: "deny", agentMessage: result.output }) + "\\n",
|
|
414
|
+
);
|
|
415
|
+
process.exit(0);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
process.stdout.write(JSON.stringify({ permission: "allow" }) + "\\n");
|
|
419
|
+
} else if (
|
|
208
420
|
name === "beforeShellExecution" ||
|
|
209
421
|
name === "beforeMCPExecution" ||
|
|
210
422
|
name === "beforeReadFile" ||
|
|
@@ -212,6 +424,8 @@ if (
|
|
|
212
424
|
) {
|
|
213
425
|
process.stdout.write(JSON.stringify({ permission: "allow" }) + "\\n");
|
|
214
426
|
} else if (name === "beforeSubmitPrompt") {
|
|
427
|
+
// Cursor's beforeSubmitPrompt cannot inject context \u2014 only allow/deny the
|
|
428
|
+
// submission. Per-message context is folded into sessionStart instead.
|
|
215
429
|
process.stdout.write(JSON.stringify({ continue: true }) + "\\n");
|
|
216
430
|
}
|
|
217
431
|
process.exit(0);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/engine.ts","../src/live-adapter.ts","../src/manifest.ts"],"sourcesContent":["import type { HoldpointConfig } from \"@holdpoint/types\";\n\nexport const HOLDPOINT_CURSOR_HOOK_MARKER = \"HOLDPOINT_MANAGED=cursor\";\n\nconst HOOK_COMMAND = `node .cursor/holdpoint-hook.mjs # ${HOLDPOINT_CURSOR_HOOK_MARKER}`;\n\ninterface CursorHook {\n command: string;\n timeout?: number;\n matcher?: string;\n loop_limit?: number;\n failClosed?: boolean;\n}\n\ninterface CursorHooksJson {\n version: 1;\n hooks: Record<string, CursorHook[]>;\n}\n\nfunction hook(options: Omit<CursorHook, \"command\"> = {}): CursorHook {\n return {\n command: HOOK_COMMAND,\n ...options,\n };\n}\n\n/**\n * Generate project-level Cursor hooks.\n *\n * Cursor project hooks live at `.cursor/hooks.json`, run from the project root,\n * and can observe, inject context, or auto-continue the agent loop. Holdpoint\n * uses a single generated script so hook behavior stays in sync with\n * `checks.yaml` after every `holdpoint update`.\n */\nexport function buildHooksJson(config: HoldpointConfig): string {\n const hooks: CursorHooksJson[\"hooks\"] = {\n beforeSubmitPrompt: [hook({ timeout: 30 })],\n preToolUse: [hook({ timeout: 30, matcher: \"Shell|Read|Write|Grep|Task|MCP:.*\" })],\n postToolUse: [hook({ timeout: 30 })],\n postToolUseFailure: [hook({ timeout: 30 })],\n beforeShellExecution: [hook({ timeout: 30 })],\n afterShellExecution: [hook({ timeout: 30 })],\n beforeMCPExecution: [hook({ timeout: 30 })],\n afterMCPExecution: [hook({ timeout: 30 })],\n beforeReadFile: [hook({ timeout: 30 })],\n afterFileEdit: [hook({ timeout: 30 })],\n subagentStart: [hook({ timeout: 30 })],\n subagentStop: [hook({ timeout: 600, loop_limit: 5 })],\n preCompact: [hook({ timeout: 30 })],\n afterAgentResponse: [hook({ timeout: 30 })],\n stop: [hook({ timeout: 600, loop_limit: 5 })],\n };\n\n if (config.session_context_files?.length) {\n hooks.sessionStart = [hook({ timeout: 30 })];\n }\n\n return JSON.stringify({ version: 1, hooks }, null, 2) + \"\\n\";\n}\n\n/**\n * Generate `.cursor/holdpoint-hook.mjs`.\n *\n * The script speaks Cursor's native hook protocol:\n * - `sessionStart` returns `additional_context` when session_context_files exist.\n * - `stop` and completed `subagentStop` run Holdpoint checks and return a\n * `followup_message` on failure so Cursor keeps iterating.\n * - all other hooks are used for Live telemetry and emit either an allow\n * response or no output, depending on that hook's schema.\n */\nexport function buildCheckScript(): string {\n return `#!/usr/bin/env node\n// AUTO-GENERATED by Holdpoint — do not edit. Re-generate: npx holdpoint update\nimport { execSync } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { isAbsolute, join, relative, resolve } from \"node:path\";\n\nconst CHECK_COMMAND = \"node_modules/.bin/holdpoint check --staged\";\nconst LIVE_COMMAND = \"node_modules/.bin/holdpoint event --engine cursor --from-hook\";\nconst MAX_CONTEXT_CHARS = 100_000;\nconst MAX_CHECK_OUTPUT_CHARS = 60_000;\nconst CHECK_MAX_BUFFER_BYTES = 1024 * 1024 * 10;\n\nfunction readInput() {\n try {\n const raw = readFileSync(0, \"utf8\").trim();\n return raw ? JSON.parse(raw) : {};\n } catch {\n return {};\n }\n}\n\nfunction resolveRepoRoot(cwd = process.cwd()) {\n try {\n return execSync(\"git rev-parse --show-toplevel\", {\n cwd,\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n return cwd;\n }\n}\n\nfunction eventName(input) {\n return String(input && input.hook_event_name ? input.hook_event_name : \"\");\n}\n\nfunction truncateText(value, maxChars) {\n const text = String(value || \"\");\n if (text.length <= maxChars) return { text, truncated: false, originalLength: text.length };\n return {\n text: text.slice(0, maxChars) + \"\\\\n\\\\n[Holdpoint output truncated to \" + maxChars + \" chars.]\",\n truncated: true,\n originalLength: text.length,\n };\n}\n\nfunction isPathInsideRoot(repoRoot, absPath) {\n const rel = relative(repoRoot, absPath);\n return rel === \"\" || (!rel.startsWith(\"..\") && !isAbsolute(rel));\n}\n\nfunction sendLiveEvent(input) {\n try {\n execSync(LIVE_COMMAND, {\n input: JSON.stringify(input),\n encoding: \"utf8\",\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n });\n } catch {\n // Live telemetry is best-effort and must never break Cursor's hook flow.\n }\n}\n\nfunction readSessionContext(repoRoot) {\n const configPath = join(repoRoot, \".github/holdpoint/generated/checks.immutable.json\");\n if (!existsSync(configPath)) return undefined;\n try {\n const config = JSON.parse(readFileSync(configPath, \"utf8\"));\n const files = Array.isArray(config.session_context_files) ? config.session_context_files : [];\n const parts = [];\n for (const file of files) {\n if (typeof file !== \"string\" || !file.trim()) continue;\n const abs = resolve(repoRoot, file);\n if (!isPathInsideRoot(repoRoot, abs) || !existsSync(abs)) continue;\n try {\n parts.push(\"<!-- \" + file + \" -->\\\\n\" + readFileSync(abs, \"utf8\"));\n } catch {}\n }\n if (parts.length === 0) return undefined;\n const context = truncateText(parts.join(\"\\\\n\\\\n\"), MAX_CONTEXT_CHARS);\n return {\n additional_context: context.text,\n truncated: context.truncated,\n originalLength: context.originalLength,\n emittedLength: context.text.length,\n };\n } catch {\n return undefined;\n }\n}\n\nfunction runHoldpointChecks(repoRoot) {\n const startedAt = Date.now();\n try {\n const output = execSync(CHECK_COMMAND, {\n cwd: repoRoot,\n stdio: \"pipe\",\n encoding: \"utf8\",\n maxBuffer: CHECK_MAX_BUFFER_BYTES,\n });\n return {\n ok: true,\n durationMs: Date.now() - startedAt,\n output: String(output || \"\").trim(),\n };\n } catch (error) {\n const output = [error && error.stdout, error && error.stderr, error && error.message]\n .filter(Boolean)\n .join(\"\\\\n\")\n .trim();\n const truncated = truncateText(output, MAX_CHECK_OUTPUT_CHARS);\n return {\n ok: false,\n durationMs: Date.now() - startedAt,\n output: truncated.text || \"Holdpoint checks failed. Fix the issues above, then re-attempt.\",\n truncated: truncated.truncated,\n originalLength: truncated.originalLength,\n };\n }\n}\n\nfunction shouldRunCompletionChecks(input) {\n const name = eventName(input);\n if (name === \"stop\") return true;\n if (name === \"subagentStop\") {\n return input && input.status === \"completed\";\n }\n return false;\n}\n\nconst input = readInput();\nconst cwd = typeof input.cwd === \"string\" ? input.cwd : process.cwd();\nconst repoRoot = resolveRepoRoot(cwd);\nconst name = eventName(input);\n\nif (name === \"sessionStart\") {\n const context = readSessionContext(repoRoot);\n sendLiveEvent({\n ...input,\n holdpoint_context: context\n ? {\n truncated: context.truncated,\n originalLength: context.originalLength,\n emittedLength: context.emittedLength,\n }\n : undefined,\n });\n if (context?.additional_context) {\n writeFileSync(1, JSON.stringify({ additional_context: context.additional_context }) + \"\\\\n\");\n }\n process.exit(0);\n}\n\nif (shouldRunCompletionChecks(input)) {\n const result = runHoldpointChecks(repoRoot);\n sendLiveEvent({ ...input, holdpoint_check: result });\n if (result.ok) {\n process.exit(0);\n }\n process.stdout.write(\n JSON.stringify({\n followup_message:\n result.output +\n \"\\\\n\\\\nHoldpoint checks failed. Fix the issues above, then run the checks again before finishing.\",\n }) + \"\\\\n\",\n );\n process.exit(0);\n}\n\nsendLiveEvent(input);\n\nif (\n name === \"preToolUse\" ||\n name === \"beforeShellExecution\" ||\n name === \"beforeMCPExecution\" ||\n name === \"beforeReadFile\" ||\n name === \"subagentStart\"\n) {\n process.stdout.write(JSON.stringify({ permission: \"allow\" }) + \"\\\\n\");\n} else if (name === \"beforeSubmitPrompt\") {\n process.stdout.write(JSON.stringify({ continue: true }) + \"\\\\n\");\n}\nprocess.exit(0);\n`;\n}\n\n/**\n * Generate a standalone context-injection script for sessionStart tests.\n * Cursor uses the same generated dispatcher for context, telemetry, and gates.\n */\nexport function buildContextScript(): string {\n return buildCheckScript();\n}\n\n/**\n * Generate .cursorrules additions from a HoldpointConfig.\n *\n * Cursor now enforces Holdpoint through `.cursor/hooks.json`; this rules block\n * remains useful context for the agent and for Cursor cloud cases where not all\n * local hook stages are available.\n */\nexport function buildEngine(config: HoldpointConfig): string {\n const deterministicList = config.checks\n .filter((c) => c.cmd !== undefined)\n .map((c) => ` - [${c.when ?? \"always\"}] ${c.label}: \\`${c.cmd ?? \"(no cmd)\"}\\``)\n .join(\"\\n\");\n\n const promptList = config.checks\n .filter((c) => c.prompt !== undefined)\n .map((c) => ` - [${c.when ?? \"always\"}] ${c.label}: ${c.prompt ?? \"\"}`)\n .join(\"\\n\");\n\n return `# ─── Holdpoint Rules (auto-generated) ─────────────────────────────────────────\n# DO NOT EDIT this block manually. Re-generate with: npx holdpoint update\n\n## Mandatory pre-completion checks\n\nHoldpoint also installed Cursor project hooks in \\`.cursor/hooks.json\\`. Before\nmarking ANY task as done or making a final commit, you MUST:\n\n1. Run all Holdpoint tasks and confirm they pass:\n${deterministicList || \" (no tasks configured)\"}\n\n2. Act on all matching agent prompts:\n${promptList || \" (no prompt checks configured)\"}\n\n3. If any task exits non-zero, fix the underlying issue before\n proceeding. Do NOT suppress errors or skip tasks.\n\n4. For prompt checks, explicitly state in your response that you have acted on\n each item before marking the task complete.\n\n## Running checks\n Run: \\`node_modules/.bin/holdpoint check --staged\\` to execute all tasks.\n Fix all failures before proceeding.\n\n# ─── End Holdpoint Rules ───────────────────────────────────────────────────────\n`;\n}\n","import { execFileSync } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { EventV1 } from \"@holdpoint/live-protocol\";\nimport type { TranslateHookInputOptions } from \"@holdpoint/sdk\";\n\ninterface CursorHookInput {\n conversation_id?: string;\n generation_id?: string;\n session_id?: string;\n hook_event_name?: string;\n cwd?: string;\n workspace_roots?: string[];\n model?: string;\n prompt?: string;\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: string;\n tool_use_id?: string;\n duration?: number;\n duration_ms?: number;\n error_message?: string;\n failure_type?: string;\n status?: string;\n reason?: string;\n command?: string;\n output?: string;\n file_path?: string;\n modified_files?: string[];\n subagent_id?: string;\n subagent_type?: string;\n task?: string;\n holdpoint_check?: {\n ok?: boolean;\n durationMs?: number;\n output?: string;\n };\n holdpoint_context?: {\n truncated?: boolean;\n originalLength?: number;\n emittedLength?: number;\n };\n}\n\nfunction asObject(value: unknown): Record<string, unknown> {\n return value != null && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction sha12(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 12);\n}\n\nfunction resolveRepoRoot(cwd: string): string {\n try {\n return realpathSync(\n execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim(),\n );\n } catch {\n try {\n return realpathSync(cwd);\n } catch {\n return resolve(cwd);\n }\n }\n}\n\nfunction extractStringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((entry): entry is string => typeof entry === \"string\")\n : [];\n}\n\nfunction normalizeWriteTarget(cwd: string, target: string): string {\n const resolved = resolve(cwd, target);\n return existsSync(resolved) ? realpathSync.native(resolved) : resolved;\n}\n\nfunction extractWriteTargets(input: CursorHookInput, cwd: string): string[] | undefined {\n const candidates = new Set<string>();\n const toolInput = asObject(input.tool_input);\n\n for (const key of [\"file_path\", \"filePath\", \"path\"]) {\n const value = toolInput[key] ?? (input as Record<string, unknown>)[key];\n if (typeof value === \"string\" && value.trim()) {\n candidates.add(normalizeWriteTarget(cwd, value));\n }\n }\n\n for (const key of [\"paths\", \"file_paths\", \"modified_files\"]) {\n for (const value of extractStringArray(\n toolInput[key] ?? (input as Record<string, unknown>)[key],\n )) {\n if (value.trim()) {\n candidates.add(normalizeWriteTarget(cwd, value));\n }\n }\n }\n\n return candidates.size > 0 ? [...candidates] : undefined;\n}\n\nfunction truncate(value: string, max: number): { value: string; truncatedAt?: number } {\n if (value.length <= max) return { value };\n return { value: value.slice(0, max), truncatedAt: max };\n}\n\nfunction sessionId(input: CursorHookInput): string | undefined {\n return input.conversation_id ?? input.session_id ?? input.generation_id;\n}\n\nfunction mapSessionEndReason(\n reason: string | undefined,\n): \"user\" | \"completed\" | \"error\" | undefined {\n if (reason === \"completed\" || reason === \"error\") return reason;\n if (reason === \"aborted\" || reason === \"window_close\" || reason === \"user_close\") return \"user\";\n return reason ? \"completed\" : undefined;\n}\n\nfunction buildCursorHookEvent(raw: unknown, options?: TranslateHookInputOptions): EventV1 | null {\n const input = asObject(raw) as CursorHookInput;\n const id = sessionId(input);\n const hookEventName = asString(input.hook_event_name);\n if (!id || !hookEventName) {\n return null;\n }\n\n const cwd = asString(input.cwd) ?? input.workspace_roots?.[0] ?? options?.cwd ?? process.cwd();\n const repoRoot = resolveRepoRoot(cwd);\n const base = {\n v: 1 as const,\n id: randomUUID(),\n ts: Date.now(),\n engine: \"cursor\",\n session_id: id,\n project_hash: sha12(repoRoot),\n cwd: repoRoot,\n };\n const toolName = asString(input.tool_name) ?? (input.command ? \"Shell\" : undefined);\n const toolUseId = input.tool_use_id ?? input.generation_id ?? randomUUID();\n const writeTargets = extractWriteTargets(input, cwd);\n\n switch (hookEventName) {\n case \"sessionStart\":\n return {\n ...base,\n type: \"session_start\",\n payload: {\n ...(input.holdpoint_context?.truncated ? { tools_available: [\"context_truncated\"] } : {}),\n },\n };\n case \"sessionEnd\":\n return {\n ...base,\n type: \"session_end\",\n payload: {\n ...(mapSessionEndReason(asString(input.reason) ?? asString(input.status))\n ? { reason: mapSessionEndReason(asString(input.reason) ?? asString(input.status)) }\n : {}),\n },\n };\n case \"beforeSubmitPrompt\": {\n const prompt = asString(input.prompt) ?? \"\";\n const { value, truncatedAt } = truncate(prompt, 10_000);\n return {\n ...base,\n type: \"prompt_submit\",\n payload: {\n prompt: value,\n ...(truncatedAt ? { truncated_at: truncatedAt } : {}),\n },\n };\n }\n case \"preToolUse\":\n return {\n ...base,\n type: \"tool_pre\",\n payload: {\n tool_name: toolName ?? \"unknown\",\n tool_use_id: toolUseId,\n tool_input: asObject(input.tool_input),\n ...(writeTargets ? { write_targets: writeTargets } : {}),\n },\n };\n case \"postToolUse\":\n case \"afterShellExecution\":\n case \"afterMCPExecution\":\n return {\n ...base,\n type: \"tool_post\",\n payload: {\n tool_name: toolName ?? (hookEventName === \"afterShellExecution\" ? \"Shell\" : \"unknown\"),\n tool_use_id: toolUseId,\n success: true,\n duration_ms: asNumber(input.duration) ?? asNumber(input.duration_ms) ?? 0,\n ...((input.output ?? input.tool_output)\n ? { output_summary: truncate(String(input.output ?? input.tool_output), 2_000).value }\n : {}),\n ...(writeTargets ? { write_targets: writeTargets } : {}),\n },\n };\n case \"postToolUseFailure\":\n return {\n ...base,\n type: \"tool_failure\",\n payload: {\n tool_name: toolName ?? \"unknown\",\n tool_use_id: toolUseId,\n error:\n asString(input.error_message) ?? asString(input.failure_type) ?? \"Cursor tool failed\",\n },\n };\n case \"beforeShellExecution\":\n case \"beforeMCPExecution\":\n case \"beforeReadFile\":\n return {\n ...base,\n type: \"tool_pre\",\n payload: {\n tool_name:\n hookEventName === \"beforeShellExecution\"\n ? \"Shell\"\n : hookEventName === \"beforeMCPExecution\"\n ? \"MCP\"\n : hookEventName === \"beforeReadFile\"\n ? \"Read\"\n : \"unknown\",\n tool_use_id: toolUseId,\n tool_input:\n hookEventName === \"beforeShellExecution\" && input.command\n ? { command: input.command }\n : hookEventName === \"beforeReadFile\" && input.file_path\n ? { file_path: input.file_path }\n : asObject(input.tool_input),\n ...(writeTargets ? { write_targets: writeTargets } : {}),\n },\n };\n case \"afterFileEdit\":\n return {\n ...base,\n type: \"tool_post\",\n payload: {\n tool_name: \"Write\",\n tool_use_id: toolUseId,\n success: true,\n duration_ms: asNumber(input.duration) ?? asNumber(input.duration_ms) ?? 0,\n ...(writeTargets ? { write_targets: writeTargets } : {}),\n },\n };\n case \"stop\": {\n const check = input.holdpoint_check;\n if (check?.ok === false) {\n return {\n ...base,\n type: \"stop_block\",\n payload: {\n reason: check.output ?? \"Holdpoint checks failed\",\n failing_checks: [],\n },\n };\n }\n return {\n ...base,\n type: \"stop_pass\",\n payload: {\n duration_ms: check?.durationMs ?? asNumber(input.duration_ms) ?? 0,\n },\n };\n }\n case \"subagentStart\":\n case \"subagentStop\":\n case \"preCompact\":\n case \"afterAgentResponse\":\n return {\n ...base,\n type: \"meta\",\n payload: {\n kind: \"cursor_lifecycle\",\n hook_event_name: hookEventName,\n status: input.status,\n subagent_type: input.subagent_type,\n },\n };\n default:\n return {\n ...base,\n type: \"meta\",\n payload: {\n kind: \"cursor_hook\",\n hook_event_name: hookEventName,\n },\n };\n }\n}\n\nexport const adapter = {\n id: \"cursor\",\n displayName: \"Cursor\",\n capabilities: {\n can_stream: true,\n },\n generateBridgeCommand(): string {\n return \"node_modules/.bin/holdpoint event --engine cursor --from-hook\";\n },\n translateHookInput(raw: unknown, options?: TranslateHookInputOptions) {\n return buildCursorHookEvent(raw, options);\n },\n};\n","export const manifest = {\n manifestVersion: 1,\n id: \"cursor\",\n displayName: \"Cursor\",\n} as const;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/engine.ts","../src/live-adapter.ts","../src/manifest.ts"],"sourcesContent":["import type { HoldpointConfig } from \"@holdpoint/types\";\n\nexport const HOLDPOINT_CURSOR_HOOK_MARKER = \"HOLDPOINT_MANAGED=cursor\";\n\nconst HOOK_COMMAND = `node .cursor/holdpoint-hook.mjs # ${HOLDPOINT_CURSOR_HOOK_MARKER}`;\n\nconst SECURITY_SCAN_PACKAGES = [\n \"@anthropic-ai/mcp-server-brave-search\",\n \"@anthropic-ai/mcp-server-fetch\",\n \"@modelcontextprotocol/server-filesystem\",\n \"@modelcontextprotocol/server-github\",\n \"@modelcontextprotocol/server-gitlab\",\n \"@modelcontextprotocol/server-google-maps\",\n \"@modelcontextprotocol/server-postgres\",\n \"@modelcontextprotocol/server-slack\",\n \"@modelcontextprotocol/server-memory\",\n \"@modelcontextprotocol/server-puppeteer\",\n \"@modelcontextprotocol/server-sequential-thinking\",\n \"@modelcontextprotocol/server-everything\",\n];\n\nfunction buildSecurityScanScript(): string {\n return `\n// Keep behavior in sync with packages/cli/src/lib/scan.ts.\nconst SECURITY_SCAN_VERIFIED = new Set(${JSON.stringify(SECURITY_SCAN_PACKAGES)});\nconst SECURITY_SCAN_SEVERITIES = new Set([\"high\", \"critical\"]);\nfunction securityScanReadJson(path) {\n try { return JSON.parse(readFileSync(path, \"utf8\")); } catch { return undefined; }\n}\nfunction securityScanStringArray(value) {\n return Array.isArray(value) ? value.filter((entry) => typeof entry === \"string\") : [];\n}\nfunction securityScanPackageName(value) {\n const normalized = String(value || \"\").replace(/\\\\\\\\/g, \"/\");\n const idx = normalized.lastIndexOf(\"node_modules/\");\n if (idx >= 0) {\n const parts = normalized.slice(idx + \"node_modules/\".length).split(\"/\").filter(Boolean);\n if (parts[0] && parts[0].startsWith(\"@\") && parts[1]) return parts[0] + \"/\" + parts[1];\n return parts[0];\n }\n if (normalized.startsWith(\"@\")) {\n const parts = normalized.split(\"/\");\n return parts[0] && parts[1] ? parts[0] + \"/\" + parts[1] : undefined;\n }\n if (!normalized.includes(\"/\") && /^[a-z0-9@._-]+$/i.test(normalized)) return normalized;\n return undefined;\n}\nfunction securityScanMcpEntries(config) {\n if (!config || typeof config !== \"object\") return [];\n const servers = config.mcpServers || config.servers;\n if (!servers || typeof servers !== \"object\" || Array.isArray(servers)) return [];\n return Object.entries(servers).map(([key, raw]) => {\n const server = raw && typeof raw === \"object\" && !Array.isArray(raw) ? raw : {};\n return {\n key,\n name: typeof server.name === \"string\" ? server.name : undefined,\n command: typeof server.command === \"string\" ? server.command : undefined,\n args: securityScanStringArray(server.args),\n };\n });\n}\nfunction securityScanMcp(root) {\n const results = [];\n for (const file of [join(root, \".mcp.json\"), join(root, \".claude/mcp.json\")]) {\n if (!existsSync(file)) continue;\n for (const entry of securityScanMcpEntries(securityScanReadJson(file))) {\n const values = [entry.name, entry.command, ...entry.args]\n .filter((value) => typeof value === \"string\" && value.trim())\n .map((value) => value.trim());\n const packages = values.map(securityScanPackageName).filter(Boolean);\n const verified = [...values, ...packages].some((candidate) => SECURITY_SCAN_VERIFIED.has(candidate));\n results.push({ server: entry.name || entry.key, verified });\n }\n }\n return results;\n}\nfunction securityScanPackageManager(root) {\n if (existsSync(join(root, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (existsSync(join(root, \"yarn.lock\"))) return \"yarn\";\n if (existsSync(join(root, \"package-lock.json\")) || existsSync(join(root, \"npm-shrinkwrap.json\"))) return \"npm\";\n return null;\n}\nfunction securityScanFirstTitle(value) {\n if (typeof value === \"string\") return value;\n if (Array.isArray(value)) {\n for (const entry of value) {\n const title = securityScanFirstTitle(entry);\n if (title) return title;\n }\n }\n if (value && typeof value === \"object\" && typeof value.title === \"string\") return value.title;\n return undefined;\n}\nfunction securityScanAddFinding(findings, seen, name, severity, title) {\n if (!name || typeof severity !== \"string\") return;\n const normalizedSeverity = severity.toLowerCase();\n if (!SECURITY_SCAN_SEVERITIES.has(normalizedSeverity)) return;\n const normalizedTitle = securityScanFirstTitle(title) || \"Security advisory\";\n const key = name + \"\\\\0\" + normalizedSeverity + \"\\\\0\" + normalizedTitle;\n if (seen.has(key)) return;\n seen.add(key);\n findings.push({ name, severity: normalizedSeverity, title: normalizedTitle });\n}\nfunction securityScanParseAudit(raw) {\n const findings = [];\n const seen = new Set();\n const parseOne = (data) => {\n if (!data || typeof data !== \"object\") return;\n if (data.vulnerabilities && typeof data.vulnerabilities === \"object\") {\n for (const [name, vuln] of Object.entries(data.vulnerabilities)) {\n if (!vuln || typeof vuln !== \"object\") continue;\n securityScanAddFinding(findings, seen, name, vuln.severity, vuln.via || vuln.title);\n }\n }\n if (data.advisories && typeof data.advisories === \"object\") {\n for (const advisory of Object.values(data.advisories)) {\n if (!advisory || typeof advisory !== \"object\") continue;\n securityScanAddFinding(findings, seen, advisory.module_name, advisory.severity, advisory.title);\n }\n }\n if (data.type === \"auditAdvisory\" && data.data && data.data.advisory) {\n const advisory = data.data.advisory;\n securityScanAddFinding(findings, seen, advisory.module_name, advisory.severity, advisory.title);\n }\n };\n try { parseOne(JSON.parse(raw)); }\n catch {\n for (const line of String(raw || \"\").split(/\\\\r?\\\\n/)) {\n if (!line.trim()) continue;\n try { parseOne(JSON.parse(line)); } catch {}\n }\n }\n return findings.slice(0, 5);\n}\nfunction securityScanAudit(root) {\n const pm = securityScanPackageManager(root);\n if (!pm) return { pm: null, findings: [] };\n try {\n const stdout = execSync(pm + \" audit --json\", {\n cwd: root,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n maxBuffer: 1024 * 1024 * 10,\n timeout: 8000,\n });\n return { pm, findings: securityScanParseAudit(stdout) };\n } catch (err) {\n const stdout = err && typeof err.stdout === \"string\" ? err.stdout : \"\";\n return { pm, findings: stdout.trim() ? securityScanParseAudit(stdout) : [] };\n }\n}\nfunction formatSecurityScan(root) {\n const unverified = securityScanMcp(root).filter((entry) => !entry.verified);\n const audit = securityScanAudit(root);\n if (unverified.length === 0 && audit.findings.length === 0) return null;\n const lines = [\"⚠ Holdpoint Security Scan\", \"\"];\n if (unverified.length > 0) {\n lines.push(\"MCP servers — unverified:\");\n for (const entry of unverified) lines.push(\" • \" + entry.server + \" (source unknown — review before trusting)\");\n lines.push(\"\");\n }\n if (audit.findings.length > 0) {\n lines.push((audit.pm || \"npm\") + \" audit — high/critical:\");\n for (const dep of audit.findings) lines.push(\" • \" + dep.name + \" · \" + dep.title + \" (\" + dep.severity + \")\");\n lines.push(\"\");\n }\n lines.push(\"Review these before allowing the agent to install dependencies or invoke tools.\");\n return lines.join(\"\\\\n\");\n}\n`;\n}\n\ninterface CursorHook {\n command: string;\n timeout?: number;\n matcher?: string;\n loop_limit?: number;\n failClosed?: boolean;\n}\n\ninterface CursorHooksJson {\n version: 1;\n hooks: Record<string, CursorHook[]>;\n}\n\nfunction hook(options: Omit<CursorHook, \"command\"> = {}): CursorHook {\n return {\n command: HOOK_COMMAND,\n ...options,\n };\n}\n\n/**\n * Generate project-level Cursor hooks.\n *\n * Cursor project hooks live at `.cursor/hooks.json`, run from the project root,\n * and can observe, inject context, or auto-continue the agent loop. Holdpoint\n * uses a single generated script so hook behavior stays in sync with\n * `checks.yaml` after every `holdpoint update`.\n */\nexport function buildHooksJson(config: HoldpointConfig): string {\n const hooks: CursorHooksJson[\"hooks\"] = {\n beforeSubmitPrompt: [hook({ timeout: 30 })],\n preToolUse: [hook({ timeout: 30, matcher: \"Shell|Read|Write|Grep|Task|MCP:.*\" })],\n postToolUse: [hook({ timeout: 30 })],\n postToolUseFailure: [hook({ timeout: 30 })],\n beforeShellExecution: [hook({ timeout: 30 })],\n afterShellExecution: [hook({ timeout: 30 })],\n beforeMCPExecution: [hook({ timeout: 30 })],\n afterMCPExecution: [hook({ timeout: 30 })],\n beforeReadFile: [hook({ timeout: 30 })],\n afterFileEdit: [hook({ timeout: 30 })],\n subagentStart: [hook({ timeout: 30 })],\n subagentStop: [hook({ timeout: 600, loop_limit: 5 })],\n preCompact: [hook({ timeout: 30 })],\n afterAgentResponse: [hook({ timeout: 30 })],\n stop: [hook({ timeout: 600, loop_limit: 5 })],\n sessionStart: [hook({ timeout: 30 })],\n };\n\n void config;\n return JSON.stringify({ version: 1, hooks }, null, 2) + \"\\n\";\n}\n\n/**\n * Generate `.cursor/holdpoint-hook.mjs`.\n *\n * The script speaks Cursor's native hook protocol:\n * - `sessionStart` returns `additional_context` when session_context_files exist.\n * - `stop` and completed `subagentStop` run Holdpoint checks and return a\n * `followup_message` on failure so Cursor keeps iterating.\n * - all other hooks are used for Live telemetry and emit either an allow\n * response or no output, depending on that hook's schema.\n */\nexport function buildCheckScript(): string {\n return `#!/usr/bin/env node\n// AUTO-GENERATED by Holdpoint — do not edit. Re-generate: npx holdpoint update\nimport { execSync } from \"node:child_process\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { isAbsolute, join, relative, resolve } from \"node:path\";\n${buildSecurityScanScript()}\nconst CHECK_COMMAND = \"node_modules/.bin/holdpoint check --staged\";\nconst LIVE_COMMAND = \"node_modules/.bin/holdpoint event --engine cursor --from-hook\";\nconst MAX_CONTEXT_CHARS = 100_000;\nconst MAX_CHECK_OUTPUT_CHARS = 60_000;\nconst CHECK_MAX_BUFFER_BYTES = 1024 * 1024 * 10;\n\nfunction readInput() {\n try {\n const raw = readFileSync(0, \"utf8\").trim();\n return raw ? JSON.parse(raw) : {};\n } catch {\n return {};\n }\n}\n\nfunction resolveRepoRoot(cwd = process.cwd()) {\n try {\n return execSync(\"git rev-parse --show-toplevel\", {\n cwd,\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n return cwd;\n }\n}\n\nfunction eventName(input) {\n return String(input && input.hook_event_name ? input.hook_event_name : \"\");\n}\n\nfunction truncateText(value, maxChars) {\n const text = String(value || \"\");\n if (text.length <= maxChars) return { text, truncated: false, originalLength: text.length };\n return {\n text: text.slice(0, maxChars) + \"\\\\n\\\\n[Holdpoint output truncated to \" + maxChars + \" chars.]\",\n truncated: true,\n originalLength: text.length,\n };\n}\n\nfunction isPathInsideRoot(repoRoot, absPath) {\n const rel = relative(repoRoot, absPath);\n return rel === \"\" || (!rel.startsWith(\"..\") && !isAbsolute(rel));\n}\n\nfunction sendLiveEvent(input) {\n try {\n execSync(LIVE_COMMAND, {\n input: JSON.stringify(input),\n encoding: \"utf8\",\n stdio: [\"pipe\", \"ignore\", \"ignore\"],\n });\n } catch {\n // Live telemetry is best-effort and must never break Cursor's hook flow.\n }\n}\n\nfunction readConfig(repoRoot) {\n const configPath = join(repoRoot, \".github/holdpoint/generated/checks.immutable.json\");\n if (!existsSync(configPath)) return {};\n try { return JSON.parse(readFileSync(configPath, \"utf8\")); } catch { return {}; }\n}\n\nfunction readFileContext(repoRoot, file) {\n if (typeof file !== \"string\" || !file.trim()) return null;\n const abs = resolve(repoRoot, file);\n if (!isPathInsideRoot(repoRoot, abs) || !existsSync(abs)) return null;\n try { return \"<!-- \" + file + \" -->\\\\n\" + readFileSync(abs, \"utf8\"); } catch { return null; }\n}\n\nconst DATETIME_TEXT = () =>\n \"Current date and time: \" + new Date().toISOString() + \" (UTC)\\\\n\" +\n \"Provided by Holdpoint — use this to avoid knowledge-cutoff confusion.\";\n\n// Gather agent context for a Holdpoint hook. NOTE: Cursor's beforeSubmitPrompt\n// cannot inject context (it only gates submission), so the top-level datetime\n// and message_submit checks are surfaced at sessionStart instead — the only\n// Cursor stage that accepts additional_context per the hooks API.\nfunction gatherHookContext(repoRoot, hook) {\n const cfg = readConfig(repoRoot);\n const checks = Array.isArray(cfg.checks) ? cfg.checks : [];\n const parts = [];\n let hasDatetime = false;\n const addDatetime = () => { if (!hasDatetime) { hasDatetime = true; parts.push(DATETIME_TEXT()); } };\n\n const includeHooks = hook === \"session_start\" ? [\"session_start\", \"message_submit\"] : [hook];\n\n if (hook === \"session_start\") {\n const scan = formatSecurityScan(repoRoot);\n if (scan) parts.push(scan);\n const files = Array.isArray(cfg.session_context_files) ? cfg.session_context_files : [];\n for (const f of files) { const c = readFileContext(repoRoot, f); if (c) parts.push(c); }\n }\n for (const c of checks) {\n const on = typeof c.on === \"string\" ? c.on : \"before_done\";\n if (!includeHooks.includes(on)) continue;\n if (c.inject && typeof c.inject === \"object\") {\n if (c.inject.datetime === true) addDatetime();\n if (typeof c.inject.text === \"string\" && c.inject.text.trim()) parts.push(c.inject.text);\n if (Array.isArray(c.inject.files)) for (const f of c.inject.files) { const x = readFileContext(repoRoot, f); if (x) parts.push(x); }\n } else if (typeof c.prompt === \"string\" && c.prompt.trim()) {\n parts.push(\"Holdpoint reminder [\" + (c.label || c.id || \"check\") + \"]: \" + c.prompt);\n }\n }\n // Cursor can only inject once (sessionStart), so fold the per-message datetime in here.\n if (hook === \"session_start\" && cfg.inject_datetime !== false) addDatetime();\n\n if (parts.length === 0) return undefined;\n const context = truncateText(parts.join(\"\\\\n\\\\n\"), MAX_CONTEXT_CHARS);\n return {\n additional_context: context.text,\n truncated: context.truncated,\n originalLength: context.originalLength,\n emittedLength: context.text.length,\n };\n}\n\nfunction hasCmdAt(repoRoot, hook) {\n const cfg = readConfig(repoRoot);\n const checks = Array.isArray(cfg.checks) ? cfg.checks : [];\n return checks.some((c) => typeof c.cmd === \"string\" && (typeof c.on === \"string\" ? c.on : \"before_done\") === hook);\n}\n\nfunction runHoldpointChecks(repoRoot, command = CHECK_COMMAND) {\n const startedAt = Date.now();\n try {\n const output = execSync(command, {\n cwd: repoRoot,\n stdio: \"pipe\",\n encoding: \"utf8\",\n maxBuffer: CHECK_MAX_BUFFER_BYTES,\n });\n return {\n ok: true,\n durationMs: Date.now() - startedAt,\n output: String(output || \"\").trim(),\n };\n } catch (error) {\n const output = [error && error.stdout, error && error.stderr, error && error.message]\n .filter(Boolean)\n .join(\"\\\\n\")\n .trim();\n const truncated = truncateText(output, MAX_CHECK_OUTPUT_CHARS);\n return {\n ok: false,\n durationMs: Date.now() - startedAt,\n output: truncated.text || \"Holdpoint checks failed. Fix the issues above, then re-attempt.\",\n truncated: truncated.truncated,\n originalLength: truncated.originalLength,\n };\n }\n}\n\nfunction shouldRunCompletionChecks(input) {\n const name = eventName(input);\n if (name === \"stop\") return true;\n if (name === \"subagentStop\") {\n return input && input.status === \"completed\";\n }\n return false;\n}\n\nconst input = readInput();\nconst cwd = typeof input.cwd === \"string\" ? input.cwd : process.cwd();\nconst repoRoot = resolveRepoRoot(cwd);\nconst name = eventName(input);\n\nif (name === \"sessionStart\") {\n const context = gatherHookContext(repoRoot, \"session_start\");\n sendLiveEvent({\n ...input,\n holdpoint_context: context\n ? {\n truncated: context.truncated,\n originalLength: context.originalLength,\n emittedLength: context.emittedLength,\n }\n : undefined,\n });\n if (context?.additional_context) {\n writeFileSync(1, JSON.stringify({ additional_context: context.additional_context }) + \"\\\\n\");\n }\n process.exit(0);\n}\n\nif (shouldRunCompletionChecks(input)) {\n const result = runHoldpointChecks(repoRoot);\n sendLiveEvent({ ...input, holdpoint_check: result });\n if (result.ok) {\n process.exit(0);\n }\n process.stdout.write(\n JSON.stringify({\n followup_message:\n result.output +\n \"\\\\n\\\\nHoldpoint checks failed. Fix the issues above, then run the checks again before finishing.\",\n }) + \"\\\\n\",\n );\n process.exit(0);\n}\n\nsendLiveEvent(input);\n\nif (name === \"preToolUse\") {\n // Gate on before_tool cmd checks; deny the tool if they fail.\n if (hasCmdAt(repoRoot, \"before_tool\")) {\n const result = runHoldpointChecks(repoRoot, CHECK_COMMAND + \" --hook before_tool\");\n if (!result.ok) {\n process.stdout.write(\n JSON.stringify({ permission: \"deny\", agentMessage: result.output }) + \"\\\\n\",\n );\n process.exit(0);\n }\n }\n process.stdout.write(JSON.stringify({ permission: \"allow\" }) + \"\\\\n\");\n} else if (\n name === \"beforeShellExecution\" ||\n name === \"beforeMCPExecution\" ||\n name === \"beforeReadFile\" ||\n name === \"subagentStart\"\n) {\n process.stdout.write(JSON.stringify({ permission: \"allow\" }) + \"\\\\n\");\n} else if (name === \"beforeSubmitPrompt\") {\n // Cursor's beforeSubmitPrompt cannot inject context — only allow/deny the\n // submission. Per-message context is folded into sessionStart instead.\n process.stdout.write(JSON.stringify({ continue: true }) + \"\\\\n\");\n}\nprocess.exit(0);\n`;\n}\n\n/**\n * Generate a standalone context-injection script for sessionStart tests.\n * Cursor uses the same generated dispatcher for context, telemetry, and gates.\n */\nexport function buildContextScript(): string {\n return buildCheckScript();\n}\n\n/**\n * Generate .cursorrules additions from a HoldpointConfig.\n *\n * Cursor now enforces Holdpoint through `.cursor/hooks.json`; this rules block\n * remains useful context for the agent and for Cursor cloud cases where not all\n * local hook stages are available.\n */\nexport function buildEngine(config: HoldpointConfig): string {\n const deterministicList = config.checks\n .filter((c) => c.cmd !== undefined)\n .map((c) => ` - [${c.when ?? \"always\"}] ${c.label}: \\`${c.cmd ?? \"(no cmd)\"}\\``)\n .join(\"\\n\");\n\n const promptList = config.checks\n .filter((c) => c.prompt !== undefined)\n .map((c) => ` - [${c.when ?? \"always\"}] ${c.label}: ${c.prompt ?? \"\"}`)\n .join(\"\\n\");\n\n return `# ─── Holdpoint Rules (auto-generated) ─────────────────────────────────────────\n# DO NOT EDIT this block manually. Re-generate with: npx holdpoint update\n\n## Mandatory pre-completion checks\n\nHoldpoint also installed Cursor project hooks in \\`.cursor/hooks.json\\`. Before\nmarking ANY task as done or making a final commit, you MUST:\n\n1. Run all Holdpoint tasks and confirm they pass:\n${deterministicList || \" (no tasks configured)\"}\n\n2. Act on all matching agent prompts:\n${promptList || \" (no prompt checks configured)\"}\n\n3. If any task exits non-zero, fix the underlying issue before\n proceeding. Do NOT suppress errors or skip tasks.\n\n4. For prompt checks, explicitly state in your response that you have acted on\n each item before marking the task complete.\n\n## Running checks\n Run: \\`node_modules/.bin/holdpoint check --staged\\` to execute all tasks.\n Fix all failures before proceeding.\n\n# ─── End Holdpoint Rules ───────────────────────────────────────────────────────\n`;\n}\n","import { execFileSync } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport { existsSync, realpathSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type { EventV1 } from \"@holdpoint/live-protocol\";\nimport type { TranslateHookInputOptions } from \"@holdpoint/sdk\";\n\ninterface CursorHookInput {\n conversation_id?: string;\n generation_id?: string;\n session_id?: string;\n hook_event_name?: string;\n cwd?: string;\n workspace_roots?: string[];\n model?: string;\n prompt?: string;\n tool_name?: string;\n tool_input?: unknown;\n tool_output?: string;\n tool_use_id?: string;\n duration?: number;\n duration_ms?: number;\n error_message?: string;\n failure_type?: string;\n status?: string;\n reason?: string;\n command?: string;\n output?: string;\n file_path?: string;\n modified_files?: string[];\n subagent_id?: string;\n subagent_type?: string;\n task?: string;\n holdpoint_check?: {\n ok?: boolean;\n durationMs?: number;\n output?: string;\n };\n holdpoint_context?: {\n truncated?: boolean;\n originalLength?: number;\n emittedLength?: number;\n };\n}\n\nfunction asObject(value: unknown): Record<string, unknown> {\n return value != null && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : {};\n}\n\nfunction asString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\nfunction asNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\nfunction sha12(value: string): string {\n return createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 12);\n}\n\nfunction resolveRepoRoot(cwd: string): string {\n try {\n return realpathSync(\n execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim(),\n );\n } catch {\n try {\n return realpathSync(cwd);\n } catch {\n return resolve(cwd);\n }\n }\n}\n\nfunction extractStringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((entry): entry is string => typeof entry === \"string\")\n : [];\n}\n\nfunction normalizeWriteTarget(cwd: string, target: string): string {\n const resolved = resolve(cwd, target);\n return existsSync(resolved) ? realpathSync.native(resolved) : resolved;\n}\n\nfunction extractWriteTargets(input: CursorHookInput, cwd: string): string[] | undefined {\n const candidates = new Set<string>();\n const toolInput = asObject(input.tool_input);\n\n for (const key of [\"file_path\", \"filePath\", \"path\"]) {\n const value = toolInput[key] ?? (input as Record<string, unknown>)[key];\n if (typeof value === \"string\" && value.trim()) {\n candidates.add(normalizeWriteTarget(cwd, value));\n }\n }\n\n for (const key of [\"paths\", \"file_paths\", \"modified_files\"]) {\n for (const value of extractStringArray(\n toolInput[key] ?? (input as Record<string, unknown>)[key],\n )) {\n if (value.trim()) {\n candidates.add(normalizeWriteTarget(cwd, value));\n }\n }\n }\n\n return candidates.size > 0 ? [...candidates] : undefined;\n}\n\nfunction truncate(value: string, max: number): { value: string; truncatedAt?: number } {\n if (value.length <= max) return { value };\n return { value: value.slice(0, max), truncatedAt: max };\n}\n\nfunction sessionId(input: CursorHookInput): string | undefined {\n return input.conversation_id ?? input.session_id ?? input.generation_id;\n}\n\nfunction mapSessionEndReason(\n reason: string | undefined,\n): \"user\" | \"completed\" | \"error\" | undefined {\n if (reason === \"completed\" || reason === \"error\") return reason;\n if (reason === \"aborted\" || reason === \"window_close\" || reason === \"user_close\") return \"user\";\n return reason ? \"completed\" : undefined;\n}\n\nfunction buildCursorHookEvent(raw: unknown, options?: TranslateHookInputOptions): EventV1 | null {\n const input = asObject(raw) as CursorHookInput;\n const id = sessionId(input);\n const hookEventName = asString(input.hook_event_name);\n if (!id || !hookEventName) {\n return null;\n }\n\n const cwd = asString(input.cwd) ?? input.workspace_roots?.[0] ?? options?.cwd ?? process.cwd();\n const repoRoot = resolveRepoRoot(cwd);\n const base = {\n v: 1 as const,\n id: randomUUID(),\n ts: Date.now(),\n engine: \"cursor\",\n session_id: id,\n project_hash: sha12(repoRoot),\n cwd: repoRoot,\n };\n const toolName = asString(input.tool_name) ?? (input.command ? \"Shell\" : undefined);\n const toolUseId = input.tool_use_id ?? input.generation_id ?? randomUUID();\n const writeTargets = extractWriteTargets(input, cwd);\n\n switch (hookEventName) {\n case \"sessionStart\":\n return {\n ...base,\n type: \"session_start\",\n payload: {\n ...(input.holdpoint_context?.truncated ? { tools_available: [\"context_truncated\"] } : {}),\n },\n };\n case \"sessionEnd\":\n return {\n ...base,\n type: \"session_end\",\n payload: {\n ...(mapSessionEndReason(asString(input.reason) ?? asString(input.status))\n ? { reason: mapSessionEndReason(asString(input.reason) ?? asString(input.status)) }\n : {}),\n },\n };\n case \"beforeSubmitPrompt\": {\n const prompt = asString(input.prompt) ?? \"\";\n const { value, truncatedAt } = truncate(prompt, 10_000);\n return {\n ...base,\n type: \"prompt_submit\",\n payload: {\n prompt: value,\n ...(truncatedAt ? { truncated_at: truncatedAt } : {}),\n },\n };\n }\n case \"preToolUse\":\n return {\n ...base,\n type: \"tool_pre\",\n payload: {\n tool_name: toolName ?? \"unknown\",\n tool_use_id: toolUseId,\n tool_input: asObject(input.tool_input),\n ...(writeTargets ? { write_targets: writeTargets } : {}),\n },\n };\n case \"postToolUse\":\n case \"afterShellExecution\":\n case \"afterMCPExecution\":\n return {\n ...base,\n type: \"tool_post\",\n payload: {\n tool_name: toolName ?? (hookEventName === \"afterShellExecution\" ? \"Shell\" : \"unknown\"),\n tool_use_id: toolUseId,\n success: true,\n duration_ms: asNumber(input.duration) ?? asNumber(input.duration_ms) ?? 0,\n ...((input.output ?? input.tool_output)\n ? { output_summary: truncate(String(input.output ?? input.tool_output), 2_000).value }\n : {}),\n ...(writeTargets ? { write_targets: writeTargets } : {}),\n },\n };\n case \"postToolUseFailure\":\n return {\n ...base,\n type: \"tool_failure\",\n payload: {\n tool_name: toolName ?? \"unknown\",\n tool_use_id: toolUseId,\n error:\n asString(input.error_message) ?? asString(input.failure_type) ?? \"Cursor tool failed\",\n },\n };\n case \"beforeShellExecution\":\n case \"beforeMCPExecution\":\n case \"beforeReadFile\":\n return {\n ...base,\n type: \"tool_pre\",\n payload: {\n tool_name:\n hookEventName === \"beforeShellExecution\"\n ? \"Shell\"\n : hookEventName === \"beforeMCPExecution\"\n ? \"MCP\"\n : hookEventName === \"beforeReadFile\"\n ? \"Read\"\n : \"unknown\",\n tool_use_id: toolUseId,\n tool_input:\n hookEventName === \"beforeShellExecution\" && input.command\n ? { command: input.command }\n : hookEventName === \"beforeReadFile\" && input.file_path\n ? { file_path: input.file_path }\n : asObject(input.tool_input),\n ...(writeTargets ? { write_targets: writeTargets } : {}),\n },\n };\n case \"afterFileEdit\":\n return {\n ...base,\n type: \"tool_post\",\n payload: {\n tool_name: \"Write\",\n tool_use_id: toolUseId,\n success: true,\n duration_ms: asNumber(input.duration) ?? asNumber(input.duration_ms) ?? 0,\n ...(writeTargets ? { write_targets: writeTargets } : {}),\n },\n };\n case \"stop\": {\n const check = input.holdpoint_check;\n if (check?.ok === false) {\n return {\n ...base,\n type: \"stop_block\",\n payload: {\n reason: check.output ?? \"Holdpoint checks failed\",\n failing_checks: [],\n },\n };\n }\n return {\n ...base,\n type: \"stop_pass\",\n payload: {\n duration_ms: check?.durationMs ?? asNumber(input.duration_ms) ?? 0,\n },\n };\n }\n case \"subagentStart\":\n case \"subagentStop\":\n case \"preCompact\":\n case \"afterAgentResponse\":\n return {\n ...base,\n type: \"meta\",\n payload: {\n kind: \"cursor_lifecycle\",\n hook_event_name: hookEventName,\n status: input.status,\n subagent_type: input.subagent_type,\n },\n };\n default:\n return {\n ...base,\n type: \"meta\",\n payload: {\n kind: \"cursor_hook\",\n hook_event_name: hookEventName,\n },\n };\n }\n}\n\nexport const adapter = {\n id: \"cursor\",\n displayName: \"Cursor\",\n capabilities: {\n can_stream: true,\n },\n generateBridgeCommand(): string {\n return \"node_modules/.bin/holdpoint event --engine cursor --from-hook\";\n },\n translateHookInput(raw: unknown, options?: TranslateHookInputOptions) {\n return buildCursorHookEvent(raw, options);\n },\n};\n","export const manifest = {\n manifestVersion: 1,\n id: \"cursor\",\n displayName: \"Cursor\",\n} as const;\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@holdpoint/engine-cursor",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.14",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -47,15 +47,15 @@
|
|
|
47
47
|
"LICENSE"
|
|
48
48
|
],
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@holdpoint/
|
|
50
|
+
"@holdpoint/types": "0.1.0-alpha.10",
|
|
51
51
|
"@holdpoint/sdk": "0.1.0-alpha.4",
|
|
52
|
-
"@holdpoint/
|
|
52
|
+
"@holdpoint/live-protocol": "0.1.0-alpha.4"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^25.9.1",
|
|
56
56
|
"tsup": "^8.3.5",
|
|
57
57
|
"typescript": "^6.0.3",
|
|
58
|
-
"vitest": "^4.1.
|
|
58
|
+
"vitest": "^4.1.8"
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"build": "tsup",
|