@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.
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -34
- package/plugins/lisa-cursor/hooks/hooks.json +20 -0
- package/plugins/lisa-cursor/rules/{reference/base-rules.md → base-rules-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/base-rules.md → base-rules.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/coding-philosophy.md → coding-philosophy-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/coding-philosophy.md → coding-philosophy.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/config-resolution.md → config-resolution-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/config-resolution.md → config-resolution.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/documentation-source-paths.md → documentation-source-paths-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/documentation-source-paths.md → documentation-source-paths.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/empirical-inquiry.md → empirical-inquiry-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/empirical-inquiry.md → empirical-inquiry.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/intent-routing.md → intent-routing-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/intent-routing.md → intent-routing.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/leaf-only-lifecycle.md → leaf-only-lifecycle-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/leaf-only-lifecycle.md → leaf-only-lifecycle.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/prd-lifecycle-rollup.md → prd-lifecycle-rollup-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/prd-lifecycle-rollup.md → prd-lifecycle-rollup.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/repo-scope-split.md → repo-scope-split-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/repo-scope-split.md → repo-scope-split.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/security-audit-handling.md → security-audit-handling-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/security-audit-handling.md → security-audit-handling.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/usage-accounting.md → usage-accounting-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/usage-accounting.md → usage-accounting.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/verification.md → verification-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/verification.md → verification.mdc} +6 -1
- package/plugins/lisa-cursor/rules/{reference/wiki-knowledge-source.md → wiki-knowledge-source-reference.mdc} +5 -0
- package/plugins/lisa-cursor/rules/{eager/wiki-knowledge-source.md → wiki-knowledge-source.mdc} +6 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/rules/{harper-fabric.md → harper-fabric.mdc} +5 -0
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +2 -15
- package/plugins/lisa-nestjs-cursor/hooks/hooks.json +11 -0
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +2 -19
- package/plugins/lisa-rails-cursor/hooks/hooks.json +15 -0
- package/plugins/lisa-rails-cursor/rules/{rails-conventions.md → rails-conventions.mdc} +5 -0
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +2 -34
- package/plugins/lisa-typescript-cursor/hooks/hooks.json +25 -0
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/scripts/generate-cursor-plugin-artifacts.mjs +214 -20
- package/scripts/lib/per-agent-hook-filter.mjs +138 -23
- /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.
|
|
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
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "2.124.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,28 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-rails",
|
|
3
|
-
"version": "2.124.
|
|
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,43 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-typescript",
|
|
3
|
-
"version": "2.124.
|
|
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
|
+
}
|
|
@@ -2,13 +2,34 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Generate the Cursor variant of a Lisa plugin from the built Claude artifact.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
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
|
-
//
|
|
109
|
-
// hooks
|
|
110
|
-
//
|
|
111
|
-
//
|
|
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
|
-
//
|
|
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
|
|
143
|
-
|
|
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)) {
|