Skip to content

feat(init): add runok init subcommand#152

Merged
fohte merged 29 commits intomainfrom
fohte/impl-runok-migration-init-cmd
Mar 6, 2026
Merged

feat(init): add runok init subcommand#152
fohte merged 29 commits intomainfrom
fohte/impl-runok-migration-init-cmd

Conversation

@fohte
Copy link
Owner

@fohte fohte commented Mar 5, 2026

Why

  • Lower the barrier to adopting runok

What

  • Add a runok init subcommand with an interactive setup wizard
    • Scope selection (user/project), automatic detection and migration of Claude Code Bash permissions, hook registration, and runok.yml generation in a single flow

Open with Devin

fohte and others added 25 commits March 3, 2026 03:28
The init subcommand provides a wizard that sets up runok configuration
for user-level and/or project-level scopes. It supports Claude Code
integration by parsing existing permission entries from settings.json,
converting Bash permissions to runok rules, registering a PreToolUse
hook, and optionally removing the migrated permissions.

Key features:
- --scope user|project to target a specific scope
- -y for non-interactive mode with sensible defaults
- --force to overwrite existing configuration files
- Automatic detection and conversion of Claude Code permissions
- Hook registration in .claude/settings.json
Claude Code permission entries use space-separated arguments
(e.g. `Bash(npm install *)`), not colon-separated
(e.g. `Bash(npm:install:*)`). The `convert_bash_pattern` function
was based on a wrong assumption about the format and has been removed.
Patterns from settings.json are now used as-is without transformation.
- Replace verbose boilerplate comments with a single
  yaml-language-server $schema directive, which provides editor
  autocompletion via JSON Schema
- Remove unnecessary "# runok configuration" and example rules
  comments from the generated config
- Use assert_eq! with exact values instead of partial matchers
  (ends_with, starts_with, field-level JSON checks) across all
  test files
- Add settings.json verification to project_flow_with_claude_code
  integration test to cover permission removal and hook registration
The hooks array requires objects with {"type": "command", "command": "..."},
not plain strings. The previous format caused `claude doctor` to report
"Expected object, but received string" and broke the user's settings.json.

Also replace partial assertions (is_none/is_object) with exact assert_eq!
on full JSON values in remove_permissions tests.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The hook format fix in docs (README.md, claude-code.md) will be handled
in a separate branch to keep this PR focused on the init command logic.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The init output was hard to read: confirmation prompts blended into
surrounding text, the "Skipped non-Bash entries" list was noisy and
unhelpful (non-Bash entries being skipped is expected behavior), and
the summary didn't show what was actually converted.

- Use ColorfulTheme for confirmation prompts so they stand out visually
- Print converted rules before prompting so users see what will change
- Remove skipped entries output (non-Bash skipping is the default behavior)

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Users couldn't tell what would actually change when answering "Remove
converted permissions?" or "Register runok hook?" -- the prompts gave
no visibility into the resulting settings.json modifications.

Show a unified diff of settings.json before each confirmation prompt
so users can see the exact changes before deciding. Uses the `similar`
crate for proper Myers diff output.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The original settings.json used 3-space indentation while
serde_json::to_string_pretty uses 2-space, causing every line to
appear as changed in the diff output. Normalize the original content
through serde before comparing so only structural changes are shown.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Two separate confirmation prompts ("Remove permissions?" and "Register
hook?") made the flow feel fragmented and didn't clearly communicate
what would happen. Users couldn't see the full picture before deciding.

Show all planned changes as numbered steps with colored unified diffs,
then ask a single "Apply these changes?" prompt. Also add the converted
runok.yml rules to the preview so users see where permissions move to.

Add unit tests for preview_remove_permissions, preview_register_hook,
and normalize_json.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
…rove test coverage

wizard.rs was 1230 lines with mixed responsibilities (preview/diff,
setup orchestration, public API). Split into three files for
readability:
- wizard/mod.rs: public API, types, helpers, integration tests
- wizard/preview.rs: JSON preview/diff utilities and their tests
- wizard/setup.rs: ScopeResult and setup_scope orchestration

Also includes fixes from previous session that were not yet committed:
- Prompter trait DI to enable per-step test control
- remove_permissions now preserves non-Bash entries
- preview_register_hook detects legacy hook format
- Added parameterized integration tests for mixed permission scenarios
The previous wizard had two issues:
1. When no --scope flag was given, it asked two separate yes/no
   questions ("Set up user-level?" then "Set up project-level?")
   which was clunky. Now it uses a single select prompt to choose
   between User (global) and Project (local).
2. Claude Code integration changes were confirmed per-step (up to 3
   separate confirmations), which was tedious. Now all pending changes
   are shown as a combined diff with a single "Apply these changes?"
   confirmation — accept applies everything, decline skips everything.

Also adds `select` method to the Prompter trait alongside the existing
`confirm`, with implementations for DialoguerPrompter, AutoYesPrompter,
and test SequencePrompter (now uses a Response enum for type-safe
confirm/select queuing).
Project-level config is shared among all developers, so hook
registration should be skipped (not everyone uses runok) and
permission migration should be opt-in rather than automatic.

- Add HookPolicy (Register/Skip) and MigrationPolicy (Always/Ask)
- User scope: auto-migrate permissions and register hook
- Project scope: ask before migrating (default no), never register hook
- Show detection message before diff preview
- Update tests for new project scope behavior
- Change e2e test to use user scope for Claude Code integration
Claude Code uses `:*` as a prefix-match operator (e.g. `Bash(npm run:*)`
matches commands starting with `npm run`). runok has no `:*` syntax so
the literal string `npm run:*` would be treated as a pattern with a
colon character followed by a wildcard, which is not the intended
semantics. Convert `:*` suffix to a space + glob (e.g. `npm run *`).
The --force flag was redundant because the wizard already shows a
confirmation prompt before overwriting. Removing it simplifies the UX:
`-y` with existing config now overwrites directly.

Also changed user scope to ask "Migrate from Claude Code?" instead of
auto-migrating, giving users control over migration in all scopes.
The migration prompt default is true for user scope (most users want
migration) and false for project scope (shared config is more cautious).

Fixed the diff preview for runok.yml to show existing file content as
the "before" side instead of an empty string.
The migration prompt appeared before the "Detected Claude Code
configuration" message, which was confusing because the user was asked
about migration without context. Now the detection message is shown
first, then the migration prompt follows naturally.

Also ensures migration is only asked when Claude Code configuration
is actually detected (has migratable rules or hook to register).
The previous commit changed when migration prompts appear (only when
Claude Code config is detected) but integration tests still passed
because AutoYesPrompter silently returns defaults for any prompt.

Replace AutoYesPrompter with SequencePrompter + assert_exhausted in
tests that verify Claude Code integration, so the exact number of
confirm calls is validated. This catches regressions where prompts
are added or removed without updating tests.
runok init's purpose is to create runok.yml. Previously, declining the
migration/apply prompt resulted in no runok.yml being created at all,
which defeated the purpose of running the command.

Now runok.yml is always created: with converted rules if migration is
accepted, or as boilerplate if declined.
Integration tests should verify observable behavior (file contents,
file existence) not internal details like prompt call counts. Reverted
SequencePrompter back to AutoYesPrompter where specific response
sequences are not needed.
When Claude Code configuration was detected but the user declined
migration (and there were no other changes to apply), runok.yml was
still silently created with boilerplate content. This was confusing
because the user said "no" but a file was created anyway.

Now runok.yml is only created when:
- No Claude Code config is detected (normal init flow)
- The user approves changes (migration and/or hook)
The conditional logic in setup_scope had been patched ad-hoc, causing
inconsistent behavior across different combinations of settings.json
existence, Bash permissions, hook state, scope, and user responses.
A comprehensive pattern table with 23 valid combinations was defined
to specify the exact expected behavior for each case.

- Replace ad-hoc integration tests with 23 exhaustive pattern tests
  covering every valid combination of the 6 condition axes
- Fix setup_scope to always ask "Apply these changes?" when inside the
  "Detected" block, including runok.yml creation in the diff preview
- Update unit test for project scope migration-declined case to match
  the new behavior (boilerplate created when Apply? defaults to yes)
The pattern table comment used "-" for inapplicable fields, which was
ambiguous (could mean "no change" or "not shown"). Explicit N/A markers
and State/Response/Result column grouping make it clear which prompts
are shown and which are skipped for each combination.
…onfig axis

The previous test suite missed the "existing runok.yml" axis, which
affects whether declined changes should preserve the file or leave it
absent. Added EXISTING_CONFIG constant, setup_existing_config helper,
and ExpectedConfig::Preserved variant to cover all combinations of
the 7 condition axes (settings.json, Bash perms, hook, scope,
existing config, migration response, apply response).

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The abbreviated "E" was unclear. Use the full name and align all
column widths consistently.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
When no Claude Code configuration is detected and runok.yml already
exists, silently overwriting it was surprising. Add an "Overwrite?"
confirmation prompt (default=No) so users must explicitly opt in.

Also fix AutoYesPrompter to always return true for confirm(), matching
the -y flag semantics (previously it returned the default value, which
caused -y to decline overwrite since the default was false).

Expand the integration test matrix from 46 to 51 patterns to cover the
new Overwrite? yes/no axis for all "no Detected block + existing config"
cases.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The `runok init` subcommand was added but documentation was not updated.

Add CLI reference page for `runok init`, update Quick Start to recommend
it as the primary setup method, add a tip to Claude Code integration
page, and update CLI overview and README.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@codecov
Copy link

codecov bot commented Mar 5, 2026

Codecov Report

❌ Patch coverage is 87.82609% with 84 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.66%. Comparing base (4287cb2) to head (56bbee0).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
src/init/prompt.rs 16.66% 30 Missing ⚠️
src/init/wizard/mod.rs 89.95% 22 Missing ⚠️
src/init/claude_code.rs 91.47% 15 Missing ⚠️
src/init/wizard/preview.rs 88.04% 11 Missing ⚠️
src/main.rs 70.58% 5 Missing ⚠️
src/init/wizard/setup.rs 99.22% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #152      +/-   ##
==========================================
- Coverage   89.86%   89.66%   -0.20%     
==========================================
  Files          31       37       +6     
  Lines        6425     7115     +690     
==========================================
+ Hits         5774     6380     +606     
- Misses        651      735      +84     
Flag Coverage Δ
Linux 89.53% <87.82%> (-0.14%) ⬇️
macOS 91.41% <87.82%> (-0.41%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly improves the onboarding experience for runok by introducing an init subcommand. This new command provides an interactive wizard that streamlines the setup process, making it easier for users to configure runok.yml and integrate with Claude Code by automating permission migration and hook registration. The goal is to lower the barrier to adoption and simplify initial configuration steps.

Highlights

  • New runok init subcommand: Introduced an interactive setup wizard to simplify the initial configuration of runok, guiding users through creating runok.yml.
  • Claude Code Integration: Added functionality to automatically detect and migrate existing Claude Code Bash permissions into runok rules, and register the runok check PreToolUse hook.
  • Interactive Setup Wizard: The wizard supports scope selection (user-global or project-local configuration), previews proposed changes with diffs, and asks for user confirmation before applying.
  • Dependency Updates: Incorporated new dependencies like dialoguer for interactive prompts and similar for generating diffs, enhancing the user experience of the init subcommand.
  • Documentation: Updated README.md and added new documentation pages (/cli/init/) to reflect the new runok init command and guide users on its usage.
Changelog
  • Cargo.lock
    • Updated windows-sys dependency versions.
    • Added new dependencies: console, dialoguer, encode_unicode, shell-words, similar, unicode-segmentation, windows-targets, and zeroize.
  • Cargo.toml
    • Added dialoguer and similar as new dependencies with specific versions and features.
  • README.md
    • Updated configuration instructions to recommend using runok init.
    • Clarified Claude Code integration steps, mentioning automation by runok init.
  • docs/src/content/docs/cli/init.md
    • Added a new documentation page detailing the runok init command, its usage, flags, and the wizard's functionality.
  • docs/src/content/docs/cli/overview.md
    • Updated the CLI overview to include the newly added runok init command.
  • docs/src/content/docs/getting-started/claude-code.md
    • Added a tip highlighting how runok init --scope user can automate steps for Claude Code integration.
  • docs/src/content/docs/getting-started/quickstart.md
    • Modified the quick start guide to promote the interactive setup wizard via runok init.
  • src/cli/mod.rs
    • Added Init subcommand to the CLI, along with InitScope enum and InitArgs struct for parsing arguments.
  • src/config/mod.rs
    • Made the dirs module public to allow access from the new init module.
  • src/init/claude_code.rs
    • Added new module to handle Claude Code permissions parsing, Bash pattern conversion, and hook registration/removal.
  • src/init/config_gen.rs
    • Added new module for generating runok.yml content, including boilerplate and converted rules, and writing it to disk.
  • src/init/error.rs
    • Added new module defining custom error types for the init subcommand.
  • src/init/mod.rs
    • Added new module for the init subcommand, exporting wizard-related functionality.
  • src/init/prompt.rs
    • Added new module defining a Prompter trait and implementations for interactive (Dialoguer) and non-interactive (AutoYes) prompts.
  • src/init/wizard/mod.rs
    • Added new module containing the core logic for the interactive init wizard, handling scope selection and orchestrating setup steps.
  • src/init/wizard/preview.rs
    • Added new module for previewing changes to settings.json and runok.yml using diffs.
  • src/init/wizard/setup.rs
    • Added new module containing the detailed logic for setting up a specific configuration scope, including Claude Code integration.
  • src/lib.rs
    • Added the new init module to the library's public interface.
  • src/main.rs
    • Integrated the runok init command into the main application entry point, ensuring it runs independently of other commands.
  • tests/e2e/helpers.rs
    • Modified TestEnv to make the home directory path public for easier access in init end-to-end tests.
  • tests/e2e/init.rs
    • Added new end-to-end tests specifically for the runok init subcommand, covering various scenarios including Claude Code integration.
  • tests/e2e/main.rs
    • Included the new init test module in the end-to-end test suite.
  • tests/integration/init_wizard.rs
    • Added new comprehensive integration tests for the init wizard, covering a wide range of scenarios and user interactions.
  • tests/integration/main.rs
    • Included the new init_wizard test module in the integration test suite.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

On Linux CI, XDG_CONFIG_HOME may be set to a path outside the test's
isolated HOME directory, causing config_dir() to write runok.yml to
the wrong location. Clear XDG_CONFIG_HOME and XDG_CACHE_HOME in the
test command builder so config_dir() falls back to $HOME/.config.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 5 potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

gemini-code-assist[bot]

This comment was marked as resolved.

Permission patterns containing single quotes (e.g., `echo 'hello'`)
produced invalid YAML when embedded in single-quoted strings. Double
the single quotes per YAML spec to prevent injection.

Also replace embedded `\n` with `indoc!` in `normalize_json` test.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
devin-ai-integration[bot]

This comment was marked as resolved.

Replace inline `\n` with `concat!` for readability while preserving
the leading 2-space indent that `indoc!` auto-dedent would strip.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
devin-ai-integration[bot]

This comment was marked as resolved.

…anup, and tests

- entry_has_runok_hook now detects string format hooks (plain strings
  and string arrays inside hooks objects) in addition to the existing
  object and legacy formats
- remove_permissions now cleans both settings.json and
  settings.local.json, not just settings.json
- Parameterize 51 exhaustive integration tests into a single rstest
  with #[case] variants, reducing duplication while preserving coverage

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@fohte fohte merged commit 176d9b6 into main Mar 6, 2026
9 checks passed
@fohte fohte deleted the fohte/impl-runok-migration-init-cmd branch March 6, 2026 14:27
@fohte-bot fohte-bot bot mentioned this pull request Mar 6, 2026
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