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/.cortexhawk-lint.yml.example +21 -0
- package/.gitmessage +10 -0
- package/CHANGELOG.md +46 -1
- package/CLAUDE.md +12 -4
- package/README.md +20 -5
- package/agents/git-manager.md +10 -2
- package/commands/backlog.md +1 -0
- package/commands/cleanup.md +36 -0
- package/commands/commit.md +24 -0
- package/commands/review-pr.md +31 -0
- package/commands/ship.md +2 -1
- package/commands/task.md +1 -0
- package/cortexhawk +2 -2
- package/hooks/branch-guard.sh +9 -1
- package/hooks/compose.yml +6 -0
- package/hooks/file-guard.sh +4 -0
- package/hooks/hooks.json +6 -0
- package/hooks/lint-guard.sh +46 -0
- package/hooks/post-merge.sh +12 -0
- package/hooks/session-start.sh +8 -5
- package/install.sh +370 -124
- package/mcp/README.md +36 -0
- package/mcp/github.json +11 -0
- package/package.json +1 -1
- package/profiles/api.json +2 -1
- package/profiles/fullstack.json +2 -1
- package/scripts/autodetect-profile.sh +6 -2
- package/scripts/interactive-init.sh +20 -12
- package/scripts/lint-guard-runner.sh +132 -0
- package/scripts/post-merge-cleanup.sh +143 -0
- package/scripts/refresh-context.sh +51 -0
- package/settings.json +12 -1
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
|
+
```
|
package/mcp/github.json
ADDED
package/package.json
CHANGED
package/profiles/api.json
CHANGED
package/profiles/fullstack.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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": [
|