Unverified 提交 e25a24de authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Reorganize Claude commands and add permission hooks (#2305)

## Summary - Move Claude commands to `dyad/` namespace (e2e-rebase, pr-fix) - Add new commands: fix-issue, gh-push, gh-rebase, lint, session-debug - Add `gh-permission-hook.py` to block destructive gh commands (except PRs) - Expand allowed bash commands in settings.json - Update AGENTS.md to reference `/dyad:lint` skill and fix typo ## Test plan - [ ] Verify commands work with `/dyad:<command>` syntax - [ ] Test that gh-permission-hook blocks issue modifications but allows PR operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Reorganized Claude commands under the /dyad namespace and added a GitHub CLI permission hook that auto-approves read-only commands plus PR and issue updates while blocking destructive actions. Added new skills (including deflaking E2E and split PR fix steps), hardened the hook, and added tests. - **New Features** - Moved e2e-rebase and pr-fix to dyad/; added fix-issue, pr-push, gh-rebase, lint, session-debug, deflake-e2e; split pr-fix into pr-fix:comments and pr-fix:actions. - Updated AGENTS.md to reference /dyad:lint and fixed a typo. - **Permissions** - Added gh-permission-hook.py (PreToolUse) for Bash gh commands; auto-approves read-only ops, PR modification commands, issue create/edit/close/reopen/comment, PR review thread ops, and PR/issue comment replies and updates; blocks destructive actions across issues, releases, gists, labels, secrets, repos, workflows, config, and auth. - Hardened checks: require gh as the executed command (handles env/sudo/command wrappers); detect shell injection (;, &&/||, &, newlines, ANSI-C $'…', process substitution); allow safe pipes to jq and common text filters (head/tail/grep/wc/sort/uniq/cut/tr) and stderr redirects; parse --method/-X (incl. equals syntax); allow GraphQL queries plus specific PR review thread/comment mutations; added unit tests with good/bad fixtures. - Updated settings.json to narrow read-only gh allowlist, expand safe Bash commands, and register the PreToolUse hook. <sup>Written for commit 3237d344cdc2850a97a9a4856bff54bd25be102b. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Streamlines Claude command suite and hardens GitHub CLI usage in the workspace. > > - Reorganizes commands under `/.claude/commands/dyad/` and adds new skills: `deflake-e2e`, `fix-issue`, `gh-rebase`, `lint`, `pr-push`, `session-debug`; splits `pr-fix` into `pr-fix:comments` and `pr-fix:actions` > - Adds `/.claude/hooks/gh-permission-hook.py` to auto-approve read-only/PR operations and block destructive `gh` commands; supports GraphQL query/limited mutations and safe piping > - Introduces unit tests (`.claude/hooks/tests/*`) with good/bad command fixtures and a test runner > - Updates `.claude/settings.json` to expand safe Bash allowlist and register `PreToolUse` hook; tweaks `.gitignore` and `AGENTS.md` (references `/dyad:lint`) > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 3237d344cdc2850a97a9a4856bff54bd25be102b. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com>
上级 eddedf2b
# Deflake E2E Tests
Identify and fix flaky E2E tests by running them repeatedly and investigating failures.
## Arguments
- `$ARGUMENTS`: (Optional) Specific E2E test file(s) to deflake (e.g., `main.spec.ts` or `e2e-tests/main.spec.ts`). If not provided, will prompt to deflake the entire test suite.
## Instructions
1. **Check if specific tests are provided:**
If `$ARGUMENTS` is empty or not provided, ask the user:
> "No specific tests provided. Do you want to deflake the entire E2E test suite? This can take a very long time as each test will be run 10 times."
Wait for user confirmation before proceeding. If they decline, ask them to provide specific test files.
2. **Install dependencies:**
```
npm install
```
3. **Build the app binary:**
```
npm run pre:e2e
```
This step is required before running E2E tests.
4. **Run tests repeatedly to detect flakiness:**
For each test file, run it 10 times:
```
npm run e2e -- e2e-tests/<testfile>.spec.ts --repeat-each=10
```
Notes:
- If `$ARGUMENTS` is provided without the `e2e-tests/` prefix, add it
- If `$ARGUMENTS` is provided without the `.spec.ts` suffix, add it
- A test is considered **flaky** if it fails at least once out of 10 runs
5. **For each flaky test, investigate with debug logs:**
Run the failing test with Playwright browser debugging enabled:
```
DEBUG=pw:browser npm run e2e -- e2e-tests/<testfile>.spec.ts
```
Analyze the debug output to understand:
- Timing issues (race conditions, elements not ready)
- Animation/transition interference
- Network timing variability
- State leaking between tests
- Snapshot comparison differences
6. **Fix the flaky test:**
Common fixes following Playwright best practices:
- Use `await expect(locator).toBeVisible()` before interacting with elements
- Use `await page.waitForLoadState('networkidle')` for network-dependent tests
- Use stable selectors (data-testid, role, text) instead of fragile CSS selectors
- Add explicit waits for animations: `await page.waitForTimeout(300)` (use sparingly)
- Use `await expect(locator).toHaveScreenshot()` options like `maxDiffPixelRatio` for visual tests
- Ensure proper test isolation (clean state before/after tests)
**IMPORTANT:** Do NOT change any application code. Assume the application code is correct. Only modify test files and snapshot baselines.
7. **Update snapshot baselines if needed:**
If the flakiness is due to legitimate visual differences:
```
npm run e2e -- e2e-tests/<testfile>.spec.ts --update-snapshots
```
8. **Verify the fix:**
Re-run the test 10 times to confirm it's no longer flaky:
```
npm run e2e -- e2e-tests/<testfile>.spec.ts --repeat-each=10
```
The test should pass all 10 runs consistently.
9. **Summarize results:**
Report to the user:
- Which tests were identified as flaky
- What was causing the flakiness
- What fixes were applied
- Verification results (all 10 runs passing)
- Any tests that could not be fixed and need further investigation
# Fix Issue
Create a plan to fix a GitHub issue, then send it to be worked on remotely after approval.
## Arguments
- `$ARGUMENTS`: GitHub issue number or URL.
## Instructions
1. **Fetch the GitHub issue:**
First, extract the issue number from `$ARGUMENTS`:
- If `$ARGUMENTS` is a number (e.g., `123`), use it directly
- If `$ARGUMENTS` is a URL (e.g., `https://github.com/owner/repo/issues/123`), extract the issue number from the path
Then fetch the issue:
```
gh issue view <issue-number> --json title,body,comments,labels,assignees
```
2. **Analyze the issue:**
- Understand what the issue is asking for
- Identify the type of work (bug fix, feature, refactor, etc.)
- Note any specific requirements or constraints mentioned
3. **Explore the codebase:**
- Search for relevant files and code related to the issue
- Understand the current implementation
- Identify what needs to change
- Look at existing tests to understand testing patterns used in the project
4. **Determine testing approach:**
Consider what kind of testing is appropriate for this change:
- **E2E test**: For user-facing features or complete user flows. Prefer this when the change involves UI interactions or would require mocking many dependencies to unit test.
- **Unit test**: For pure business logic, utility functions, or isolated components.
- **No new tests**: Only for trivial changes (typos, config tweaks, etc.)
Note: Per project guidelines, avoid writing many E2E tests for one feature. Prefer one or two E2E tests with broad coverage. If unsure, ask the user for guidance on testing approach.
5. **Create a detailed plan:**
Write a plan that includes:
- **Summary**: Brief description of the issue and proposed solution
- **Files to modify**: List of files that will need changes
- **Implementation steps**: Ordered list of specific changes to make
- **Testing approach**: What tests to add (E2E, unit, or none) and why
- **Potential risks**: Any concerns or edge cases to consider
6. **Request plan approval:**
Present the plan to the user and use `ExitPlanMode` to request approval. The plan should be clear enough that it can be executed without further clarification.
7. **Ask how to proceed:**
After the plan is approved, ask the user whether they want to:
- **Continue locally**: Implement the plan in the current session
- **Send to remote**: Push to a remote Claude session for implementation
8. **Execute based on user choice:**
- If **local**: Proceed to implement the plan step by step, then run `/dyad:pr-push` when complete
- If **remote**: Use `ExitPlanMode` with `pushToRemote: true` and share the remote session URL with the user
# GitHub Rebase
Rebase the current branch on the latest upstream changes, resolve conflicts, and push.
## Instructions
1. **Determine the base branch:**
```
git remote -v
git branch -vv
```
Identify which remote and branch the current branch is tracking or should rebase onto (typically `main` or `master` from `upstream` or `origin`).
2. **Fetch the latest changes:**
```
git fetch --all
```
3. **Rebase onto the base branch:**
```
git rebase <remote>/<base-branch>
```
For example: `git rebase upstream/main`
4. **If there are merge conflicts:**
- Identify the conflicting files from the rebase output
- Read each conflicting file and understand both versions of the changes
- Resolve the conflicts by editing the files to combine changes appropriately
- Stage the resolved files:
```
git add <resolved-file>
```
- Continue the rebase:
```
git rebase --continue
```
- Repeat until all conflicts are resolved and the rebase completes
5. **Run lint and push:**
Run the `/dyad:pr-push` skill to run lint checks, fix any issues, and push the rebased branch.
6. **Summarize the results:**
- Report that the rebase was successful
- List any conflicts that were resolved
- Note any lint fixes that were applied
- Confirm the branch has been pushed
# Lint
Run pre-commit checks including formatting, linting, and type-checking, and fix any errors.
## Instructions
1. **Run formatting check and fix:**
```
npm run prettier
```
This will automatically fix any formatting issues.
2. **Run linting with auto-fix:**
```
npm run lint:fix
```
This will fix any auto-fixable lint errors.
3. **Fix remaining lint errors manually:**
If there are lint errors that could not be auto-fixed, read the affected files and fix the errors manually. Common issues include:
- Unused variables or imports (remove them)
- Missing return types (add them)
- Any other ESLint rule violations
4. **Run type-checking:**
```
npm run ts
```
5. **Fix any type errors:**
If there are type errors, read the affected files and fix them. Common issues include:
- Type mismatches (correct the types)
- Missing type annotations (add them)
- Null/undefined handling issues (add appropriate checks)
6. **Re-run all checks to verify:**
After making manual fixes, re-run the checks to ensure everything passes:
```
npm run prettier && npm run lint && npm run ts
```
7. **Summarize the results:**
- Report which checks passed
- List any fixes that were made manually
- If any errors could not be fixed, explain why and ask the user for guidance
- If all checks pass, confirm the code is ready to commit
# PR Fix
Address all outstanding issues on a GitHub Pull Request by handling both review comments and failing CI checks.
## Arguments
- `$ARGUMENTS`: Optional PR number or URL. If not provided, uses the current branch's PR.
## Instructions
This is a meta-skill that orchestrates two sub-skills to comprehensively fix PR issues.
1. **Run `/dyad:pr-fix:comments`** to handle all unresolved review comments:
- Address valid code review concerns
- Resolve invalid concerns with explanations
- Flag ambiguous issues for human attention
2. **Run `/dyad:pr-fix:actions`** to handle failing CI checks:
- Fix failing tests (unit and E2E)
- Update snapshots if needed
- Ensure all checks pass
3. **Summary:**
After both sub-skills complete, provide a consolidated summary of:
- Review comments addressed, resolved, or flagged
- CI checks that were fixed
- Any remaining issues requiring human attention
# PR Fix: Actions
Fix failing CI checks and GitHub Actions on a Pull Request.
## Arguments
- `$ARGUMENTS`: Optional PR number or URL. If not provided, uses the current branch's PR.
## Instructions
1. **Determine the PR to work on:**
- If `$ARGUMENTS` contains a PR number or URL, use that
- Otherwise, get the current branch's PR using `gh pr view --json number,url,title,body --jq '.'`
- If no PR is found, inform the user and stop
2. **Check for failing CI checks:**
```
gh pr checks <PR_NUMBER>
```
Identify which checks are failing:
- Lint/formatting checks
- Type checks
- Unit tests
- E2E/Playwright tests
- Build checks
3. **For failing lint/formatting checks:**
- Run `npm run lint:fix` to auto-fix lint issues
- Run `npm run prettier` to fix formatting
- Review the changes made
4. **For failing type checks:**
- Run `npm run ts` to identify type errors
- Read the relevant files and fix the type issues
- Re-run type checks to verify fixes
5. **For failing unit tests:**
- Run the failing tests locally to reproduce:
```
npm run test -- <test-file-pattern>
```
- Investigate the test failures
- Fix the underlying code issues or update tests if the behavior change is intentional
6. **For failing Playwright/E2E tests:**
- Check if the failures are snapshot-related by examining the CI logs or PR comments
- If snapshots need updating, run the `/dyad:e2e-rebase` skill to fix them
- If the failures are not snapshot-related:
- Run the failing tests locally with debug output:
```
DEBUG=pw:browser npm run e2e -- <test-file>
```
- Investigate and fix the underlying issues
7. **For failing build checks:**
- Run the build locally:
```
npm run build
```
- Fix any build errors that appear
8. **After making all fixes, verify:**
- Run the full lint check: `npm run lint`
- Run type checks: `npm run ts`
- Run relevant unit tests
- Optionally run E2E tests locally if they were failing
9. **Commit and push the changes:**
If any changes were made:
```
git add -A
git commit -m "Fix failing CI checks
- <summary of fix 1>
- <summary of fix 2>
...
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
```
Then run `/dyad:pr-push` to push the changes.
10. **Provide a summary to the user:**
- List which checks were failing
- Describe what was fixed for each
- Note any checks that could not be fixed and require human attention
# PR Fix: Comments
Read all unresolved GitHub PR comments and address or resolve them appropriately.
## Arguments
- `$ARGUMENTS`: Optional PR number or URL. If not provided, uses the current branch's PR.
## Instructions
1. **Determine the PR to work on:**
- If `$ARGUMENTS` is provided:
- If it's a number (e.g., `123`), use it as the PR number
- If it's a URL (e.g., `https://github.com/owner/repo/pull/123`), extract the PR number from the path
- Otherwise, get the current branch's PR using `gh pr view --json number,url,title,body --jq '.'`
- If no PR is found, inform the user and stop
2. **Fetch all unresolved PR review threads:**
Use the GitHub GraphQL API to get all review threads and their resolution status:
```
gh api graphql -f query='
query($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
reviewThreads(first: 100) {
nodes {
id
isResolved
isOutdated
path
line
comments(first: 10) {
nodes {
id
databaseId
body
author { login }
createdAt
}
}
}
}
}
}
}
' -f owner=OWNER -f repo=REPO -F pr=PR_NUMBER
```
Filter to only unresolved threads (`isResolved: false`).
3. **For each unresolved review thread, categorize it:**
Read the comment(s) in the thread and determine which category it falls into:
- **Valid issue**: A legitimate code review concern that should be addressed (bug, improvement, style issue, etc.)
- **Not a valid issue**: The reviewer may have misunderstood something, the concern is already addressed elsewhere, or the suggestion conflicts with project requirements
- **Ambiguous**: The comment is unclear, requires significant discussion, or involves a judgment call that needs human input
4. **Handle each category:**
**For valid issues:**
- Read the relevant file(s) mentioned in the comment
- Understand the context and the requested change
- Make the necessary code changes to address the feedback
- The thread will be marked as resolved when the code is pushed (GitHub auto-resolves when the code changes)
**For not valid issues:**
- Reply to the thread explaining why the concern doesn't apply:
```
gh api repos/{owner}/{repo}/pulls/<PR_NUMBER>/comments/<COMMENT_ID>/replies \
-f body="<explanation of why this doesn't need to be addressed>"
```
Note: `{owner}` and `{repo}` are auto-replaced by `gh` CLI. Replace `<PR_NUMBER>` with the PR number and `<COMMENT_ID>` with the **first comment's `databaseId`** from the thread's `comments.nodes[0].databaseId` field in the GraphQL response (not the thread's `id`).
- Resolve the thread using GraphQL:
```
gh api graphql -f query='
mutation($threadId: ID!) {
resolveReviewThread(input: {threadId: $threadId}) {
thread { isResolved }
}
}
' -f threadId=<THREAD_ID>
```
Note: Replace `<THREAD_ID>` with the thread's `id` field from the GraphQL response.
**For ambiguous issues:**
- Reply to the thread flagging it for human attention:
```
gh api repos/{owner}/{repo}/pulls/<PR_NUMBER>/comments/<COMMENT_ID>/replies \
-f body="🚩 **Flagged for human review**: <explanation of why this needs human input>"
```
Note: Replace `<PR_NUMBER>` with the PR number and `<COMMENT_ID>` with the **first comment's `databaseId`** from the thread's `comments.nodes[0].databaseId` field in the GraphQL response.
- Do NOT resolve the thread - leave it open for discussion
5. **After processing all comments, verify and commit changes:**
If any code changes were made:
- Run `/dyad:lint` to ensure code passes all checks
- Stage and commit the changes:
```
git add -A
git commit -m "Address PR review comments
- <summary of change 1>
- <summary of change 2>
...
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
```
6. **Push the changes:**
Run the `/dyad:pr-push` skill to lint, fix any issues, and push.
7. **Provide a summary to the user:**
Report:
- **Addressed**: List of comments that were fixed with code changes
- **Resolved (not valid)**: List of comments that were resolved with explanations
- **Flagged for human attention**: List of ambiguous comments left open
- Any issues encountered during the process
# PR Push
Commit any uncommitted changes, run lint checks, fix any issues, and push the current branch.
## Instructions
1. **Check for uncommitted changes:**
Run `git status` to check for any uncommitted changes (staged, unstaged, or untracked files).
If there are uncommitted changes:
- Identify files that should NOT be committed (e.g., `.env`, `.env.*`, `credentials.*`, `*.secret`, `*.key`, `*.pem`, `.DS_Store`, `node_modules/`, `*.log`, temporary files, or anything that looks like it contains secrets or personal configuration)
- Stage and commit all OTHER files with a descriptive commit message summarizing the changes
- Keep track of any files you ignored so you can report them at the end
If there are no uncommitted changes, proceed to the next step.
2. **Run lint checks:**
Run the `/dyad:lint` skill to ensure the code passes all pre-commit checks. Fix any issues that arise.
3. **If lint made changes, amend the last commit:**
If the lint skill made any changes, stage and amend them into the last commit:
```
git add -A
git commit --amend --no-edit
```
4. **Push the branch:**
```
git push --force-with-lease
```
Note: `--force-with-lease` is used because the commit may have been amended. It's safer than `--force` as it will fail if someone else has pushed to the branch.
5. **Summarize the results:**
- Report any uncommitted changes that were committed in step 1
- Report any files that were IGNORED and not committed (if any), explaining why they were skipped
- Report any lint fixes that were applied
- Confirm the branch has been pushed
# Session Debug
Analyze session debugging data to identify errors and issues that may have caused a user-reported problem.
## Arguments
- `$ARGUMENTS`: Two space-separated arguments expected:
1. URL to a JSON file containing session debugging data (starts with `http://` or `https://`)
2. GitHub issue number or URL
## Instructions
1. **Parse and validate the arguments:**
Split `$ARGUMENTS` on whitespace to get exactly two arguments:
- First argument: session data URL (must start with `http://` or `https://`)
- Second argument: GitHub issue identifier (number like `123` or full URL like `https://github.com/owner/repo/issues/123`)
**Validation:** If fewer than two arguments are provided, inform the user:
> "Usage: /dyad:session-debug <session-data-url> <issue-number-or-url>"
> "Example: /dyad:session-debug https://example.com/session.json 123"
Then stop execution.
2. **Fetch the GitHub issue:**
```
gh issue view <issue-number> --json title,body,comments,labels
```
Understand:
- What problem the user is reporting
- Steps to reproduce (if provided)
- Expected vs actual behavior
- Any error messages the user mentioned
3. **Fetch the session debugging data:**
Use `WebFetch` to retrieve the JSON session data from the provided URL.
4. **Analyze the session data:**
Look for suspicious entries including:
- **Errors**: Any error messages, stack traces, or exception logs
- **Warnings**: Warning-level log entries that may indicate problems
- **Failed requests**: HTTP errors, timeout failures, connection issues
- **Unexpected states**: Null values where data was expected, empty responses
- **Timing anomalies**: Unusually long operations, timeouts
- **User actions before failure**: What the user did leading up to the issue
5. **Correlate with the reported issue:**
For each suspicious entry found, assess:
- Does the timing match when the user reported the issue occurring?
- Does the error message relate to the feature/area the user mentioned?
- Could this error cause the symptoms the user described?
6. **Rank the findings:**
Create a ranked list of potential causes, ordered by likelihood:
```
## Most Likely Causes
### 1. [Error/Issue Name]
- **Evidence**: What was found in the session data
- **Timestamp**: When it occurred
- **Correlation**: How it relates to the reported issue
- **Confidence**: High/Medium/Low
### 2. [Error/Issue Name]
...
```
7. **Provide recommendations:**
For each high-confidence finding, suggest:
- Where in the codebase to investigate
- Potential root causes
- Suggested fixes if apparent
8. **Summarize:**
- Total errors/warnings found
- Top 3 most likely causes
- Recommended next steps for investigation
# PR Fix
Address review comments and failing checks on a GitHub Pull Request.
## Arguments
- `$ARGUMENTS`: Optional PR number or URL. If not provided, uses the current branch's PR.
## Instructions
1. **Determine the PR to work on:**
- If `$ARGUMENTS` contains a PR number or URL, use that
- Otherwise, get the current branch's PR using `gh pr view --json number,url,title,body --jq '.'`
- If no PR is found, inform the user and stop
2. **Fetch all PR review comments:**
```
gh pr view <PR_NUMBER> --json reviews,comments --jq '.'
gh api repos/{owner}/{repo}/pulls/<PR_NUMBER>/comments
```
3. **Analyze the PR comments and identify actionable items:**
- Look for code review comments that request changes
- Look for general review comments with feedback
- Prioritize comments from reviewers that are:
- Requesting specific code changes
- Pointing out bugs or issues
- Suggesting improvements that should be addressed
- Ignore comments that are:
- Simple acknowledgments or approvals
- Questions that have already been answered
- Nitpicks explicitly marked as optional
4. **Check for failing CI checks:**
```
gh pr checks <PR_NUMBER>
```
Note which checks are failing, particularly Playwright/E2E tests.
5. **For each actionable review comment:**
- Read the relevant file(s) mentioned in the comment
- Understand the context and the requested change
- Make the necessary code changes to address the feedback
- Keep track of what was changed
6. **If there are failing Playwright/E2E tests:**
- Check if the failures are snapshot-related by examining the PR comments for Playwright test results
- If snapshots need updating, run the `/e2e-rebase` skill to fix them
- If the failures are not snapshot-related, investigate and fix the underlying test issues
7. **After making all changes, verify the fixes:**
- Run relevant linters: `npm run lint:fix`
- Run type checks if TypeScript files were modified: `npm run ts`
- Run any relevant unit tests for modified code
8. **Review all changes made:**
```
git diff
git status
```
Ensure the changes are reasonable and address the review feedback appropriately.
9. **Commit and push the changes:**
- Stage all modified files
- Create a commit with a descriptive message summarizing what was addressed:
```
git add -A
git commit -m "Address PR review feedback
- <summary of change 1>
- <summary of change 2>
...
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>"
git push
```
10. **Provide a summary to the user:**
- List the review comments that were addressed
- List any failing checks that were fixed
- Note any comments that were intentionally not addressed and why
- Mention if any issues could not be resolved and require human attention
差异被折叠。
# gh-permission-hook Tests
Unit tests for the GitHub CLI permission hook.
## Running Tests
```sh
python3 .claude/hooks/tests/test_gh_permission_hook.py
```
Or from the tests directory:
```sh
cd .claude/hooks/tests
python3 test_gh_permission_hook.py
```
## Test Files
- **good_commands.txt**: Commands that should be **allowed** by the hook (auto-approved or passed through for manual approval). These include read-only operations and explicitly allowed PR modification commands.
- **bad_commands.txt**: Commands that should be **blocked** by the hook. These include destructive operations, shell injection attempts, and operations that could modify issues, releases, repos, etc.
## Adding Test Cases
To add new test cases, simply add commands to the appropriate file:
- Add safe commands to `good_commands.txt`
- Add dangerous commands to `bad_commands.txt`
Lines starting with `#` are treated as comments and ignored.
差异被折叠。
差异被折叠。
#!/usr/bin/env python3
"""
Unit tests for gh-permission-hook.py
This test loads commands from good_commands.txt and bad_commands.txt
and verifies that the hook correctly allows/denies them.
Run with: python .claude/hooks/tests/test_gh_permission_hook.py
"""
import json
import subprocess
import sys
from pathlib import Path
def load_commands(filename: str) -> list[str]:
"""Load commands from a file, ignoring comments and empty lines."""
filepath = Path(__file__).parent / filename
commands = []
with open(filepath, "r") as f:
for line in f:
line = line.strip()
# Skip empty lines and comments
if line and not line.startswith("#"):
commands.append(line)
return commands
def run_hook(command: str) -> dict:
"""
Run the permission hook with the given command and return the result.
Returns a dict with:
- 'decision': 'allow', 'deny', or 'none' (no decision/passthrough)
- 'reason': the reason string if a decision was made
"""
hook_path = Path(__file__).parent.parent / "gh-permission-hook.py"
input_data = json.dumps({
"tool_name": "Bash",
"tool_input": {
"command": command
}
})
result = subprocess.run(
[sys.executable, str(hook_path)],
input=input_data,
capture_output=True,
text=True
)
if result.stdout.strip():
try:
output = json.loads(result.stdout.strip())
hook_output = output.get("hookSpecificOutput", {})
return {
"decision": hook_output.get("permissionDecision", "none"),
"reason": hook_output.get("permissionDecisionReason", "")
}
except json.JSONDecodeError:
return {"decision": "none", "reason": f"Invalid JSON output: {result.stdout}"}
return {"decision": "none", "reason": "No output (passthrough)"}
def test_good_commands() -> tuple[int, int, list[str]]:
"""Test that good commands are allowed or passed through."""
commands = load_commands("good_commands.txt")
passed = 0
failed = 0
failures = []
for cmd in commands:
result = run_hook(cmd)
# Good commands should be 'allow' or 'none' (passthrough for manual approval)
# They should NOT be 'deny'
if result["decision"] == "deny":
failed += 1
failures.append(f" FAIL (blocked): {cmd}\n Reason: {result['reason']}")
else:
passed += 1
return passed, failed, failures
def test_bad_commands() -> tuple[int, int, list[str]]:
"""Test that bad commands are denied."""
commands = load_commands("bad_commands.txt")
passed = 0
failed = 0
failures = []
for cmd in commands:
result = run_hook(cmd)
# Bad commands should be 'deny'
# 'allow' is definitely wrong, 'none' means it wasn't caught
if result["decision"] != "deny":
failed += 1
failures.append(f" FAIL (not blocked): {cmd}\n Decision: {result['decision']}, Reason: {result['reason']}")
else:
passed += 1
return passed, failed, failures
def main():
print("=" * 60)
print("Testing gh-permission-hook.py")
print("=" * 60)
print()
# Test good commands
print("Testing GOOD commands (should be allowed)...")
good_passed, good_failed, good_failures = test_good_commands()
print(f" Passed: {good_passed}, Failed: {good_failed}")
if good_failures:
print("\n Failures:")
for failure in good_failures:
print(failure)
print()
# Test bad commands
print("Testing BAD commands (should be blocked)...")
bad_passed, bad_failed, bad_failures = test_bad_commands()
print(f" Passed: {bad_passed}, Failed: {bad_failed}")
if bad_failures:
print("\n Failures:")
for failure in bad_failures:
print(failure)
print()
# Summary
print("=" * 60)
total_passed = good_passed + bad_passed
total_failed = good_failed + bad_failed
print(f"TOTAL: {total_passed} passed, {total_failed} failed")
print("=" * 60)
if total_failed > 0:
sys.exit(1)
else:
print("\nAll tests passed!")
sys.exit(0)
if __name__ == "__main__":
main()
...@@ -3,12 +3,52 @@ ...@@ -3,12 +3,52 @@
"allow": [ "allow": [
"Edit", "Edit",
"Write", "Write",
"Skill(dyad:*)",
"Bash(npm run:*)", "Bash(npm run:*)",
"Bash(npm test:*)",
"Bash(npm install:*)", "Bash(npm install:*)",
"Bash(npm update:*)",
"Bash(npm ls:*)",
"Bash(DEBUG=pw:browser npm run e2e:*)",
"Bash(npx playwright show-trace:*)",
"Bash(git:*)", "Bash(git:*)",
"Bash(gh pr:*)",
"Bash(gh issue:*)", "Bash(gh api:*)",
"Bash(gh pr view:*)",
"Bash(gh pr list:*)",
"Bash(gh pr status:*)",
"Bash(gh pr diff:*)",
"Bash(gh pr checks:*)",
"Bash(gh pr create:*)",
"Bash(gh pr edit:*)",
"Bash(gh pr ready:*)",
"Bash(gh pr review:*)",
"Bash(gh pr close:*)",
"Bash(gh pr reopen:*)",
"Bash(gh pr merge:*)",
"Bash(gh pr comment:*)",
"Bash(gh issue view:*)",
"Bash(gh issue list:*)",
"Bash(gh issue status:*)",
"Bash(gh issue create:*)",
"Bash(gh issue edit:*)",
"Bash(gh issue close:*)",
"Bash(gh issue reopen:*)",
"Bash(gh issue comment:*)",
"Bash(gh repo view:*)", "Bash(gh repo view:*)",
"Bash(gh run view:*)",
"Bash(gh run list:*)",
"Bash(ps:*)",
"Bash(lsof:*)",
"Bash(pkill:*)",
"Bash(jq:*)",
"Bash(which:*)",
"Bash(echo:*)",
"Bash(pwd:*)",
"Bash(ls:*)", "Bash(ls:*)",
"Bash(find:*)", "Bash(find:*)",
...@@ -18,6 +58,7 @@ ...@@ -18,6 +58,7 @@
"Bash(wc:*)", "Bash(wc:*)",
"Bash(head:*)", "Bash(head:*)",
"Bash(tail:*)", "Bash(tail:*)",
"Bash(xargs cat:*)",
"Bash(cat:*)", "Bash(cat:*)",
"Bash(less:*)", "Bash(less:*)",
"Bash(file:*)", "Bash(file:*)",
...@@ -33,5 +74,19 @@ ...@@ -33,5 +74,19 @@
"Bash(cut:*)", "Bash(cut:*)",
"Bash(diff:*)" "Bash(diff:*)"
] ]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/gh-permission-hook.py",
"timeout": 5000
}
]
}
]
} }
} }
...@@ -101,4 +101,10 @@ out/ ...@@ -101,4 +101,10 @@ out/
sqlite.db sqlite.db
userData/ userData/
.env.local .env.local
.idea/ .idea/
\ No newline at end of file
# Claude Code
.claude/settings.local.json
# Python
__pycache__/
\ No newline at end of file
...@@ -14,6 +14,14 @@ npm run init-precommit ...@@ -14,6 +14,14 @@ npm run init-precommit
RUN THE FOLLOWING CHECKS before you do a commit. RUN THE FOLLOWING CHECKS before you do a commit.
If you have access to the `/dyad:lint` skill, use it to run all pre-commit checks automatically:
```
/dyad:lint
```
Otherwise, run the following commands directly:
**Formatting** **Formatting**
```sh ```sh
...@@ -32,7 +40,7 @@ If you get any lint errors, you can usually fix it by doing: ...@@ -32,7 +40,7 @@ If you get any lint errors, you can usually fix it by doing:
npm run lint:fix npm run lint:fix
``` ```
**\*Type-checks** **Type-checks**
```sh ```sh
npm run ts npm run ts
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论