@codyswann/lisa 2.124.3 → 2.124.5

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 +212 -20
  80. package/scripts/lib/per-agent-hook-filter.mjs +133 -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, relative ./hooks/ command paths) via
18
+ * 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,66 @@ 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-relative form: the
118
+ * `${CLAUDE_PLUGIN_ROOT}/` prefix becomes `./` (e.g.
119
+ * `${CLAUDE_PLUGIN_ROOT}/hooks/block-no-verify.sh` → `./hooks/block-no-verify.sh`).
120
+ *
121
+ * Path-resolution caveat (issue #1055 security review): Cursor exposes NO
122
+ * plugin-root token for hook commands — its hooks reference documents only
123
+ * `CURSOR_PROJECT_DIR` / `CLAUDE_PROJECT_DIR` (workspace root), and is silent on
124
+ * how plugin-bundled `hooks/hooks.json` commands resolve. `./` is therefore the
125
+ * only plugin-relative form available, and is what the Cursor plugin structure
126
+ * implies for a plugin-bundled file. Plugin-hook FIRING (and thus the exact CWD
127
+ * these resolve against) is not verifiable via `cursor-agent --plugin-dir` — it
128
+ * is an IDE/marketplace concern tracked as a PR follow-up. If a future Cursor
129
+ * release resolves plugin-hook `./` against the project root rather than the
130
+ * plugin root, a malicious repo could shadow a guard hook; revisit this then.
131
+ *
132
+ * @param {string} command
133
+ * @returns {string}
134
+ */
135
+ const toCursorCommandPath = command =>
136
+ typeof command === "string"
137
+ ? command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}\//g, "./")
138
+ : command;
139
+
140
+ /** Claude PascalCase → Copilot event-name map (per Copilot's docs). */
141
+ const COPILOT_EVENTS = {
142
+ PreToolUse: "preToolUse",
143
+ PostToolUse: "postToolUse",
144
+ SessionStart: "sessionStart",
145
+ SessionEnd: "sessionEnd",
146
+ UserPromptSubmit: "userPromptSubmitted",
147
+ Stop: "agentStop",
148
+ SubagentStart: "subagentStart", // not supported but include for symmetry
149
+ SubagentStop: "subagentStop",
150
+ };
151
+
152
+ /**
153
+ * Claude PascalCase → Cursor event-name map. Verified against the official
154
+ * Cursor hooks reference (issue #1055; the prior "keep PascalCase, loader
155
+ * auto-normalizes" assumption was wrong).
156
+ */
157
+ const CURSOR_EVENTS = {
158
+ PreToolUse: "preToolUse",
159
+ PostToolUse: "postToolUse",
160
+ SessionStart: "sessionStart",
161
+ SessionEnd: "sessionEnd",
162
+ UserPromptSubmit: "beforeSubmitPrompt",
163
+ Stop: "stop",
164
+ SubagentStart: "subagentStart",
165
+ SubagentStop: "subagentStop",
166
+ PreCompact: "preCompact",
167
+ };
168
+
113
169
  /**
114
170
  * Translate Claude PascalCase event names to a target agent's native casing.
115
171
  *
116
172
  * Per the Wave 1 audit's Wave 3 contract step 4 + step 6:
117
- * - Cursor: keep PascalCase (loader auto-normalizes)
118
- * - Codex: keep PascalCase
173
+ * - Cursor: rewrite to Cursor camelCase event names (preToolUse, postToolUse,
174
+ * sessionStart, beforeSubmitPrompt, stop, …)
175
+ * - Codex / agy: keep PascalCase
119
176
  * - Copilot: rewrite to lowercase / camelCase per Copilot's docs
120
177
  *
121
178
  * @param {string} eventName Claude event name (e.g. "PreToolUse")
@@ -123,18 +180,9 @@ const scriptNameFromCommand = cmd => {
123
180
  * @returns {string} Translated event name
124
181
  */
125
182
  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;
183
+ if (agent === "copilot") return COPILOT_EVENTS[eventName] ?? eventName;
184
+ if (agent === "cursor") return CURSOR_EVENTS[eventName] ?? eventName;
185
+ return eventName;
138
186
  }
139
187
 
140
188
  /**
@@ -204,12 +252,15 @@ export function shouldShipHook(hook, _eventName, agent, opts = {}) {
204
252
  * Returns the new hook block (or undefined when the block ends up empty after
205
253
  * filtering, which means the manifest should omit the hooks field entirely).
206
254
  *
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).
255
+ * This function returns the Claude-NESTED block shape (with translated event
256
+ * keys) and is used by the Copilot generator. Cursor does NOT use it — Cursor
257
+ * needs the flattened hooks/hooks.json schema and goes through
258
+ * buildCursorHooksJson instead. The "agy" branch still works (3 universal
259
+ * scripts survive, PascalCase events) and is exercised by unit tests as
260
+ * conceptual ship-list documentation, but agy hooks are NOT emitted through this
261
+ * path — they ship as a plugin-bundled root hooks.json built by
262
+ * generate-agy-plugin-artifacts.mjs (only block-no-verify is portable; agy lacks
263
+ * SessionStart).
213
264
  *
214
265
  * @param {Record<string, Array<{ matcher?: string, hooks: Array<object> }>>} hookBlock
215
266
  * The Claude-format hook block from .claude-plugin/plugin.json.
@@ -258,3 +309,62 @@ export function filterHooksForAgent(hookBlock, agent, opts = {}) {
258
309
  export function filterScriptsForAgent(scriptFilenames, agent) {
259
310
  return scriptFilenames.filter(name => shouldShipScript(name, agent));
260
311
  }
312
+
313
+ /**
314
+ * Build the Cursor-native `hooks/hooks.json` structure from a Claude-format hook
315
+ * block.
316
+ *
317
+ * Cursor's hooks file uses a flattened schema that differs from Claude's nested
318
+ * `.claude-plugin/plugin.json` block:
319
+ *
320
+ * Claude: { "<ClaudeEvent>": [ { matcher, hooks: [ { type: "command", command } ] } ] }
321
+ * Cursor: { version: 1, hooks: { "<cursorEvent>": [ { command, matcher? } ] } }
322
+ *
323
+ * The transformation:
324
+ * 1. Filters each handler for the cursor agent (drops inject-rules.sh,
325
+ * enforce-team-first.sh, and `entire hooks claude-code *` calls).
326
+ * 2. Translates Claude PascalCase event names to Cursor camelCase.
327
+ * 3. Flattens each matcher-group into one `{ command, matcher? }` per surviving
328
+ * handler (unwrapping Claude's `{ type: "command", command }`).
329
+ * 4. Rewrites `${CLAUDE_PLUGIN_ROOT}/hooks/<x>` command paths to the
330
+ * Cursor-relative `./hooks/<x>`.
331
+ *
332
+ * Note: this intentionally re-walks the hook block rather than sharing a
333
+ * skeleton with `filterHooksForAgent`. The DRY extraction was deferred (issue
334
+ * #1055 review): `filterHooksForAgent` is on the Copilot path and emits the
335
+ * Claude-NESTED shape, whereas this emits Cursor's FLAT shape — unifying the
336
+ * walk would risk a sibling-generator regression for marginal gain.
337
+ *
338
+ * @param {Record<string, Array<{ matcher?: string, hooks: Array<{ type?: string, command: string }> }>>} hookBlock
339
+ * The Claude-format hook block from `.claude-plugin/plugin.json`.
340
+ * @returns {{ version: number, hooks: Record<string, Array<{ command: string, matcher?: string }>> } | undefined}
341
+ * The Cursor hooks structure, or undefined when no hooks survive (the caller
342
+ * then omits `hooks/hooks.json` entirely).
343
+ */
344
+ export function buildCursorHooksJson(hookBlock) {
345
+ if (!hookBlock || typeof hookBlock !== "object") return undefined;
346
+
347
+ /** @type {Record<string, Array<{ command: string, matcher?: string }>>} */
348
+ const hooks = {};
349
+
350
+ for (const [claudeEventName, entries] of Object.entries(hookBlock)) {
351
+ if (!Array.isArray(entries)) continue;
352
+ const flattened = [];
353
+ for (const entry of entries) {
354
+ const handlerArray = entry?.hooks;
355
+ if (!Array.isArray(handlerArray)) continue;
356
+ for (const handler of handlerArray) {
357
+ if (!shouldShipHook(handler, claudeEventName, "cursor")) continue;
358
+ const command = toCursorCommandPath(handler.command);
359
+ flattened.push(
360
+ entry.matcher ? { command, matcher: entry.matcher } : { command }
361
+ );
362
+ }
363
+ }
364
+ if (flattened.length > 0) {
365
+ hooks[translateEventName(claudeEventName, "cursor")] = flattened;
366
+ }
367
+ }
368
+
369
+ return Object.keys(hooks).length > 0 ? { version: 1, hooks } : undefined;
370
+ }