Skip to content

refactor(cli): extract shared Options concern for CLI commands#44

Merged
abechan1 merged 4 commits into
mainfrom
refactor/option
Jul 4, 2026
Merged

refactor(cli): extract shared Options concern for CLI commands#44
abechan1 merged 4 commits into
mainfrom
refactor/option

Conversation

@abechan1

@abechan1 abechan1 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

App/Slides/Lint/Serve each hand-rolled constructor dependency injection,
--help text, and argv flag parsing with a lot of repeated boilerplate.
Introduce Slidict::Cli::Options, a concern that provides:

  • options name: -> { default }, ... to generate attr_readers and
    initialize assignment from one declaration instead of spelling out
    each dependency in both the method signature and the body.
  • flag/parse_flags!/flags_help so a -x/--xxx switch is declared
    once (with its arg placeholder and description) and drives both argv
    parsing and the --help listing, instead of a hand-written case/when
    parser and a separately hand-formatted help heredoc that could drift
    apart.
  • usage/print_help for the surrounding Usage:/Commands: text, with
    flags_help interpolated in for the option list.
  • Named SUCCESS/FAILURE exit code constants instead of bare 0/1 literals
    scattered through every command method.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

Summary by CodeRabbit

  • 新機能

    • slidict init により LLM/機能設定用の .env を作成し、.gitignore に追記します。
    • --language で生成するタイトル/箇条書きを指定言語化し、対話質問も翻訳します。
  • 改善

    • CLI の引数・フラグ表示が整理され、ヘルプ内容が自動生成されるようになりました。
    • 成功・失敗の扱いとエラーメッセージの一貫性を向上しました。
    • 起動時に .env を読み込み、未設定は補完(CLIフラグ優先)するようになりました。

App/Slides/Lint/Serve each hand-rolled constructor dependency injection,
--help text, and argv flag parsing with a lot of repeated boilerplate.
Introduce Slidict::Cli::Options, a concern that provides:

- `options name: -> { default }, ...` to generate attr_readers and
  initialize assignment from one declaration instead of spelling out
  each dependency in both the method signature and the body.
- `flag`/`parse_flags!`/`flags_help` so a `-x`/`--xxx` switch is declared
  once (with its arg placeholder and description) and drives both argv
  parsing and the --help listing, instead of a hand-written case/when
  parser and a separately hand-formatted help heredoc that could drift
  apart.
- `usage`/`print_help` for the surrounding Usage:/Commands: text, with
  `flags_help` interpolated in for the option list.
- Named SUCCESS/FAILURE exit code constants instead of bare 0/1 literals
  scattered through every command method.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

CLI の共通オプション DSL、.env 読み込み、init コマンド、--language 対応の LLM 生成が追加されました。App/Lint/Serve/Slides と関連テスト、README、CHANGELOG、起動スクリプトが更新されています。

Changes

CLI 共通化と新機能追加

Layer / File(s) Summary
Options DSL の実装
lib/slidict/cli/options.rb, lib/slidict.rb
CLI 向けの共通 DSL、フラグ解析、ヘルプ生成、終了コード定数が追加され、起動時の require 順に slidict/cli/options が挿入された。
App の init と言語付き生成
lib/slidict/cli/app.rb, spec/slidict/cli/app_spec.rb
init コマンド、.env 作成と .gitignore 更新、環境変数既定値、--language 翻訳質問、LLM 生成呼び出しが追加され、対応テストが増えた。
Lint / Serve / Slides の DSL 適用
lib/slidict/cli/lint.rb, lib/slidict/cli/serve.rb, lib/slidict/cli/slides.rb
Lint、Serve、Slides が共通 DSL を使う形に置き換わり、フラグ解析、ヘルプ生成、戻り値が SUCCESS / FAILURE に統一された。
Env 読み込みと関連文書
bin/slidict, lib/slidict/env.rb, .gitignore, README.md, CHANGELOG.md, lib/slidict/version.rb
.env 読み込みモジュールと起動時ロード、.gitignore、README、CHANGELOG、バージョンが更新された。
LLM クライアントの言語対応
lib/slidict/llm/client.rb, spec/slidict/llm/client_spec.rb
スライド生成プロンプトと翻訳処理が言語指定を受け取り、翻訳文の引用符除去が追加され、対応テストが増えた。
Env 読み込みのテスト
spec/slidict/env_spec.rb, spec/spec_helper.rb
Slidict::Env.load! の振る舞いを検証するテストと、ENV を一時変更するヘルパーが追加された。

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • slidict/slidict-cli#29: lib/slidict/cli/app.rb を含む CLI の実行・引数処理に関わる変更が共通しているため。
  • slidict/slidict-cli#28: serve 系の CLI 実装と戻り値の変更が関連しているため。

Suggested labels: codex

Poem

ぴょんと跳ねたら、.env が来た
フラグもヘルプも、すっきり整列
ことばは翻訳、耳はぴん
SUCCESS ぴかり、草むらきらり
うさぎは今日も、CLI を祝う 🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed CLIコマンド共通のOptions concern抽出という変更内容を的確に要約しており、主要な差分と一致しています。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/option

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
lib/slidict/cli/options.rb (1)

82-85: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

フラグのキー導出は long switch を優先してください。

Line 83 は switches.last 依存なので、flag("--output", "-o", ...) のように宣言順が変わると :o に保存されます。Flag#long と同じ考え方で long switch を優先すると、DSL の順序依存を避けられます。

修正案
         def flag(*switches, desc:, arg: nil, coerce: nil, group: :default)
-          key = switches.last.sub(/\A-+/, "").tr("-", "_").to_sym
+          canonical = switches.find { |switch| switch.start_with?("--") } || switches.last
+          key = canonical.sub(/\A-+/, "").tr("-", "_").to_sym
           flag_groups[group] << Flag.new(switches: switches, key: key, arg: arg, coerce: coerce, desc: desc)
         end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/slidict/cli/options.rb` around lines 82 - 85, The flag key derivation in
`Options#flag` currently depends on `switches.last`, so the saved key changes
with declaration order and can become the short switch name. Update
`Options#flag` to derive `key` by preferring the long switch first, consistent
with `Flag#long`, and only fall back to another switch if no long one exists.
This will keep `flag("--output", "-o", ...)` and similar DSL calls stable
regardless of argument order.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/slidict/cli/options.rb`:
- Around line 64-74: The options DSL in options and its generated initialize
currently accepts arbitrary **overrides and silently ignores keys not present in
defaults, which can hide typos in DI keyword names. Add explicit validation in
the generated initialize to compare overrides.keys against defaults.keys and
raise ArgumentError for any unknown keyword, while still applying resolve_option
only to the allowed keys so behavior matches normal Ruby keyword arguments.

---

Nitpick comments:
In `@lib/slidict/cli/options.rb`:
- Around line 82-85: The flag key derivation in `Options#flag` currently depends
on `switches.last`, so the saved key changes with declaration order and can
become the short switch name. Update `Options#flag` to derive `key` by
preferring the long switch first, consistent with `Flag#long`, and only fall
back to another switch if no long one exists. This will keep `flag("--output",
"-o", ...)` and similar DSL calls stable regardless of argument order.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 617a9aac-88c5-4b47-9921-163e887cd6b2

📥 Commits

Reviewing files that changed from the base of the PR and between 989334a and 1613658.

📒 Files selected for processing (6)
  • lib/slidict.rb
  • lib/slidict/cli/app.rb
  • lib/slidict/cli/lint.rb
  • lib/slidict/cli/options.rb
  • lib/slidict/cli/serve.rb
  • lib/slidict/cli/slides.rb

Comment on lines +64 to +74
def options(**defaults)
attr_reader(*defaults.keys)

include(Module.new do
define_method(:initialize) do |**overrides|
defaults.each do |name, default|
instance_variable_set(:"@#{name}", resolve_option(name, default, overrides))
end
end
end)
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

未定義のキーワード引数を黙って無視しないでください。

Line 68 の **overridesdefaults に存在しないキーも受け取れますが、Line 69-71 では無視されます。DI 用のキーワード名を typo した場合にデフォルトへフォールバックしてしまうため、通常の Ruby キーワード引数と同じく ArgumentError にした方が安全です。

修正案
         def options(**defaults)
           attr_reader(*defaults.keys)

           include(Module.new do
             define_method(:initialize) do |**overrides|
+              unknown = overrides.keys - defaults.keys
+              unless unknown.empty?
+                label = unknown.one? ? "keyword" : "keywords"
+                names = unknown.map { |key| ":#{key}" }.join(", ")
+                raise ArgumentError, "unknown #{label}: #{names}"
+              end
+
               defaults.each do |name, default|
                 instance_variable_set(:"@#{name}", resolve_option(name, default, overrides))
               end
             end
           end)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def options(**defaults)
attr_reader(*defaults.keys)
include(Module.new do
define_method(:initialize) do |**overrides|
defaults.each do |name, default|
instance_variable_set(:"@#{name}", resolve_option(name, default, overrides))
end
end
end)
end
def options(**defaults)
attr_reader(*defaults.keys)
include(Module.new do
define_method(:initialize) do |**overrides|
unknown = overrides.keys - defaults.keys
unless unknown.empty?
label = unknown.one? ? "keyword" : "keywords"
names = unknown.map { |key| ":#{key}" }.join(", ")
raise ArgumentError, "unknown #{label}: #{names}"
end
defaults.each do |name, default|
instance_variable_set(:"@#{name}", resolve_option(name, default, overrides))
end
end
end)
end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/slidict/cli/options.rb` around lines 64 - 74, The options DSL in options
and its generated initialize currently accepts arbitrary **overrides and
silently ignores keys not present in defaults, which can hide typos in DI
keyword names. Add explicit validation in the generated initialize to compare
overrides.keys against defaults.keys and raise ArgumentError for any unknown
keyword, while still applying resolve_option only to the allowed keys so
behavior matches normal Ruby keyword arguments.

abechan1 and others added 3 commits July 3, 2026 23:24
Adds --language LANG so LLM-generated slide titles/bullets, and any
interactive questions still needing an answer, are produced in a
language other than English (e.g. Japanese). Questions are translated
one at a time as plain text rather than a batched JSON array, since
that's a task small/local models follow far more reliably; translation
failures fall back to English with a warning instead of aborting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets SLIDICT_LLM_BASE_URL, SLIDICT_LLM_API_KEY, SLIDICT_LLM_MODEL,
SLIDICT_FRAMEWORK, and SLIDICT_METHOD be set via a .env file instead of
the shell environment, so per-project config doesn't need to live in a
shell profile. CLI flags still take precedence over both .env and real
environment variables. `slidict init` scaffolds a starter .env and adds
it to .gitignore so secrets like API keys stay out of version control.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
spec/slidict/llm/client_spec.rb (1)

108-135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

RuboCop指摘(セミコロン/長い行/Block too many lines)と重複コードの解消。

instance_double(Net::HTTP) を作って request_body をキャプチャするパターンが3箇所(115, 129, 222行目付近)でほぼ同一に重複しており、それが原因で以下のRuboCop違反が発生しています。

  • Line 115, 129, 222: Style/Semicolonrequest_body = req.body; response
  • Line 220: Layout/LineLength (125/120)
  • Line 210-246: Metrics/BlockLength ([27/25])

共通ヘルパーを抽出することで重複と3つの違反をまとめて解消できます。

♻️ 提案
def stub_http_capturing_request(content)
  http = instance_double(Net::HTTP)
  response = Net::HTTPOK.new("1.1", "200", "OK")
  allow(response).to receive(:body)
    .and_return({ "choices" => [{ "message" => { "content" => content } }] }.to_json)
  captured = nil
  allow(http).to receive(:request) do |req|
    captured = req.body
    response
  end
  allow(Net::HTTP).to receive(:start).and_yield(http)
  -> { captured }
end
     it "includes the language instruction in the prompt when language: is given" do
-      content = [{ "title" => "Observability", "bullets" => %w[a b] }].to_json
-      http = instance_double(Net::HTTP)
-      response = Net::HTTPOK.new("1.1", "200", "OK")
-      allow(response).to receive(:body).and_return({ "choices" => [{ "message" => { "content" => content } }] }.to_json)
-      request_body = nil
-      allow(http).to receive(:request) { |req| request_body = req.body; response }
-      allow(Net::HTTP).to receive(:start).and_yield(http)
+      content = [{ "title" => "Observability", "bullets" => %w[a b] }].to_json
+      request_body = stub_http_capturing_request(content)

       client.generate_slides(deck, language: "Japanese")

-      expect(JSON.parse(request_body)["messages"].first["content"]).to include("Japanese")
+      expect(JSON.parse(request_body.call)["messages"].first["content"]).to include("Japanese")
     end

同様の置き換えを123-135行目・217-230行目にも適用してください。

Also applies to: 209-246

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/slidict/llm/client_spec.rb` around lines 108 - 135, The spec has
repeated Net::HTTP stubbing/setup across multiple examples, which is causing the
semicolon, line-length, and block-length RuboCop offenses. Extract the shared
HTTP mock/capture logic into a small helper (for example, a method or shared
helper near generate_slides examples) and reuse it in the language-related
examples and the later block that stubs request bodies. Keep the helper centered
around the existing generate_slides, instance_double(Net::HTTP), and
request_body capture pattern so the duplicated setup is removed from each
example.

Source: Linters/SAST tools

lib/slidict/cli/app.rb (2)

39-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

--language のヘルプ説明が実際の挙動と食い違っています。

説明文は「LLM生成スライドのみに影響」となっていますが、questions_for(386-403行目)では対話質問の翻訳にも --language が使われています。ユーザーが誤解する可能性があるため説明を更新することを推奨します。

📝 提案
       flag "--language",     arg: "LANG",  desc: "Generate slide titles and bullets in the given language\n" \
-                                                  "(e.g. Japanese); only affects LLM-generated slides"
+                                                  "(e.g. Japanese); also translates the interactive prompts\n" \
+                                                  "asked below; only takes effect when an LLM is configured"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/slidict/cli/app.rb` around lines 39 - 40, The `--language` help text in
`App` is misleading because it says the option only affects LLM-generated
slides, but `questions_for` also uses it when translating interactive questions.
Update the description on the `flag "--language"` definition to reflect both
behaviors, and keep the wording aligned with how `questions_for` applies the
language setting.

322-327: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

.env の作成時にパーミッションを制限すべきです。

ENV_TEMPLATE のコメントには SLIDICT_LLM_API_KEY=sk-... のような秘密情報の保存が想定されていますが、File.write はデフォルトのumask依存パーミッションで作成するため、他ユーザーが読み取れる可能性があります。

🔒 修正案
       def write_env_file
         return false if File.exist?(".env")

         File.write(".env", ENV_TEMPLATE)
+        File.chmod(0o600, ".env")
         true
       end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/slidict/cli/app.rb` around lines 322 - 327, `.env` 作成時の権限が umask
依存のままで、秘密情報を他ユーザーが読める可能性があります。`write_env_file` で `.env` を作る処理を見直し、`File.write`
だけでなく `File.open` や `FileUtils.touch` などで作成権限を明示的に 0600
相当に制限するように修正してください。`ENV_TEMPLATE` を書き込む前に、`.env`
の所有者のみ読み書き可能であることを保証する実装にしてください。
spec/slidict/cli/app_spec.rb (1)

237-263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

SLIDICT_METHOD の優先順位・デフォルト適用が実質未検証です。

2つのテストとも SLIDICT_FRAMEWORK(ファイル拡張子)のみをアサートしており、SLIDICT_METHOD/--method が実際に反映されているかは確認していません(250行目のテスト名は「--method の優先」も謳っていますが --method フラグ自体渡していません)。options[:presentation_method] や生成された内容を検証するアサーションの追加を推奨します。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@spec/slidict/cli/app_spec.rb` around lines 237 - 263, The current CLI specs
only verify framework-based file output, so SLIDICT_METHOD/--method behavior is
not actually covered. Update the app_spec examples around cli.run to assert the
presentation method is set from options[:presentation_method] or reflected in
generated content, and add a true override case that passes --method alongside
the env vars so the precedence over SLIDICT_METHOD is verified.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/slidict/cli/app.rb`:
- Around line 313-334: `init` のファイルI/Oエラーが未捕捉で、`write_env_file` と
`ensure_env_gitignored` が権限エラー等をそのまま例外送出しています。`init` かこれらのメソッド周辺で
`Errno::EACCES` などを捕捉し、`auth` や LLM 呼び出しと同様に丁寧なエラーメッセージを `@output` に出して
`FAILURE` を返すようにしてください。`init`, `write_env_file`, `ensure_env_gitignored`
を基点に、成功時の `SUCCESS` と同じ一貫した戻り値設計に揃えてください。

---

Nitpick comments:
In `@lib/slidict/cli/app.rb`:
- Around line 39-40: The `--language` help text in `App` is misleading because
it says the option only affects LLM-generated slides, but `questions_for` also
uses it when translating interactive questions. Update the description on the
`flag "--language"` definition to reflect both behaviors, and keep the wording
aligned with how `questions_for` applies the language setting.
- Around line 322-327: `.env` 作成時の権限が umask
依存のままで、秘密情報を他ユーザーが読める可能性があります。`write_env_file` で `.env` を作る処理を見直し、`File.write`
だけでなく `File.open` や `FileUtils.touch` などで作成権限を明示的に 0600
相当に制限するように修正してください。`ENV_TEMPLATE` を書き込む前に、`.env`
の所有者のみ読み書き可能であることを保証する実装にしてください。

In `@spec/slidict/cli/app_spec.rb`:
- Around line 237-263: The current CLI specs only verify framework-based file
output, so SLIDICT_METHOD/--method behavior is not actually covered. Update the
app_spec examples around cli.run to assert the presentation method is set from
options[:presentation_method] or reflected in generated content, and add a true
override case that passes --method alongside the env vars so the precedence over
SLIDICT_METHOD is verified.

In `@spec/slidict/llm/client_spec.rb`:
- Around line 108-135: The spec has repeated Net::HTTP stubbing/setup across
multiple examples, which is causing the semicolon, line-length, and block-length
RuboCop offenses. Extract the shared HTTP mock/capture logic into a small helper
(for example, a method or shared helper near generate_slides examples) and reuse
it in the language-related examples and the later block that stubs request
bodies. Keep the helper centered around the existing generate_slides,
instance_double(Net::HTTP), and request_body capture pattern so the duplicated
setup is removed from each example.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1d0c3881-dc1e-4df0-b5f1-4af32ab5b4fc

📥 Commits

Reviewing files that changed from the base of the PR and between 1613658 and e9cfe9c.

📒 Files selected for processing (13)
  • .gitignore
  • CHANGELOG.md
  • README.md
  • bin/slidict
  • lib/slidict.rb
  • lib/slidict/cli/app.rb
  • lib/slidict/env.rb
  • lib/slidict/llm/client.rb
  • lib/slidict/version.rb
  • spec/slidict/cli/app_spec.rb
  • spec/slidict/env_spec.rb
  • spec/slidict/llm/client_spec.rb
  • spec/spec_helper.rb
✅ Files skipped from review due to trivial changes (2)
  • CHANGELOG.md
  • lib/slidict/version.rb
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/slidict.rb

Comment thread lib/slidict/cli/app.rb
Comment on lines +313 to +334
def init
env_created = write_env_file
@output.puts(env_created ? "Created .env" : ".env already exists, leaving it unchanged")

gitignore_updated = ensure_env_gitignored
@output.puts "Added .env to .gitignore" if gitignore_updated
SUCCESS
end

def write_env_file
return false if File.exist?(".env")

File.write(".env", ENV_TEMPLATE)
true
end

def ensure_env_gitignored
return false if File.exist?(".gitignore") && File.readlines(".gitignore", chomp: true).include?(".env")

File.open(".gitignore", "a") { |f| f.puts(".env") }
true
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

init でのファイルI/Oエラーが未捕捉です。

write_env_file/ensure_env_gitignored は書き込み不可なディレクトリ(権限なし、読み取り専用FSなど)で Errno::EACCES 等を素のまま送出します。他のコマンド(auth, LLM呼び出しなど)はエラーを捕捉して丁寧なメッセージ + FAILURE を返す方針なので、init だけスタックトレースで落ちるのは一貫性を欠きます。

🛡️ 修正案
       def init
         env_created = write_env_file
         `@output.puts`(env_created ? "Created .env" : ".env already exists, leaving it unchanged")

         gitignore_updated = ensure_env_gitignored
         `@output.puts` "Added .env to .gitignore" if gitignore_updated
         SUCCESS
+      rescue SystemCallError => e
+        `@output.puts` "Error: could not write files (#{e.message})"
+        FAILURE
       end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def init
env_created = write_env_file
@output.puts(env_created ? "Created .env" : ".env already exists, leaving it unchanged")
gitignore_updated = ensure_env_gitignored
@output.puts "Added .env to .gitignore" if gitignore_updated
SUCCESS
end
def write_env_file
return false if File.exist?(".env")
File.write(".env", ENV_TEMPLATE)
true
end
def ensure_env_gitignored
return false if File.exist?(".gitignore") && File.readlines(".gitignore", chomp: true).include?(".env")
File.open(".gitignore", "a") { |f| f.puts(".env") }
true
end
def init
env_created = write_env_file
`@output.puts`(env_created ? "Created .env" : ".env already exists, leaving it unchanged")
gitignore_updated = ensure_env_gitignored
`@output.puts` "Added .env to .gitignore" if gitignore_updated
SUCCESS
rescue SystemCallError => e
`@output.puts` "Error: could not write files (#{e.message})"
FAILURE
end
def write_env_file
return false if File.exist?(".env")
File.write(".env", ENV_TEMPLATE)
true
end
def ensure_env_gitignored
return false if File.exist?(".gitignore") && File.readlines(".gitignore", chomp: true).include?(".env")
File.open(".gitignore", "a") { |f| f.puts(".env") }
true
end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/slidict/cli/app.rb` around lines 313 - 334, `init`
のファイルI/Oエラーが未捕捉で、`write_env_file` と `ensure_env_gitignored`
が権限エラー等をそのまま例外送出しています。`init` かこれらのメソッド周辺で `Errno::EACCES` などを捕捉し、`auth` や LLM
呼び出しと同様に丁寧なエラーメッセージを `@output` に出して `FAILURE` を返すようにしてください。`init`,
`write_env_file`, `ensure_env_gitignored` を基点に、成功時の `SUCCESS`
と同じ一貫した戻り値設計に揃えてください。

@abechan1 abechan1 merged commit 6e7fc61 into main Jul 4, 2026
5 checks passed
@abechan1 abechan1 deleted the refactor/option branch July 4, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant