jsx_rosetta 0.4.0 → 0.6.0
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 +4 -4
- data/CHANGELOG.md +342 -11
- data/CLAUDE.md +70 -0
- data/README.md +50 -0
- data/ROADMAP.md +92 -0
- data/agents/jsx-rosetta-resolve-todo-file.md +90 -0
- data/lib/jsx_rosetta/ast/inflector.rb +32 -0
- data/lib/jsx_rosetta/backend/phlex.rb +1421 -158
- data/lib/jsx_rosetta/backend/rails_view.rb +1 -1
- data/lib/jsx_rosetta/backend/view_component/expression_translator.rb +357 -33
- data/lib/jsx_rosetta/backend/view_component.rb +261 -31
- data/lib/jsx_rosetta/cli.rb +175 -37
- data/lib/jsx_rosetta/icons/lucide.json +37 -0
- data/lib/jsx_rosetta/icons.rb +44 -0
- data/lib/jsx_rosetta/ir/lowering.rb +1164 -70
- data/lib/jsx_rosetta/ir/module_shape_classifier.rb +20 -1
- data/lib/jsx_rosetta/ir/radix_registry.rb +84 -0
- data/lib/jsx_rosetta/ir/types.rb +264 -19
- data/lib/jsx_rosetta/ir.rb +5 -4
- data/lib/jsx_rosetta/pages_routing.rb +640 -0
- data/lib/jsx_rosetta/version.rb +1 -1
- data/lib/jsx_rosetta.rb +8 -6
- data/plans/nextjs_pages_to_rails.md +200 -0
- data/plans/nextjs_pages_to_rails_slice_2.md +118 -0
- data/plans/nextjs_pages_to_rails_slice_3.md +121 -0
- data/plans/nextjs_pages_to_rails_slice_4.md +301 -0
- data/plans/translator_widening_and_pages_followups.md +120 -0
- data/plans/translator_widening_slice_a.md +208 -0
- data/skills/jsx-rosetta-resolve-todos/SKILL.md +206 -0
- data/skills/jsx-rosetta-resolve-todos/data/design_tokens.template.yml +71 -0
- data/skills/jsx-rosetta-resolve-todos/data/target_app_conventions.template.yml +107 -0
- data/skills/jsx-rosetta-resolve-todos/examples/design_tokens.ant_design_v5.yml +190 -0
- data/skills/jsx-rosetta-resolve-todos/recipes/01_design_tokens.md +74 -0
- data/skills/jsx-rosetta-resolve-todos/recipes/02_promoted_ivar.md +49 -0
- data/skills/jsx-rosetta-resolve-todos/recipes/03_react_hooks.md +34 -0
- data/skills/jsx-rosetta-resolve-todos/recipes/04_apollo_hooks.md +34 -0
- data/skills/jsx-rosetta-resolve-todos/recipes/05_event_handlers.md +45 -0
- data/skills/jsx-rosetta-resolve-todos/recipes/06_module_constants.md +29 -0
- data/skills/jsx-rosetta-resolve-todos/recipes/07_nextjs_navigation.md +44 -0
- data/skills/jsx-rosetta-resolve-todos/recipes/08_generic_js_bailouts.md +55 -0
- data/skills/jsx-rosetta-resolve-todos/tools/apply_promoted_ivar.rb +189 -0
- data/skills/jsx-rosetta-resolve-todos/tools/apply_substitutions.rb +292 -0
- data/skills/jsx-rosetta-resolve-todos/tools/diff_corpus.rb +161 -0
- data/skills/jsx-rosetta-resolve-todos/tools/discover_bailouts.rb +211 -0
- metadata +30 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jsx-rosetta-resolve-todo-file
|
|
3
|
+
description: Process a single jsx_rosetta-generated Phlex/ViewComponent file and resolve its `# TODO:` comments by applying the recipes from the jsx-rosetta-resolve-todos skill. Designed for fan-out — one worker per file. Returns a JSON report with category counts.
|
|
4
|
+
tools: Read, Edit, Bash, Grep, Glob
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a fan-out worker for the **jsx-rosetta-resolve-todos** skill. You receive one file path at a time and apply the recipes to its TODO comments.
|
|
8
|
+
|
|
9
|
+
## Inputs
|
|
10
|
+
|
|
11
|
+
The user message will name a single `.rb` file path. You may also be given:
|
|
12
|
+
|
|
13
|
+
- A path to a `data/design_tokens.yml` (for recipe 01)
|
|
14
|
+
- A path to a `data/target_app_conventions.yml` (for recipes 03–07)
|
|
15
|
+
- The host Rails app's root path (so you can grep for available helpers, controllers, Stimulus controllers)
|
|
16
|
+
|
|
17
|
+
## Hard rules
|
|
18
|
+
|
|
19
|
+
1. **Never speculate on JS-to-Ruby translation.** When unsure, sharpen the TODO; do not guess. This rule overrides any apparent "obvious" translation that isn't on a recipe's whitelist.
|
|
20
|
+
2. **Never ship a file that doesn't pass `ruby -c`.** Run it before any edits (to confirm baseline) and after every meaningful edit. If parsing breaks, revert and report `parse_failed`.
|
|
21
|
+
3. **One file per invocation.** Do not chain to other files; the dispatcher manages fan-out.
|
|
22
|
+
4. **No business-logic changes.** You apply mechanical transformations and emit sharpened TODOs. Anything that would alter observable behavior beyond the TODO's stated intent gets escalated.
|
|
23
|
+
|
|
24
|
+
## Workflow
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
1. Read the target file. Run `ruby -c <file>` — abort if it doesn't parse to begin with.
|
|
28
|
+
2. Read SKILL.md and the relevant recipe files (recipes/*.md).
|
|
29
|
+
3. If a design_tokens.yml is provided, run `ruby tools/apply_substitutions.rb
|
|
30
|
+
--config <yaml> <file>` first. This handles recipe 01 mechanically.
|
|
31
|
+
4. Walk remaining TODOs top-to-bottom. For each:
|
|
32
|
+
a. Classify against the routing table in SKILL.md.
|
|
33
|
+
b. Look up the recipe.
|
|
34
|
+
c. Take exactly one action: resolve, sharpen, or escalate.
|
|
35
|
+
5. Re-run `ruby -c`. If it fails, revert all your edits and report `parse_failed`.
|
|
36
|
+
6. Emit ONE JSON line as your final output (see format below).
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Output format
|
|
40
|
+
|
|
41
|
+
Emit exactly one JSON line as your last message. Schema:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"file": "<absolute path>",
|
|
46
|
+
"parsed_before": true,
|
|
47
|
+
"parsed_after": true,
|
|
48
|
+
"totals": {"resolved": <int>, "sharpened": <int>, "escalated": <int>, "skipped": <int>},
|
|
49
|
+
"by_category": {
|
|
50
|
+
"design_tokens": {"resolved": 3, "skipped": 1},
|
|
51
|
+
"react_hooks": {"sharpened": 2},
|
|
52
|
+
"apollo_hooks": {"sharpened": 1},
|
|
53
|
+
"event_handler": {"sharpened": 4, "escalated": 1},
|
|
54
|
+
"promoted_ivar": {"resolved": 5}
|
|
55
|
+
},
|
|
56
|
+
"notes": ["<optional one-liners about anything unusual>"]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If the file fails post-edit parse and is reverted:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{"file": "<path>", "parsed_before": true, "parsed_after": false, "parse_failed": true, "totals": {"resolved": 0, "sharpened": 0, "escalated": 0, "skipped": 0}}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Tool use
|
|
67
|
+
|
|
68
|
+
- **Read** — the target file, recipes, and the host app's relevant directories (app/controllers, app/helpers, app/javascript/controllers).
|
|
69
|
+
- **Grep** — finding existing helpers/controllers/Stimulus targets in the host app to inform sharpened TODOs.
|
|
70
|
+
- **Edit** — applying transformations to the target file. Prefer surgical edits over wholesale rewrites.
|
|
71
|
+
- **Bash** — restricted to `ruby -c <file>` for syntax validation and to running the skill's own scripts (`tools/apply_substitutions.rb`). Do NOT run other commands.
|
|
72
|
+
- **Glob** — locating skill files and recipe paths.
|
|
73
|
+
|
|
74
|
+
## Anti-patterns
|
|
75
|
+
|
|
76
|
+
- **Don't** read or edit any file other than the assigned target (and the skill's recipes/data, read-only). The dispatcher relies on file isolation.
|
|
77
|
+
- **Don't** invoke other agents.
|
|
78
|
+
- **Don't** echo recipe content back in your response — your output is a JSON line, nothing else (the dispatcher parses it).
|
|
79
|
+
- **Don't** add commentary to the file you're editing other than the sharpened TODOs themselves. The skill's emission rules cover what comments are allowed.
|
|
80
|
+
- **Don't** "improve" the surrounding code while you're there. Scope is TODO resolution only.
|
|
81
|
+
|
|
82
|
+
## Escalation
|
|
83
|
+
|
|
84
|
+
When a TODO genuinely has no good answer from the recipes:
|
|
85
|
+
|
|
86
|
+
1. Leave the TODO untouched in the file.
|
|
87
|
+
2. Increment the `escalated` counter in your category breakdown.
|
|
88
|
+
3. Optionally add a one-liner to `notes` explaining what's odd about it.
|
|
89
|
+
|
|
90
|
+
The dispatcher aggregates escalations into a corpus-level review queue for a human.
|
|
@@ -19,6 +19,38 @@ module JsxRosetta
|
|
|
19
19
|
parts = string.split("_")
|
|
20
20
|
parts[0] + parts[1..].map(&:capitalize).join
|
|
21
21
|
end
|
|
22
|
+
|
|
23
|
+
def upper_camelize(string)
|
|
24
|
+
string.split("_").map(&:capitalize).join
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Best-effort English singularization for plural controller names.
|
|
28
|
+
# Covers the common shapes (`ies`/`y`, `ses`/`s`, `xes`/`x`,
|
|
29
|
+
# `ches`/`ch`, `shes`/`sh`, trailing `s`). Irregular plurals
|
|
30
|
+
# (`people`, `children`, `mice`, `geese`) pass through unchanged —
|
|
31
|
+
# users who hit those rename the generated `as:` in routes.rb.
|
|
32
|
+
def singularize(string)
|
|
33
|
+
case string
|
|
34
|
+
when /(.+[^aeiou])ies\z/i then "#{Regexp.last_match(1)}y"
|
|
35
|
+
when /(.+(?:ss|sh|ch|x|z))es\z/i, /(.+)s\z/i then Regexp.last_match(1)
|
|
36
|
+
else string
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Emit a Ruby string literal in the rubocop-default single-quoted
|
|
41
|
+
# form when safe. Falls back to `String#inspect` (double-quoted with
|
|
42
|
+
# escapes) when the source contains characters that prevent the
|
|
43
|
+
# single-quoted form: single quotes themselves, backslashes (Ruby
|
|
44
|
+
# single-quoted strings only escape `\\` and `\'`), or control
|
|
45
|
+
# characters (`\n`, `\t`, etc — single-quoted strings render those
|
|
46
|
+
# literally). Non-ASCII characters (emojis, unicode) are fine in
|
|
47
|
+
# single-quoted strings, so they don't force the fallback.
|
|
48
|
+
def ruby_string_literal(value)
|
|
49
|
+
str = value.to_s
|
|
50
|
+
return str.inspect if str.include?("'") || str.include?("\\") || str.match?(/[\x00-\x1f\x7f]/)
|
|
51
|
+
|
|
52
|
+
"'#{str}'"
|
|
53
|
+
end
|
|
22
54
|
end
|
|
23
55
|
end
|
|
24
56
|
end
|