caruso 0.7.4 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8b934cb6295f95d2246260f6eb77a4b31c999bb35e73a379457fa9c9d27d7d2
4
- data.tar.gz: 9da817fab3ff58b8a363c631ad945d206f3d4ed3f9fc65efd8f0225b3d731a6b
3
+ metadata.gz: c881250221622ad116a1da441ca090a80fa1830f3d51b675b4a0919853d82287
4
+ data.tar.gz: 4276216066d112d8bed3e99c5d2928bd6d28ba1b44c4d2847f95f0f9447b803f
5
5
  SHA512:
6
- metadata.gz: 4d135ab13fc7b995cfb1fa029900902d458f1ec84d8eb7d7a1e2a54daeecf757cbfceee0a8491b8e9aeb427e7f18c01800dd227652859ea01f40fd472a55ca67
7
- data.tar.gz: 94aeb96c6fb123a76d0056785ce12489fc5d4d2d176491e5da2d35c93f3d74e4c2d53509c7932958d687abd22510e95ef30ba3fffe2cfc141389434143166332
6
+ metadata.gz: ebf987777a3b98062f132330f17ba646840085cd63a123c04d944997002c42c1049cb325f0eb66dfee4ecb0dfb4127b279a96e6a298aa73b1bb61f56ce7caba5
7
+ data.tar.gz: fe109bde2907c60ef0c15f3eaa4e4ea7fc1a4f98d2f0c05f7ceaad78b7e2ab0ea7c632fa7851497df8630aeff7adb145f7bab093059af136519cfdb80ad62e5d
@@ -0,0 +1,76 @@
1
+ ---
2
+ name: cursor-spec-reviewer
3
+ description: "Use this agent when you need to verify that converted Cursor rule files (.mdc) produced by Caruso strictly conform to the official Cursor documentation and specifications. This includes reviewing frontmatter structure, metadata fields, file naming conventions, and content formatting. Launch this agent after any conversion operation to validate output correctness.\\n\\nExamples:\\n\\n- Example 1:\\n user: \"caruso plugin install document-skills@skills\"\\n assistant: \"I've installed the document-skills plugin and converted the files to Cursor format. Let me now verify the output is correct.\"\\n <commentary>\\n Since a conversion just happened, use the Task tool to launch the cursor-spec-reviewer agent to validate all generated .mdc files in .cursor/rules/caruso/ against the official Cursor specification.\\n </commentary>\\n assistant: \"Now let me use the cursor-spec-reviewer agent to validate the converted files against the Cursor specification.\"\\n\\n- Example 2:\\n user: \"Can you check if these .mdc files are valid Cursor rules?\"\\n assistant: \"I'll use the cursor-spec-reviewer agent to thoroughly validate these files against the official Cursor documentation.\"\\n <commentary>\\n The user is asking for validation of Cursor rule files. Use the Task tool to launch the cursor-spec-reviewer agent to perform a detailed specification review.\\n </commentary>\\n\\n- Example 3:\\n user: \"I just ran caruso plugin update and want to make sure everything still looks right\"\\n assistant: \"Let me launch the cursor-spec-reviewer agent to verify the updated files conform to Cursor's specifications.\"\\n <commentary>\\n After a plugin update, converted files may have changed. Use the Task tool to launch the cursor-spec-reviewer agent to re-validate all output files.\\n </commentary>"
4
+ model: opus
5
+ memory: project
6
+ ---
7
+
8
+ You are a Cursor IDE specification reviewer. Your job is to review ALL output produced by Caruso's conversion pipeline and verify it conforms to the official Cursor documentation.
9
+
10
+ ## Step 1: Fetch the Official Cursor Documentation
11
+
12
+ **MANDATORY.** Before reviewing ANY file, use WebFetch to read the current official Cursor docs:
13
+
14
+ 1. `https://cursor.com/docs/agent/hooks` — hooks specification
15
+ 2. `https://cursor.com/docs/context/rules` — rules specification
16
+ 3. `https://cursor.com/docs/context/commands` — commands specification
17
+
18
+ These are the ONLY authoritative sources. Do not rely on your training data, assumptions, or third-party sources. If a fetch fails, note the gap in your report.
19
+
20
+ Read each page carefully and extract the exact specification: required fields, valid values, supported events, JSON schemas, file formats, and any constraints or defaults.
21
+
22
+ ## Step 2: Inventory Caruso Output
23
+
24
+ Find all files Caruso generated. Check these locations:
25
+ - `.cursor/rules/caruso/` — converted rule files (`.mdc`)
26
+ - `.cursor/hooks.json` — merged hooks configuration
27
+ - `.cursor/hooks/caruso/` — hook scripts and wrappers
28
+ - `.cursor/commands/caruso/` — converted command files
29
+ - `.cursor/scripts/caruso/` — copied skill scripts
30
+
31
+ ## Step 3: Validate Each File Against the Fetched Specs
32
+
33
+ For every file found, validate it against the specification you extracted in Step 1. Check:
34
+ - File format and structure match the spec exactly
35
+ - All required fields are present with correct types
36
+ - No invalid or unknown fields
37
+ - Field values are within the allowed set (e.g., valid event names, correct boolean types)
38
+ - Referenced file paths (scripts, commands) actually exist on disk
39
+ - No leftover `${CLAUDE_PLUGIN_ROOT}` placeholders (these should have been rewritten)
40
+ - Scripts are executable and non-empty
41
+
42
+ Use the spec as your checklist — if the docs say a field is required, verify it exists. If the docs list valid values, verify the value is in that list. Do not invent requirements beyond what the docs state.
43
+
44
+ ## Step 4: Report
45
+
46
+ For each file, report:
47
+ - **Status**: PASS | WARNING | FAIL
48
+ - **Issues**: what's wrong and why (cite the spec)
49
+
50
+ End with a summary: total files, pass/warn/fail counts, overall verdict.
51
+
52
+ ## Rules
53
+
54
+ 1. **The fetched docs are your only authority.** If you can't confirm something from the docs, say so — don't guess.
55
+ 2. **Be precise about types.** String `"false"` vs boolean `false` matters. A wrong type is FAIL.
56
+ 3. **Spec violations are FAIL. Style concerns are WARNING.** Keep them separate.
57
+ 4. **Record learnings in your agent memory** for future reviews.
58
+
59
+ # Persistent Agent Memory
60
+
61
+ You have a persistent Persistent Agent Memory directory at `/Users/philipp/.superset/worktrees/caruso/ralph/.claude/agent-memory/cursor-spec-reviewer/`. Its contents persist across conversations.
62
+
63
+ As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes — and if nothing is written yet, record what you learned.
64
+
65
+ Guidelines:
66
+ - `MEMORY.md` is always loaded into your system prompt — lines after 200 will be truncated, so keep it concise
67
+ - Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md
68
+ - Record insights about problem constraints, strategies that worked or failed, and lessons learned
69
+ - Update or remove memories that turn out to be wrong or outdated
70
+ - Organize memory semantically by topic, not chronologically
71
+ - Use the Write and Edit tools to update your memory files
72
+ - Since this memory is project-scope and shared with your team via version control, tailor your memories to this project
73
+
74
+ ## MEMORY.md
75
+
76
+ Your MEMORY.md is currently empty. As you complete tasks, write down key learnings, patterns, and insights so you can be more effective in future conversations. Anything saved in MEMORY.md will be included in your system prompt next time.
@@ -41,7 +41,7 @@ module Caruso
41
41
  # Just return content as-is, Claude Code commands are already compatible
42
42
 
43
43
  # Add note about bash execution if it contains ! prefix
44
- if content.include?("!`")
44
+ if content.include?("`!")
45
45
  add_bash_execution_note(content)
46
46
  else
47
47
  content
@@ -28,6 +28,12 @@ module Caruso
28
28
  PermissionRequest
29
29
  ].freeze
30
30
 
31
+ # Cursor stop events need output format translation and loop_limit.
32
+ STOP_EVENTS = %w[stop subagentStop].freeze
33
+
34
+ WRAPPER_PATH = File.join(".cursor", "hooks", "caruso", "_cc_stop_wrapper.sh").freeze
35
+ WRAPPER_SOURCE = File.expand_path("../scripts/cc_stop_wrapper.sh", __dir__).freeze
36
+
31
37
  # Contains translated hook commands keyed by event (for clean uninstall tracking).
32
38
  attr_reader :translated_hooks
33
39
 
@@ -46,6 +52,9 @@ module Caruso
46
52
  # Copy any referenced scripts
47
53
  copied_scripts = copy_hook_scripts(cursor_hooks, hooks_file)
48
54
 
55
+ # Wrap stop hook commands for Cursor compatibility (CC→Cursor output translation)
56
+ wrapper_scripts = wrap_stop_hooks(cursor_hooks)
57
+
49
58
  # Merge into existing .cursor/hooks.json
50
59
  merge_hooks(cursor_hooks)
51
60
 
@@ -55,6 +64,7 @@ module Caruso
55
64
  # Return list of created/modified files for tracking
56
65
  created = [".cursor/hooks.json"]
57
66
  created += copied_scripts
67
+ created += wrapper_scripts
58
68
  created
59
69
  end
60
70
 
@@ -105,19 +115,28 @@ module Caruso
105
115
  next
106
116
  end
107
117
 
108
- command = hook["command"]
109
- next unless command
118
+ cursor_entry = build_cursor_hook(event_name, matcher, hook)
119
+ next unless cursor_entry
110
120
 
111
- cursor_event = resolve_cursor_event(event_name, matcher)
112
- cursor_hook = { "command" => command }
113
- cursor_hook["timeout"] = hook["timeout"] if hook["timeout"]
114
- (hooks[cursor_event] ||= []) << cursor_hook
121
+ event, entry = cursor_entry
122
+ (hooks[event] ||= []) << entry
115
123
  end
116
124
  end
117
125
 
118
126
  { hooks: hooks, skipped_prompts: skipped }
119
127
  end
120
128
 
129
+ def build_cursor_hook(event_name, matcher, hook)
130
+ command = hook["command"]
131
+ return unless command
132
+
133
+ cursor_event = resolve_cursor_event(event_name, matcher)
134
+ cursor_hook = { "command" => command }
135
+ cursor_hook["timeout"] = hook["timeout"] if hook["timeout"]
136
+ cursor_hook["loop_limit"] = nil if STOP_EVENTS.include?(cursor_event)
137
+ [cursor_event, cursor_hook]
138
+ end
139
+
121
140
  def warn_skipped(skipped_events, skipped_prompts)
122
141
  if skipped_events.any?
123
142
  unique_skipped = skipped_events.uniq
@@ -184,6 +203,31 @@ module Caruso
184
203
  target_path
185
204
  end
186
205
 
206
+ def wrap_stop_hooks(cursor_hooks)
207
+ has_stop = STOP_EVENTS.any? { |event| cursor_hooks.key?(event) }
208
+ return [] unless has_stop
209
+
210
+ wrapper_path = install_wrapper_script
211
+
212
+ STOP_EVENTS.each do |event|
213
+ next unless cursor_hooks[event]
214
+
215
+ cursor_hooks[event].each do |hook|
216
+ hook["command"] = "#{WRAPPER_PATH} #{hook['command']}"
217
+ end
218
+ end
219
+
220
+ [wrapper_path]
221
+ end
222
+
223
+ def install_wrapper_script
224
+ FileUtils.mkdir_p(File.dirname(WRAPPER_PATH))
225
+ FileUtils.cp(WRAPPER_SOURCE, WRAPPER_PATH)
226
+ File.chmod(0o755, WRAPPER_PATH)
227
+ puts "Installed stop hook wrapper: #{WRAPPER_PATH}"
228
+ WRAPPER_PATH
229
+ end
230
+
187
231
  def merge_hooks(new_hooks)
188
232
  hooks_path = File.join(".cursor", "hooks.json")
189
233
  existing = read_existing_hooks(hooks_path)
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ # Translates Claude Code stop hook output to Cursor format.
3
+ #
4
+ # CC stop hooks communicate via:
5
+ # Exit 2 + stderr reason -> block (continue conversation)
6
+ # Exit 0 + {"decision":"block","reason":"..."} -> block
7
+ # Exit 0 + anything else -> allow stop
8
+ #
9
+ # Cursor stop hooks expect:
10
+ # Exit 0 + {"followup_message":"..."} -> continue with message
11
+ # Exit 0 + no output -> allow stop
12
+ #
13
+ # Usage: cc_stop_wrapper.sh <original-script> [args...]
14
+
15
+ set -uo pipefail
16
+
17
+ SCRIPT="$1"
18
+ shift
19
+
20
+ STDERR_TMP=$(mktemp) || exit 1
21
+ trap 'rm -f "$STDERR_TMP"' EXIT
22
+
23
+ OUTPUT=$("$SCRIPT" "$@" 2>"$STDERR_TMP")
24
+ EXIT_CODE=$?
25
+
26
+ # CC exit 2 = block with stderr reason
27
+ if [ $EXIT_CODE -eq 2 ]; then
28
+ REASON=$(cat "$STDERR_TMP")
29
+ if [ -n "$REASON" ] && command -v jq >/dev/null 2>&1; then
30
+ jq -n --arg msg "$REASON" '{"followup_message": $msg}'
31
+ fi
32
+ exit 0
33
+ fi
34
+
35
+ # CC exit 0 + JSON {"decision":"block"} -> translate to followup_message
36
+ if [ $EXIT_CODE -eq 0 ] && [ -n "$OUTPUT" ] && command -v jq >/dev/null 2>&1; then
37
+ DECISION=$(echo "$OUTPUT" | jq -r '.decision // empty' 2>/dev/null)
38
+ if [ "$DECISION" = "block" ]; then
39
+ REASON=$(echo "$OUTPUT" | jq -r '.reason // empty' 2>/dev/null)
40
+ [ -n "$REASON" ] && jq -n --arg msg "$REASON" '{"followup_message": $msg}'
41
+ exit 0
42
+ fi
43
+ fi
44
+
45
+ # Pass through anything else unchanged
46
+ [ -n "$OUTPUT" ] && echo "$OUTPUT"
47
+ exit $EXIT_CODE
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "open3"
5
+
6
+ module Caruso
7
+ # Translates Claude Code stop hook output to Cursor format.
8
+ #
9
+ # Claude Code stop hooks communicate via:
10
+ # - Exit 2 + stderr message → block (continue the conversation)
11
+ # - Exit 0 + {"decision":"block","reason":"..."} → block
12
+ # - Exit 0 + no decision → allow stop
13
+ #
14
+ # Cursor stop hooks expect:
15
+ # - Exit 0 + {"followup_message":"..."} → continue with message
16
+ # - Exit 0 + no output → allow stop
17
+ class StopHookTranslator
18
+ def self.translate(stdout:, stderr:, exit_code:)
19
+ new(stdout: stdout, stderr: stderr, exit_code: exit_code).translate
20
+ end
21
+
22
+ def initialize(stdout:, stderr:, exit_code:)
23
+ @stdout = stdout.to_s.strip
24
+ @stderr = stderr.to_s.strip
25
+ @exit_code = exit_code
26
+ end
27
+
28
+ def translate
29
+ return translate_exit2 if @exit_code == 2
30
+ return translate_json_decision if @exit_code.zero? && block_decision?
31
+
32
+ # Pass through: non-blocking exit 0 or any other exit code
33
+ { stdout: @stdout, exit_code: @exit_code }
34
+ end
35
+
36
+ private
37
+
38
+ def translate_exit2
39
+ if @stderr.empty?
40
+ { stdout: "", exit_code: 0 }
41
+ else
42
+ { stdout: JSON.generate({ "followup_message" => @stderr }), exit_code: 0 }
43
+ end
44
+ end
45
+
46
+ def translate_json_decision
47
+ reason = parsed_output&.fetch("reason", nil).to_s
48
+ if reason.empty?
49
+ { stdout: "", exit_code: 0 }
50
+ else
51
+ { stdout: JSON.generate({ "followup_message" => reason }), exit_code: 0 }
52
+ end
53
+ end
54
+
55
+ def block_decision?
56
+ parsed_output&.fetch("decision", nil) == "block"
57
+ end
58
+
59
+ def parsed_output
60
+ return @parsed_output if defined?(@parsed_output)
61
+
62
+ @parsed_output = @stdout.empty? ? nil : JSON.parse(@stdout)
63
+ rescue JSON::ParserError
64
+ @parsed_output = nil
65
+ end
66
+ end
67
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Caruso
4
- VERSION = "0.7.4"
4
+ VERSION = "0.7.6"
5
5
  end
@@ -0,0 +1,13 @@
1
+ # Claude Code Reference
2
+
3
+ Official documentation: https://code.claude.com/docs
4
+
5
+ - Hooks: https://code.claude.com/docs/en/hooks
6
+ - Hooks guide: https://code.claude.com/docs/en/hooks-guide
7
+ - Plugins: https://code.claude.com/docs/en/plugins
8
+ - Plugins reference: https://code.claude.com/docs/en/plugins-reference
9
+ - Create plugins: https://code.claude.com/docs/en/create-plugins
10
+ - Discover plugins: https://code.claude.com/docs/en/discover-plugins
11
+ - Plugin marketplaces: https://code.claude.com/docs/en/plugin-marketplaces
12
+ - Skills: https://code.claude.com/docs/en/skills
13
+ - Sub-agents: https://code.claude.com/docs/en/sub-agents
@@ -0,0 +1,8 @@
1
+ # Cursor Reference
2
+
3
+ Official documentation: https://cursor.com/docs
4
+
5
+ - Hooks: https://cursor.com/docs/agent/hooks
6
+ - Modes: https://cursor.com/docs/agent/modes
7
+ - Rules: https://cursor.com/docs/context/rules
8
+ - Commands: https://cursor.com/docs/context/commands
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caruso
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.4
4
+ version: 0.7.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philipp Comans
@@ -130,6 +130,7 @@ executables:
130
130
  extensions: []
131
131
  extra_rdoc_files: []
132
132
  files:
133
+ - ".claude/agents/cursor-spec-reviewer.md"
133
134
  - ".claude/hooks/check-gemfile-lock.sh"
134
135
  - ".claude/settings.json"
135
136
  - ".rspec"
@@ -162,19 +163,12 @@ files:
162
163
  - lib/caruso/remover.rb
163
164
  - lib/caruso/safe_dir.rb
164
165
  - lib/caruso/safe_file.rb
166
+ - lib/caruso/scripts/cc_stop_wrapper.sh
167
+ - lib/caruso/stop_hook_translator.rb
165
168
  - lib/caruso/version.rb
166
169
  - package-lock.json
167
- - reference/claude_code_create_marketplaces.md
168
- - reference/claude_code_hooks.md
169
- - reference/claude_code_marketplaces.md
170
- - reference/claude_code_plugins.md
171
- - reference/claude_code_slash_commands.md
172
- - reference/cursor_commands.md
173
- - reference/cursor_hooks.md
174
- - reference/cursor_modes.md
175
- - reference/cursor_rules.md
176
- - reference/plugins.md
177
- - reference/steering_docs.md
170
+ - reference/claude_code.md
171
+ - reference/cursor.md
178
172
  - tasks.md
179
173
  homepage: https://github.com/pcomans/caruso
180
174
  licenses: