@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
@@ -1,24 +1,11 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
7
7
  },
8
8
  "dependencies": [
9
9
  "lisa-typescript"
10
- ],
11
- "hooks": {
12
- "PreToolUse": [
13
- {
14
- "matcher": "Write|Edit",
15
- "hooks": [
16
- {
17
- "type": "command",
18
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/block-migration-edits.sh"
19
- }
20
- ]
21
- }
22
- ]
23
- }
10
+ ]
24
11
  }
@@ -0,0 +1,11 @@
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "preToolUse": [
5
+ {
6
+ "command": "${CURSOR_PLUGIN_ROOT}/hooks/block-migration-edits.sh",
7
+ "matcher": "Write|Edit"
8
+ }
9
+ ]
10
+ }
11
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,28 +1,11 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
7
7
  },
8
8
  "dependencies": [
9
9
  "lisa"
10
- ],
11
- "hooks": {
12
- "PostToolUse": [
13
- {
14
- "matcher": "Write|Edit",
15
- "hooks": [
16
- {
17
- "type": "command",
18
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/rubocop-on-edit.sh"
19
- },
20
- {
21
- "type": "command",
22
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/sg-scan-on-edit.sh"
23
- }
24
- ]
25
- }
26
- ]
27
- }
10
+ ]
28
11
  }
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "postToolUse": [
5
+ {
6
+ "command": "${CURSOR_PLUGIN_ROOT}/hooks/rubocop-on-edit.sh",
7
+ "matcher": "Write|Edit"
8
+ },
9
+ {
10
+ "command": "${CURSOR_PLUGIN_ROOT}/hooks/sg-scan-on-edit.sh",
11
+ "matcher": "Write|Edit"
12
+ }
13
+ ]
14
+ }
15
+ }
@@ -1,3 +1,8 @@
1
+ ---
2
+ description: "Rails Coding Conventions"
3
+ alwaysApply: true
4
+ ---
5
+
1
6
  # Rails Coding Conventions
2
7
 
3
8
  This rule enforces Rails-specific coding standards for consistency, maintainability, and performance.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,43 +1,11 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
7
7
  },
8
8
  "dependencies": [
9
9
  "lisa"
10
- ],
11
- "hooks": {
12
- "PreToolUse": [
13
- {
14
- "matcher": "Write|Edit",
15
- "hooks": [
16
- {
17
- "type": "command",
18
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/block-suppress-directives.sh"
19
- }
20
- ]
21
- }
22
- ],
23
- "PostToolUse": [
24
- {
25
- "matcher": "Write|Edit",
26
- "hooks": [
27
- {
28
- "type": "command",
29
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/format-on-edit.sh"
30
- },
31
- {
32
- "type": "command",
33
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/sg-scan-on-edit.sh"
34
- },
35
- {
36
- "type": "command",
37
- "command": "${CLAUDE_PLUGIN_ROOT}/hooks/lint-on-edit.sh"
38
- }
39
- ]
40
- }
41
- ]
42
- }
10
+ ]
43
11
  }
@@ -0,0 +1,25 @@
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "preToolUse": [
5
+ {
6
+ "command": "${CURSOR_PLUGIN_ROOT}/hooks/block-suppress-directives.sh",
7
+ "matcher": "Write|Edit"
8
+ }
9
+ ],
10
+ "postToolUse": [
11
+ {
12
+ "command": "${CURSOR_PLUGIN_ROOT}/hooks/format-on-edit.sh",
13
+ "matcher": "Write|Edit"
14
+ },
15
+ {
16
+ "command": "${CURSOR_PLUGIN_ROOT}/hooks/sg-scan-on-edit.sh",
17
+ "matcher": "Write|Edit"
18
+ },
19
+ {
20
+ "command": "${CURSOR_PLUGIN_ROOT}/hooks/lint-on-edit.sh",
21
+ "matcher": "Write|Edit"
22
+ }
23
+ ]
24
+ }
25
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.124.4",
3
+ "version": "2.124.6",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -2,13 +2,34 @@
2
2
  /**
3
3
  * Generate the Cursor variant of a Lisa plugin from the built Claude artifact.
4
4
  *
5
- * Cursor reads `.claude-plugin/plugin.json` natively via its dual-namespace
6
- * loader (also accepts `.cursor-plugin/plugin.json`). The Cursor variant
7
- * therefore keeps the `.claude-plugin/` manifest but filters the hooks block
8
- * to drop `inject-rules.sh` (Cursor auto-loads `rules/` from plugins natively,
9
- * so shipping the polyfill would double-inject), `enforce-team-first.sh`
10
- * (Claude-team-specific), and the `entire hooks claude-code *` analytics calls
11
- * (Claude-only).
5
+ * The variant is reshaped to match the official Cursor plugin spec (issue
6
+ * #1055). Rule application was verified empirically by loading the generated
7
+ * `.mdc` at Cursor's canonical `.cursor/rules/` location (NEW `.mdc` = applied,
8
+ * OLD nested `.md` = UNKNOWN, both with 0 tool calls). Note: the
9
+ * `cursor-agent --plugin-dir` headless flag loads a plugin's skills/agents/
10
+ * commands but does NOT inject its `rules/*.mdc` into model context, so it
11
+ * cannot be used to prove rule application — see evidence/cursor-rule-probe-1055.md:
12
+ *
13
+ * - Manifest: keep `.claude-plugin/plugin.json` — `cursor-agent --plugin-dir`
14
+ * loads it (CLI scope). The IDE marketplace's `.cursor-plugin/` + a Cursor
15
+ * `marketplace.json` is a separate, untested follow-up and is NOT emitted here.
16
+ * - Rules: Cursor discovers `rules/*.mdc` files carrying YAML frontmatter and
17
+ * ignores plain `.md`. The built Claude artifact ships a nested
18
+ * `rules/eager/*.md` + `rules/reference/*.md` tree, so the variant flattens it
19
+ * to top-level `rules/<name>.mdc` (eager → `alwaysApply: true`) and
20
+ * `rules/<name>-reference.mdc` (reference → `alwaysApply: false`). The
21
+ * `-reference` suffix avoids a same-path collision: eager and reference share
22
+ * all base names.
23
+ * - Hooks: emitted as a Cursor-native `hooks/hooks.json` (flattened schema,
24
+ * camelCase event names, `${CURSOR_PLUGIN_ROOT}/hooks/` command paths —
25
+ * plugin hooks run with the project root as cwd, so the plugin-root token is
26
+ * required, not a bare `./`) — NOT inline in the manifest. `inject-rules.sh`
27
+ * is dropped because rules now ship as native
28
+ * `.mdc` (the single delivery path; injecting would double-deliver);
29
+ * `enforce-team-first.sh` (Claude-team-specific) and the
30
+ * `entire hooks claude-code *` analytics calls (Claude-only) are dropped too.
31
+ * - MCP: renamed from `.mcp.json` to `mcp.json` — Cursor auto-discovers the
32
+ * un-dotted filename.
12
33
  *
13
34
  * The variant's `hooks/` directory mirrors the surviving script ship-list.
14
35
  *
@@ -25,7 +46,7 @@ import fs from "node:fs";
25
46
  import path from "node:path";
26
47
  import { fileURLToPath } from "node:url";
27
48
  import {
28
- filterHooksForAgent,
49
+ buildCursorHooksJson,
29
50
  filterScriptsForAgent,
30
51
  } from "./lib/per-agent-hook-filter.mjs";
31
52
 
@@ -82,6 +103,167 @@ function copyDir(src, dst, keep = () => true) {
82
103
  walk(src, "");
83
104
  }
84
105
 
106
+ /**
107
+ * Titleize a rule slug for a fallback frontmatter description.
108
+ *
109
+ * @param {string} name e.g. "base-rules"
110
+ * @returns {string} e.g. "Base Rules"
111
+ */
112
+ function titleizeRuleName(name) {
113
+ return name
114
+ .split("-")
115
+ .map(part => (part ? part[0].toUpperCase() + part.slice(1) : part))
116
+ .join(" ")
117
+ .trim();
118
+ }
119
+
120
+ /**
121
+ * Derive a single-line frontmatter description from a rule body.
122
+ *
123
+ * Prefers the first markdown H1 heading; falls back to the titleized slug. The
124
+ * result is always a non-empty single line (Cursor frontmatter `description`).
125
+ *
126
+ * @param {string} body Rule markdown content.
127
+ * @param {string} name Rule slug.
128
+ * @returns {string}
129
+ */
130
+ function deriveRuleDescription(body, name) {
131
+ // Strip fenced code blocks first so a `#`-prefixed line inside one (a shell
132
+ // comment, a Markdown example, etc.) is not mistaken for the rule's H1.
133
+ const withoutFences = body.replace(/^(```|~~~).*$[\s\S]*?^\1.*$/gm, "");
134
+ const h1 = /^#\s+(.+?)\s*$/m.exec(withoutFences);
135
+ const text = (h1 ? h1[1] : "").replace(/\s+/g, " ").trim();
136
+ return text || titleizeRuleName(name);
137
+ }
138
+
139
+ /**
140
+ * YAML-quote a description value for `.mdc` frontmatter.
141
+ *
142
+ * @param {string} value
143
+ * @returns {string}
144
+ */
145
+ function yamlQuote(value) {
146
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
147
+ }
148
+
149
+ /**
150
+ * Rewrite intra-rule cross-link URLs from the nested `.md` layout to the flat
151
+ * `.mdc` layout so links in flattened rules still resolve.
152
+ *
153
+ * Only the URL inside a Markdown `](...)` link is rewritten — the link TEXT is
154
+ * left untouched (so `[reference/base-rules.md](../reference/base-rules.md)`
155
+ * keeps its readable label while the target becomes `base-rules-reference.mdc`).
156
+ * Two URL shapes are handled:
157
+ * - tier-prefixed: `(../)?eager/<slug>.md` → `<slug>.mdc`;
158
+ * `(../)?reference/<slug>.md` → `<slug>-reference.mdc`
159
+ * - bare same-dir: `<slug>.md` (no slash) → `<slug>.mdc`
160
+ * Optional `#fragment` suffixes are preserved. Constraint: a bare same-dir link
161
+ * is assumed to target the eager / top-level rule of that slug — the real eager
162
+ * bodies always use an explicit `../reference/<name>.md` URL for the reference
163
+ * tier, so bare links never need the `-reference` suffix. URLs with any other
164
+ * shape (deeper paths, external `.md`, http links) are left as-is.
165
+ *
166
+ * @param {string} body Rule markdown content.
167
+ * @returns {string}
168
+ */
169
+ function rewriteRuleLinks(body) {
170
+ return body.replace(/\]\(([^)]+)\)/g, (match, url) => {
171
+ const tiered =
172
+ /^(?:\.\.\/)?(eager|reference)\/([A-Za-z0-9._-]+)\.md(#[^)]*)?$/.exec(
173
+ url
174
+ );
175
+ if (tiered) {
176
+ const [, tier, slug, fragment = ""] = tiered;
177
+ const base = tier === "reference" ? `${slug}-reference` : slug;
178
+ return `](${base}.mdc${fragment})`;
179
+ }
180
+ const bare = /^([A-Za-z0-9._-]+)\.md(#[^)]*)?$/.exec(url);
181
+ if (bare) {
182
+ const [, slug, fragment = ""] = bare;
183
+ return `](${slug}.mdc${fragment})`;
184
+ }
185
+ return match;
186
+ });
187
+ }
188
+
189
+ /**
190
+ * Transform the copied nested `rules/` tree into flat Cursor-native
191
+ * `rules/<name>.mdc` files with YAML frontmatter.
192
+ *
193
+ * The base plugin splits rules into `rules/eager/*.md` (always-on) and
194
+ * `rules/reference/*.md` (on-request); stack plugins instead ship a single
195
+ * always-on `rules/<name>.md` at the top level. Both layouts are normalized:
196
+ * - eager rule → `rules/<name>.mdc` with `alwaysApply: true`
197
+ * - reference rule → `rules/<name>-reference.mdc` with `alwaysApply: false`
198
+ * (the `-reference` suffix prevents a same-path collision, since eager and
199
+ * reference share base names)
200
+ * - top-level stack rule → `rules/<name>.mdc` with `alwaysApply: true`
201
+ * Plain top-level `.md` rules are rewritten in place to `.mdc`; the nested
202
+ * `rules/eager/` and `rules/reference/` subdirs are removed afterward.
203
+ *
204
+ * @param {string} outDir Cursor variant output directory.
205
+ */
206
+ function transformRules(outDir) {
207
+ const rulesDir = path.join(outDir, "rules");
208
+ if (!fs.existsSync(rulesDir)) return;
209
+
210
+ /**
211
+ * Write one `.mdc` rule with frontmatter, rewriting intra-rule links.
212
+ *
213
+ * @param {string} srcFile Absolute path of the source `.md` rule.
214
+ * @param {string} slug Output rule slug (filename without extension).
215
+ * @param {boolean} alwaysApply Frontmatter `alwaysApply` value.
216
+ */
217
+ const writeMdc = (srcFile, slug, alwaysApply) => {
218
+ const body = fs.readFileSync(srcFile, "utf8");
219
+ const frontmatter = `---\ndescription: ${yamlQuote(
220
+ deriveRuleDescription(body, slug)
221
+ )}\nalwaysApply: ${alwaysApply}\n---\n\n`;
222
+ fs.writeFileSync(
223
+ path.join(rulesDir, `${slug}.mdc`),
224
+ frontmatter + rewriteRuleLinks(body)
225
+ );
226
+ };
227
+
228
+ // Eager / reference subdir layout (base plugin).
229
+ const tiers = [
230
+ { sub: "eager", alwaysApply: true, suffix: "" },
231
+ { sub: "reference", alwaysApply: false, suffix: "-reference" },
232
+ ];
233
+ for (const { sub, alwaysApply, suffix } of tiers) {
234
+ const subDir = path.join(rulesDir, sub);
235
+ if (!fs.existsSync(subDir)) continue;
236
+ for (const entry of fs.readdirSync(subDir)) {
237
+ if (!entry.endsWith(".md")) continue;
238
+ const slug = entry.slice(0, -".md".length);
239
+ writeMdc(path.join(subDir, entry), `${slug}${suffix}`, alwaysApply);
240
+ }
241
+ fs.rmSync(subDir, { recursive: true, force: true });
242
+ }
243
+
244
+ // Top-level stack rules (e.g. lisa-rails, lisa-harper-fabric): single
245
+ // always-on `.md` → `.mdc` with alwaysApply:true.
246
+ for (const entry of fs.readdirSync(rulesDir)) {
247
+ if (!entry.endsWith(".md")) continue;
248
+ const srcFile = path.join(rulesDir, entry);
249
+ if (!fs.statSync(srcFile).isFile()) continue;
250
+ writeMdc(srcFile, entry.slice(0, -".md".length), true);
251
+ fs.rmSync(srcFile);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Rename a copied `.mcp.json` to Cursor's auto-discovered `mcp.json`.
257
+ *
258
+ * @param {string} outDir Cursor variant output directory.
259
+ */
260
+ function renameMcpFile(outDir) {
261
+ const dotMcp = path.join(outDir, ".mcp.json");
262
+ if (fs.existsSync(dotMcp)) {
263
+ fs.renameSync(dotMcp, path.join(outDir, "mcp.json"));
264
+ }
265
+ }
266
+
85
267
  /**
86
268
  * Generate the Cursor variant.
87
269
  *
@@ -105,11 +287,10 @@ export function generateCursorVariant(srcDir, outDir, version) {
105
287
  // Drop the `.codex-plugin/` directory — Cursor does not consume it.
106
288
  if (relPath.startsWith(".codex-plugin/") || relPath === ".codex-plugin")
107
289
  return false;
108
- // Defensively drop any hooks/hooks.json. The base build now emits the Codex
109
- // hooks manifest under .codex-plugin/ (stripped above), not here (issue
110
- // #1058), but guard against a regression reintroducing it: Cursor reads its
111
- // (filtered) hooks from .claude-plugin/plugin.json, not a Codex-shaped file.
112
- // The surviving .sh scripts in hooks/ are kept below.
290
+ // Drop any source-provided hooks/hooks.json (e.g. a Codex-shaped leak from a
291
+ // regression; the base build emits Codex hooks under .codex-plugin/, stripped
292
+ // above). We emit our OWN Cursor-shaped hooks/hooks.json below from the
293
+ // manifest's hook block, so any copied one would be wrong.
113
294
  if (relPath === path.join("hooks", "hooks.json")) return false;
114
295
  // Drop Codex-specific per-skill openai.yaml artifacts — Cursor does not use them.
115
296
  // These live at skills/<n>/agents/openai.yaml and are generated by the Codex
@@ -135,18 +316,31 @@ export function generateCursorVariant(srcDir, outDir, version) {
135
316
  }
136
317
  }
137
318
 
138
- // 2. Read + filter the manifest.
319
+ // 1b. Flatten the nested rules/ tree into Cursor-native rules/*.mdc files.
320
+ transformRules(outDir);
321
+
322
+ // 1c. Rename .mcp.json → mcp.json (Cursor auto-discovers the un-dotted name).
323
+ renameMcpFile(outDir);
324
+
325
+ // 2. Read the manifest, stamp the version, and strip the inline hook block.
326
+ // Cursor reads hooks from hooks/hooks.json (emitted in 2a), never inline.
139
327
  const manifestPath = path.join(outDir, ".claude-plugin", "plugin.json");
140
328
  const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
141
329
  manifest.version = version;
142
- const filteredHooks = filterHooksForAgent(manifest.hooks ?? {}, "cursor");
143
- if (filteredHooks) {
144
- manifest.hooks = filteredHooks;
145
- } else {
146
- delete manifest.hooks;
147
- }
330
+ const cursorHooks = buildCursorHooksJson(manifest.hooks ?? {});
331
+ delete manifest.hooks;
148
332
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
149
333
 
334
+ // 2a. Emit hooks/hooks.json in Cursor's native shape when any hooks survive.
335
+ if (cursorHooks) {
336
+ const cursorHooksDir = path.join(outDir, "hooks");
337
+ fs.mkdirSync(cursorHooksDir, { recursive: true });
338
+ fs.writeFileSync(
339
+ path.join(cursorHooksDir, "hooks.json"),
340
+ JSON.stringify(cursorHooks, null, 2) + "\n"
341
+ );
342
+ }
343
+
150
344
  // 3. Filter the hooks/ directory to match the script ship-list.
151
345
  const hooksDir = path.join(outDir, "hooks");
152
346
  if (fs.existsSync(hooksDir)) {