kodelyth-ecc 1.5.4 → 1.5.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/.github/ISSUE_TEMPLATE/bug_report.md +47 -0
- package/.github/ISSUE_TEMPLATE/new_agent.md +52 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +53 -0
- package/CHANGELOG.md +27 -0
- package/CLAUDE.md +15 -1
- package/README.md +4 -4
- package/VERSION +1 -1
- package/agents/debug-detective.md +1 -0
- package/agents/dependency-doctor.md +1 -0
- package/agents/incident-commander.md +1 -0
- package/agents/load-tester.md +1 -0
- package/agents/migration-guide.md +1 -0
- package/agents/pair-programmer.md +1 -0
- package/agents/release-captain.md +1 -0
- package/commands/debug-blitz.md +70 -0
- package/commands/doctor.md +75 -0
- package/commands/onboard.md +72 -0
- package/commands/pre-release.md +66 -0
- package/commands/refactor-sprint.md +77 -0
- package/commands/security-audit.md +66 -0
- package/commands/update.md +92 -0
- package/install.ps1 +16 -11
- package/install.sh +21 -15
- package/package.json +2 -2
- package/rules/common/agents.md +141 -45
- package/rules/elixir/coding-style.md +46 -0
- package/rules/elixir/hooks.md +26 -0
- package/rules/elixir/patterns.md +67 -0
- package/rules/elixir/security.md +58 -0
- package/rules/elixir/testing.md +64 -0
- package/rules/ruby/coding-style.md +41 -0
- package/rules/ruby/hooks.md +23 -0
- package/rules/ruby/patterns.md +71 -0
- package/rules/ruby/security.md +56 -0
- package/rules/ruby/testing.md +65 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ex"
|
|
4
|
+
- "**/*.exs"
|
|
5
|
+
---
|
|
6
|
+
# Elixir Hooks
|
|
7
|
+
|
|
8
|
+
> This file extends [common/hooks.md](../common/hooks.md) with Elixir specific content.
|
|
9
|
+
|
|
10
|
+
## PostToolUse Hooks
|
|
11
|
+
|
|
12
|
+
Configure in `~/.claude/settings.json`:
|
|
13
|
+
|
|
14
|
+
- **mix format**: Auto-format `.ex` / `.exs` files after edit
|
|
15
|
+
```bash
|
|
16
|
+
mix format <file>
|
|
17
|
+
```
|
|
18
|
+
- **Credo**: Run on edited files for style warnings
|
|
19
|
+
```bash
|
|
20
|
+
mix credo <file>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Warnings
|
|
24
|
+
|
|
25
|
+
- Warn when `IO.inspect` is left in non-test `.ex` files (use `Logger` instead)
|
|
26
|
+
- Warn when `dbg()` (Elixir 1.14+) is left in production code paths
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ex"
|
|
4
|
+
- "**/*.exs"
|
|
5
|
+
---
|
|
6
|
+
# Elixir Patterns
|
|
7
|
+
|
|
8
|
+
> This file extends [common/patterns.md](../common/patterns.md) with Elixir specific content.
|
|
9
|
+
|
|
10
|
+
## with for Multi-Step Operations
|
|
11
|
+
|
|
12
|
+
```elixir
|
|
13
|
+
def create_user(params) do
|
|
14
|
+
with {:ok, validated} <- validate(params),
|
|
15
|
+
{:ok, user} <- Repo.insert(User.changeset(%User{}, validated)),
|
|
16
|
+
:ok <- send_welcome_email(user) do
|
|
17
|
+
{:ok, user}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## GenServer Pattern
|
|
23
|
+
|
|
24
|
+
```elixir
|
|
25
|
+
defmodule MyApp.Cache do
|
|
26
|
+
use GenServer
|
|
27
|
+
|
|
28
|
+
def start_link(opts), do: GenServer.start_link(__MODULE__, %{}, opts)
|
|
29
|
+
|
|
30
|
+
def get(pid, key), do: GenServer.call(pid, {:get, key})
|
|
31
|
+
def put(pid, key, value), do: GenServer.cast(pid, {:put, key, value})
|
|
32
|
+
|
|
33
|
+
@impl true
|
|
34
|
+
def init(state), do: {:ok, state}
|
|
35
|
+
|
|
36
|
+
@impl true
|
|
37
|
+
def handle_call({:get, key}, _from, state), do: {:reply, Map.get(state, key), state}
|
|
38
|
+
|
|
39
|
+
@impl true
|
|
40
|
+
def handle_cast({:put, key, value}, state), do: {:noreply, Map.put(state, key, value)}
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Context Modules (Phoenix)
|
|
45
|
+
|
|
46
|
+
```elixir
|
|
47
|
+
defmodule MyApp.Accounts do
|
|
48
|
+
alias MyApp.Accounts.User
|
|
49
|
+
alias MyApp.Repo
|
|
50
|
+
|
|
51
|
+
def get_user!(id), do: Repo.get!(User, id)
|
|
52
|
+
|
|
53
|
+
def create_user(attrs) do
|
|
54
|
+
%User{}
|
|
55
|
+
|> User.changeset(attrs)
|
|
56
|
+
|> Repo.insert()
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Tagged Tuples for Errors
|
|
62
|
+
|
|
63
|
+
Always return `{:ok, result}` or `{:error, reason}` — never bare values from functions that can fail.
|
|
64
|
+
|
|
65
|
+
## Reference
|
|
66
|
+
|
|
67
|
+
See skill: `phoenix-patterns` for Phoenix LiveView, contexts, and Ecto query patterns.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ex"
|
|
4
|
+
- "**/*.exs"
|
|
5
|
+
---
|
|
6
|
+
# Elixir Security
|
|
7
|
+
|
|
8
|
+
> This file extends [common/security.md](../common/security.md) with Elixir specific content.
|
|
9
|
+
|
|
10
|
+
## Secret Management
|
|
11
|
+
|
|
12
|
+
```elixir
|
|
13
|
+
# config/runtime.exs — read from environment at runtime, never compile-time
|
|
14
|
+
config :my_app, :stripe_key,
|
|
15
|
+
System.fetch_env!("STRIPE_SECRET_KEY") # raises if missing
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Never put secrets in `config/config.exs` or `config/dev.exs` committed to git.
|
|
19
|
+
|
|
20
|
+
## SQL Injection
|
|
21
|
+
|
|
22
|
+
Always use Ecto parameterized queries:
|
|
23
|
+
|
|
24
|
+
```elixir
|
|
25
|
+
# UNSAFE — never do this
|
|
26
|
+
Repo.query("SELECT * FROM users WHERE email = '#{email}'")
|
|
27
|
+
|
|
28
|
+
# SAFE
|
|
29
|
+
from(u in User, where: u.email == ^email) |> Repo.one()
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Atom Exhaustion
|
|
33
|
+
|
|
34
|
+
Never convert untrusted user input to atoms — the atom table is not garbage collected:
|
|
35
|
+
|
|
36
|
+
```elixir
|
|
37
|
+
# UNSAFE
|
|
38
|
+
String.to_atom(user_input)
|
|
39
|
+
|
|
40
|
+
# SAFE
|
|
41
|
+
String.to_existing_atom(user_input) # only if atom must already exist
|
|
42
|
+
# or keep it as a string
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Security Scanning
|
|
46
|
+
|
|
47
|
+
- **Sobelow** for Phoenix/Elixir static security analysis:
|
|
48
|
+
```bash
|
|
49
|
+
mix sobelow --config
|
|
50
|
+
```
|
|
51
|
+
- **mix audit** for dependency vulnerability scanning:
|
|
52
|
+
```bash
|
|
53
|
+
mix hex.audit
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Reference
|
|
57
|
+
|
|
58
|
+
See skill: `security-review` for OWASP top 10 and authentication patterns.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ex"
|
|
4
|
+
- "**/*.exs"
|
|
5
|
+
- "**/test/**"
|
|
6
|
+
---
|
|
7
|
+
# Elixir Testing
|
|
8
|
+
|
|
9
|
+
> This file extends [common/testing.md](../common/testing.md) with Elixir specific content.
|
|
10
|
+
|
|
11
|
+
## Framework
|
|
12
|
+
|
|
13
|
+
Use **ExUnit** (built-in). Use **Mox** for behaviour-based mocking.
|
|
14
|
+
|
|
15
|
+
## Structure
|
|
16
|
+
|
|
17
|
+
```elixir
|
|
18
|
+
defmodule MyApp.AccountsTest do
|
|
19
|
+
use MyApp.DataCase
|
|
20
|
+
|
|
21
|
+
alias MyApp.Accounts
|
|
22
|
+
|
|
23
|
+
describe "create_user/1" do
|
|
24
|
+
test "creates a user with valid attrs" do
|
|
25
|
+
attrs = %{name: "Alice", email: "alice@example.com"}
|
|
26
|
+
assert {:ok, user} = Accounts.create_user(attrs)
|
|
27
|
+
assert user.email == "alice@example.com"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
test "returns error with invalid attrs" do
|
|
31
|
+
assert {:error, changeset} = Accounts.create_user(%{})
|
|
32
|
+
assert "can't be blank" in errors_on(changeset).email
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Coverage
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
mix test --cover
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Use **excoveralls** for detailed coverage reports:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
mix coveralls
|
|
48
|
+
mix coveralls.html
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Async Tests
|
|
52
|
+
|
|
53
|
+
Mark tests as `async: true` when they don't share state:
|
|
54
|
+
|
|
55
|
+
```elixir
|
|
56
|
+
defmodule MyApp.PureTest do
|
|
57
|
+
use ExUnit.Case, async: true
|
|
58
|
+
...
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Reference
|
|
63
|
+
|
|
64
|
+
See skill: `elixir-testing` for ExUnit async patterns, Mox setup, and property-based testing with StreamData.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.rb"
|
|
4
|
+
- "**/*.rake"
|
|
5
|
+
- "**/Gemfile"
|
|
6
|
+
- "**/Rakefile"
|
|
7
|
+
---
|
|
8
|
+
# Ruby Coding Style
|
|
9
|
+
|
|
10
|
+
> This file extends [common/coding-style.md](../common/coding-style.md) with Ruby specific content.
|
|
11
|
+
|
|
12
|
+
## Standards
|
|
13
|
+
|
|
14
|
+
- Follow the **Ruby Style Guide** (rubocop default)
|
|
15
|
+
- Use **frozen_string_literal: true** at the top of every file
|
|
16
|
+
- Prefer `do...end` for multi-line blocks, `{ }` for single-line
|
|
17
|
+
|
|
18
|
+
## Immutability
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
# frozen_string_literal: true
|
|
22
|
+
|
|
23
|
+
User = Data.define(:name, :email) # Ruby 3.2+ immutable value object
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Formatting
|
|
27
|
+
|
|
28
|
+
- **RuboCop** for linting and style enforcement
|
|
29
|
+
- **StandardRB** as a zero-config RuboCop config alternative
|
|
30
|
+
- Line length: 120 characters max
|
|
31
|
+
|
|
32
|
+
## Naming
|
|
33
|
+
|
|
34
|
+
- `snake_case` for methods and variables
|
|
35
|
+
- `CamelCase` for classes and modules
|
|
36
|
+
- `SCREAMING_SNAKE_CASE` for constants
|
|
37
|
+
- Predicate methods end with `?`, destructive methods end with `!`
|
|
38
|
+
|
|
39
|
+
## Reference
|
|
40
|
+
|
|
41
|
+
See skill: `ruby-patterns` for comprehensive Ruby idioms, Rails patterns, and concurrency.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.rb"
|
|
4
|
+
- "**/*.rake"
|
|
5
|
+
---
|
|
6
|
+
# Ruby Hooks
|
|
7
|
+
|
|
8
|
+
> This file extends [common/hooks.md](../common/hooks.md) with Ruby specific content.
|
|
9
|
+
|
|
10
|
+
## PostToolUse Hooks
|
|
11
|
+
|
|
12
|
+
Configure in `~/.claude/settings.json`:
|
|
13
|
+
|
|
14
|
+
- **RuboCop**: Auto-lint `.rb` files after edit
|
|
15
|
+
```bash
|
|
16
|
+
rubocop --autocorrect <file>
|
|
17
|
+
```
|
|
18
|
+
- **Syntax check**: Run `ruby -c <file>` after editing
|
|
19
|
+
|
|
20
|
+
## Warnings
|
|
21
|
+
|
|
22
|
+
- Warn about `puts` / `p` statements in non-test `.rb` files (use `Rails.logger` or a logger instead)
|
|
23
|
+
- Warn when `binding.pry` or `byebug` is left in edited files
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.rb"
|
|
4
|
+
- "**/*.rake"
|
|
5
|
+
---
|
|
6
|
+
# Ruby Patterns
|
|
7
|
+
|
|
8
|
+
> This file extends [common/patterns.md](../common/patterns.md) with Ruby specific content.
|
|
9
|
+
|
|
10
|
+
## Service Objects
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# frozen_string_literal: true
|
|
14
|
+
|
|
15
|
+
class CreateUserService
|
|
16
|
+
def initialize(params)
|
|
17
|
+
@params = params
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def call
|
|
21
|
+
user = User.new(@params)
|
|
22
|
+
user.save!
|
|
23
|
+
user
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Usage
|
|
28
|
+
result = CreateUserService.new(params).call
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Value Objects
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
# frozen_string_literal: true
|
|
35
|
+
|
|
36
|
+
Address = Data.define(:street, :city, :country)
|
|
37
|
+
|
|
38
|
+
address = Address.new(street: "123 Main St", city: "London", country: "UK")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Query Objects
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
# frozen_string_literal: true
|
|
45
|
+
|
|
46
|
+
class ActiveUsersQuery
|
|
47
|
+
def initialize(relation = User.all)
|
|
48
|
+
@relation = relation
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def call
|
|
52
|
+
@relation.where(status: :active).order(created_at: :desc)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Modules for Composition
|
|
58
|
+
|
|
59
|
+
Prefer composition over inheritance for shared behavior:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
module Auditable
|
|
63
|
+
def self.included(base)
|
|
64
|
+
base.before_action :track_activity
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Reference
|
|
70
|
+
|
|
71
|
+
See skill: `rails-patterns` for Rails-specific patterns including concerns, callbacks, and ActiveRecord best practices.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.rb"
|
|
4
|
+
- "**/*.rake"
|
|
5
|
+
---
|
|
6
|
+
# Ruby Security
|
|
7
|
+
|
|
8
|
+
> This file extends [common/security.md](../common/security.md) with Ruby specific content.
|
|
9
|
+
|
|
10
|
+
## Secret Management
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# Never hardcode secrets
|
|
14
|
+
api_key = ENV.fetch("STRIPE_SECRET_KEY") # raises KeyError if missing, not nil
|
|
15
|
+
|
|
16
|
+
# Rails credentials (encrypted)
|
|
17
|
+
Rails.application.credentials.stripe[:secret_key]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## SQL Injection
|
|
21
|
+
|
|
22
|
+
Always use parameterized queries — never string interpolation:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
# UNSAFE
|
|
26
|
+
User.where("email = '#{params[:email]}'")
|
|
27
|
+
|
|
28
|
+
# SAFE
|
|
29
|
+
User.where(email: params[:email])
|
|
30
|
+
User.where("email = ?", params[:email])
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Mass Assignment
|
|
34
|
+
|
|
35
|
+
Use strong parameters in Rails controllers:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
def user_params
|
|
39
|
+
params.require(:user).permit(:name, :email)
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Security Scanning
|
|
44
|
+
|
|
45
|
+
- **Brakeman** for static security analysis of Rails apps:
|
|
46
|
+
```bash
|
|
47
|
+
brakeman -q
|
|
48
|
+
```
|
|
49
|
+
- **bundler-audit** for dependency CVE scanning:
|
|
50
|
+
```bash
|
|
51
|
+
bundle audit check --update
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Reference
|
|
55
|
+
|
|
56
|
+
See skill: `security-review` for OWASP top 10 and auth patterns.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.rb"
|
|
4
|
+
- "**/*_spec.rb"
|
|
5
|
+
- "**/spec/**"
|
|
6
|
+
---
|
|
7
|
+
# Ruby Testing
|
|
8
|
+
|
|
9
|
+
> This file extends [common/testing.md](../common/testing.md) with Ruby specific content.
|
|
10
|
+
|
|
11
|
+
## Framework
|
|
12
|
+
|
|
13
|
+
Use **RSpec** as the testing framework. Use **FactoryBot** for fixtures.
|
|
14
|
+
|
|
15
|
+
## Structure
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
# frozen_string_literal: true
|
|
19
|
+
|
|
20
|
+
RSpec.describe CreateUserService do
|
|
21
|
+
subject(:service) { described_class.new(params) }
|
|
22
|
+
|
|
23
|
+
let(:params) { { name: "Alice", email: "alice@example.com" } }
|
|
24
|
+
|
|
25
|
+
describe "#call" do
|
|
26
|
+
context "with valid params" do
|
|
27
|
+
it "creates a user" do
|
|
28
|
+
expect { service.call }.to change(User, :count).by(1)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "with invalid params" do
|
|
33
|
+
let(:params) { { name: "", email: "bad" } }
|
|
34
|
+
|
|
35
|
+
it "raises an error" do
|
|
36
|
+
expect { service.call }.to raise_error(ActiveRecord::RecordInvalid)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Coverage
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
COVERAGE=true bundle exec rspec
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use **SimpleCov** for coverage reporting. Target 90%+ for new code.
|
|
50
|
+
|
|
51
|
+
## Factories
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
FactoryBot.define do
|
|
55
|
+
factory :user do
|
|
56
|
+
sequence(:email) { |n| "user#{n}@example.com" }
|
|
57
|
+
name { Faker::Name.name }
|
|
58
|
+
status { :active }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Reference
|
|
64
|
+
|
|
65
|
+
See skill: `ruby-testing` for detailed RSpec patterns, shared examples, and Rails request specs.
|