senren-ui 0.1.4 → 0.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +8 -4
- data/lib/generators/senren/install/install_generator.rb +4 -7
- data/lib/generators/senren/install/templates/conventions.md.tt +12 -2
- data/lib/senren/rails/agent_rules_writer.rb +175 -0
- data/lib/senren/rails/doctor.rb +26 -13
- data/lib/senren/rails/host_paths.rb +12 -3
- data/lib/senren/rails/installer.rb +3 -2
- data/lib/senren/rails/llms_writer.rb +5 -132
- data/lib/senren/rails/version.rb +1 -1
- data/lib/senren/rails.rb +1 -0
- data/lib/tasks/senren.rake +16 -4
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e233d7736e61036693bb895d99e0fd11a37eb9e183979fa9ea4f1d17d1686354
|
|
4
|
+
data.tar.gz: dbe764908e51d5007e876c412e2cdffeac6d2b36ac10b8b16187f376ef1e24f0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a9bd5abdf7f39b9afd87d94e263b256c4e8fd226d5505bee5e1c7cd031e44fe917442b17479718fe39d23909ceee0f94e064073a7717b6309508471db18ce8b
|
|
7
|
+
data.tar.gz: a40ab2909ac0f30900fe8e2aace6eba439820ac83cd2ccd37c6037bfcf372225c02d1268a721c3b38a4c243872c35b833ead558621a65a8399b9e8ea632d96b8
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,35 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
7
7
|
v0.x is a pre-stable line: minor bumps may break things; patch bumps are
|
|
8
8
|
bug fixes only.
|
|
9
9
|
|
|
10
|
+
## [0.1.5] — 2026-05-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Multi-agent instruction sync system (`AgentRulesWriter`). A single
|
|
15
|
+
source-of-truth file (`.senren/agent-rules.md`) plus marker-managed
|
|
16
|
+
adapter files for Codex (`AGENTS.md`), Claude (`CLAUDE.md`),
|
|
17
|
+
Copilot (`.github/copilot-instructions.md`), and Cursor
|
|
18
|
+
(`.cursor/rules/senren.mdc`).
|
|
19
|
+
- New rake task `senren:agents:sync`.
|
|
20
|
+
- Plan 014 and Plan 015 documentation.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `LlmsWriter` is now a thin backward-compatible wrapper that delegates
|
|
25
|
+
to `AgentRulesWriter`. No more `public/llms*.txt` generation.
|
|
26
|
+
- `senren:llms:generate` kept as deprecated alias.
|
|
27
|
+
- `Doctor` checks now validate agent instruction files instead of
|
|
28
|
+
`public/llms*.txt`.
|
|
29
|
+
- Doctor `run!` refactored into `runtime_checks` + `installation_checks`.
|
|
30
|
+
- Install generator no longer creates `public/` directory.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Deprecated `senren:llms:generate` task now passes `registry:` kwarg
|
|
35
|
+
consistently with all other call sites.
|
|
36
|
+
- Release checklist items updated to reflect agent sync system.
|
|
37
|
+
- Test assertion style standardized on Minitest-native `refute`.
|
|
38
|
+
|
|
10
39
|
## [0.1.4] — 2026-05-02
|
|
11
40
|
|
|
12
41
|
### Fixed
|
data/README.md
CHANGED
|
@@ -14,8 +14,7 @@ skill system and a source-copy install model inspired by shadcn/ui.
|
|
|
14
14
|
- A registry of well-tested ViewComponents and Stimulus controllers.
|
|
15
15
|
- A centralized `.senren/skill.md` file so AI coding agents understand
|
|
16
16
|
every installed component, its dependencies, and its anti-patterns.
|
|
17
|
-
-
|
|
18
|
-
Senren without scraping your codebase.
|
|
17
|
+
- A multi-agent instruction sync for Codex, Claude, Copilot, and Cursor.
|
|
19
18
|
|
|
20
19
|
## What Senren is not
|
|
21
20
|
|
|
@@ -52,7 +51,7 @@ bin/rails generate senren:component picker --no-client # without Stimulus
|
|
|
52
51
|
bin/rails senren:add dialog --client # install interactive official component
|
|
53
52
|
bin/rails senren:add button # install static official component
|
|
54
53
|
bin/rails senren:skill:sync # rebuild .senren/skill.md
|
|
55
|
-
bin/rails senren:
|
|
54
|
+
bin/rails senren:agents:sync # rebuild .senren/agent-rules + adapters
|
|
56
55
|
bin/rails senren:doctor # check installation health
|
|
57
56
|
```
|
|
58
57
|
|
|
@@ -96,12 +95,17 @@ After install, your app contains:
|
|
|
96
95
|
- `.senren/registry.yml` — mirror of the gem-side registry.
|
|
97
96
|
- `.senren/installed_components.yml` — ledger of installed components.
|
|
98
97
|
- `.senren/conventions.md` — Senren conventions for humans and agents.
|
|
99
|
-
-
|
|
98
|
+
- `.senren/agent-rules.md` — source of truth for generated agent rules.
|
|
99
|
+
- `AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`,
|
|
100
|
+
`.cursor/rules/senren.mdc` — marker-managed adapter files for each agent.
|
|
100
101
|
|
|
101
102
|
The skill file uses `<!-- senren:skill:start -->` / `:end` markers; only
|
|
102
103
|
the region between them is rewritten by the generator, so any notes you
|
|
103
104
|
add outside the markers are preserved.
|
|
104
105
|
|
|
106
|
+
Agent adapter files are also marker-managed, so Senren updates only its own
|
|
107
|
+
generated block and preserves your existing instructions outside that block.
|
|
108
|
+
|
|
105
109
|
## Component list
|
|
106
110
|
|
|
107
111
|
See `registry/components.yml` for the canonical list. v0.1 ships:
|
|
@@ -27,10 +27,6 @@ module Senren
|
|
|
27
27
|
empty_directory 'app/assets/stylesheets'
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
def create_public_dir
|
|
31
|
-
empty_directory 'public'
|
|
32
|
-
end
|
|
33
|
-
|
|
34
30
|
def copy_base_files
|
|
35
31
|
template 'base_component.rb.tt', 'app/components/senren/base_component.rb'
|
|
36
32
|
template 'senren.css.tt', 'app/assets/stylesheets/senren.css'
|
|
@@ -47,14 +43,15 @@ module Senren
|
|
|
47
43
|
Senren::Rails::SkillWriter.new(paths: host_paths).sync!
|
|
48
44
|
end
|
|
49
45
|
|
|
50
|
-
def
|
|
51
|
-
say_status :senren, '
|
|
52
|
-
Senren::Rails::
|
|
46
|
+
def write_agent_files
|
|
47
|
+
say_status :senren, 'syncing Codex/Cursor/Claude/Copilot instruction files'
|
|
48
|
+
Senren::Rails::AgentRulesWriter.new(paths: host_paths).sync!
|
|
53
49
|
end
|
|
54
50
|
|
|
55
51
|
def print_next_steps
|
|
56
52
|
say "\nSenren installed."
|
|
57
53
|
say 'Next: bin/rails senren:add button card badge alert dialog'
|
|
54
|
+
say 'Then: bin/rails senren:agents:sync'
|
|
58
55
|
end
|
|
59
56
|
|
|
60
57
|
private
|
|
@@ -38,8 +38,11 @@ and obey it strictly.
|
|
|
38
38
|
| `.senren/registry.yml` | Generator (mirror of gem registry) |
|
|
39
39
|
| `.senren/installed_components.yml` | Generator (ledger) |
|
|
40
40
|
| `.senren/conventions.md` | This file - safe to edit |
|
|
41
|
-
|
|
|
42
|
-
| `
|
|
41
|
+
| `.senren/agent-rules.md` | Generator (do not edit) |
|
|
42
|
+
| `AGENTS.md` | Generated region only (between markers) |
|
|
43
|
+
| `CLAUDE.md` | Generated region only (between markers) |
|
|
44
|
+
| `.github/copilot-instructions.md` | Generated region only (between markers) |
|
|
45
|
+
| `.cursor/rules/senren.mdc` | Generated region only (between markers) |
|
|
43
46
|
|
|
44
47
|
## Adding a component
|
|
45
48
|
|
|
@@ -64,3 +67,10 @@ Senren never overwrites those files unless you pass `--force`.
|
|
|
64
67
|
|
|
65
68
|
You can write app-specific notes outside those markers. They are preserved
|
|
66
69
|
across `bin/rails senren:skill:sync`.
|
|
70
|
+
|
|
71
|
+
## Agent instruction maintenance
|
|
72
|
+
|
|
73
|
+
Senren writes a shared source file at `.senren/agent-rules.md` and syncs
|
|
74
|
+
adapter files for Codex/Cursor/Claude/Copilot. For adapter files, only the
|
|
75
|
+
generated block between markers is managed by Senren; your custom notes
|
|
76
|
+
outside markers are preserved.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
module Senren
|
|
6
|
+
module Rails
|
|
7
|
+
# Generates a single source-of-truth rules file plus adapter files for
|
|
8
|
+
# Copilot, Cursor, Claude, and Codex.
|
|
9
|
+
#
|
|
10
|
+
# Adapter files are marker-managed to avoid overwriting existing project
|
|
11
|
+
# instructions outside Senren's generated block.
|
|
12
|
+
class AgentRulesWriter
|
|
13
|
+
START_MARKER = '<!-- senren:agent:start -->'
|
|
14
|
+
END_MARKER = '<!-- senren:agent:end -->'
|
|
15
|
+
|
|
16
|
+
attr_reader :registry, :paths
|
|
17
|
+
|
|
18
|
+
def initialize(registry: Registry.load!, paths: HostPaths.new)
|
|
19
|
+
@registry = registry
|
|
20
|
+
@paths = paths
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def sync!
|
|
24
|
+
paths.ensure_agent_dirs!
|
|
25
|
+
files = []
|
|
26
|
+
files << write_full_file(paths.agent_rules_file, render_source_rules)
|
|
27
|
+
files << write_adapter_file(paths.codex_agents_md, render_codex_adapter)
|
|
28
|
+
files << write_adapter_file(paths.claude_md, render_claude_adapter)
|
|
29
|
+
files << write_adapter_file(paths.copilot_instructions, render_copilot_adapter)
|
|
30
|
+
files << write_adapter_file(paths.cursor_rule_file, render_cursor_adapter, prefix: cursor_frontmatter)
|
|
31
|
+
files
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def installed_names
|
|
37
|
+
path = paths.installed_components
|
|
38
|
+
return [] unless path.exist?
|
|
39
|
+
|
|
40
|
+
ledger = YAML.safe_load_file(path) || {}
|
|
41
|
+
Array(ledger['installed']).filter_map { |entry| registry.find(entry['name']) }.map(&:name).sort
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def render_source_rules
|
|
45
|
+
names = installed_names
|
|
46
|
+
<<~MD
|
|
47
|
+
# Senren Agent Rules
|
|
48
|
+
|
|
49
|
+
Generated by senren-ui. Do not edit by hand.
|
|
50
|
+
Run `bin/rails senren:agents:sync` to regenerate.
|
|
51
|
+
|
|
52
|
+
This file is the Senren source of truth for AI coding agents in this app.
|
|
53
|
+
|
|
54
|
+
## Hard Rules
|
|
55
|
+
|
|
56
|
+
- Use Senren components before writing custom HTML.
|
|
57
|
+
- Use ViewComponent for reusable UI.
|
|
58
|
+
- Use Turbo for server state.
|
|
59
|
+
- Use Stimulus only for local behavior.
|
|
60
|
+
- Do not introduce React, Vue, Alpine, or external state frameworks.
|
|
61
|
+
- Use semantic Tailwind tokens; do not hard-code color families.
|
|
62
|
+
|
|
63
|
+
## Important Files
|
|
64
|
+
|
|
65
|
+
- `.senren/skill.md` - centralized component guidance
|
|
66
|
+
- `.senren/conventions.md` - project conventions
|
|
67
|
+
- `.senren/registry.yml` - component registry mirror
|
|
68
|
+
- `.senren/installed_components.yml` - local install ledger
|
|
69
|
+
|
|
70
|
+
## Installed Components (#{names.size})
|
|
71
|
+
|
|
72
|
+
#{format_names(names)}
|
|
73
|
+
MD
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def render_codex_adapter
|
|
77
|
+
<<~MD
|
|
78
|
+
## Senren UI
|
|
79
|
+
|
|
80
|
+
Follow `.senren/agent-rules.md` as the source of truth.
|
|
81
|
+
|
|
82
|
+
- Prefer Senren components before custom HTML.
|
|
83
|
+
- Keep reusable UI in ViewComponent.
|
|
84
|
+
- Use Turbo for server state, Stimulus for local behavior.
|
|
85
|
+
- Do not add React, Vue, Alpine, or external state frameworks.
|
|
86
|
+
- Use semantic Tailwind tokens.
|
|
87
|
+
MD
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def render_claude_adapter
|
|
91
|
+
<<~MD
|
|
92
|
+
## Senren UI
|
|
93
|
+
|
|
94
|
+
@.senren/agent-rules.md
|
|
95
|
+
|
|
96
|
+
Apply Senren conventions and component-first rules from the imported file.
|
|
97
|
+
MD
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def render_copilot_adapter
|
|
101
|
+
<<~MD
|
|
102
|
+
## Senren UI
|
|
103
|
+
|
|
104
|
+
Use `.senren/agent-rules.md` as the source of truth for this repository.
|
|
105
|
+
|
|
106
|
+
- Prefer Senren components before custom HTML.
|
|
107
|
+
- Reusable UI must use ViewComponent.
|
|
108
|
+
- Turbo handles server state; Stimulus handles local behavior.
|
|
109
|
+
- Do not introduce React, Vue, Alpine, or external state frameworks.
|
|
110
|
+
- Use semantic Tailwind tokens.
|
|
111
|
+
MD
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def render_cursor_adapter
|
|
115
|
+
<<~MD
|
|
116
|
+
## Senren UI
|
|
117
|
+
|
|
118
|
+
Follow `.senren/agent-rules.md` as the source of truth.
|
|
119
|
+
|
|
120
|
+
- Prefer Senren components before custom HTML.
|
|
121
|
+
- Reusable UI uses ViewComponent.
|
|
122
|
+
- Turbo for server state, Stimulus for local behavior.
|
|
123
|
+
- No React/Vue/Alpine/external state frameworks.
|
|
124
|
+
- Use semantic Tailwind tokens.
|
|
125
|
+
MD
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def cursor_frontmatter
|
|
129
|
+
<<~MDC
|
|
130
|
+
---
|
|
131
|
+
description: Senren UI repository conventions
|
|
132
|
+
alwaysApply: true
|
|
133
|
+
---
|
|
134
|
+
MDC
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def format_names(names)
|
|
138
|
+
return '_No components installed yet._' if names.empty?
|
|
139
|
+
|
|
140
|
+
names.map { |name| "- #{name}" }.join("\n")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def write_full_file(path, content)
|
|
144
|
+
atomic_write(path, content)
|
|
145
|
+
path
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def write_adapter_file(path, generated, prefix: '')
|
|
149
|
+
existing = path.exist? ? path.read : prefix.to_s
|
|
150
|
+
updated = inject(existing, generated)
|
|
151
|
+
atomic_write(path, updated)
|
|
152
|
+
path
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def inject(existing, generated)
|
|
156
|
+
if existing.include?(START_MARKER) && existing.include?(END_MARKER)
|
|
157
|
+
before = existing.split(START_MARKER, 2).first
|
|
158
|
+
tail = existing.split(START_MARKER, 2).last
|
|
159
|
+
after = tail.split(END_MARKER, 2).last
|
|
160
|
+
"#{before}#{START_MARKER}\n\n#{generated.rstrip}\n\n#{END_MARKER}#{after}"
|
|
161
|
+
else
|
|
162
|
+
body = existing.rstrip
|
|
163
|
+
prefix = body.empty? ? '' : "#{body}\n\n"
|
|
164
|
+
"#{prefix}#{START_MARKER}\n\n#{generated.rstrip}\n\n#{END_MARKER}\n"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def atomic_write(path, content)
|
|
169
|
+
tmp = "#{path}.tmp"
|
|
170
|
+
File.write(tmp, content)
|
|
171
|
+
File.rename(tmp, path)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
data/lib/senren/rails/doctor.rb
CHANGED
|
@@ -17,19 +17,7 @@ module Senren
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def run!
|
|
20
|
-
results =
|
|
21
|
-
results << check('ViewComponent gem available') { defined?(::ViewComponent) }
|
|
22
|
-
results << check('TailwindCSS stylesheet present') { paths.stylesheet_path.exist? }
|
|
23
|
-
results << check('Stimulus directory present') { paths.stimulus_dir.directory? }
|
|
24
|
-
results << check('Turbo gem available') { defined?(::Turbo) || gem_loadable?('turbo-rails') }
|
|
25
|
-
results << check('.senren directory exists') { paths.senren_dir.directory? }
|
|
26
|
-
results << check('.senren/skill.md exists') { paths.skill_file.file? }
|
|
27
|
-
results << check('.senren/registry.yml exists') { paths.registry_mirror.file? }
|
|
28
|
-
results << check('.senren/installed_components.yml exists') { paths.installed_components.file? }
|
|
29
|
-
results << check('public/llms.txt exists') { paths.llms_short.file? }
|
|
30
|
-
results << check('public/llms-full.txt exists') { paths.llms_full.file? }
|
|
31
|
-
results << check('app/components/senren exists') { paths.components_dir.directory? }
|
|
32
|
-
results << check('app/javascript/controllers/senren exists') { paths.stimulus_dir.directory? }
|
|
20
|
+
results = runtime_checks + installation_checks
|
|
33
21
|
installed = installed_count
|
|
34
22
|
results << Result.new("#{installed} component(s) installed", installed >= 0, nil)
|
|
35
23
|
|
|
@@ -57,6 +45,31 @@ module Senren
|
|
|
57
45
|
false
|
|
58
46
|
end
|
|
59
47
|
|
|
48
|
+
def runtime_checks
|
|
49
|
+
[
|
|
50
|
+
check('ViewComponent gem available') { defined?(::ViewComponent) },
|
|
51
|
+
check('TailwindCSS stylesheet present') { paths.stylesheet_path.exist? },
|
|
52
|
+
check('Stimulus directory present') { paths.stimulus_dir.directory? },
|
|
53
|
+
check('Turbo gem available') { defined?(::Turbo) || gem_loadable?('turbo-rails') }
|
|
54
|
+
]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def installation_checks
|
|
58
|
+
[
|
|
59
|
+
check('.senren directory exists') { paths.senren_dir.directory? },
|
|
60
|
+
check('.senren/skill.md exists') { paths.skill_file.file? },
|
|
61
|
+
check('.senren/registry.yml exists') { paths.registry_mirror.file? },
|
|
62
|
+
check('.senren/installed_components.yml exists') { paths.installed_components.file? },
|
|
63
|
+
check('.senren/agent-rules.md exists') { paths.agent_rules_file.file? },
|
|
64
|
+
check('AGENTS.md exists') { paths.codex_agents_md.file? },
|
|
65
|
+
check('CLAUDE.md exists') { paths.claude_md.file? },
|
|
66
|
+
check('.github/copilot-instructions.md exists') { paths.copilot_instructions.file? },
|
|
67
|
+
check('.cursor/rules/senren.mdc exists') { paths.cursor_rule_file.file? },
|
|
68
|
+
check('app/components/senren exists') { paths.components_dir.directory? },
|
|
69
|
+
check('app/javascript/controllers/senren exists') { paths.stimulus_dir.directory? }
|
|
70
|
+
]
|
|
71
|
+
end
|
|
72
|
+
|
|
60
73
|
def installed_count
|
|
61
74
|
return 0 unless paths.installed_components.file?
|
|
62
75
|
|
|
@@ -16,6 +16,7 @@ module Senren
|
|
|
16
16
|
def registry_mirror = senren_dir.join('registry.yml')
|
|
17
17
|
def installed_components = senren_dir.join('installed_components.yml')
|
|
18
18
|
def conventions_file = senren_dir.join('conventions.md')
|
|
19
|
+
def agent_rules_file = senren_dir.join('agent-rules.md')
|
|
19
20
|
|
|
20
21
|
def components_dir = root.join('app', 'components', 'senren')
|
|
21
22
|
def base_component_path = components_dir.join('base_component.rb')
|
|
@@ -24,12 +25,20 @@ module Senren
|
|
|
24
25
|
|
|
25
26
|
def stimulus_dir = root.join('app', 'javascript', 'controllers', 'senren')
|
|
26
27
|
|
|
27
|
-
def
|
|
28
|
-
def
|
|
28
|
+
def github_dir = root.join('.github')
|
|
29
|
+
def copilot_instructions = github_dir.join('copilot-instructions.md')
|
|
30
|
+
def cursor_rules_dir = root.join('.cursor', 'rules')
|
|
31
|
+
def cursor_rule_file = cursor_rules_dir.join('senren.mdc')
|
|
32
|
+
def claude_md = root.join('CLAUDE.md')
|
|
33
|
+
def codex_agents_md = root.join('AGENTS.md')
|
|
29
34
|
|
|
30
35
|
def ensure_dirs!
|
|
31
36
|
[senren_dir, components_dir, stimulus_dir,
|
|
32
|
-
stylesheet_path.dirname,
|
|
37
|
+
stylesheet_path.dirname, github_dir, cursor_rules_dir].each(&:mkpath)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ensure_agent_dirs!
|
|
41
|
+
[senren_dir, github_dir, cursor_rules_dir].each(&:mkpath)
|
|
33
42
|
end
|
|
34
43
|
end
|
|
35
44
|
end
|
|
@@ -5,7 +5,7 @@ require 'fileutils'
|
|
|
5
5
|
module Senren
|
|
6
6
|
module Rails
|
|
7
7
|
# Idempotent installer that lays down the .senren directory, base
|
|
8
|
-
# component, stylesheet, and
|
|
8
|
+
# component, stylesheet, and agent instruction files. Reused by the install
|
|
9
9
|
# generator and by ad-hoc rake task entry points.
|
|
10
10
|
class Installer
|
|
11
11
|
attr_reader :paths, :stdout
|
|
@@ -20,7 +20,7 @@ module Senren
|
|
|
20
20
|
install_static_files(force: force)
|
|
21
21
|
mirror_registry
|
|
22
22
|
SkillWriter.new(paths: paths).sync!
|
|
23
|
-
|
|
23
|
+
AgentRulesWriter.new(paths: paths).sync!
|
|
24
24
|
print_next_steps
|
|
25
25
|
true
|
|
26
26
|
end
|
|
@@ -73,6 +73,7 @@ module Senren
|
|
|
73
73
|
|
|
74
74
|
bin/rails senren:add button card badge alert
|
|
75
75
|
bin/rails senren:add dialog dropdown_menu
|
|
76
|
+
bin/rails senren:agents:sync
|
|
76
77
|
bin/rails senren:doctor
|
|
77
78
|
|
|
78
79
|
Read .senren/skill.md and .senren/conventions.md to get oriented.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'yaml'
|
|
4
|
-
|
|
5
3
|
module Senren
|
|
6
4
|
module Rails
|
|
7
|
-
#
|
|
8
|
-
#
|
|
5
|
+
# Backward-compatible wrapper around AgentRulesWriter.
|
|
6
|
+
#
|
|
7
|
+
# Legacy llms generation now maps to agent rules synchronization and no longer
|
|
8
|
+
# writes public llms files.
|
|
9
9
|
class LlmsWriter
|
|
10
10
|
attr_reader :registry, :paths
|
|
11
11
|
|
|
@@ -15,134 +15,7 @@ module Senren
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def generate!
|
|
18
|
-
paths.
|
|
19
|
-
paths.llms_full.parent.mkpath
|
|
20
|
-
atomic_write(paths.llms_short, render_short)
|
|
21
|
-
atomic_write(paths.llms_full, render_full)
|
|
22
|
-
[paths.llms_short, paths.llms_full]
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
private
|
|
26
|
-
|
|
27
|
-
def installed
|
|
28
|
-
path = paths.installed_components
|
|
29
|
-
return [] unless path.exist?
|
|
30
|
-
|
|
31
|
-
ledger = YAML.safe_load_file(path) || {}
|
|
32
|
-
Array(ledger['installed']).filter_map { |e| registry.find(e['name']) }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def render_short
|
|
36
|
-
names = installed.map(&:name).sort
|
|
37
|
-
<<~TXT
|
|
38
|
-
# Senren UI
|
|
39
|
-
|
|
40
|
-
Generated by senren-ui. Do not edit by hand. Run
|
|
41
|
-
`bin/rails senren:llms:generate` to regenerate.
|
|
42
|
-
|
|
43
|
-
Senren UI is the local Rails UI system used by this application.
|
|
44
|
-
Use `.senren/skill.md` as the primary AI Agent guide.
|
|
45
|
-
|
|
46
|
-
## Hard Rules
|
|
47
|
-
|
|
48
|
-
- Use Senren components before writing custom HTML.
|
|
49
|
-
- Use ViewComponent for reusable UI.
|
|
50
|
-
- Use Turbo for server state.
|
|
51
|
-
- Use Stimulus only for local behavior.
|
|
52
|
-
- Do not introduce React, Vue, Alpine, or any external state framework.
|
|
53
|
-
- Do not hard-code colors; use semantic Tailwind tokens.
|
|
54
|
-
|
|
55
|
-
## Important Files
|
|
56
|
-
|
|
57
|
-
- `.senren/skill.md` - centralized AI agent guide
|
|
58
|
-
- `.senren/registry.yml` - mirror of installable components
|
|
59
|
-
- `.senren/installed_components.yml` - what is currently installed
|
|
60
|
-
- `.senren/conventions.md` - Senren conventions for humans and agents
|
|
61
|
-
|
|
62
|
-
## Installed Components (#{names.size})
|
|
63
|
-
|
|
64
|
-
#{names.map { |n| "- #{n}" }.join("\n")}
|
|
65
|
-
TXT
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def render_full
|
|
69
|
-
comps = installed.sort_by(&:name)
|
|
70
|
-
|
|
71
|
-
out = []
|
|
72
|
-
out << '# Senren UI - Full Snapshot'
|
|
73
|
-
out << ''
|
|
74
|
-
out << 'Generated by senren-ui. Do not edit by hand. Run'
|
|
75
|
-
out << '`bin/rails senren:llms:generate` to regenerate.'
|
|
76
|
-
out << ''
|
|
77
|
-
out << '## Hard Rules'
|
|
78
|
-
out << ''
|
|
79
|
-
out << '- Use Senren components before writing custom HTML.'
|
|
80
|
-
out << '- Server-rendered HTML first; ViewComponent for reusable UI.'
|
|
81
|
-
out << '- Hotwire (Turbo + Stimulus) is the only client runtime.'
|
|
82
|
-
out << '- TailwindCSS with semantic tokens (`bg-background`, `text-foreground`, ...).'
|
|
83
|
-
out << '- Do not introduce React, Vue, Alpine, or external state frameworks.'
|
|
84
|
-
out << '- Components copied into `app/components/senren/` are owned by this app; edit them directly.'
|
|
85
|
-
out << ''
|
|
86
|
-
out << '## Installed Component Inventory'
|
|
87
|
-
out << ''
|
|
88
|
-
|
|
89
|
-
registry.groups.each do |group|
|
|
90
|
-
group_comps = comps.select { |c| c.category == group['id'] }
|
|
91
|
-
next if group_comps.empty?
|
|
92
|
-
|
|
93
|
-
out << "### #{group['title']}"
|
|
94
|
-
out << ''
|
|
95
|
-
out << group['description'].to_s
|
|
96
|
-
out << ''
|
|
97
|
-
group_comps.each { |c| out << render_component(c) }
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
out << '## Recipes'
|
|
101
|
-
out << ''
|
|
102
|
-
registry.recipes.each do |id, recipe|
|
|
103
|
-
out << "### #{id}"
|
|
104
|
-
out << ''
|
|
105
|
-
out << recipe['description'].to_s
|
|
106
|
-
out << ''
|
|
107
|
-
out << 'Components:'
|
|
108
|
-
recipe['components'].each { |c| out << "- #{c}" }
|
|
109
|
-
out << ''
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
out.join("\n")
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def render_component(comp)
|
|
116
|
-
ruby_class = "Senren::#{comp.name.split('_').map { |w| w[0].upcase + w[1..] }.join}Component"
|
|
117
|
-
s = []
|
|
118
|
-
s << "#### #{comp.name}#{' (stub)' if comp.stub?}"
|
|
119
|
-
s << ''
|
|
120
|
-
s << "Category: #{comp.category}. " \
|
|
121
|
-
"Client: #{comp.client? ? "yes (#{comp.controller})" : 'no'}. " \
|
|
122
|
-
"Variants: #{comp.variants.empty? ? 'none' : comp.variants.join(', ')}."
|
|
123
|
-
s << ''
|
|
124
|
-
s << "Use for: #{comp.use_for.join('; ')}." unless comp.use_for.empty?
|
|
125
|
-
s << "Avoid: #{comp.avoid.join('; ')}." unless comp.avoid.empty?
|
|
126
|
-
s << ''
|
|
127
|
-
s << 'Rails usage:'
|
|
128
|
-
s << ''
|
|
129
|
-
s << '```erb'
|
|
130
|
-
s << if comp.variants.any?
|
|
131
|
-
"<%= render #{ruby_class}.new(variant: :#{comp.variants.first}) do %>"
|
|
132
|
-
else
|
|
133
|
-
"<%= render #{ruby_class}.new do %>"
|
|
134
|
-
end
|
|
135
|
-
s << ' ...'
|
|
136
|
-
s << '<% end %>'
|
|
137
|
-
s << '```'
|
|
138
|
-
s << ''
|
|
139
|
-
s.join("\n")
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def atomic_write(path, content)
|
|
143
|
-
tmp = "#{path}.tmp"
|
|
144
|
-
File.write(tmp, content)
|
|
145
|
-
File.rename(tmp, path)
|
|
18
|
+
AgentRulesWriter.new(registry: registry, paths: paths).sync!
|
|
146
19
|
end
|
|
147
20
|
end
|
|
148
21
|
end
|
data/lib/senren/rails/version.rb
CHANGED
data/lib/senren/rails.rb
CHANGED
|
@@ -9,6 +9,7 @@ module Senren
|
|
|
9
9
|
autoload :Registry, 'senren/rails/registry'
|
|
10
10
|
autoload :ComponentCopier, 'senren/rails/component_copier'
|
|
11
11
|
autoload :SkillWriter, 'senren/rails/skill_writer'
|
|
12
|
+
autoload :AgentRulesWriter, 'senren/rails/agent_rules_writer'
|
|
12
13
|
autoload :LlmsWriter, 'senren/rails/llms_writer'
|
|
13
14
|
autoload :Installer, 'senren/rails/installer'
|
|
14
15
|
autoload :Doctor, 'senren/rails/doctor'
|
data/lib/tasks/senren.rake
CHANGED
|
@@ -16,27 +16,39 @@ namespace :senren do
|
|
|
16
16
|
installed = copier.install(names, client_override: options[:client_override], force: options[:force])
|
|
17
17
|
|
|
18
18
|
Senren::Rails::SkillWriter.new(registry: registry, paths: paths).sync!
|
|
19
|
-
Senren::Rails::
|
|
19
|
+
Senren::Rails::AgentRulesWriter.new(registry: registry, paths: paths).sync!
|
|
20
20
|
|
|
21
21
|
puts "Installed: #{installed.join(', ')}"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
namespace :skill do
|
|
25
|
-
desc 'Rebuild .senren/skill.md
|
|
25
|
+
desc 'Rebuild .senren/skill.md and refresh agent instruction adapters.'
|
|
26
26
|
task sync: :environment do
|
|
27
27
|
paths = Senren::Rails::HostPaths.new
|
|
28
28
|
registry = Senren::Rails::Registry.load!
|
|
29
29
|
file = Senren::Rails::SkillWriter.new(registry: registry, paths: paths).sync!
|
|
30
30
|
puts "Wrote #{file}"
|
|
31
|
+
Senren::Rails::AgentRulesWriter.new(registry: registry, paths: paths).sync!
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
namespace :agents do
|
|
36
|
+
desc 'Regenerate .senren/agent-rules.md and adapter instruction files.'
|
|
37
|
+
task sync: :environment do
|
|
38
|
+
paths = Senren::Rails::HostPaths.new
|
|
39
|
+
registry = Senren::Rails::Registry.load!
|
|
40
|
+
files = Senren::Rails::AgentRulesWriter.new(registry: registry, paths: paths).sync!
|
|
41
|
+
files.each { |f| puts "Wrote #{f}" }
|
|
31
42
|
end
|
|
32
43
|
end
|
|
33
44
|
|
|
34
45
|
namespace :llms do
|
|
35
|
-
desc '
|
|
46
|
+
desc 'Deprecated alias for senren:agents:sync.'
|
|
36
47
|
task generate: :environment do
|
|
48
|
+
puts 'senren:llms:generate is deprecated. Running senren:agents:sync instead.'
|
|
37
49
|
paths = Senren::Rails::HostPaths.new
|
|
38
50
|
registry = Senren::Rails::Registry.load!
|
|
39
|
-
files = Senren::Rails::
|
|
51
|
+
files = Senren::Rails::AgentRulesWriter.new(registry: registry, paths: paths).sync!
|
|
40
52
|
files.each { |f| puts "Wrote #{f}" }
|
|
41
53
|
end
|
|
42
54
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: senren-ui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- vutt
|
|
@@ -66,6 +66,7 @@ files:
|
|
|
66
66
|
- lib/generators/senren/install/templates/installed_components.yml.tt
|
|
67
67
|
- lib/generators/senren/install/templates/senren.css.tt
|
|
68
68
|
- lib/senren/rails.rb
|
|
69
|
+
- lib/senren/rails/agent_rules_writer.rb
|
|
69
70
|
- lib/senren/rails/component_copier.rb
|
|
70
71
|
- lib/senren/rails/doctor.rb
|
|
71
72
|
- lib/senren/rails/engine.rb
|