@codyswann/lisa 2.124.4 → 2.124.6

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.
Files changed (81) hide show
  1. package/package.json +1 -1
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa-agy/plugin.json +1 -1
  5. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  6. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  7. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  8. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  9. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  11. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -34
  12. package/plugins/lisa-cursor/hooks/hooks.json +20 -0
  13. package/plugins/lisa-cursor/rules/{reference/base-rules.md → base-rules-reference.mdc} +5 -0
  14. package/plugins/lisa-cursor/rules/{eager/base-rules.md → base-rules.mdc} +6 -1
  15. package/plugins/lisa-cursor/rules/{reference/coding-philosophy.md → coding-philosophy-reference.mdc} +5 -0
  16. package/plugins/lisa-cursor/rules/{eager/coding-philosophy.md → coding-philosophy.mdc} +6 -1
  17. package/plugins/lisa-cursor/rules/{reference/config-resolution.md → config-resolution-reference.mdc} +5 -0
  18. package/plugins/lisa-cursor/rules/{eager/config-resolution.md → config-resolution.mdc} +6 -1
  19. package/plugins/lisa-cursor/rules/{reference/documentation-source-paths.md → documentation-source-paths-reference.mdc} +5 -0
  20. package/plugins/lisa-cursor/rules/{eager/documentation-source-paths.md → documentation-source-paths.mdc} +6 -1
  21. package/plugins/lisa-cursor/rules/{reference/empirical-inquiry.md → empirical-inquiry-reference.mdc} +5 -0
  22. package/plugins/lisa-cursor/rules/{eager/empirical-inquiry.md → empirical-inquiry.mdc} +6 -1
  23. package/plugins/lisa-cursor/rules/{reference/intent-routing.md → intent-routing-reference.mdc} +5 -0
  24. package/plugins/lisa-cursor/rules/{eager/intent-routing.md → intent-routing.mdc} +6 -1
  25. package/plugins/lisa-cursor/rules/{reference/leaf-only-lifecycle.md → leaf-only-lifecycle-reference.mdc} +5 -0
  26. package/plugins/lisa-cursor/rules/{eager/leaf-only-lifecycle.md → leaf-only-lifecycle.mdc} +6 -1
  27. package/plugins/lisa-cursor/rules/{reference/prd-lifecycle-rollup.md → prd-lifecycle-rollup-reference.mdc} +5 -0
  28. package/plugins/lisa-cursor/rules/{eager/prd-lifecycle-rollup.md → prd-lifecycle-rollup.mdc} +6 -1
  29. package/plugins/lisa-cursor/rules/{reference/repo-scope-split.md → repo-scope-split-reference.mdc} +5 -0
  30. package/plugins/lisa-cursor/rules/{eager/repo-scope-split.md → repo-scope-split.mdc} +6 -1
  31. package/plugins/lisa-cursor/rules/{reference/security-audit-handling.md → security-audit-handling-reference.mdc} +5 -0
  32. package/plugins/lisa-cursor/rules/{eager/security-audit-handling.md → security-audit-handling.mdc} +6 -1
  33. package/plugins/lisa-cursor/rules/{reference/usage-accounting.md → usage-accounting-reference.mdc} +5 -0
  34. package/plugins/lisa-cursor/rules/{eager/usage-accounting.md → usage-accounting.mdc} +6 -1
  35. package/plugins/lisa-cursor/rules/{reference/verification.md → verification-reference.mdc} +5 -0
  36. package/plugins/lisa-cursor/rules/{eager/verification.md → verification.mdc} +6 -1
  37. package/plugins/lisa-cursor/rules/{reference/wiki-knowledge-source.md → wiki-knowledge-source-reference.mdc} +5 -0
  38. package/plugins/lisa-cursor/rules/{eager/wiki-knowledge-source.md → wiki-knowledge-source.mdc} +6 -1
  39. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  41. package/plugins/lisa-expo-agy/plugin.json +1 -1
  42. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  46. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  47. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-harper-fabric-cursor/rules/{harper-fabric.md → harper-fabric.mdc} +5 -0
  50. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  53. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +2 -15
  55. package/plugins/lisa-nestjs-cursor/hooks/hooks.json +11 -0
  56. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  58. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  59. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  63. package/plugins/lisa-rails-agy/plugin.json +1 -1
  64. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +2 -19
  66. package/plugins/lisa-rails-cursor/hooks/hooks.json +15 -0
  67. package/plugins/lisa-rails-cursor/rules/{rails-conventions.md → rails-conventions.mdc} +5 -0
  68. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  71. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +2 -34
  73. package/plugins/lisa-typescript-cursor/hooks/hooks.json +25 -0
  74. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  76. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  77. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  79. package/scripts/generate-cursor-plugin-artifacts.mjs +214 -20
  80. package/scripts/lib/per-agent-hook-filter.mjs +138 -23
  81. /package/plugins/lisa-expo-cursor/{.mcp.json → mcp.json} +0 -0
@@ -13,8 +13,11 @@
13
13
  * - Codex: ship universal + SubagentStart (handled by the Codex generator's own
14
14
  * ship list — this module is not invoked for Codex; Codex uses the existing
15
15
  * generate-codex-plugin-artifacts.mjs path)
16
- * - Cursor: strip inject-rules.sh (Cursor auto-loads rules/ natively), strip
17
- * Claude-team-specific scripts and `entire hooks claude-code *` calls
16
+ * - Cursor: emit hooks to a Cursor-native hooks/hooks.json (camelCase events,
17
+ * flattened {command, matcher} entries, ${CURSOR_PLUGIN_ROOT}/hooks/ command
18
+ * paths) via buildCursorHooksJson; strip inject-rules.sh (rules ship as native
19
+ * rules/*.mdc — the single delivery path; injecting would double-deliver),
20
+ * Claude-team-specific scripts, and `entire hooks claude-code *` calls
18
21
  * - agy: this filter is NOT consumed for agy. agy hooks ship as a
19
22
  * plugin-bundled ROOT hooks.json emitted by
20
23
  * generate-agy-plugin-artifacts.mjs (its own AGY_PLUGIN_HOOKS map is the
@@ -50,7 +53,7 @@ const SCRIPT_RULES = {
50
53
  "inject-rules.sh": {
51
54
  claude: true,
52
55
  codex: true,
53
- cursor: false, // collision: Cursor auto-loads rules/ natively
56
+ cursor: false, // rules ship as native rules/*.mdc (single delivery path); injecting would double-deliver
54
57
  agy: false, // rules delivered via AGENTS.md bake, not a hook (rules-once invariant)
55
58
  copilot: true, // conservative default; conditionally stripped if rules-auto-load probe positive
56
59
  },
@@ -110,12 +113,69 @@ const scriptNameFromCommand = cmd => {
110
113
  return match ? match[1] : null;
111
114
  };
112
115
 
116
+ /**
117
+ * Rewrite a Claude hook command to the Cursor plugin-root form: the
118
+ * `${CLAUDE_PLUGIN_ROOT}/` (or `${CURSOR_PLUGIN_ROOT}/`) prefix is normalized to
119
+ * `${CURSOR_PLUGIN_ROOT}/` (e.g. `${CLAUDE_PLUGIN_ROOT}/hooks/block-no-verify.sh`
120
+ * → `${CURSOR_PLUGIN_ROOT}/hooks/block-no-verify.sh`).
121
+ *
122
+ * Why the token, not a bare `./` (issue #1055, CodeRabbit security review):
123
+ * Cursor plugin hook commands execute with the OPENED PROJECT ROOT as their cwd
124
+ * — NOT the plugin directory (confirmed by a Cursor maintainer on the forum:
125
+ * https://forum.cursor.com/t/inconsistent-working-directory-for-plugin-hook-commands/153236).
126
+ * A bare `./hooks/<script>.sh` would therefore (a) fail to resolve to the
127
+ * plugin's bundled script and (b) let a malicious repo shadow a guard hook with
128
+ * its own project-root `./hooks/*`. `${CURSOR_PLUGIN_ROOT}` is the maintainer-
129
+ * endorsed placeholder for the plugin install dir (`${CLAUDE_PLUGIN_ROOT}` also
130
+ * works in Cursor, but we normalize to the Cursor-native name).
131
+ *
132
+ * @param {string} command
133
+ * @returns {string}
134
+ */
135
+ const toCursorCommandPath = command =>
136
+ typeof command === "string"
137
+ ? command.replace(
138
+ /\$\{(?:CLAUDE|CURSOR)_PLUGIN_ROOT\}\//g,
139
+ "${CURSOR_PLUGIN_ROOT}/"
140
+ )
141
+ : command;
142
+
143
+ /** Claude PascalCase → Copilot event-name map (per Copilot's docs). */
144
+ const COPILOT_EVENTS = {
145
+ PreToolUse: "preToolUse",
146
+ PostToolUse: "postToolUse",
147
+ SessionStart: "sessionStart",
148
+ SessionEnd: "sessionEnd",
149
+ UserPromptSubmit: "userPromptSubmitted",
150
+ Stop: "agentStop",
151
+ SubagentStart: "subagentStart", // not supported but include for symmetry
152
+ SubagentStop: "subagentStop",
153
+ };
154
+
155
+ /**
156
+ * Claude PascalCase → Cursor event-name map. Verified against the official
157
+ * Cursor hooks reference (issue #1055; the prior "keep PascalCase, loader
158
+ * auto-normalizes" assumption was wrong).
159
+ */
160
+ const CURSOR_EVENTS = {
161
+ PreToolUse: "preToolUse",
162
+ PostToolUse: "postToolUse",
163
+ SessionStart: "sessionStart",
164
+ SessionEnd: "sessionEnd",
165
+ UserPromptSubmit: "beforeSubmitPrompt",
166
+ Stop: "stop",
167
+ SubagentStart: "subagentStart",
168
+ SubagentStop: "subagentStop",
169
+ PreCompact: "preCompact",
170
+ };
171
+
113
172
  /**
114
173
  * Translate Claude PascalCase event names to a target agent's native casing.
115
174
  *
116
175
  * Per the Wave 1 audit's Wave 3 contract step 4 + step 6:
117
- * - Cursor: keep PascalCase (loader auto-normalizes)
118
- * - Codex: keep PascalCase
176
+ * - Cursor: rewrite to Cursor camelCase event names (preToolUse, postToolUse,
177
+ * sessionStart, beforeSubmitPrompt, stop, …)
178
+ * - Codex / agy: keep PascalCase
119
179
  * - Copilot: rewrite to lowercase / camelCase per Copilot's docs
120
180
  *
121
181
  * @param {string} eventName Claude event name (e.g. "PreToolUse")
@@ -123,18 +183,9 @@ const scriptNameFromCommand = cmd => {
123
183
  * @returns {string} Translated event name
124
184
  */
125
185
  export function translateEventName(eventName, agent) {
126
- if (agent !== "copilot") return eventName;
127
- const COPILOT_EVENTS = {
128
- PreToolUse: "preToolUse",
129
- PostToolUse: "postToolUse",
130
- SessionStart: "sessionStart",
131
- SessionEnd: "sessionEnd",
132
- UserPromptSubmit: "userPromptSubmitted",
133
- Stop: "agentStop",
134
- SubagentStart: "subagentStart", // not supported but include for symmetry
135
- SubagentStop: "subagentStop",
136
- };
137
- return COPILOT_EVENTS[eventName] ?? eventName;
186
+ if (agent === "copilot") return COPILOT_EVENTS[eventName] ?? eventName;
187
+ if (agent === "cursor") return CURSOR_EVENTS[eventName] ?? eventName;
188
+ return eventName;
138
189
  }
139
190
 
140
191
  /**
@@ -204,12 +255,15 @@ export function shouldShipHook(hook, _eventName, agent, opts = {}) {
204
255
  * Returns the new hook block (or undefined when the block ends up empty after
205
256
  * filtering, which means the manifest should omit the hooks field entirely).
206
257
  *
207
- * This function is invoked only for cursor/copilot. The "agy" branch still
208
- * works (3 universal scripts survive, PascalCase events) and is exercised by
209
- * unit tests as conceptual ship-list documentation, but agy hooks are NOT
210
- * emitted through this path they ship as a plugin-bundled root hooks.json
211
- * built by generate-agy-plugin-artifacts.mjs (only block-no-verify is portable;
212
- * agy lacks SessionStart).
258
+ * This function returns the Claude-NESTED block shape (with translated event
259
+ * keys) and is used by the Copilot generator. Cursor does NOT use it — Cursor
260
+ * needs the flattened hooks/hooks.json schema and goes through
261
+ * buildCursorHooksJson instead. The "agy" branch still works (3 universal
262
+ * scripts survive, PascalCase events) and is exercised by unit tests as
263
+ * conceptual ship-list documentation, but agy hooks are NOT emitted through this
264
+ * path — they ship as a plugin-bundled root hooks.json built by
265
+ * generate-agy-plugin-artifacts.mjs (only block-no-verify is portable; agy lacks
266
+ * SessionStart).
213
267
  *
214
268
  * @param {Record<string, Array<{ matcher?: string, hooks: Array<object> }>>} hookBlock
215
269
  * The Claude-format hook block from .claude-plugin/plugin.json.
@@ -258,3 +312,64 @@ export function filterHooksForAgent(hookBlock, agent, opts = {}) {
258
312
  export function filterScriptsForAgent(scriptFilenames, agent) {
259
313
  return scriptFilenames.filter(name => shouldShipScript(name, agent));
260
314
  }
315
+
316
+ /**
317
+ * Build the Cursor-native `hooks/hooks.json` structure from a Claude-format hook
318
+ * block.
319
+ *
320
+ * Cursor's hooks file uses a flattened schema that differs from Claude's nested
321
+ * `.claude-plugin/plugin.json` block:
322
+ *
323
+ * Claude: { "<ClaudeEvent>": [ { matcher, hooks: [ { type: "command", command } ] } ] }
324
+ * Cursor: { version: 1, hooks: { "<cursorEvent>": [ { command, matcher? } ] } }
325
+ *
326
+ * The transformation:
327
+ * 1. Filters each handler for the cursor agent (drops inject-rules.sh,
328
+ * enforce-team-first.sh, and `entire hooks claude-code *` calls).
329
+ * 2. Translates Claude PascalCase event names to Cursor camelCase.
330
+ * 3. Flattens each matcher-group into one `{ command, matcher? }` per surviving
331
+ * handler (unwrapping Claude's `{ type: "command", command }`).
332
+ * 4. Rewrites `${CLAUDE_PLUGIN_ROOT}/hooks/<x>` command paths to the
333
+ * Cursor plugin-root form `${CURSOR_PLUGIN_ROOT}/hooks/<x>` (plugin hooks
334
+ * run with the project root as cwd, so a bare `./` would not resolve to the
335
+ * bundled script and could be shadowed by a repo-local `./hooks/*`).
336
+ *
337
+ * Note: this intentionally re-walks the hook block rather than sharing a
338
+ * skeleton with `filterHooksForAgent`. The DRY extraction was deferred (issue
339
+ * #1055 review): `filterHooksForAgent` is on the Copilot path and emits the
340
+ * Claude-NESTED shape, whereas this emits Cursor's FLAT shape — unifying the
341
+ * walk would risk a sibling-generator regression for marginal gain.
342
+ *
343
+ * @param {Record<string, Array<{ matcher?: string, hooks: Array<{ type?: string, command: string }> }>>} hookBlock
344
+ * The Claude-format hook block from `.claude-plugin/plugin.json`.
345
+ * @returns {{ version: number, hooks: Record<string, Array<{ command: string, matcher?: string }>> } | undefined}
346
+ * The Cursor hooks structure, or undefined when no hooks survive (the caller
347
+ * then omits `hooks/hooks.json` entirely).
348
+ */
349
+ export function buildCursorHooksJson(hookBlock) {
350
+ if (!hookBlock || typeof hookBlock !== "object") return undefined;
351
+
352
+ /** @type {Record<string, Array<{ command: string, matcher?: string }>>} */
353
+ const hooks = {};
354
+
355
+ for (const [claudeEventName, entries] of Object.entries(hookBlock)) {
356
+ if (!Array.isArray(entries)) continue;
357
+ const flattened = [];
358
+ for (const entry of entries) {
359
+ const handlerArray = entry?.hooks;
360
+ if (!Array.isArray(handlerArray)) continue;
361
+ for (const handler of handlerArray) {
362
+ if (!shouldShipHook(handler, claudeEventName, "cursor")) continue;
363
+ const command = toCursorCommandPath(handler.command);
364
+ flattened.push(
365
+ entry.matcher ? { command, matcher: entry.matcher } : { command }
366
+ );
367
+ }
368
+ }
369
+ if (flattened.length > 0) {
370
+ hooks[translateEventName(claudeEventName, "cursor")] = flattened;
371
+ }
372
+ }
373
+
374
+ return Object.keys(hooks).length > 0 ? { version: 1, hooks } : undefined;
375
+ }