cortexhawk 3.1.1 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/mcp/README.md CHANGED
@@ -15,6 +15,7 @@ cp mcp/context7.json .mcp.json
15
15
 
16
16
  | Server | Purpose | Requires |
17
17
  |---|---|---|
18
+ | `github.json` | GitHub API — PRs, issues, reviews, repos | npx + `GITHUB_PERSONAL_ACCESS_TOKEN` env |
18
19
  | `context7.json` | Library docs lookup via Context7 | npx |
19
20
  | `sequential-thinking.json` | Step-by-step reasoning for complex problems | npx |
20
21
  | `puppeteer.json` | Browser automation and testing | npx |
@@ -35,3 +36,38 @@ Create or edit `.mcp.json` at your project root:
35
36
  ```
36
37
 
37
38
  Then restart Claude Code to activate.
39
+
40
+ ## GitHub Token Setup
41
+
42
+ `github.json` requires a `GITHUB_PERSONAL_ACCESS_TOKEN`. Options (pick one):
43
+
44
+ **Option 1 — `gh auth token` (recommended, zero extra credentials)**
45
+ ```bash
46
+ # ~/.zshrc or ~/.bashrc
47
+ export GITHUB_PERSONAL_ACCESS_TOKEN=$(gh auth token)
48
+ ```
49
+ Reuses existing `gh` CLI auth — nothing new to manage.
50
+
51
+ **Option 2 — `.claude/settings.json` (project-level, gitignored)**
52
+ ```json
53
+ {
54
+ "env": {
55
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxx"
56
+ }
57
+ }
58
+ ```
59
+
60
+ **Option 3 — Shell profile (direct)**
61
+ ```bash
62
+ export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxxx
63
+ ```
64
+
65
+ **Option 4 — `direnv` (`.envrc`, add to `.gitignore`)**
66
+ ```bash
67
+ export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_xxxx
68
+ ```
69
+
70
+ **Option 5 — Password manager CLI**
71
+ ```bash
72
+ export GITHUB_PERSONAL_ACCESS_TOKEN=$(op read "op://vault/github-pat/token")
73
+ ```
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "github": {
4
+ "command": "npx",
5
+ "args": ["-y", "@modelcontextprotocol/server-github"],
6
+ "env": {
7
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
8
+ }
9
+ }
10
+ }
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortexhawk",
3
- "version": "3.1.1",
3
+ "version": "3.3.0",
4
4
  "description": "Open-source development toolkit for Claude Code — optimized agents, skills, commands, hooks, and modes",
5
5
  "bin": {
6
6
  "cortexhawk": "./cortexhawk"
package/profiles/api.json CHANGED
@@ -23,5 +23,6 @@
23
23
  "workflow/commit",
24
24
  "workflow/confidence-check",
25
25
  "workflow/pr-review-comments"
26
- ]
26
+ ],
27
+ "mcp": ["github", "sequential-thinking"]
27
28
  }
@@ -23,5 +23,6 @@
23
23
  "workflow/commit",
24
24
  "workflow/confidence-check",
25
25
  "workflow/pr-review-comments"
26
- ]
26
+ ],
27
+ "mcp": ["github", "context7", "sequential-thinking"]
27
28
  }
@@ -46,7 +46,7 @@ for skill_file in "$CORTEXHAWK_DIR"/skills/*/*/SKILL.md; do
46
46
  done
47
47
 
48
48
  # --- Generate JSON ---
49
- PROFILE_FILE="/tmp/cortexhawk-autodetect-$$.json"
49
+ PROFILE_FILE=$(mktemp /tmp/cortexhawk-autodetect-XXXXXX)
50
50
  SKILL_JSON=$(echo "$SKILLS" | tr ',' '\n' | sed 's/.*/ "&"/' | paste -sd ',' - | sed 's/,/,\n/g')
51
51
  cat > "$PROFILE_FILE" << EOF
52
52
  {
@@ -57,7 +57,11 @@ $SKILL_JSON
57
57
  ]
58
58
  }
59
59
  EOF
60
- SKILL_COUNT=$(echo "$SKILLS" | tr ',' '\n' | wc -l | tr -d ' ')
60
+ if [ -z "$SKILLS" ]; then
61
+ SKILL_COUNT=0
62
+ else
63
+ SKILL_COUNT=$(echo "$SKILLS" | tr ',' '\n' | wc -l | tr -d ' ')
64
+ fi
61
65
 
62
66
  # --- Stack snapshot ---
63
67
  if [ -d "docs/.context" ]; then
@@ -20,11 +20,13 @@ echo " 1) Claude Code (default) — fully supported"
20
20
  echo " 2) Kimi CLI (experimental)"
21
21
  echo " 3) Codex CLI (experimental)"
22
22
  echo " 4) All (install for all CLIs simultaneously)"
23
- read -r -p "Choice [1-4] (default: 1): " target_choice
23
+ echo " 5) Auto-detect (install for all CLIs found on system)"
24
+ read -r -p "Choice [1-5] (default: 1): " target_choice
24
25
  case "$target_choice" in
25
26
  2) TARGET_CLI="kimi" ;;
26
27
  3) TARGET_CLI="codex" ;;
27
28
  4) TARGET_CLI="all" ;;
29
+ 5) TARGET_CLI="auto" ;;
28
30
  *) TARGET_CLI="claude" ;;
29
31
  esac
30
32
  green " → $TARGET_CLI"
@@ -32,14 +34,19 @@ echo ""
32
34
 
33
35
  # --- Step 2: Scope ---
34
36
  bold "2. Install Scope"
35
- echo " 1) Local project install in current directory (default)"
36
- echo " 2) Global — install in ~/.${TARGET_CLI}/ (shared across all projects)"
37
- read -r -p "Choice [1-2] (default: 1): " scope_choice
38
- case "$scope_choice" in
39
- 2) GLOBAL=true ;;
40
- *) GLOBAL=false ;;
41
- esac
42
- green "$([ "$GLOBAL" = true ] && echo "global" || echo "local")"
37
+ if [ "$TARGET_CLI" = "all" ] || [ "$TARGET_CLI" = "auto" ]; then
38
+ GLOBAL=false
39
+ green " → local (always local for multi-target install)"
40
+ else
41
+ echo " 1) Local project — install in current directory (default)"
42
+ echo " 2) Global — install in ~/.${TARGET_CLI}/ (shared across all projects)"
43
+ read -r -p "Choice [1-2] (default: 1): " scope_choice
44
+ case "$scope_choice" in
45
+ 2) GLOBAL=true ;;
46
+ *) GLOBAL=false ;;
47
+ esac
48
+ green " → $([ "$GLOBAL" = true ] && echo "global" || echo "local")"
49
+ fi
43
50
  echo ""
44
51
 
45
52
  # --- Step 3: Skills ---
@@ -86,7 +93,7 @@ case "$skill_choice" in
86
93
  fi
87
94
 
88
95
  # Generate custom profile JSON
89
- PROFILE_FILE="/tmp/cortexhawk-custom-$(date +%s).json"
96
+ PROFILE_FILE=$(mktemp /tmp/cortexhawk-custom-XXXXXX)
90
97
  SKILL_LINES=""
91
98
  SKILL_COUNT=0
92
99
  for category in $SELECTED_CATS; do
@@ -122,11 +129,12 @@ echo ""
122
129
  read -r -p " API key (press Enter to skip): " SKILLSMP_KEY
123
130
  if [ -n "$SKILLSMP_KEY" ]; then
124
131
  if [ -f ".env" ] && grep -q '^SKILLSMP_API_KEY=' .env 2>/dev/null; then
125
- sed -i "s/^SKILLSMP_API_KEY=.*/SKILLSMP_API_KEY=$SKILLSMP_KEY/" .env
132
+ sed_inplace "s/^SKILLSMP_API_KEY=.*/SKILLSMP_API_KEY=$SKILLSMP_KEY/" .env
126
133
  else
127
134
  echo "SKILLSMP_API_KEY=$SKILLSMP_KEY" >> .env
128
135
  fi
129
- green " → saved to .env"
136
+ chmod 600 .env
137
+ green " → saved to .env (permissions: owner-only)"
130
138
  else
131
139
  yellow " → skipped (use --search with local REGISTRY.md only)"
132
140
  fi
@@ -0,0 +1,132 @@
1
+ #!/bin/bash
2
+ # lint-guard-runner.sh — Formatter/linter execution with detection cache + parallel linters
3
+ # Called by hooks/lint-guard.sh
4
+ # Formatters: sequential (git add must not race). Linters: parallel (check-only).
5
+
6
+ REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
7
+ [ -z "$REPO_ROOT" ] && exit 0
8
+ STAGED=$(git diff --cached --name-only 2>/dev/null)
9
+ [ -z "$STAGED" ] && exit 0
10
+
11
+ # Opt-out config
12
+ CONF_FILE="$REPO_ROOT/.claude/git-workflow.conf"
13
+ LINT_SKIP=$(grep '^LINT_SKIP=' "$CONF_FILE" 2>/dev/null | cut -d= -f2 | tr ',' '\n')
14
+
15
+ lint_cfg() {
16
+ local key="$1" default="${2:-auto}"
17
+ if [ -f "$REPO_ROOT/.cortexhawk-lint.yml" ]; then
18
+ local val
19
+ val=$(grep -E "^\s+$key:" "$REPO_ROOT/.cortexhawk-lint.yml" 2>/dev/null \
20
+ | sed 's/.*: *//' | tr -d ' \r')
21
+ echo "${val:-$default}"
22
+ else
23
+ echo "$default"
24
+ fi
25
+ }
26
+
27
+ TIMEOUT_SECS=$(lint_cfg "timeout" "30")
28
+ case "$TIMEOUT_SECS" in ''|*[!0-9]*|0) TIMEOUT_SECS=30 ;; esac
29
+ FAIL_ON_FMT=$(lint_cfg "fail_on_formatter" "false")
30
+ TIMEOUT_CMD=""
31
+ command -v timeout &>/dev/null && TIMEOUT_CMD="timeout $TIMEOUT_SECS"
32
+ command -v gtimeout &>/dev/null && [ -z "$TIMEOUT_CMD" ] && TIMEOUT_CMD="gtimeout $TIMEOUT_SECS"
33
+
34
+ # --- DETECTION CACHE (1hr TTL, safe key=value, no source) ---
35
+ mkdir -p "$REPO_ROOT/.claude" 2>/dev/null || true
36
+ CACHE="$REPO_ROOT/.claude/lint-guard-cache"
37
+ _mtime() { stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null || date +%s; }
38
+ [ -f "$CACHE" ] && [ $(( $(date +%s) - $(_mtime "$CACHE") )) -gt 3600 ] && rm -f "$CACHE"
39
+ cache_get() { grep -m1 "^$1=" "$CACHE" 2>/dev/null | cut -d= -f2; }
40
+ cache_set() { local _tmp; _tmp=$(mktemp "${CACHE}.XXXXXX"); { grep -v "^$1=" "$CACHE" 2>/dev/null; echo "$1=$2"; } > "$_tmp" && mv "$_tmp" "$CACHE" || rm -f "$_tmp"; }
41
+
42
+ # --- TOOL DETECTION ---
43
+ STAGED_JS=$(echo "$STAGED" | grep -E '\.(js|ts|jsx|tsx|mjs|cjs)$' || true)
44
+ STAGED_CSS=$(echo "$STAGED" | grep -E '\.(css|scss|sass|less)$' || true)
45
+ STAGED_PY=$(echo "$STAGED" | grep -E '\.py$' || true)
46
+ STAGED_RS=$(echo "$STAGED" | grep -E '\.rs$' || true)
47
+ STAGED_GO=$(echo "$STAGED" | grep -E '\.go$' || true)
48
+
49
+ has_config() {
50
+ case "$1" in
51
+ prettier) ls "$REPO_ROOT"/.prettierrc* "$REPO_ROOT"/prettier.config.* 2>/dev/null | grep -q . \
52
+ || grep -qs '"prettier"' "$REPO_ROOT/package.json" 2>/dev/null ;;
53
+ eslint) ls "$REPO_ROOT"/.eslintrc* "$REPO_ROOT"/eslint.config.* 2>/dev/null | grep -q . ;;
54
+ black) grep -qs '\[tool\.black\]' "$REPO_ROOT/pyproject.toml" 2>/dev/null ;;
55
+ flake8) [ -f "$REPO_ROOT/.flake8" ] || grep -qs '\[flake8\]' "$REPO_ROOT/setup.cfg" 2>/dev/null ;;
56
+ mypy) grep -qs '\[tool\.mypy\]' "$REPO_ROOT/pyproject.toml" 2>/dev/null || [ -f "$REPO_ROOT/mypy.ini" ] ;;
57
+ stylelint) ls "$REPO_ROOT"/.stylelintrc* "$REPO_ROOT"/stylelint.config.* 2>/dev/null | grep -q . ;;
58
+ rustfmt) [ -f "$REPO_ROOT/rustfmt.toml" ] || [ -f "$REPO_ROOT/.rustfmt.toml" ] ;;
59
+ gofmt) true ;;
60
+ esac
61
+ }
62
+
63
+ c_has_config() {
64
+ local key="HAS_$(echo "$1" | tr '[:lower:]' '[:upper:]')"
65
+ local cached; cached=$(cache_get "$key")
66
+ if [ -n "$cached" ]; then [ "$cached" = "1" ]; return $?; fi
67
+ if has_config "$1"; then cache_set "$key" "1"; return 0
68
+ else cache_set "$key" "0"; return 1; fi
69
+ }
70
+
71
+ is_enabled() {
72
+ echo "$LINT_SKIP" | grep -qx "$1" && return 1
73
+ local val; val=$(lint_cfg "$1"); [ "$val" != "false" ]
74
+ }
75
+
76
+ tool_ok() { command -v "$1" &>/dev/null; }
77
+
78
+ # Pre-populate cache before parallel stage to avoid write races
79
+ for _t in prettier eslint black flake8 mypy stylelint rustfmt gofmt; do
80
+ c_has_config "$_t" >/dev/null 2>&1
81
+ done
82
+
83
+ # --- FORMATTERS (sequential — git add must not race) ---
84
+ run_formatter() {
85
+ local name="$1" cmd="$2" files="$3"
86
+ is_enabled "$name" || return 0
87
+ c_has_config "$name" || return 0
88
+ tool_ok "$name" || { echo "lint-guard: $name not in PATH, skipping"; return 0; }
89
+ [ -z "$files" ] && return 0
90
+ echo "lint-guard: $name (auto-fix)..."
91
+ # shellcheck disable=SC2086
92
+ if echo "$files" | tr '\n' '\0' | xargs -0 $TIMEOUT_CMD $cmd 2>/dev/null; then
93
+ echo "$files" | tr '\n' '\0' | xargs -0 git add 2>/dev/null || true
94
+ else
95
+ echo "lint-guard: $name formatter failed" >&2
96
+ [ "$FAIL_ON_FMT" = "true" ] && exit 2
97
+ fi
98
+ }
99
+
100
+ [ -n "$STAGED_JS" ] && run_formatter "prettier" "prettier --write" "$STAGED_JS"
101
+ [ -n "$STAGED_CSS" ] && run_formatter "stylelint" "stylelint --fix" "$STAGED_CSS"
102
+ [ -n "$STAGED_PY" ] && run_formatter "black" "black" "$STAGED_PY"
103
+ [ -n "$STAGED_RS" ] && run_formatter "rustfmt" "rustfmt" "$STAGED_RS"
104
+ [ -n "$STAGED_GO" ] && run_formatter "gofmt" "gofmt -w" "$STAGED_GO"
105
+
106
+ # --- LINTERS (parallel — check-only, no file writes) ---
107
+ ERR_DIR=$(mktemp -d)
108
+ trap 'rm -rf "$ERR_DIR"' EXIT
109
+
110
+ run_linter_bg() {
111
+ local name="$1" cmd="$2" files="$3"
112
+ is_enabled "$name" || return 0
113
+ c_has_config "$name" || return 0
114
+ tool_ok "$name" || { echo "lint-guard: $name not in PATH, skipping"; return 0; }
115
+ [ -z "$files" ] && return 0
116
+ echo "lint-guard: $name (check)..."
117
+ # shellcheck disable=SC2086
118
+ if ! echo "$files" | tr '\n' '\0' | xargs -0 $TIMEOUT_CMD $cmd 2>&1; then
119
+ echo "BLOCKED by lint-guard: $name errors found. Fix above, re-stage, and retry." >&2
120
+ touch "$ERR_DIR/$name"
121
+ fi
122
+ }
123
+
124
+ # Output from parallel linters may interleave on screen; ERR_DIR signals are reliable regardless.
125
+ [ -n "$STAGED_JS" ] && run_linter_bg "eslint" "eslint --max-warnings=0" "$STAGED_JS" &
126
+ [ -n "$STAGED_PY" ] && run_linter_bg "flake8" "flake8" "$STAGED_PY" &
127
+ [ -n "$STAGED_PY" ] && run_linter_bg "mypy" "mypy --no-error-summary" "$STAGED_PY" &
128
+ wait
129
+
130
+ ERRORS=$(find "$ERR_DIR" -type f 2>/dev/null | wc -l | tr -d ' ')
131
+ [ "$ERRORS" -gt 0 ] && exit 2
132
+ exit 0
@@ -0,0 +1,143 @@
1
+ #!/bin/bash
2
+ # post-merge-cleanup.sh — Delete merged branches and optionally create new feature branch
3
+ # Called by /cleanup command and post-merge hook
4
+ # Args: --auto (silent mode, no prompts, skip remote deletion)
5
+
6
+ set -e
7
+
8
+ # Parse arguments
9
+ AUTO_MODE=false
10
+ [ "$1" = "--auto" ] && AUTO_MODE=true
11
+ [ ! -t 0 ] && AUTO_MODE=true # No TTY (Claude Bash tool, CI, pipe) → auto
12
+
13
+ # Validate git repository
14
+ if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
15
+ echo "Error: not a git repository"
16
+ exit 1
17
+ fi
18
+
19
+ # --- Branching detection with fallback layers ---
20
+ BRANCHING="direct-main"
21
+ MAIN_BRANCH="main"
22
+ WORK_BRANCH=""
23
+
24
+ # Layer 1: Read .claude/git-workflow.conf (safe parsing, no source)
25
+ if [ -f ".claude/git-workflow.conf" ]; then
26
+ BRANCHING=$(grep -E '^BRANCHING=' ".claude/git-workflow.conf" | head -1 | cut -d= -f2 | tr -d '"' | tr -d "'")
27
+ WORK_BRANCH=$(grep -E '^WORK_BRANCH=' ".claude/git-workflow.conf" | head -1 | cut -d= -f2 | tr -d '"' | tr -d "'")
28
+
29
+ case "$BRANCHING" in
30
+ dev-branch)
31
+ MAIN_BRANCH="${WORK_BRANCH:-dev}"
32
+ ;;
33
+ gitflow)
34
+ MAIN_BRANCH="develop"
35
+ ;;
36
+ feature-branches|direct-main)
37
+ MAIN_BRANCH="main"
38
+ ;;
39
+ esac
40
+ fi
41
+
42
+ # Layer 2: Fallback to CLAUDE.md
43
+ if [ ! -f ".claude/git-workflow.conf" ] && [ -f "CLAUDE.md" ]; then
44
+ if grep -q "^## Git Workflow" "CLAUDE.md"; then
45
+ DETECTED=$(grep -i "branching:" "CLAUDE.md" | head -1 | sed 's/.*: //' | awk '{print $1}')
46
+ case "$DETECTED" in
47
+ dev-branch)
48
+ BRANCHING="dev-branch"
49
+ WORK_BRANCH=$(grep -i "working branch:" "CLAUDE.md" | head -1 | sed 's/.*: //' | tr -d ')')
50
+ MAIN_BRANCH="${WORK_BRANCH:-dev}"
51
+ ;;
52
+ gitflow)
53
+ BRANCHING="gitflow"
54
+ MAIN_BRANCH="develop"
55
+ ;;
56
+ feature-branches)
57
+ BRANCHING="feature-branches"
58
+ MAIN_BRANCH="main"
59
+ ;;
60
+ direct-main)
61
+ BRANCHING="direct-main"
62
+ MAIN_BRANCH="main"
63
+ ;;
64
+ esac
65
+ fi
66
+ fi
67
+
68
+ # Layer 3: Git-based fallback (detect main vs master)
69
+ if ! git rev-parse --verify "$MAIN_BRANCH" >/dev/null 2>&1; then
70
+ if git rev-parse --verify main >/dev/null 2>&1; then
71
+ MAIN_BRANCH="main"
72
+ elif git rev-parse --verify master >/dev/null 2>&1; then
73
+ MAIN_BRANCH="master"
74
+ else
75
+ echo "Error: branch $MAIN_BRANCH does not exist"
76
+ exit 1
77
+ fi
78
+ fi
79
+
80
+ [ "$AUTO_MODE" = false ] && echo "Detected branching: $BRANCHING, main branch: $MAIN_BRANCH"
81
+
82
+ # --- Merged branch detection ---
83
+ CURRENT_BRANCH=$(git branch --show-current)
84
+ MERGED_BRANCHES=$(git branch --merged "$MAIN_BRANCH" 2>/dev/null | grep -v '^\*' | grep -vE '(main|master|dev|develop)$' | sed 's/^[[:space:]]*//' || true)
85
+
86
+ if [ -z "$MERGED_BRANCHES" ]; then
87
+ [ "$AUTO_MODE" = false ] && echo "No merged branches to clean up"
88
+ exit 0
89
+ fi
90
+
91
+ BRANCH_COUNT=$(echo "$MERGED_BRANCHES" | wc -l | tr -d ' ')
92
+ [ "$AUTO_MODE" = false ] && echo "Found $BRANCH_COUNT merged branch(es): $(echo "$MERGED_BRANCHES" | tr '\n' ' ')"
93
+
94
+ # --- Check for remote ---
95
+ REMOTE_EXISTS=true
96
+ if ! git remote get-url origin >/dev/null 2>&1; then
97
+ REMOTE_EXISTS=false
98
+ [ "$AUTO_MODE" = false ] && echo "Warning: no remote 'origin' configured, skipping remote operations"
99
+ fi
100
+
101
+ # --- Delete merged branches ---
102
+ while IFS= read -r branch; do
103
+ [ -z "$branch" ] && continue
104
+
105
+ # Local deletion
106
+ if [ "$AUTO_MODE" = true ]; then
107
+ git branch -d "$branch" 2>/dev/null || echo "Failed to delete $branch (may have unmerged changes)"
108
+ else
109
+ read -r -p "Delete local branch '$branch'? [y/N]: " confirm
110
+ if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
111
+ git branch -d "$branch" || echo "Failed to delete $branch (may have unmerged changes)"
112
+ fi
113
+ fi
114
+
115
+ # Remote deletion (skip in auto mode)
116
+ if [ "$AUTO_MODE" = false ] && [ "$REMOTE_EXISTS" = true ]; then
117
+ if git rev-parse --verify "refs/remotes/origin/$branch" >/dev/null 2>&1; then
118
+ read -r -p "Delete remote branch 'origin/$branch'? [y/N]: " confirm_remote
119
+ if [ "$confirm_remote" = "y" ] || [ "$confirm_remote" = "Y" ]; then
120
+ git push origin --delete "$branch" 2>/dev/null || echo "Failed to delete remote branch $branch (may not exist or no permissions)"
121
+ fi
122
+ fi
123
+ fi
124
+ done <<< "$MERGED_BRANCHES"
125
+
126
+ # --- Post-cleanup actions ---
127
+ if [ "$CURRENT_BRANCH" = "$MAIN_BRANCH" ] && [ "$REMOTE_EXISTS" = true ]; then
128
+ [ "$AUTO_MODE" = false ] && echo "Pulling latest from $MAIN_BRANCH..."
129
+ git pull origin "$MAIN_BRANCH" 2>/dev/null || echo "Failed to pull from $MAIN_BRANCH (network issue or conflicts)"
130
+ fi
131
+
132
+ # Feature branch creation prompt (only for feature-branches strategy, not in auto mode)
133
+ if [ "$AUTO_MODE" = false ] && [ "$BRANCHING" = "feature-branches" ] && [ "$CURRENT_BRANCH" != "$MAIN_BRANCH" ]; then
134
+ read -r -p "Create new feature branch? [y/N]: " create_branch
135
+ if [ "$create_branch" = "y" ] || [ "$create_branch" = "Y" ]; then
136
+ read -r -p "Branch name (default: feat/$(date +%s)): " branch_name
137
+ branch_name="${branch_name:-feat/$(date +%s)}"
138
+ git checkout -b "$branch_name" || echo "Failed to create branch $branch_name"
139
+ fi
140
+ fi
141
+
142
+ [ "$AUTO_MODE" = false ] && echo "Cleanup complete!"
143
+ exit 0
@@ -0,0 +1,51 @@
1
+ #!/bin/bash
2
+ # refresh-context.sh — Regenerate docs/.context/_shared.md mid-session
3
+ # Called by agents after modifying backlog, completing tasks, or committing code
4
+
5
+ [ -d "docs/.context" ] && [ ! -L "docs/.context" ] || exit 0
6
+
7
+ SHARED="docs/.context/_shared.md"
8
+
9
+ echo "# Shared Context" > "$SHARED"
10
+ echo "" >> "$SHARED"
11
+ echo "_Auto-generated at $(date '+%Y-%m-%d %H:%M'). Last refresh: $(date '+%H:%M:%S')._" >> "$SHARED"
12
+ echo "" >> "$SHARED"
13
+
14
+ # Backlog summary
15
+ if [ -f "docs/backlog.md" ]; then
16
+ ACTIVE=$(grep -c '| todo |' docs/backlog.md 2>/dev/null || true)
17
+ : "${ACTIVE:=0}"
18
+ DEFERRED=$(grep -c '| deferred |' docs/backlog.md 2>/dev/null || true)
19
+ : "${DEFERRED:=0}"
20
+ DONE=$(grep -c '| done |' docs/backlog.md 2>/dev/null || true)
21
+ : "${DONE:=0}"
22
+ echo "## Backlog" >> "$SHARED"
23
+ echo "- Active: $ACTIVE | Deferred: $DEFERRED | Done: $DONE" >> "$SHARED"
24
+ grep '| todo |' docs/backlog.md >> "$SHARED" 2>/dev/null || true
25
+ echo "" >> "$SHARED"
26
+ fi
27
+
28
+ # Recent commits
29
+ if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
30
+ echo "## Recent Commits" >> "$SHARED"
31
+ git log --oneline -5 2>/dev/null >> "$SHARED" || true
32
+ echo "" >> "$SHARED"
33
+ fi
34
+
35
+ # User context
36
+ if [ -f "docs/.context/_user.md" ]; then
37
+ cat "docs/.context/_user.md" >> "$SHARED"
38
+ echo "" >> "$SHARED"
39
+ fi
40
+
41
+ # Active warnings
42
+ echo "## Warnings" >> "$SHARED"
43
+ WARNINGS=0
44
+ if [ -f ".env.example" ] && [ ! -f ".env" ]; then
45
+ echo "- .env missing — copy from .env.example" >> "$SHARED"
46
+ WARNINGS=$((WARNINGS + 1))
47
+ fi
48
+ if [ "$WARNINGS" -eq 0 ]; then
49
+ echo "- None" >> "$SHARED"
50
+ fi
51
+ echo "" >> "$SHARED"
package/settings.json CHANGED
@@ -14,7 +14,18 @@
14
14
  "Write(*)",
15
15
  "Edit(*)"
16
16
  ],
17
- "deny": []
17
+ "deny": [
18
+ "Write(/etc/*)",
19
+ "Write(~/.bashrc)",
20
+ "Write(~/.zshrc)",
21
+ "Write(~/.profile)",
22
+ "Write(~/.ssh/*)",
23
+ "Edit(/etc/*)",
24
+ "Edit(~/.bashrc)",
25
+ "Edit(~/.zshrc)",
26
+ "Edit(~/.profile)",
27
+ "Edit(~/.ssh/*)"
28
+ ]
18
29
  },
19
30
  "hooks": {
20
31
  "SessionStart": [