@alessandroraffa/tangyr 0.21.3 → 0.21.4

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 CHANGED
@@ -25777,7 +25777,11 @@ function compileCursorHooks(sourceHooksJson, mappings, lossReport, sourceLabel)
25777
25777
  }
25778
25778
  const untranslatable = /* @__PURE__ */ new Set();
25779
25779
  hooks[mapping.target_event] = entries.map((entry) => {
25780
- const matcher = entry.matcher === void 0 ? void 0 : translateCursorMatcher(entry.matcher, untranslatable);
25780
+ const matcher = entry.matcher === void 0 ? void 0 : translateMatcher(
25781
+ entry.matcher,
25782
+ CURSOR_TOOL_NAMES,
25783
+ untranslatable
25784
+ );
25781
25785
  return {
25782
25786
  type: "command",
25783
25787
  command: entry.command,
@@ -25815,6 +25819,23 @@ function compileCursorHooks(sourceHooksJson, mappings, lossReport, sourceLabel)
25815
25819
  }
25816
25820
  return { payload: hooks, translated, dropped, droppedEvents };
25817
25821
  }
25822
+ function translateCopilotMatchers(payload, untranslatable) {
25823
+ const entries = Array.isArray(payload) ? payload : [payload];
25824
+ const translated = entries.map((entry) => {
25825
+ if (!isRecord(entry) || typeof entry.matcher !== "string") {
25826
+ return entry;
25827
+ }
25828
+ return {
25829
+ ...entry,
25830
+ matcher: translateMatcher(
25831
+ entry.matcher,
25832
+ COPILOT_TOOL_NAMES,
25833
+ untranslatable
25834
+ )
25835
+ };
25836
+ });
25837
+ return Array.isArray(payload) ? translated : translated[0];
25838
+ }
25818
25839
  function compileOpenCodeHooks(sourceHooksJson, lossReport, sourceLabel) {
25819
25840
  const sourceHooks = isRecord(sourceHooksJson) && isRecord(sourceHooksJson.hooks) ? sourceHooksJson.hooks : {};
25820
25841
  for (const eventName of Object.keys(sourceHooks)) {
@@ -25918,8 +25939,22 @@ function compileCopilotHooks(sourceHooksJson, mappings, lossReport, sourceLabel)
25918
25939
  sourceEvent
25919
25940
  );
25920
25941
  if (mapping.status === "mapped" && mapping.target_event) {
25921
- hooks[mapping.target_event] = payload;
25942
+ const untranslatable = /* @__PURE__ */ new Set();
25943
+ hooks[mapping.target_event] = translateCopilotMatchers(
25944
+ payload,
25945
+ untranslatable
25946
+ );
25922
25947
  translated += 1;
25948
+ if (untranslatable.size > 0) {
25949
+ addInformational(
25950
+ lossReport,
25951
+ "copilot",
25952
+ "hooks",
25953
+ sourceLabel,
25954
+ "warning",
25955
+ `${sourceEvent}: ${[...untranslatable].map((m) => `'${m}'`).join(", ")} ${untranslatable.size === 1 ? "names a tool" : "name tools"} with no known Copilot equivalent and ${untranslatable.size === 1 ? "was" : "were"} written through unchanged. Copilot matches on its own tool names \u2014 measured: bash, view, create, edit, grep \u2014 and the match is a full, case-sensitive regex, so a hook scoped to a name it does not use is accepted and never runs. To fix: add the name to COPILOT_TOOL_NAMES in the compiler once its Copilot equivalent has been measured.`
25956
+ );
25957
+ }
25923
25958
  if (mapping.severity || mapping.reason) {
25924
25959
  addInformational(
25925
25960
  lossReport,
@@ -25963,21 +25998,39 @@ var CURSOR_TOOL_NAMES = {
25963
25998
  Write: "Write",
25964
25999
  Grep: "Grep"
25965
26000
  };
25966
- function translateCursorMatcher(matcher, untranslatable) {
26001
+ var COPILOT_TOOL_NAMES = {
26002
+ Bash: "bash",
26003
+ Read: "view",
26004
+ Write: "create",
26005
+ Edit: "edit",
26006
+ MultiEdit: "edit",
26007
+ Grep: "grep",
26008
+ // Copilot's own names map to themselves, so a kit written against this
26009
+ // mapping is not reported as using something unknown.
26010
+ bash: "bash",
26011
+ view: "view",
26012
+ create: "create",
26013
+ edit: "edit",
26014
+ grep: "grep"
26015
+ };
26016
+ function translateMatcher(matcher, table, untranslatable) {
25967
26017
  const translated = [];
25968
26018
  for (const token of matcher.split("|")) {
25969
26019
  const trimmed = token.trim();
25970
26020
  if (!trimmed) continue;
25971
- const mapped = CURSOR_TOOL_NAMES[trimmed];
25972
- if (mapped === void 0) {
25973
- untranslatable.add(trimmed);
25974
- if (!translated.includes(trimmed)) translated.push(trimmed);
26021
+ const mapped = table[trimmed];
26022
+ if (mapped !== void 0) {
26023
+ if (!translated.includes(mapped)) translated.push(mapped);
25975
26024
  continue;
25976
26025
  }
25977
- if (!translated.includes(mapped)) translated.push(mapped);
26026
+ if (PLAIN_TOOL_NAME.test(trimmed)) {
26027
+ untranslatable.add(trimmed);
26028
+ }
26029
+ if (!translated.includes(trimmed)) translated.push(trimmed);
25978
26030
  }
25979
26031
  return translated.join("|");
25980
26032
  }
26033
+ var PLAIN_TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/u;
25981
26034
  function extractHookCommands(payload) {
25982
26035
  const entries = Array.isArray(payload) ? payload : [payload];
25983
26036
  const found = [];
@@ -12,7 +12,7 @@ notes:
12
12
  - Source events below are the events the canonical agent-coding kit registers, plus SessionStart.
13
13
  - An event a kit registers but this file does not list is not silently dropped — the compilers report it as unmapped and name this file. Keeping the two in step is the point of that report.
14
14
  - SessionStart, UserPromptSubmit and PostToolUseFailure were added on 2026-09-04. UserPromptSubmit and PostToolUseFailure were already registered by the kit and were being dropped for every non-claude-code target with no reason attached, because an event absent from this file was indistinguishable from one deliberately dropped.
15
- - Copilot event coverage must still be runtime-probed because official pages do not expose a single stable denominator.
15
+ - Copilot event coverage was settled on 2026-09-07 and no longer needs a probe: the CLI ships a JSON schema (schemas/api.schema.json, definition HookType) enumerating seventeen hook events with descriptions, and one live session confirmed the file is loaded and fires. The note this replaces said no stable denominator was published; the vendor ships one inside the tool.
16
16
  - Codex hook support is currently a macOS/Linux baseline; Windows remains unsupported unless vendor documentation changes.
17
17
 
18
18
  source_events:
@@ -42,53 +42,63 @@ targets:
42
42
 
43
43
  copilot:
44
44
  delivery: mapped
45
- requires_probe: true
45
+ # Measured on 2026-09-07 against GitHub Copilot CLI 1.0.83, which ships a
46
+ # JSON schema documenting its hook contract — schemas/api.schema.json,
47
+ # definition HookType — and which was probed live in a real agent turn.
48
+ # These are results, not a reading of a binary.
49
+ #
50
+ # The event enum has seventeen values, all camelCase, each with a written
51
+ # description: preToolUse, preMcpToolCall, postToolUse, postToolUseFailure,
52
+ # userPromptSubmitted, userPromptTransformed, sessionStart, sessionEnd,
53
+ # postResult, prePRDescription, errorOccurred, agentStop, subagentStart,
54
+ # subagentStop, preCompact, permissionRequest, notification.
55
+ #
56
+ # Every source event below has an equivalent. The entries this replaces
57
+ # said otherwise: six were marked `probe` with the reason "not probeable on
58
+ # the machine this mapping was last measured on", and PreCompact was a
59
+ # `drop` claiming "no documented user-level equivalent" for an event the
60
+ # vendor's own schema documents. `Stop` pointed at sessionEnd — the session
61
+ # ending — where agentStop is the agent stopping, which is what it means.
62
+ #
63
+ # The file shape was already right and is unchanged: Copilot accepts Claude
64
+ # Code's nested matcher/hooks structure under a top-level `hooks` object,
65
+ # and its log says so when it does not. What made a kit's hooks reach
66
+ # Copilot and do nothing is the matcher: Copilot matches on its own tool
67
+ # names — measured bash, view, create, edit, grep — with a full,
68
+ # case-sensitive regex, so `Bash` never matched `bash`. Translation lives
69
+ # in COPILOT_TOOL_NAMES in compiler/hooks.ts.
70
+ #
71
+ # One limit worth recording: in prompt mode (-p) repository hooks under
72
+ # .github/hooks/ load only once the folder is trusted. User-level hooks in
73
+ # ~/.copilot/hooks/ have no such gate, which is how this was probed.
46
74
  source_to_target:
47
75
  SessionStart:
48
- status: probe
49
- reason: >-
50
- Not probed, and not probeable on the machine this mapping was last measured on
51
- (2026-09-04): no Copilot CLI is installed there, and ~/.copilot/hooks is empty, so no
52
- hook can be made to fire from a terminal. To settle it: install the Copilot CLI, register
53
- a hook per event that appends its payload to a file, and drive one session that submits a
54
- prompt, runs a tool, fails a tool and ends.
76
+ status: mapped
77
+ target_event: sessionStart
55
78
  UserPromptSubmit:
56
- status: probe
57
- reason: >-
58
- Not probed, and not probeable on the machine this mapping was last measured on
59
- (2026-09-04): no Copilot CLI is installed there, and ~/.copilot/hooks is empty, so no
60
- hook can be made to fire from a terminal. To settle it: install the Copilot CLI, register
61
- a hook per event that appends its payload to a file, and drive one session that submits a
62
- prompt, runs a tool, fails a tool and ends.
63
- PostToolUseFailure:
64
- status: probe
65
- reason: >-
66
- Not probed, and not probeable on the machine this mapping was last measured on
67
- (2026-09-04): no Copilot CLI is installed there, and ~/.copilot/hooks is empty, so no
68
- hook can be made to fire from a terminal. To settle it: install the Copilot CLI, register
69
- a hook per event that appends its payload to a file, and drive one session that submits a
70
- prompt, runs a tool, fails a tool and ends.
79
+ status: mapped
80
+ target_event: userPromptSubmitted
71
81
  PreToolUse:
72
82
  status: mapped
73
83
  target_event: preToolUse
74
84
  PostToolUse:
75
85
  status: mapped
76
86
  target_event: postToolUse
87
+ PostToolUseFailure:
88
+ status: mapped
89
+ target_event: postToolUseFailure
77
90
  PreCompact:
78
- status: drop
79
- severity: warning
80
- reason: No documented user-level equivalent in the active baseline.
91
+ status: mapped
92
+ target_event: preCompact
81
93
  SubagentStart:
82
- status: probe
83
- reason: The runtime may expose a related event, but the adapter must not hard-code it as baseline support.
94
+ status: mapped
95
+ target_event: subagentStart
84
96
  SubagentStop:
85
- status: probe
86
- reason: The runtime may expose a related event, but the adapter must not hard-code it as baseline support.
97
+ status: mapped
98
+ target_event: subagentStop
87
99
  Stop:
88
100
  status: mapped
89
- target_event: sessionEnd
90
- severity: warning
91
- reason: Closest documented session-end lifecycle event, not a one-to-one semantic equivalent.
101
+ target_event: agentStop
92
102
 
93
103
  codex:
94
104
  delivery: partial
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alessandroraffa/tangyr",
3
- "version": "0.21.3",
3
+ "version": "0.21.4",
4
4
  "description": "CLI for the Tangyr discipline — install and manage operating kits for AI coding tools",
5
5
  "license": "MIT",
6
6
  "engines": {