@alessandroraffa/tangyr 0.21.1 → 0.21.2
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 +43 -25
- package/mappings/hook-events.yaml +40 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25741,8 +25741,8 @@ function compileCursorHooks(sourceHooksJson, mappings, lossReport, sourceLabel)
|
|
|
25741
25741
|
sourceEvent
|
|
25742
25742
|
);
|
|
25743
25743
|
if (mapping.status === "mapped" && mapping.target_event) {
|
|
25744
|
-
const
|
|
25745
|
-
if (
|
|
25744
|
+
const entries = extractHookCommands(payload);
|
|
25745
|
+
if (entries.length === 0) {
|
|
25746
25746
|
droppedEvents.push(sourceEvent);
|
|
25747
25747
|
addHookLoss(
|
|
25748
25748
|
lossReport,
|
|
@@ -25755,26 +25755,24 @@ function compileCursorHooks(sourceHooksJson, mappings, lossReport, sourceLabel)
|
|
|
25755
25755
|
dropped += 1;
|
|
25756
25756
|
continue;
|
|
25757
25757
|
}
|
|
25758
|
-
|
|
25758
|
+
const untranslatable = /* @__PURE__ */ new Set();
|
|
25759
|
+
hooks[mapping.target_event] = entries.map((entry) => {
|
|
25760
|
+
const matcher = entry.matcher === void 0 ? void 0 : translateCursorMatcher(entry.matcher, untranslatable);
|
|
25761
|
+
return {
|
|
25762
|
+
type: "command",
|
|
25763
|
+
command: entry.command,
|
|
25764
|
+
...matcher !== void 0 ? { matcher } : {}
|
|
25765
|
+
};
|
|
25766
|
+
});
|
|
25759
25767
|
translated += 1;
|
|
25760
|
-
if (
|
|
25761
|
-
addHookLoss(
|
|
25762
|
-
lossReport,
|
|
25763
|
-
"cursor",
|
|
25764
|
-
sourceEvent,
|
|
25765
|
-
sourceLabel,
|
|
25766
|
-
"error",
|
|
25767
|
-
`${sourceEvent}: the source registers ${extracted.remaining.length + 1} hooks on this event and Cursor's entry holds one command. Only '${extracted.command}' was written; ${extracted.remaining.map((c) => `'${c}'`).join(", ")} ${extracted.remaining.length === 1 ? "does" : "do"} not run under Cursor. To fix: give this event a single entry point that dispatches to the others, or accept the loss knowingly.`
|
|
25768
|
-
);
|
|
25769
|
-
}
|
|
25770
|
-
if (extracted.matcher) {
|
|
25768
|
+
if (untranslatable.size > 0) {
|
|
25771
25769
|
addInformational(
|
|
25772
25770
|
lossReport,
|
|
25773
25771
|
"cursor",
|
|
25774
25772
|
"hooks",
|
|
25775
25773
|
sourceLabel,
|
|
25776
25774
|
"warning",
|
|
25777
|
-
`${sourceEvent}:
|
|
25775
|
+
`${sourceEvent}: ${[...untranslatable].map((m) => `'${m}'`).join(", ")} ${untranslatable.size === 1 ? "names a tool" : "name tools"} with no known Cursor equivalent and ${untranslatable.size === 1 ? "was" : "were"} written through unchanged. Cursor matches on its own tool names \u2014 measured: Shell, Read, Write, Grep \u2014 so a hook scoped to a name it does not use will not run. To fix: add the name to CURSOR_TOOL_NAMES in the compiler once its Cursor equivalent has been measured.`
|
|
25778
25776
|
);
|
|
25779
25777
|
}
|
|
25780
25778
|
continue;
|
|
@@ -25932,7 +25930,35 @@ function compileCopilotHooks(sourceHooksJson, mappings, lossReport, sourceLabel)
|
|
|
25932
25930
|
}
|
|
25933
25931
|
return { payload: { hooks }, translated, dropped, droppedEvents };
|
|
25934
25932
|
}
|
|
25935
|
-
|
|
25933
|
+
var CURSOR_TOOL_NAMES = {
|
|
25934
|
+
Bash: "Shell",
|
|
25935
|
+
Edit: "Write",
|
|
25936
|
+
MultiEdit: "Write",
|
|
25937
|
+
// Cursor's own names map to themselves. Without these a kit that already
|
|
25938
|
+
// writes a Cursor matcher — the natural thing to do once this mapping is
|
|
25939
|
+
// documented — would have it reported as untranslatable and warned about,
|
|
25940
|
+
// while it is in fact the only spelling that works.
|
|
25941
|
+
Shell: "Shell",
|
|
25942
|
+
Read: "Read",
|
|
25943
|
+
Write: "Write",
|
|
25944
|
+
Grep: "Grep"
|
|
25945
|
+
};
|
|
25946
|
+
function translateCursorMatcher(matcher, untranslatable) {
|
|
25947
|
+
const translated = [];
|
|
25948
|
+
for (const token of matcher.split("|")) {
|
|
25949
|
+
const trimmed = token.trim();
|
|
25950
|
+
if (!trimmed) continue;
|
|
25951
|
+
const mapped = CURSOR_TOOL_NAMES[trimmed];
|
|
25952
|
+
if (mapped === void 0) {
|
|
25953
|
+
untranslatable.add(trimmed);
|
|
25954
|
+
if (!translated.includes(trimmed)) translated.push(trimmed);
|
|
25955
|
+
continue;
|
|
25956
|
+
}
|
|
25957
|
+
if (!translated.includes(mapped)) translated.push(mapped);
|
|
25958
|
+
}
|
|
25959
|
+
return translated.join("|");
|
|
25960
|
+
}
|
|
25961
|
+
function extractHookCommands(payload) {
|
|
25936
25962
|
const entries = Array.isArray(payload) ? payload : [payload];
|
|
25937
25963
|
const found = [];
|
|
25938
25964
|
for (const entry of entries) {
|
|
@@ -25949,15 +25975,7 @@ function extractHookCommand(payload) {
|
|
|
25949
25975
|
}
|
|
25950
25976
|
}
|
|
25951
25977
|
}
|
|
25952
|
-
|
|
25953
|
-
return { command: "", remaining: [] };
|
|
25954
|
-
}
|
|
25955
|
-
const [first, ...rest] = found;
|
|
25956
|
-
return {
|
|
25957
|
-
command: first.command,
|
|
25958
|
-
...first.matcher !== void 0 ? { matcher: first.matcher } : {},
|
|
25959
|
-
remaining: rest.map((r) => r.command)
|
|
25960
|
-
};
|
|
25978
|
+
return found;
|
|
25961
25979
|
}
|
|
25962
25980
|
function normalizeHookEventRule(value, target, sourceEvent) {
|
|
25963
25981
|
if (typeof value === "string") {
|
|
@@ -143,81 +143,61 @@ targets:
|
|
|
143
143
|
|
|
144
144
|
cursor:
|
|
145
145
|
delivery: native-versioned-object
|
|
146
|
-
#
|
|
146
|
+
# Measured on 2026-09-06 against cursor-agent 2026.09.02-c22c1a3, by writing a
|
|
147
|
+
# .cursor/hooks.json, running a real agent turn, and checking whether the hook's
|
|
148
|
+
# script had run. Every entry below is a result, not a reading of the binary.
|
|
147
149
|
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
150
|
+
# The steps are camelCase and come from a 21-value enum: beforeShellExecution,
|
|
151
|
+
# beforeMCPExecution, afterShellExecution, afterMCPExecution, beforeReadFile,
|
|
152
|
+
# afterFileEdit, beforeTabFileRead, afterTabFileEdit, stop, beforeSubmitPrompt,
|
|
153
|
+
# afterAgentResponse, afterAgentThought, sessionStart, sessionEnd, preCompact,
|
|
154
|
+
# subagentStart, subagentStop, preToolUse, postToolUse, postToolUseFailure,
|
|
155
|
+
# workspaceOpen. Every one of this file's source events has an equivalent there.
|
|
151
156
|
#
|
|
152
|
-
#
|
|
153
|
-
# beforeReadFile, beforeShellExecution, postToolUse, postToolUseFailure,
|
|
154
|
-
# preCompact, preToolUse, subagentStart, subagentStop
|
|
157
|
+
# What the 2026-09-04 note above this block got wrong, and how:
|
|
155
158
|
#
|
|
156
|
-
# The
|
|
157
|
-
# and
|
|
159
|
+
# 1. The enum was read from the GUI app and had twelve values. The CLI's has
|
|
160
|
+
# twenty-one, sessionStart and beforeSubmitPrompt among them. Two events
|
|
161
|
+
# recorded here as having no equivalent do have one.
|
|
162
|
+
# 2. PascalCase was suspected of being the whole defect. It is not: a file in
|
|
163
|
+
# Claude Code's own shape with PascalCase keys and { matcher, hooks: [] }
|
|
164
|
+
# entries did not fire either, though the binary carries a detector for
|
|
165
|
+
# that shape. Case and shape had to change together.
|
|
166
|
+
# 3. The entry was thought to hold one command per event, so the compiler
|
|
167
|
+
# reported a collapse. It holds an array: three commands on one step all
|
|
168
|
+
# ran, in parallel. There is nothing to collapse.
|
|
158
169
|
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
# 2. preCompact, subagentStart and subagentStop ARE in that enum, while the entries below
|
|
163
|
-
# say they have "no documented equivalent". That claim is wrong, whatever the case
|
|
164
|
-
# question turns out to be.
|
|
165
|
-
# 3. The same file resolves ~/.claude/settings.json and <project>/.claude/settings.json as
|
|
166
|
-
# hook sources alongside ~/.cursor/hooks.json. Cursor may already be reading the
|
|
167
|
-
# Claude Code artifact this CLI writes, which would make its own hooks.json redundant.
|
|
168
|
-
#
|
|
169
|
-
# Nothing below was changed on this evidence. Minified code read without running it is a
|
|
170
|
-
# strong hint and not a measurement, and swapping one unverified mapping for another is the
|
|
171
|
-
# same mistake in the other direction. To settle it: install this kit for cursor, open the
|
|
172
|
-
# Cursor GUI, run one agent turn that executes a shell command, and check whether the hook
|
|
173
|
-
# script ran — then repeat with the keys lowercased. Cursor has no CLI on the machine this
|
|
174
|
-
# was measured on, which is why it stopped here.
|
|
170
|
+
# One finding has no entry here because it belongs to the compiler: `matcher` is
|
|
171
|
+
# honoured, alternation included, but against Cursor's tool names. A hook scoped
|
|
172
|
+
# to "Bash" never runs; "Shell" does. See CURSOR_TOOL_NAMES in compiler/hooks.ts.
|
|
175
173
|
source_to_target:
|
|
176
174
|
SessionStart:
|
|
177
|
-
status:
|
|
178
|
-
|
|
179
|
-
Not in the observed enum under any casing, and not probeable without the GUI. See the
|
|
180
|
-
note above this block.
|
|
175
|
+
status: mapped
|
|
176
|
+
target_event: sessionStart
|
|
181
177
|
UserPromptSubmit:
|
|
182
|
-
status:
|
|
183
|
-
|
|
184
|
-
Not in the observed enum; the nearest candidate is beforeSubmitPrompt, which is a
|
|
185
|
-
different contract. See the note above this block.
|
|
186
|
-
PostToolUseFailure:
|
|
187
|
-
status: probe
|
|
188
|
-
reason: >-
|
|
189
|
-
postToolUseFailure is in the observed enum, camelCased. Unverified. See the note above
|
|
190
|
-
this block.
|
|
178
|
+
status: mapped
|
|
179
|
+
target_event: beforeSubmitPrompt
|
|
191
180
|
PreToolUse:
|
|
192
181
|
status: mapped
|
|
193
|
-
target_event:
|
|
182
|
+
target_event: preToolUse
|
|
194
183
|
PostToolUse:
|
|
195
184
|
status: mapped
|
|
196
|
-
target_event:
|
|
185
|
+
target_event: postToolUse
|
|
186
|
+
PostToolUseFailure:
|
|
187
|
+
status: mapped
|
|
188
|
+
target_event: postToolUseFailure
|
|
197
189
|
PreCompact:
|
|
198
|
-
status:
|
|
199
|
-
|
|
200
|
-
reason: >-
|
|
201
|
-
preCompact is in the observed enum, camelCased. The previous entry here said there was
|
|
202
|
-
no documented equivalent, which the enum contradicts. See the note above this block.
|
|
190
|
+
status: mapped
|
|
191
|
+
target_event: preCompact
|
|
203
192
|
SubagentStart:
|
|
204
|
-
status:
|
|
205
|
-
|
|
206
|
-
reason: >-
|
|
207
|
-
subagentStart is in the observed enum, camelCased. The previous entry here said there was
|
|
208
|
-
no documented equivalent, which the enum contradicts. See the note above this block.
|
|
193
|
+
status: mapped
|
|
194
|
+
target_event: subagentStart
|
|
209
195
|
SubagentStop:
|
|
210
|
-
status:
|
|
211
|
-
|
|
212
|
-
reason: >-
|
|
213
|
-
subagentStop is in the observed enum, camelCased. The previous entry here said there was
|
|
214
|
-
no documented equivalent, which the enum contradicts. See the note above this block.
|
|
196
|
+
status: mapped
|
|
197
|
+
target_event: subagentStop
|
|
215
198
|
Stop:
|
|
216
|
-
status:
|
|
217
|
-
|
|
218
|
-
reason: >-
|
|
219
|
-
Not in the observed enum. `stop` appears in that file but as an unrelated identifier, so
|
|
220
|
-
this stays a drop rather than becoming a guess. See the note above this block.
|
|
199
|
+
status: mapped
|
|
200
|
+
target_event: stop
|
|
221
201
|
|
|
222
202
|
# OpenCode and Cline exchange no declarative hook file at all: hooks are
|
|
223
203
|
# plugin code (an OpenCode plugin, a @cline/sdk AgentPlugin), which a kit
|