Initial import from garrytan/gstack@026751e (main snapshot via local relay)
Some checks failed
Workflow Lint / actionlint (push) Has been cancelled
Build CI Image / build (push) Has been cancelled
Skill Docs Freshness / check-freshness (push) Has been cancelled
Periodic Evals / build-image (push) Has been cancelled
Periodic Evals / evals (map[file:test/codex-e2e.test.ts name:e2e-codex]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/gemini-e2e.test.ts name:e2e-gemini]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-design.test.ts name:e2e-design]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-plan.test.ts name:e2e-plan]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-bugs.test.ts name:e2e-qa-bugs]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-workflow.test.ts name:e2e-qa-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-review.test.ts name:e2e-review]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-workflow.test.ts name:e2e-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-routing-e2e.test.ts name:e2e-routing]) (push) Has been cancelled
Some checks failed
Workflow Lint / actionlint (push) Has been cancelled
Build CI Image / build (push) Has been cancelled
Skill Docs Freshness / check-freshness (push) Has been cancelled
Periodic Evals / build-image (push) Has been cancelled
Periodic Evals / evals (map[file:test/codex-e2e.test.ts name:e2e-codex]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/gemini-e2e.test.ts name:e2e-gemini]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-design.test.ts name:e2e-design]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-plan.test.ts name:e2e-plan]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-bugs.test.ts name:e2e-qa-bugs]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-qa-workflow.test.ts name:e2e-qa-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-review.test.ts name:e2e-review]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-e2e-workflow.test.ts name:e2e-workflow]) (push) Has been cancelled
Periodic Evals / evals (map[file:test/skill-routing-e2e.test.ts name:e2e-routing]) (push) Has been cancelled
Source: https://github.com/garrytan/gstack/commit/026751e
This commit is contained in:
87
freeze/SKILL.md
Normal file
87
freeze/SKILL.md
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: freeze
|
||||
version: 0.1.0
|
||||
description: |
|
||||
Restrict file edits to a specific directory for the session. Blocks Edit and
|
||||
Write outside the allowed path. Use when debugging to prevent accidentally
|
||||
"fixing" unrelated code, or when you want to scope changes to one module.
|
||||
Use when asked to "freeze", "restrict edits", "only edit this folder",
|
||||
or "lock down edits". (gstack)
|
||||
triggers:
|
||||
- freeze edits to directory
|
||||
- lock editing scope
|
||||
- restrict file changes
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- AskUserQuestion
|
||||
hooks:
|
||||
PreToolUse:
|
||||
- matcher: "Edit"
|
||||
hooks:
|
||||
- type: command
|
||||
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-freeze.sh"
|
||||
statusMessage: "Checking freeze boundary..."
|
||||
- matcher: "Write"
|
||||
hooks:
|
||||
- type: command
|
||||
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-freeze.sh"
|
||||
statusMessage: "Checking freeze boundary..."
|
||||
---
|
||||
<!-- AUTO-GENERATED from SKILL.md.tmpl — do not edit directly -->
|
||||
<!-- Regenerate: bun run gen:skill-docs -->
|
||||
|
||||
# /freeze — Restrict Edits to a Directory
|
||||
|
||||
Lock file edits to a specific directory. Any Edit or Write operation targeting
|
||||
a file outside the allowed path will be **blocked** (not just warned).
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"freeze","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
Ask the user which directory to restrict edits to. Use AskUserQuestion:
|
||||
|
||||
- Question: "Which directory should I restrict edits to? Files outside this path will be blocked from editing."
|
||||
- Text input (not multiple choice) — the user types a path.
|
||||
|
||||
Once the user provides a directory path:
|
||||
|
||||
1. Resolve it to an absolute path:
|
||||
```bash
|
||||
FREEZE_DIR=$(cd "<user-provided-path>" 2>/dev/null && pwd)
|
||||
echo "$FREEZE_DIR"
|
||||
```
|
||||
|
||||
2. Ensure trailing slash and save to the freeze state file:
|
||||
```bash
|
||||
FREEZE_DIR="${FREEZE_DIR%/}/"
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||||
STATE_DIR="$GSTACK_STATE_ROOT"
|
||||
mkdir -p "$STATE_DIR"
|
||||
echo "$FREEZE_DIR" > "$STATE_DIR/freeze-dir.txt"
|
||||
echo "Freeze boundary set: $FREEZE_DIR"
|
||||
```
|
||||
|
||||
Tell the user: "Edits are now restricted to `<path>/`. Any Edit or Write
|
||||
outside this directory will be blocked. To change the boundary, run `/freeze`
|
||||
again. To remove it, run `/unfreeze` or end the session."
|
||||
|
||||
## How it works
|
||||
|
||||
The hook reads `file_path` from the Edit/Write tool input JSON, then checks
|
||||
whether the path starts with the freeze directory. If not, it returns
|
||||
`permissionDecision: "deny"` to block the operation.
|
||||
|
||||
The freeze boundary persists for the session via the state file. The hook
|
||||
script reads it on every Edit/Write invocation.
|
||||
|
||||
## Notes
|
||||
|
||||
- The trailing `/` on the freeze directory prevents `/src` from matching `/src-old`
|
||||
- Freeze applies to Edit and Write tools only — Read, Bash, Glob, Grep are unaffected
|
||||
- This prevents accidental edits, not a security boundary — Bash commands like `sed` can still modify files outside the boundary
|
||||
- To deactivate, run `/unfreeze` or end the conversation
|
||||
86
freeze/SKILL.md.tmpl
Normal file
86
freeze/SKILL.md.tmpl
Normal file
@@ -0,0 +1,86 @@
|
||||
---
|
||||
name: freeze
|
||||
version: 0.1.0
|
||||
description: |
|
||||
Restrict file edits to a specific directory for the session. Blocks Edit and
|
||||
Write outside the allowed path. Use when debugging to prevent accidentally
|
||||
"fixing" unrelated code, or when you want to scope changes to one module.
|
||||
Use when asked to "freeze", "restrict edits", "only edit this folder",
|
||||
or "lock down edits". (gstack)
|
||||
triggers:
|
||||
- freeze edits to directory
|
||||
- lock editing scope
|
||||
- restrict file changes
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- AskUserQuestion
|
||||
hooks:
|
||||
PreToolUse:
|
||||
- matcher: "Edit"
|
||||
hooks:
|
||||
- type: command
|
||||
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-freeze.sh"
|
||||
statusMessage: "Checking freeze boundary..."
|
||||
- matcher: "Write"
|
||||
hooks:
|
||||
- type: command
|
||||
command: "bash ${CLAUDE_SKILL_DIR}/bin/check-freeze.sh"
|
||||
statusMessage: "Checking freeze boundary..."
|
||||
sensitive: true
|
||||
---
|
||||
|
||||
# /freeze — Restrict Edits to a Directory
|
||||
|
||||
Lock file edits to a specific directory. Any Edit or Write operation targeting
|
||||
a file outside the allowed path will be **blocked** (not just warned).
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.gstack/analytics
|
||||
echo '{"skill":"freeze","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
Ask the user which directory to restrict edits to. Use AskUserQuestion:
|
||||
|
||||
- Question: "Which directory should I restrict edits to? Files outside this path will be blocked from editing."
|
||||
- Text input (not multiple choice) — the user types a path.
|
||||
|
||||
Once the user provides a directory path:
|
||||
|
||||
1. Resolve it to an absolute path:
|
||||
```bash
|
||||
FREEZE_DIR=$(cd "<user-provided-path>" 2>/dev/null && pwd)
|
||||
echo "$FREEZE_DIR"
|
||||
```
|
||||
|
||||
2. Ensure trailing slash and save to the freeze state file:
|
||||
```bash
|
||||
FREEZE_DIR="${FREEZE_DIR%/}/"
|
||||
eval "$(~/.claude/skills/gstack/bin/gstack-paths)"
|
||||
STATE_DIR="$GSTACK_STATE_ROOT"
|
||||
mkdir -p "$STATE_DIR"
|
||||
echo "$FREEZE_DIR" > "$STATE_DIR/freeze-dir.txt"
|
||||
echo "Freeze boundary set: $FREEZE_DIR"
|
||||
```
|
||||
|
||||
Tell the user: "Edits are now restricted to `<path>/`. Any Edit or Write
|
||||
outside this directory will be blocked. To change the boundary, run `/freeze`
|
||||
again. To remove it, run `/unfreeze` or end the session."
|
||||
|
||||
## How it works
|
||||
|
||||
The hook reads `file_path` from the Edit/Write tool input JSON, then checks
|
||||
whether the path starts with the freeze directory. If not, it returns
|
||||
`permissionDecision: "deny"` to block the operation.
|
||||
|
||||
The freeze boundary persists for the session via the state file. The hook
|
||||
script reads it on every Edit/Write invocation.
|
||||
|
||||
## Notes
|
||||
|
||||
- The trailing `/` on the freeze directory prevents `/src` from matching `/src-old`
|
||||
- Freeze applies to Edit and Write tools only — Read, Bash, Glob, Grep are unaffected
|
||||
- This prevents accidental edits, not a security boundary — Bash commands like `sed` can still modify files outside the boundary
|
||||
- To deactivate, run `/unfreeze` or end the conversation
|
||||
79
freeze/bin/check-freeze.sh
Executable file
79
freeze/bin/check-freeze.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env bash
|
||||
# check-freeze.sh — PreToolUse hook for /freeze skill
|
||||
# Reads JSON from stdin, checks if file_path is within the freeze boundary.
|
||||
# Returns {"permissionDecision":"deny","message":"..."} to block, or {} to allow.
|
||||
set -euo pipefail
|
||||
|
||||
# Read stdin
|
||||
INPUT=$(cat)
|
||||
|
||||
# Locate the freeze directory state file
|
||||
STATE_DIR="${CLAUDE_PLUGIN_DATA:-$HOME/.gstack}"
|
||||
FREEZE_FILE="$STATE_DIR/freeze-dir.txt"
|
||||
|
||||
# If no freeze file exists, allow everything (not yet configured)
|
||||
if [ ! -f "$FREEZE_FILE" ]; then
|
||||
echo '{}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FREEZE_DIR=$(tr -d '[:space:]' < "$FREEZE_FILE")
|
||||
|
||||
# If freeze dir is empty, allow
|
||||
if [ -z "$FREEZE_DIR" ]; then
|
||||
echo '{}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract file_path from tool_input JSON
|
||||
# Try grep/sed first, fall back to Python for escaped quotes
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*:[[:space:]]*"//;s/"$//' || true)
|
||||
|
||||
# Python fallback if grep returned empty
|
||||
if [ -z "$FILE_PATH" ]; then
|
||||
FILE_PATH=$(printf '%s' "$INPUT" | python3 -c 'import sys,json; print(json.loads(sys.stdin.read()).get("tool_input",{}).get("file_path",""))' 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# If we couldn't extract a file path, allow (don't block on parse failure)
|
||||
if [ -z "$FILE_PATH" ]; then
|
||||
echo '{}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Resolve file_path to absolute if it isn't already
|
||||
case "$FILE_PATH" in
|
||||
/*) ;; # already absolute
|
||||
*)
|
||||
FILE_PATH="$(pwd)/$FILE_PATH"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Normalize: remove double slashes and trailing slash
|
||||
FILE_PATH=$(printf '%s' "$FILE_PATH" | sed 's|/\+|/|g;s|/$||')
|
||||
|
||||
# Resolve symlinks and .. sequences (POSIX-portable, works on macOS)
|
||||
_resolve_path() {
|
||||
local _dir _base
|
||||
_dir="$(dirname "$1")"
|
||||
_base="$(basename "$1")"
|
||||
_dir="$(cd "$_dir" 2>/dev/null && pwd -P || printf '%s' "$_dir")"
|
||||
printf '%s/%s' "$_dir" "$_base"
|
||||
}
|
||||
FILE_PATH=$(_resolve_path "$FILE_PATH")
|
||||
FREEZE_DIR=$(_resolve_path "$FREEZE_DIR")
|
||||
|
||||
# Check: does the file path start with the freeze directory?
|
||||
case "$FILE_PATH" in
|
||||
"${FREEZE_DIR}/"*|"${FREEZE_DIR}")
|
||||
# Inside freeze boundary — allow
|
||||
echo '{}'
|
||||
;;
|
||||
*)
|
||||
# Outside freeze boundary — deny
|
||||
# Log hook fire event
|
||||
mkdir -p ~/.gstack/analytics 2>/dev/null || true
|
||||
echo '{"event":"hook_fire","skill":"freeze","pattern":"boundary_deny","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null || echo "unknown")'"}' >> ~/.gstack/analytics/skill-usage.jsonl 2>/dev/null || true
|
||||
|
||||
printf '{"permissionDecision":"deny","message":"[freeze] Blocked: %s is outside the freeze boundary (%s). Only edits within the frozen directory are allowed."}\n' "$FILE_PATH" "$FREEZE_DIR"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user