<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tasuku Yamashita</title>
    <description>The latest articles on DEV Community by Tasuku Yamashita (@tasuku43).</description>
    <link>https://dev.to/tasuku43</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3747667%2Ff7501754-4d81-48fd-98b9-34812201e925.jpeg</url>
      <title>DEV Community: Tasuku Yamashita</title>
      <link>https://dev.to/tasuku43</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tasuku43"/>
    <language>en</language>
    <item>
      <title>Managing Claude Code Bash permissions with YAML and tests</title>
      <dc:creator>Tasuku Yamashita</dc:creator>
      <pubDate>Wed, 29 Apr 2026 15:27:05 +0000</pubDate>
      <link>https://dev.to/tasuku43/managing-claude-code-bash-permissions-with-yaml-and-tests-ff7</link>
      <guid>https://dev.to/tasuku43/managing-claude-code-bash-permissions-with-yaml-and-tests-ff7</guid>
      <description>&lt;p&gt;I built &lt;code&gt;cc-bash-guard&lt;/code&gt;, a small policy engine for Claude Code Bash permissions.&lt;/p&gt;

&lt;p&gt;It runs as a Claude Code &lt;code&gt;PreToolUse&lt;/code&gt; hook. Before Claude Code executes a Bash command, &lt;code&gt;cc-bash-guard&lt;/code&gt; evaluates the command and returns &lt;code&gt;allow&lt;/code&gt;, &lt;code&gt;ask&lt;/code&gt;, or &lt;code&gt;deny&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tasuku43/cc-bash-guard" rel="noopener noreferrer"&gt;https://github.com/tasuku43/cc-bash-guard&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;If you want to allow only &lt;code&gt;git status&lt;/code&gt;, you can write a rule like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git status&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git&lt;/span&gt;
        &lt;span class="na"&gt;semantic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;verb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allow&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bash&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-c&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status'"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bash&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-c&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status'"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-C&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--force&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule does not match the raw string &lt;code&gt;git status&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It matches a parsed semantic field: the &lt;code&gt;git&lt;/code&gt; command whose &lt;code&gt;verb&lt;/code&gt; is &lt;code&gt;status&lt;/code&gt;. That means equivalent forms such as &lt;code&gt;/usr/bin/git status&lt;/code&gt; or &lt;code&gt;bash -c 'git status'&lt;/code&gt; can be treated as the same intent, while &lt;code&gt;git push --force origin main&lt;/code&gt; does not match this rule.&lt;/p&gt;

&lt;p&gt;Claude Code's native permissions are still useful. &lt;code&gt;cc-bash-guard&lt;/code&gt; is not a replacement for them. It is a semantic and testable policy layer you can add as a Bash hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why string prefixes start to hurt
&lt;/h2&gt;

&lt;p&gt;For simple cases, native permission patterns are enough. If you only care about &lt;code&gt;git status&lt;/code&gt;, a prefix like &lt;code&gt;Bash(git status*)&lt;/code&gt; can work.&lt;/p&gt;

&lt;p&gt;The problem is that agents do not always emit commands in the exact shape you expect. The same human intent can show up in several forms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/bin/git status
bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'git status'&lt;/span&gt;
&lt;span class="nb"&gt;env &lt;/span&gt;bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'git status'&lt;/span&gt;
&lt;span class="nb"&gt;command &lt;/span&gt;git status
git &lt;span class="nt"&gt;-C&lt;/span&gt; repo status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To a person, these are all basically "run git status". To a string prefix, they are different.&lt;/p&gt;

&lt;p&gt;If you broaden the pattern too much, you risk allowing things you did not mean to allow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push &lt;span class="nt"&gt;--force&lt;/span&gt; origin main
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt;
git clean &lt;span class="nt"&gt;-fdx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AWS has a similar problem. You may want to allow a read-only profile, ask for a write profile, and deny especially risky operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws &lt;span class="nt"&gt;--profile&lt;/span&gt; &lt;span class="nb"&gt;readonly &lt;/span&gt;sts get-caller-identity
aws &lt;span class="nt"&gt;--profile&lt;/span&gt; write cloudformation deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not "allow AWS" or "deny AWS". It is "look at the intent inside this command family".&lt;/p&gt;

&lt;h2&gt;
  
  
  What cc-bash-guard gives you
&lt;/h2&gt;

&lt;p&gt;The main goals are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;write permissions by semantic intent, not only command strings&lt;/li&gt;
&lt;li&gt;test the permissions you write&lt;/li&gt;
&lt;li&gt;keep the runtime hook lightweight even as the policy grows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The semantic parser currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;aws&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kubectl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gws&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helmfile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;argocd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docker&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these commands, rules can match fields such as subcommand, profile, namespace, HTTP method, dry-run, force flags, and similar command-specific fields.&lt;/p&gt;

&lt;p&gt;For commands without semantic support, you can still use &lt;code&gt;command.name&lt;/code&gt;, &lt;code&gt;command.name_in&lt;/code&gt;, or narrow raw regex &lt;code&gt;patterns&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;basic read-only tools&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name_in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pwd&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ls&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;wc&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pwd"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ls"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rm&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-rf&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tmp"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;narrow fallback pattern&lt;/span&gt;
      &lt;span class="na"&gt;patterns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^custom-tool&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;s+status(&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s"&gt;s|$)"&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom-tool&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom-tool&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;all"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is to keep &lt;code&gt;allow&lt;/code&gt; narrow. Prefer semantic rules when available. For unsupported commands, prefer &lt;code&gt;command.name&lt;/code&gt; or &lt;code&gt;command.name_in&lt;/code&gt;. Use &lt;code&gt;patterns&lt;/code&gt; only when you really need raw string matching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify once, evaluate fast
&lt;/h2&gt;

&lt;p&gt;Policies tend to grow. If tests are part of the policy, an obvious question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the hook run all those tests every time Claude Code executes Bash?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;You edit YAML files, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cc-bash-guard verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;verify&lt;/code&gt; checks the policy and tests, then writes verified data for the hook. At runtime, the hook uses that verified data instead of re-reading every YAML include and rerunning every test.&lt;/p&gt;

&lt;p&gt;Claude Code settings permissions are still part of the final decision. They are not bundled into the artifact, but their contents are part of the artifact fingerprint. If Claude Code settings change, you need to run &lt;code&gt;cc-bash-guard verify&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;If the verified data is missing, stale, or incompatible, the hook fails closed.&lt;/p&gt;

&lt;p&gt;The main commands are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cc-bash-guard suggest &lt;span class="s2"&gt;"git status"&lt;/span&gt;           &lt;span class="c"&gt;# print a pasteable starter rule&lt;/span&gt;
cc-bash-guard verify                         &lt;span class="c"&gt;# validate policy and write hook data&lt;/span&gt;
cc-bash-guard explain &lt;span class="s2"&gt;"bash -c 'git status'"&lt;/span&gt; &lt;span class="c"&gt;# inspect why a command is allow/ask/deny&lt;/span&gt;
cc-bash-guard hook                           &lt;span class="c"&gt;# evaluate Claude Code Bash calls&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Splitting read-only and destructive Git operations
&lt;/h2&gt;

&lt;p&gt;The first example allowed only &lt;code&gt;git status&lt;/code&gt;. In practice, you may want to allow several read-only Git verbs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git read-only&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git&lt;/span&gt;
        &lt;span class="na"&gt;semantic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;verb_in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;diff&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;log&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;show&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allow&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;read-only&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;commands"&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;diff&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--cached"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;log&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--oneline"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;show&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;HEAD"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;verb&lt;/code&gt; matches a single semantic value. &lt;code&gt;verb_in&lt;/code&gt; matches any value in a list.&lt;/p&gt;

&lt;p&gt;This rule allows common read-only Git commands. It does not match &lt;code&gt;git push&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For operations you explicitly want to block, add &lt;code&gt;deny&lt;/code&gt; rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git force push&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git&lt;/span&gt;
        &lt;span class="na"&gt;semantic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;verb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;push&lt;/span&gt;
          &lt;span class="na"&gt;force&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deny&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;force&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push"&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--force&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-f&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git destructive cleanup&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git&lt;/span&gt;
        &lt;span class="na"&gt;semantic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;verb_in&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;reset&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;clean&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deny&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;destructive&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cleanup"&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reset&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--hard"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;clean&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-fdx"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reset&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--soft&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;HEAD~1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the part that becomes awkward with shell regex alone. You have to think about option order, wrappers, absolute paths, and equivalent command forms.&lt;/p&gt;

&lt;p&gt;Semantic parsing does not prove safety. It just gives you a more precise way to express what you mean to allow, ask, or deny.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS by profile, service, and operation
&lt;/h2&gt;

&lt;p&gt;AWS CLI commands also change meaning depending on profile, service, and operation.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;allow a read-only profile&lt;/li&gt;
&lt;li&gt;ask for a write profile&lt;/li&gt;
&lt;li&gt;deny explicitly dangerous operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Profile-based rules can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS readonly profile&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
        &lt;span class="na"&gt;semantic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;readonly&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allow&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AWS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;readonly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;profile"&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--profile&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;readonly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sts&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;get-caller-identity"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--profile&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;readonly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;ls"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--profile&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cloudformation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploy"&lt;/span&gt;

  &lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS write profile&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
        &lt;span class="na"&gt;semantic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;profile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AWS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;profile&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;requires&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;confirmation"&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--profile&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;write&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cloudformation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploy"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--profile&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;readonly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sts&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;get-caller-identity"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want something narrower, match service and operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS identity&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
        &lt;span class="na"&gt;semantic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sts&lt;/span&gt;
          &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;get-caller-identity&lt;/span&gt;
      &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;allow&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;AWS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;identity&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;check"&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sts&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;get-caller-identity"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--profile&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;readonly&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sts&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;get-caller-identity"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cloudformation&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deploy"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is not that profile names magically prove safety. IAM still matters. The point is that profile-based and operation-based permission rules are straightforward to express in YAML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule-local tests and end-to-end tests
&lt;/h2&gt;

&lt;p&gt;Rules can carry their own tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;permission&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git status&lt;/span&gt;
      &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git&lt;/span&gt;
        &lt;span class="na"&gt;semantic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;verb&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
        &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--force&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a rule-local test. It proves what this specific rule matches and does not match.&lt;/p&gt;

&lt;p&gt;But the final hook decision does not come from one rule alone. It can involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple cc-bash-guard rules&lt;/li&gt;
&lt;li&gt;Claude Code settings permissions&lt;/li&gt;
&lt;li&gt;fallback behavior&lt;/li&gt;
&lt;li&gt;compound shell commands&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is what top-level tests are for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/usr/bin/git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bash&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-c&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status'"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;bash&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-c&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status'"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-C&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;repo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;

  &lt;span class="na"&gt;ask&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;

  &lt;span class="na"&gt;deny&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--force&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;push&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;--force&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;origin&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt;

  &lt;span class="na"&gt;abstain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;unknown-tool&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;status"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compound example is important. If &lt;code&gt;git status&lt;/code&gt; is allowed but &lt;code&gt;git push --force origin main&lt;/code&gt; is denied, then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git push &lt;span class="nt"&gt;--force&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;should still deny.&lt;/p&gt;

&lt;h2&gt;
  
  
  What abstain means
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;abstain&lt;/code&gt; means "no matching rule" or "this source has no opinion".&lt;/p&gt;

&lt;p&gt;It is not a final hook decision. If every source abstains, &lt;code&gt;cc-bash-guard&lt;/code&gt; falls back to &lt;code&gt;ask&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Decision precedence is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deny &amp;gt; ask &amp;gt; allow &amp;gt; abstain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some examples:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;cc-bash-guard policy&lt;/th&gt;
&lt;th&gt;Claude Code settings&lt;/th&gt;
&lt;th&gt;final decision&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;abstain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;policy allows and Claude settings have no matching rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;explicit confirmation wins over allow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deny&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deny&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;deny wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ask is not overridden by allow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deny&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deny&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;deny wins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;abstain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;allow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Claude settings allow when policy has no match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;abstain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Claude settings ask when policy has no match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;abstain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deny&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deny&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Claude settings deny when policy has no match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;abstain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;abstain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ask&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;no source matched, so ask&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is one of the main reasons I wanted top-level tests. They let you test the final merged decision, not only a single rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let Claude Code help maintain the policy
&lt;/h2&gt;

&lt;p&gt;I do not expect people to hand-write a perfect policy on day one.&lt;/p&gt;

&lt;p&gt;The workflow I want is more like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;write or ask Claude Code to draft rules&lt;/li&gt;
&lt;li&gt;add rule-local and top-level tests&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;cc-bash-guard verify&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;inspect surprises with &lt;code&gt;cc-bash-guard explain&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;adjust rules and tests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cc-bash-guard explain &lt;span class="s2"&gt;"git push --force origin main"&lt;/span&gt;
cc-bash-guard explain &lt;span class="nt"&gt;--why-not&lt;/span&gt; allow &lt;span class="s2"&gt;"git status &amp;gt; /tmp/out"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The explain output is useful for humans, but it is also useful when asking Claude Code to improve the policy.&lt;/p&gt;

&lt;p&gt;You can say things like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Migrate my current Claude Code Bash permissions into cc-bash-guard policy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Explain why this command was denied, then update the rule and tests if needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The important part is that the agent has a verification loop. It can edit YAML, run &lt;code&gt;verify&lt;/code&gt;, inspect failures with &lt;code&gt;explain&lt;/code&gt;, and iterate.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;suggest&lt;/code&gt; is available too, but I treat it as a helper. It prints a pasteable starter rule. It does not automatically edit your config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook setup
&lt;/h2&gt;

&lt;p&gt;Claude Code calls &lt;code&gt;cc-bash-guard&lt;/code&gt; as a &lt;code&gt;PreToolUse&lt;/code&gt; hook.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cc-bash-guard init&lt;/code&gt; prints a hook configuration snippet you can add to Claude Code settings. It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cc-bash-guard hook"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook receives the Bash command Claude Code is about to run. It evaluates the command using the verified policy artifact and current Claude Code settings, then returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;allow&lt;/code&gt;: skip the Claude Code permission prompt&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ask&lt;/code&gt;: ask the user to confirm&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deny&lt;/code&gt;: block the Bash tool call&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After editing policy files, included files, Claude Code settings, or upgrading the binary, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cc-bash-guard verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hook does not regenerate artifacts at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Claude Code Bash permissions are useful, but string patterns can become too coarse when you only want to slightly widen what is allowed.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cc-bash-guard&lt;/code&gt; fills that gap with semantic rules and tests.&lt;/p&gt;

&lt;p&gt;The goal is not only to lock everything down. It is to make common read-only operations smooth, keep risky or ambiguous commands visible, and leave a reviewed YAML trail of the decisions you want.&lt;/p&gt;

&lt;p&gt;Instead of trying to write the perfect policy upfront, you can grow it over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this should pass&lt;/li&gt;
&lt;li&gt;this should be denied&lt;/li&gt;
&lt;li&gt;this should ask&lt;/li&gt;
&lt;li&gt;this near miss should stay outside the rule&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the workflow I wanted for Claude Code Bash permissions.&lt;/p&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tasuku43/cc-bash-guard" rel="noopener noreferrer"&gt;https://github.com/tasuku43/cc-bash-guard&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>bash</category>
      <category>security</category>
      <category>ai</category>
    </item>
    <item>
      <title>Manage Git worktrees declaratively with YAML (plan/apply + guardrails) — gion</title>
      <dc:creator>Tasuku Yamashita</dc:creator>
      <pubDate>Mon, 02 Feb 2026 14:46:44 +0000</pubDate>
      <link>https://dev.to/tasuku43/manage-git-worktrees-declaratively-with-yaml-planapply-guardrails-gion-378n</link>
      <guid>https://dev.to/tasuku43/manage-git-worktrees-declaratively-with-yaml-planapply-guardrails-gion-378n</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; I built &lt;strong&gt;gion&lt;/strong&gt;, a CLI to manage &lt;strong&gt;Git worktrees&lt;/strong&gt; declaratively.&lt;br&gt;&lt;br&gt;
Write your desired state in &lt;code&gt;gion.yaml&lt;/code&gt;, then &lt;strong&gt;plan/apply&lt;/strong&gt; to create/update/cleanup in bulk &lt;strong&gt;with guardrails&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
It also supports &lt;strong&gt;task-scoped workspaces&lt;/strong&gt; (grouping multiple repos for a &lt;strong&gt;monorepo-like workflow&lt;/strong&gt;) and fast navigation via &lt;strong&gt;giongo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Repo / docs:&lt;br&gt;
&lt;a href="https://github.com/tasuku43/gion" rel="noopener noreferrer"&gt;https://github.com/tasuku43/gion&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;After I started using AI coding agents more often, I ended up doing more parallel work. That naturally led me to &lt;code&gt;git worktree&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But the more parallel tasks I had, the more I kept asking myself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where should I create the next worktree?&lt;/li&gt;
&lt;li&gt;Navigating across many worktrees is annoying.&lt;/li&gt;
&lt;li&gt;Can I delete this safely? (Or will I accidentally lose local changes?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are already tools around worktrees, but I wanted two things in particular:&lt;/p&gt;

&lt;p&gt;1) &lt;strong&gt;Guardrails during cleanup&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
   I wanted to prevent “I didn’t mean to delete it, but it was deletable anyway.”&lt;/p&gt;

&lt;p&gt;2) &lt;strong&gt;Task-scoped workspaces&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
   I wanted a “box per task” that can contain &lt;strong&gt;one or more worktrees&lt;/strong&gt;, possibly across &lt;strong&gt;multiple repositories&lt;/strong&gt;, so I can run my coding agent from the workspace root — and then delete the whole box when done.&lt;/p&gt;

&lt;p&gt;That’s what &lt;strong&gt;gion&lt;/strong&gt; is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdqcs7okhzr8ui9o7ln8.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgdqcs7okhzr8ui9o7ln8.gif" alt="Demo: gion reconciles workspaces via plan/apply (create/update/delete)" width="760" height="456"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The core workflow is &lt;strong&gt;Create / Move / Cleanup&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create&lt;/strong&gt;: edit &lt;code&gt;gion.yaml&lt;/code&gt; manually, or use &lt;code&gt;gion manifest add&lt;/code&gt; (interactive) → &lt;code&gt;gion apply&lt;/code&gt; (Plan → confirm → Apply)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move&lt;/strong&gt;: use &lt;code&gt;giongo&lt;/code&gt; to search and jump (no state changes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleanup&lt;/strong&gt;: edit &lt;code&gt;gion.yaml&lt;/code&gt; manually, or use &lt;code&gt;gion manifest gc&lt;/code&gt; (automatic) / &lt;code&gt;gion manifest rm&lt;/code&gt; (interactive) → &lt;code&gt;gion apply&lt;/code&gt; (Plan → confirm → Apply)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tip: &lt;code&gt;gion manifest&lt;/code&gt; can be shortened to &lt;code&gt;gion m&lt;/code&gt; or &lt;code&gt;gion man&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  How it works: &lt;code&gt;gion.yaml&lt;/code&gt; and the &lt;code&gt;manifest&lt;/code&gt; subcommands
&lt;/h2&gt;

&lt;p&gt;The center of gion is &lt;strong&gt;&lt;code&gt;gion.yaml&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gion.yaml&lt;/code&gt; is the &lt;strong&gt;source of truth&lt;/strong&gt; (“desired state” / inventory).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gion manifest ...&lt;/code&gt; is an &lt;strong&gt;entry point&lt;/strong&gt; to update that YAML (you can also edit it directly).&lt;/li&gt;
&lt;li&gt;After any update (via command or direct editing), you run &lt;strong&gt;&lt;code&gt;gion apply&lt;/code&gt;&lt;/strong&gt; to reconcile the filesystem with the desired state.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gion apply&lt;/code&gt; computes a &lt;strong&gt;plan&lt;/strong&gt;, shows the diff, and then applies it when you confirm.&lt;/li&gt;
&lt;li&gt;By default, &lt;code&gt;gion manifest ...&lt;/code&gt; will run &lt;code&gt;gion apply&lt;/code&gt; after rewriting &lt;code&gt;gion.yaml&lt;/code&gt;. Use &lt;code&gt;--no-apply&lt;/code&gt; if you want to update &lt;code&gt;gion.yaml&lt;/code&gt; only and apply later.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Terminology: worktree vs workspace
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;Git worktree&lt;/strong&gt; is a working directory that checks out a branch (or a specific commit).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;workspace&lt;/strong&gt; (in gion’s terms) is a &lt;strong&gt;task-scoped directory&lt;/strong&gt; that can contain &lt;strong&gt;one or more worktrees&lt;/strong&gt; — potentially from multiple repos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A typical layout looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GION_ROOT/
├─ gion.yaml           # desired state (inventory)
├─ bare/               # shared bare repo store
└─ workspaces/         # task-scoped workspaces
   ├─ PROJ-123/        # workspace_id (task)
   │  ├─ backend/      # worktree (repo: backend)
   │  ├─ frontend/
   │  └─ docs/
   └─ PROJ-456/
      └─ backend/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Create: review a plan, then create in bulk
&lt;/h2&gt;

&lt;p&gt;There are two ways to declare “create this workspace”:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;gion manifest add&lt;/code&gt;, or&lt;/li&gt;
&lt;li&gt;Edit &lt;code&gt;gion.yaml&lt;/code&gt; directly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In both cases, you’re declaring a desired state first, and then reconciling with &lt;strong&gt;&lt;code&gt;gion apply&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
1) compute the plan&lt;br&gt;
2) show the diff&lt;br&gt;
3) apply when you confirm&lt;/p&gt;
&lt;h3&gt;
  
  
  Four creation modes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;gion manifest add&lt;/code&gt; supports four entry paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;repo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;issue&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;review&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;preset&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The entry point differs, but the destination is the same: everything ends up in &lt;code&gt;gion.yaml&lt;/code&gt; as desired state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqeedcq6ufp7q11726t1c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqeedcq6ufp7q11726t1c.png" alt="Screenshot: gion manifest add modes (repo/issue/review/preset)" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;issue&lt;/code&gt; / &lt;code&gt;review&lt;/code&gt;: queue up many, then apply once
&lt;/h4&gt;

&lt;p&gt;If you use &lt;code&gt;issue&lt;/code&gt; / &lt;code&gt;review&lt;/code&gt;, you’ll need the &lt;code&gt;gh&lt;/code&gt; CLI (GitHub-only).&lt;br&gt;
These correspond to &lt;code&gt;--issue&lt;/code&gt; / &lt;code&gt;--review&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The intended flow is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;select multiple Issues / PRs&lt;/li&gt;
&lt;li&gt;add them to &lt;code&gt;gion.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;gion apply&lt;/code&gt; once&lt;/li&gt;
&lt;li&gt;review the plan, then apply&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is great when you want to spin up many review/issue worktrees quickly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfe4u3a1iz9uppv1kw4u.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfe4u3a1iz9uppv1kw4u.gif" alt="Demo: select multiple Issues/PRs and create worktrees in one apply" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;repo&lt;/code&gt;: create a single workspace quickly
&lt;/h4&gt;

&lt;p&gt;If you just want the fastest “create one workspace”, &lt;code&gt;repo&lt;/code&gt; is the simplest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;choose a repo&lt;/li&gt;
&lt;li&gt;set a workspace id&lt;/li&gt;
&lt;li&gt;confirm the plan&lt;/li&gt;
&lt;li&gt;apply&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmokwx7dosfgl3rtj7im7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmokwx7dosfgl3rtj7im7.png" alt="Screenshot: repo mode (create one workspace quickly)" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;preset&lt;/code&gt;: group multiple repos under one task workspace (monorepo-like workflow)
&lt;/h4&gt;

&lt;p&gt;A workspace is a “task box”, so it’s common to want something like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backend + frontend + docs&lt;/li&gt;
&lt;li&gt;backend + infra&lt;/li&gt;
&lt;li&gt;or any other multi-repo set&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;strong&gt;presets&lt;/strong&gt;, you define the set once, and then reuse it to create workspaces repeatedly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F57acy19xsjfgt4do8179.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F57acy19xsjfgt4do8179.png" alt="Screenshot: create a preset (define a reusable repo set)" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7aqcmz5e0etprx10d46m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7aqcmz5e0etprx10d46m.png" alt="Screenshot: create a workspace from a preset (multi-repo)" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Direct YAML editing
&lt;/h4&gt;

&lt;p&gt;Direct editing is useful when you want to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adjust branch names in bulk&lt;/li&gt;
&lt;li&gt;create/remove multiple workspaces at once&lt;/li&gt;
&lt;li&gt;refactor/reorganize existing definitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After editing, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gion apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll get a plan showing &lt;strong&gt;creates / deletes / updates&lt;/strong&gt; together, so you can calmly review what will happen before applying.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsperyjrfddicopxrbba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpsperyjrfddicopxrbba.png" alt="Screenshot: plan shows create/delete/update changes together" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Move: search and jump to a workspace/worktree
&lt;/h2&gt;

&lt;p&gt;Once worktrees start to accumulate, you waste time thinking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Where was I working on that?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For navigation, use &lt;strong&gt;&lt;code&gt;giongo&lt;/code&gt;&lt;/strong&gt; (installed alongside &lt;code&gt;gion&lt;/code&gt; via Homebrew/mise).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;giongo&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; change any state.&lt;/li&gt;
&lt;li&gt;It lists both &lt;strong&gt;workspaces&lt;/strong&gt; and &lt;strong&gt;worktrees&lt;/strong&gt;, and lets you filter and select.&lt;/li&gt;
&lt;li&gt;Select a &lt;strong&gt;workspace&lt;/strong&gt; → jump to the workspace directory&lt;/li&gt;
&lt;li&gt;Select a &lt;strong&gt;worktree&lt;/strong&gt; → jump to that repo’s working directory&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffi7w0edfr2ktvxpkvono.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffi7w0edfr2ktvxpkvono.gif" alt="Demo: giongo lists and jumps to workspaces/worktrees" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Shell integration: make &lt;code&gt;giongo&lt;/code&gt; change directories
&lt;/h3&gt;

&lt;p&gt;If you want selection → &lt;code&gt;cd&lt;/code&gt;, you need a small shell integration. The README includes options, but the simplest is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;giongo init&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(That prints a function you can paste into &lt;code&gt;~/.zshrc&lt;/code&gt; or &lt;code&gt;~/.bashrc&lt;/code&gt; for permanent setup.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Cleanup: conservative &lt;code&gt;gc&lt;/code&gt;, and guarded &lt;code&gt;rm&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;As worktrees pile up, you eventually pause and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Is it safe to delete this?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;gion splits cleanup into two commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gion manifest gc&lt;/code&gt;: &lt;strong&gt;automatic, conservative cleanup&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gion manifest rm&lt;/code&gt;: &lt;strong&gt;manual selection with guardrails&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;gion manifest gc&lt;/code&gt;: conservative auto-cleanup
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;gion manifest gc&lt;/code&gt; proposes candidates that are &lt;strong&gt;highly likely safe&lt;/strong&gt; to delete.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;workspaces whose checked-out branches are already merged into the default branch can be reclaimed&lt;/li&gt;
&lt;li&gt;anything ambiguous (uncommitted / unpushed / unreadable state, etc.) is excluded by default&lt;/li&gt;
&lt;li&gt;even “created but no commits” workspaces are excluded so you don’t delete something by accident&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short: &lt;code&gt;gc&lt;/code&gt; aims to keep false-positives very low.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90lzkd6aubtu9p87o4z5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F90lzkd6aubtu9p87o4z5.png" alt="Screenshot: gc classifies safe-to-delete candidates conservatively" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;gion manifest rm&lt;/code&gt;: manual removal, with guardrails
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;gion manifest rm&lt;/code&gt; is for cases where &lt;strong&gt;a human decides&lt;/strong&gt; what to delete.&lt;/p&gt;

&lt;p&gt;It supports interactive selection and then a final confirmation. During selection, each workspace gets lightweight tags like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[dirty]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[unpushed]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[diverged]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[unknown]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What those mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;dirty&lt;/strong&gt;: working tree has uncommitted changes (including untracked files or conflicts)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;unpushed&lt;/strong&gt;: your local branch is ahead of upstream (has commits not pushed)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;diverged&lt;/strong&gt;: local and upstream have both advanced (ahead and behind)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;unknown&lt;/strong&gt;: cannot be determined (no upstream, detached HEAD, git error, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you delete something risky (e.g., &lt;code&gt;dirty&lt;/code&gt;), the &lt;strong&gt;plan&lt;/strong&gt; will show a risk summary (e.g., &lt;code&gt;risk: dirty (unstaged=2)&lt;/code&gt;) and (for dirty cases) the changed files, so you can sanity-check quickly before you confirm.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jdlxkz8lggmv0l5kk8n.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2jdlxkz8lggmv0l5kk8n.gif" alt="Demo: rm shows risk and changed files in the plan before deletion" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup via direct YAML editing
&lt;/h3&gt;

&lt;p&gt;You can also remove workspaces by editing &lt;code&gt;gion.yaml&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;Even then, &lt;code&gt;gion apply&lt;/code&gt; will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clearly show what will be removed in the plan&lt;/li&gt;
&lt;li&gt;ask for a confirmation before destructive changes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if you get nervous, you can just answer &lt;code&gt;n&lt;/code&gt; and stop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Installation (Homebrew / mise) and full usage examples are in the GitHub README. If you’ve ever felt the pain of worktree sprawl — especially in multi-repo tasks — I’d love for you to try &lt;strong&gt;gion&lt;/strong&gt; and share feedback.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tasuku43/gion" rel="noopener noreferrer"&gt;https://github.com/tasuku43/gion&lt;/a&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>productivity</category>
      <category>cli</category>
    </item>
  </channel>
</rss>
