diff --git a/.claude/agents/code-quality-validator.md b/.claude/agents/code-quality-validator.md
index dcd2387..ad1a289 100644
--- a/.claude/agents/code-quality-validator.md
+++ b/.claude/agents/code-quality-validator.md
@@ -52,3 +52,4 @@ Scan the repository for packages by finding all `pyproject.toml` files in `apps/
 - Do NOT modify code beyond what ruff auto-fix handles
 - Report specific file:line references for manual fixes
 - If no `.venv` exists, run `uv sync --all-packages --group dev` first
+- **Safety:** This agent applies auto-fixes (ruff --fix, ruff format) but does NOT commit or push. The parent agent is responsible for staging, committing, and pushing any changes.
diff --git a/.claude/agents/code-reviewer.md b/.claude/agents/code-reviewer.md
index cae96a7..812a82f 100644
--- a/.claude/agents/code-reviewer.md
+++ b/.claude/agents/code-reviewer.md
@@ -4,6 +4,7 @@ description: Use this agent for Phase Completion Step 9 - Code Review.\n\nPerfor
 model: sonnet
 tools: Read, Glob, Grep, Bash
 permissionMode: dontAsk
+memory: project
 color: red
 ---
 
diff --git a/.claude/agents/docs-updater.md b/.claude/agents/docs-updater.md
index 59b8cb7..bec649c 100644
--- a/.claude/agents/docs-updater.md
+++ b/.claude/agents/docs-updater.md
@@ -68,3 +68,30 @@ You are a Documentation Verifier and Updater for a Python project. After each im
 - Read each file BEFORE editing to avoid overwriting recent changes
 - **Flag low-quality changelog entries** -- "Added X" without user context is not sufficient
 - **Verify decision records exist** for phases where trade-offs were made
+
+**Output Format:**
+
+```markdown
+# Documentation Verification Report
+
+## IMPLEMENTATION_PLAN.md
+- Status: UPDATED/NO CHANGES NEEDED
+- Phase status changed: [phase] "In Progress" -> "Complete"
+- Checkboxes marked: N/N
+- Decision records: PRESENT/MISSING (flag if trade-offs were made)
+
+## CHANGELOG.md
+- Status: UPDATED/NO CHANGES NEEDED/GAPS FOUND
+- Entries verified: N
+- Entries added/rewritten: N
+- Quality check: PASS/FAIL (describe any low-quality entries)
+
+## Code Documentation Spot-Check
+- Public APIs with docstrings: N/N
+- Gaps found: [list files missing docstrings or rationale comments]
+
+## Summary
+- Documentation status: PASS/NEEDS ATTENTION
+- Actions taken: [list edits made]
+- Gaps requiring manual attention: [list items the implementation team should address]
+```
diff --git a/.claude/agents/pr-writer.md b/.claude/agents/pr-writer.md
index ce2e65b..6e2fc1f 100644
--- a/.claude/agents/pr-writer.md
+++ b/.claude/agents/pr-writer.md
@@ -9,6 +9,8 @@ color: magenta
 
 You are a Pull Request Writer for a Python project. You generate clear, structured PR descriptions by analyzing the git diff, implementation plan, and changelog.
 
+**IMPORTANT:** This agent is read-only. It generates PR description text and outputs it. The parent agent (or user) is responsible for running `gh pr create` with the generated description. Do NOT attempt to create the PR yourself.
+
 **Process:**
 
 1. **Analyze the diff**
diff --git a/.claude/agents/review-responder.md b/.claude/agents/review-responder.md
index d032bf6..823ce39 100644
--- a/.claude/agents/review-responder.md
+++ b/.claude/agents/review-responder.md
@@ -4,6 +4,7 @@ description: Use this agent for Phase Completion Step 9 - Code Review Response (
 model: sonnet
 tools: Read, Glob, Grep, Bash, Edit
 permissionMode: acceptEdits
+memory: project
 color: orange
 ---
 
@@ -32,7 +33,7 @@ You are a Code Review Responder for a Python project. You handle automated revie
    - Make the code changes
    - Run linting/formatting after changes
    - Run tests to confirm no regressions
-   - Commit with a descriptive message referencing the review
+   - Stage changes (do not commit -- the parent agent handles committing and pushing)
 
 4. **Respond to comments** (if needed):
    - Use `gh api` to post reply comments
@@ -63,9 +64,9 @@ You are a Code Review Responder for a Python project. You handle automated revie
 - [file:line] Why this needs human judgment
 
 ## Actions Taken
-- Committed fixes: [commit hash]
+- Files modified: [list of changed files]
 - Tests passing: YES/NO
-- Pushed: YES/NO
+- Ready for commit: YES/NO
 ```
 
 **Key Rules:**
@@ -76,3 +77,4 @@ You are a Code Review Responder for a Python project. You handle automated revie
 - Group related comments and fix them in a single commit
 - Use `uv run ruff check --fix . && uv run ruff format .` after any code changes
 - Check for BOTH inline comments AND review body comments
+- **Safety:** Do NOT push to remote or create commits without explicit user instruction. Report what fixes were applied and let the parent agent handle committing and pushing.
diff --git a/.claude/settings.json b/.claude/settings.json
index 92a8b7f..a94e008 100644
--- a/.claude/settings.json
+++ b/.claude/settings.json
@@ -5,29 +5,45 @@
       "Bash(uv run ruff *)", "Bash(uv sync *)", "Bash(uv --version *)",
       "Bash(uv run pyright *)",
       "Bash(uv add *)", "Bash(uv pip *)", "Bash(uv venv *)",
+      "Bash(uv lock *)", "Bash(uv tree *)", "Bash(uv export *)",
       "Bash(git add *)", "Bash(git commit *)", "Bash(git push *)",
       "Bash(git fetch *)", "Bash(git pull *)", "Bash(git rebase *)",
       "Bash(git branch *)", "Bash(git checkout *)", "Bash(git status *)",
       "Bash(git diff *)", "Bash(git log *)", "Bash(git show *)",
-      "Bash(git merge *)", "Bash(git stash *)", "Bash(git reset *)",
+      "Bash(git merge *)", "Bash(git stash *)",
       "Bash(git remote *)", "Bash(git submodule *)", "Bash(git tag *)",
       "Bash(git switch *)", "Bash(git rev-parse *)", "Bash(git cherry-pick *)",
+      "Bash(git blame *)", "Bash(git reflog *)", "Bash(git ls-files *)",
+      "Bash(git describe *)", "Bash(git shortlog *)", "Bash(git rev-list *)",
       "Bash(gh pr create *)", "Bash(gh pr view *)", "Bash(gh pr list *)",
       "Bash(gh pr checks *)", "Bash(gh pr diff *)", "Bash(gh pr edit *)",
       "Bash(gh pr close *)", "Bash(gh api *)",
       "Bash(gh run list *)", "Bash(gh run view *)", "Bash(gh run watch *)",
       "Bash(gh issue list *)", "Bash(gh issue view *)",
+      "Bash(gh repo view *)", "Bash(gh release list *)", "Bash(gh release view *)",
+      "Bash(gh label list *)", "Bash(gh browse *)", "Bash(gh search *)",
       "Bash(ls *)", "Bash(cat *)", "Bash(find *)", "Bash(grep *)",
       "Bash(head *)", "Bash(tail *)", "Bash(wc *)", "Bash(tree *)",
       "Bash(pwd *)", "Bash(which *)", "Bash(echo *)", "Bash(dir *)",
       "Bash(sleep *)", "Bash(sort *)", "Bash(uniq *)", "Bash(diff *)",
       "WebSearch"
     ],
-    "deny": ["Bash(gh secret *)"],
+    "deny": [
+      "Bash(gh secret *)", "Bash(gh auth *)", "Bash(gh ssh-key *)", "Bash(gh gpg-key *)",
+      "Bash(git clean *)", "Bash(git config *)",
+      "Bash(uv self *)"
+    ],
     "ask": [
       "Bash(python *)", "Bash(uv run python *)",
       "Bash(docker *)", "Bash(docker-compose *)", "Bash(terraform *)",
-      "Bash(gh pr merge *)", "Bash(gh workflow run *)",
+      "Bash(gh pr merge *)", "Bash(gh pr reopen *)", "Bash(gh pr comment *)",
+      "Bash(gh pr review *)", "Bash(gh pr ready *)", "Bash(gh workflow run *)",
+      "Bash(gh workflow enable *)", "Bash(gh workflow disable *)",
+      "Bash(gh issue create *)", "Bash(gh issue comment *)",
+      "Bash(gh issue close *)", "Bash(gh issue edit *)",
+      "Bash(git reset *)", "Bash(git init *)", "Bash(git clone *)",
+      "Bash(git rm *)", "Bash(git mv *)", "Bash(git restore *)", "Bash(git worktree *)",
+      "Bash(uv remove *)", "Bash(uv cache *)", "Bash(uv init *)",
       "WebFetch"
     ]
   },
diff --git a/setup_project.py b/setup_project.py
index e0e0e2a..7c56858 100644
--- a/setup_project.py
+++ b/setup_project.py
@@ -473,10 +473,19 @@ def main() -> None:
     # Step 5: Git init if requested
     if getattr(args, "git_init", False):
         print("\nInitializing git repository...")
-        subprocess.run(["git", "init"], check=False)
-        subprocess.run(["git", "add", "-A"], check=False)
-        subprocess.run(["git", "commit", "-m", "Initial project setup from Claude Code Python Template"], check=False)
-        print("  Git repository initialized with initial commit")
+        try:
+            subprocess.run(["git", "init"], check=True, timeout=30)
+            subprocess.run(["git", "add", "-A"], check=True, timeout=30)
+            subprocess.run(
+                ["git", "commit", "-m", "Initial project setup from Claude Code Python Template"],
+                check=True,
+                timeout=30,
+            )
+            print("  Git repository initialized with initial commit")
+        except subprocess.CalledProcessError as e:
+            print(f"  Warning: Git operation failed: {' '.join(e.cmd)} (exit code {e.returncode})")
+        except subprocess.TimeoutExpired as e:
+            print(f"  Warning: Git operation timed out after 30s: {' '.join(e.cmd)}")
 
     # Step 6: Install Claude Code plugins
     print("\nInstalling Claude Code plugins...")
@@ -492,8 +501,6 @@ def main() -> None:
                 print("  Installed security-guidance plugin")
             else:
                 print("  Warning: Failed to install security-guidance plugin")
-                if result.stderr:
-                    print(f"  Error: {result.stderr.strip()}")
                 print("  Run manually: claude plugin install security-guidance --scope project")
         except subprocess.TimeoutExpired:
             print("  Warning: Plugin installation timed out")
diff --git a/tests/test_permissions.py b/tests/test_permissions.py
index 0d3446b..17d135f 100644
--- a/tests/test_permissions.py
+++ b/tests/test_permissions.py
@@ -281,6 +281,27 @@ def test_secret_management_is_denied(self, settings: dict[str, Any]) -> None:
         assert evaluate("Bash(gh secret list)", settings) == "deny"
         assert evaluate("Bash(gh secret set TOKEN)", settings) == "deny"
 
+    def test_gh_auth_is_denied(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(gh auth login)", settings) == "deny"
+        assert evaluate("Bash(gh auth status)", settings) == "deny"
+
+    def test_gh_ssh_key_is_denied(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(gh ssh-key add key.pub)", settings) == "deny"
+
+    def test_gh_gpg_key_is_denied(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(gh gpg-key add key.gpg)", settings) == "deny"
+
+    def test_git_clean_is_denied(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(git clean -fd)", settings) == "deny"
+        assert evaluate("Bash(git clean -xfd)", settings) == "deny"
+
+    def test_git_config_is_denied(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(git config user.email foo@bar.com)", settings) == "deny"
+        assert evaluate("Bash(git config --global core.editor vim)", settings) == "deny"
+
+    def test_uv_self_is_denied(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(uv self update)", settings) == "deny"
+
     def test_rm_rf_is_not_allowed(self, settings: dict[str, Any]) -> None:
         assert evaluate("Bash(rm -rf /)", settings) != "allow"
 
@@ -307,6 +328,44 @@ def test_pr_merge_requires_confirmation(self, settings: dict[str, Any]) -> None:
     def test_workflow_run_requires_confirmation(self, settings: dict[str, Any]) -> None:
         assert evaluate("Bash(gh workflow run deploy.yml)", settings) == "ask"
 
+    def test_git_reset_requires_confirmation(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(git reset --hard HEAD~1)", settings) == "ask"
+        assert evaluate("Bash(git reset HEAD file.py)", settings) == "ask"
+
+    def test_git_destructive_operations_require_confirmation(self, settings: dict[str, Any]) -> None:
+        for cmd in ["git init", "git clone https://github.com/repo", "git rm file.py", "git mv a.py b.py"]:
+            assert evaluate(f"Bash({cmd})", settings) == "ask", f"{cmd} should require confirmation"
+
+    def test_git_restore_requires_confirmation(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(git restore file.py)", settings) == "ask"
+        assert evaluate("Bash(git restore --staged file.py)", settings) == "ask"
+
+    def test_gh_issue_mutations_require_confirmation(self, settings: dict[str, Any]) -> None:
+        for cmd in ["gh issue create --title bug", "gh issue comment 5 --body fix", "gh issue close 5", "gh issue edit 5"]:
+            assert evaluate(f"Bash({cmd})", settings) == "ask", f"{cmd} should require confirmation"
+
+    def test_gh_pr_reopen_requires_confirmation(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(gh pr reopen 42)", settings) == "ask"
+
+    def test_gh_pr_merge_auto_requires_confirmation(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(gh pr merge --auto 42)", settings) == "ask"
+
+    def test_gh_workflow_enable_disable_requires_confirmation(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(gh workflow enable deploy.yml)", settings) == "ask"
+        assert evaluate("Bash(gh workflow disable deploy.yml)", settings) == "ask"
+
+    def test_git_worktree_requires_confirmation(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(git worktree add ../feature)", settings) == "ask"
+
+    def test_uv_init_requires_confirmation(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(uv init my-project)", settings) == "ask"
+
+    def test_uv_remove_requires_confirmation(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(uv remove requests)", settings) == "ask"
+
+    def test_uv_cache_requires_confirmation(self, settings: dict[str, Any]) -> None:
+        assert evaluate("Bash(uv cache clean)", settings) == "ask"
+
 
 # ---------------------------------------------------------------------------
 # 6. Evaluation Order (end-to-end)
@@ -329,7 +388,11 @@ def test_unmatched_command_returns_none(self, settings: dict[str, Any]) -> None:
         assert evaluate("Bash(curl https://example.com)", settings) == "none"
 
     def test_git_read_operations_are_allowed(self, settings: dict[str, Any]) -> None:
-        for cmd in ["git status", "git log --oneline", "git diff HEAD"]:
+        for cmd in [
+            "git status", "git log --oneline", "git diff HEAD",
+            "git blame src/main.py", "git reflog", "git ls-files",
+            "git describe --tags", "git shortlog -sn", "git rev-list HEAD",
+        ]:
             assert evaluate(f"Bash({cmd})", settings) == "allow", f"{cmd} should be allowed"
 
     def test_git_write_operations_are_allowed(self, settings: dict[str, Any]) -> None:
@@ -364,3 +427,19 @@ def test_chained_commands_fall_through(self, settings: dict[str, Any]) -> None:
 
     def test_gh_api_is_allowed(self, settings: dict[str, Any]) -> None:
         assert evaluate("Bash(gh api repos/owner/repo/pulls)", settings) == "allow"
+
+    def test_gh_pr_review_operations_require_confirmation(self, settings: dict[str, Any]) -> None:
+        """PR comment/review/ready are state-changing and have data exfiltration risk."""
+        for cmd in ["gh pr comment 5 --body lgtm", "gh pr review 5 --approve", "gh pr ready 5"]:
+            assert evaluate(f"Bash({cmd})", settings) == "ask", f"{cmd} should require confirmation"
+
+    def test_gh_read_only_operations_are_allowed(self, settings: dict[str, Any]) -> None:
+        for cmd in [
+            "gh repo view", "gh release list", "gh release view v1.0",
+            "gh label list", "gh browse", "gh search repos python",
+        ]:
+            assert evaluate(f"Bash({cmd})", settings) == "allow", f"{cmd} should be allowed"
+
+    def test_uv_read_operations_are_allowed(self, settings: dict[str, Any]) -> None:
+        for cmd in ["uv lock", "uv tree", "uv export --format requirements-txt"]:
+            assert evaluate(f"Bash({cmd})", settings) == "allow", f"{cmd} should be allowed"
