cortexhawk 3.2.0 → 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 +25 -0
- package/CLAUDE.md +12 -4
- package/agents/git-manager.md +6 -2
- package/commands/backlog.md +1 -1
- package/commands/cleanup.md +36 -0
- package/commands/review-pr.md +31 -0
- package/commands/ship.md +1 -0
- package/commands/task.md +1 -1
- 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 +1 -1
- package/install.sh +131 -51
- 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 +1 -1
- package/scripts/interactive-init.sh +3 -2
- package/scripts/lint-guard-runner.sh +132 -0
- package/scripts/post-merge-cleanup.sh +143 -0
- package/settings.json +12 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# .cortexhawk-lint.yml — per-project lint-guard config (optional)
|
|
2
|
+
# Copy to .cortexhawk-lint.yml to override defaults.
|
|
3
|
+
# By default, all tools are auto-detected via their config files.
|
|
4
|
+
# Set a tool to false to disable it even if its config file is present.
|
|
5
|
+
|
|
6
|
+
formatters:
|
|
7
|
+
prettier: true # .prettierrc* / prettier.config.* / package.json "prettier"
|
|
8
|
+
black: true # pyproject.toml [tool.black]
|
|
9
|
+
gofmt: true # active if .go files staged (no config file required)
|
|
10
|
+
rustfmt: true # rustfmt.toml / .rustfmt.toml
|
|
11
|
+
stylelint: true # .stylelintrc* / stylelint.config.*
|
|
12
|
+
|
|
13
|
+
linters:
|
|
14
|
+
eslint: true # .eslintrc* / eslint.config.*
|
|
15
|
+
flake8: true # .flake8 / setup.cfg [flake8]
|
|
16
|
+
mypy: false # pyproject.toml [tool.mypy] / mypy.ini — set to false to disable (can be slow)
|
|
17
|
+
|
|
18
|
+
options:
|
|
19
|
+
run_on_push: false # run on git push too (overrides LINT_ON_PUSH in git-workflow.conf)
|
|
20
|
+
fail_on_formatter: false # block commit if a formatter fails (default: non-blocking)
|
|
21
|
+
timeout: 30 # max seconds per tool (requires timeout/gtimeout in PATH)
|
package/.gitmessage
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
# type(scope): subject
|
|
3
|
+
#
|
|
4
|
+
# Types: feat, fix, docs, style, refactor, test, chore, perf, security
|
|
5
|
+
# Scope: optional module/component name
|
|
6
|
+
# Subject: imperative mood, lowercase, no period, max 72 chars
|
|
7
|
+
#
|
|
8
|
+
# Body: explain WHY, not WHAT (the diff shows the what)
|
|
9
|
+
#
|
|
10
|
+
# Footer: BREAKING CHANGE: description | Closes #123 | Backlog #N
|
package/CHANGELOG.md
CHANGED
|
@@ -3,9 +3,34 @@
|
|
|
3
3
|
All notable changes to CortexHawk are documented here.
|
|
4
4
|
Format: [Keep a Changelog](https://keepachangelog.com/)
|
|
5
5
|
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [3.3.0] - 2026-02-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- `lint-guard` Phase 3 performance: linters run in parallel (`&` + `wait` + tmpdir error signaling); detection results cached in `.claude/lint-guard-cache` (1hr TTL, safe key=value); hook extracted to `scripts/lint-guard-runner.sh` to stay within 150-line limit (#142)
|
|
12
|
+
- `lint-guard` advanced YAML options: `timeout` (per-tool kill with `timeout`/`gtimeout`, default 30s), `fail_on_formatter` (block commit on formatter failure, default false), `run_on_push` in yml (overrides git-workflow.conf) (#141)
|
|
13
|
+
- `lint-guard` pre-commit delegation: if `.pre-commit-config.yaml` + `pre-commit` CLI are present, lint-guard delegates entirely to the framework — no duplication for projects already using pre-commit (#143)
|
|
14
|
+
- `lint-guard` hook (PreToolUse): auto-detects formatters and linters on staged files before commit — formatters auto-fix + re-stage (prettier, black, gofmt, rustfmt, stylelint), linters check-only + block on errors (eslint, flake8, mypy); opt-out via `LINT_SKIP` in `git-workflow.conf` or `.cortexhawk-lint.yml` (#140)
|
|
15
|
+
- `/review-pr` command: fetch, triage, and address PR review comments — batch mode by default (one commit + one batched review reply = one notification); `--sequential` flag for complex interdependent threads (#145)
|
|
16
|
+
- MCP GitHub config: `mcp/github.json` (`@modelcontextprotocol/server-github`) — unlocks native GitHub API for `git-manager`, `/ship`, `pr-review-comments`, `/review-pr`; listed as recommended in fullstack + api profiles (#146)
|
|
17
|
+
- `/cleanup` command: delete merged local/remote branches, optional post-merge hook for auto-cleanup after PR merges (#139)
|
|
18
|
+
- Smart PR detection in `/ship`: reuses existing PR branch instead of creating duplicate branches when iterating with `/task` followed by review feedback (#138)
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `branch-guard` hook: `git push --delete` (remote branch deletion) was incorrectly blocked when on a protected branch — `/cleanup` remote cleanup now works correctly
|
|
22
|
+
- `post-merge-cleanup.sh`: auto-detects missing TTY (`[ ! -t 0 ]`) and switches to auto mode — `/cleanup` called via Claude Bash tool or CI no longer hangs on `read` prompt
|
|
23
|
+
- `.gitignore`: add `docs/.context/` and `docs/.metrics/` — auto-generated session artifacts (snapshots, analytics logs, agent context) are ephemeral and should not be committed
|
|
24
|
+
- GitHub Actions (`claude.yml`, `claude-code-review.yml`): grant `pull-requests: write` + `issues: write` — Claude could read PRs but not post reviews or replies
|
|
25
|
+
- **Security (MEDIUM)**: replace predictable PID/timestamp temp paths with `mktemp` (portable, no `.json` suffix) in `autodetect-profile.sh` and `interactive-init.sh`
|
|
26
|
+
- **Security (MEDIUM)**: extend `.env` parser blocklist (`PYTHONPATH`, `GIT_SSH_COMMAND`, `NPM_CONFIG_*`, `NODE_OPTIONS`, `RUBYLIB`, `LD_AUDIT`, etc.) + add key format validation (`^[A-Z_][A-Z0-9_]{0,63}$`) to reject malformed variable names
|
|
27
|
+
- **Security (LOW)**: atomic `cache_set` in `lint-guard-runner.sh` via `mktemp` unique tmp + `mv` — eliminates race condition on concurrent hook invocations
|
|
28
|
+
- **Security**: replace `eval` with `xargs -0` in `lint-guard-runner.sh` — prevents command injection via crafted filenames in staged file lists
|
|
29
|
+
|
|
6
30
|
## [3.2.0] - 2026-02-15
|
|
7
31
|
|
|
8
32
|
### Added
|
|
33
|
+
- Component registry: `COMPONENTS` array + `copy_all_components`/`sync_all_components`/`count_component_files` — adding a new component is 1 line instead of modifying 5 functions
|
|
9
34
|
- `/commit` command: lightweight conventional commit + push without review or PR — use `/ship` for full workflow, `/commit` for quick iterations
|
|
10
35
|
- Install auto-detects existing PR/commit templates; generates CortexHawk defaults (`.github/PULL_REQUEST_TEMPLATE.md`, `.gitmessage`) if missing — agents (`git-manager`, `/ship`, `/commit`) read templates at runtime
|
|
11
36
|
- `--version` / `-v` flag: displays CortexHawk version
|
package/CLAUDE.md
CHANGED
|
@@ -6,13 +6,13 @@ Open-source development toolkit for Claude Code — optimized agents, skills, co
|
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
agents/ — 20 specialized AI agents
|
|
9
|
-
commands/ —
|
|
9
|
+
commands/ — 35 slash commands
|
|
10
10
|
scripts/ — Validation and post-install audit scripts
|
|
11
11
|
skills/ — 36 domain-specific knowledge modules
|
|
12
|
-
hooks/ —
|
|
12
|
+
hooks/ — 11 lifecycle hooks
|
|
13
13
|
modes/ — 7 behavioral presets
|
|
14
14
|
profiles/ — 3 install profiles (fullstack, api, data)
|
|
15
|
-
mcp/ — Pre-configured MCP server configs
|
|
15
|
+
mcp/ — Pre-configured MCP server configs (github, context7, sequential-thinking, puppeteer)
|
|
16
16
|
docs/ — Agent outputs (brainstorms, plans, decisions, research, audits, conversations, chains)
|
|
17
17
|
templates/ — Templates for contributing new components (agents, commands, skills, chain presets, personas)
|
|
18
18
|
CONTRIBUTING.md — Contribution guidelines
|
|
@@ -49,7 +49,7 @@ Custom agents in `.cortexhawk-agents/` at project root. Each `.md` file uses `ex
|
|
|
49
49
|
|
|
50
50
|
## Commands
|
|
51
51
|
|
|
52
|
-
`/plan` `/build` `/test` `/review` `/ship` `/commit` `/debug` `/scan` `/check` `/refactor` `/research` `/doc` `/bootstrap` `/tdd` `/optimize` `/migrate` `/monitor` `/api-gen` `/changelog` `/journal` `/brainstorm` `/simplify` `/deploy` `/export` `/backlog` `/pulse` `/map` `/learn` `/chain` `/task` `/ci` `/context` `/upgrade`
|
|
52
|
+
`/plan` `/build` `/test` `/review` `/review-pr` `/ship` `/commit` `/cleanup` `/debug` `/scan` `/check` `/refactor` `/research` `/doc` `/bootstrap` `/tdd` `/optimize` `/migrate` `/monitor` `/api-gen` `/changelog` `/journal` `/brainstorm` `/simplify` `/deploy` `/export` `/backlog` `/pulse` `/map` `/learn` `/chain` `/task` `/ci` `/context` `/upgrade`
|
|
53
53
|
|
|
54
54
|
## Skills
|
|
55
55
|
|
|
@@ -80,6 +80,7 @@ Custom agents in `.cortexhawk-agents/` at project root. Each `.md` file uses `ex
|
|
|
80
80
|
- `file-guard` (PreToolUse) — Blocks access to .env, secrets, keys
|
|
81
81
|
- `branch-guard` (PreToolUse) — Prevents direct push to protected branches
|
|
82
82
|
- `commit-guard` (PreToolUse) — Validates conventional commits, checks staged secrets
|
|
83
|
+
- `lint-guard` (PreToolUse) — Auto-detects formatters/linters on staged files; auto-fix for prettier/black/gofmt/rustfmt/stylelint, check-only for eslint/flake8/mypy
|
|
83
84
|
- `self-review` (PostToolUse) — Checks for TODO/FIXME, secrets, debug artifacts
|
|
84
85
|
- `dependency-check` (PostToolUse) — Alerts when dependency files are modified
|
|
85
86
|
- `test-reminder` (PostToolUse) — Reminds to update tests for modified source files
|
|
@@ -94,3 +95,10 @@ Custom agents in `.cortexhawk-agents/` at project root. Each `.md` file uses `ex
|
|
|
94
95
|
- Checklists > paragraphs, code examples > prose
|
|
95
96
|
- One responsibility per component
|
|
96
97
|
- All agents follow: frontmatter → description → Process → Output Format → Rules
|
|
98
|
+
|
|
99
|
+
## Git Workflow
|
|
100
|
+
|
|
101
|
+
- **Branching**: dev-branch (working branch: dev)
|
|
102
|
+
- **Commits**: conventional
|
|
103
|
+
- **PR preference**: on-demand
|
|
104
|
+
- **Auto-push**: after-commit
|
package/agents/git-manager.md
CHANGED
|
@@ -11,9 +11,10 @@ You are a release engineer managing version control workflows.
|
|
|
11
11
|
|
|
12
12
|
0. **Context** — Read `docs/.context/_shared.md` and `docs/.context/git-manager.md`
|
|
13
13
|
1. **Assess** — Review current branch state, staged changes, and recent history
|
|
14
|
+
1.5. **Detect PR** — Run `gh pr view --json state,url 2>/dev/null`, parse output; if PR exists with state=OPEN, note branch has active PR (skip creation later); if gh fails or no PR, proceed normally
|
|
14
15
|
2. **Stage** — Select files for commit, verify no secrets or debug artifacts
|
|
15
16
|
3. **Commit** — Generate conventional commit message matching change scope
|
|
16
|
-
4. **Push** — Push to remote, create PR with description and checklist
|
|
17
|
+
4. **Push** — Push to remote, create PR with description and checklist (skip if active PR detected in step 1.5)
|
|
17
18
|
5. **Manage** — Handle branching, tagging, merging, and release prep
|
|
18
19
|
|
|
19
20
|
## Commit Convention
|
|
@@ -63,5 +64,8 @@ Description: imperative mood, lowercase, no period, max 72 chars
|
|
|
63
64
|
- Always verify no secrets in staged files before commit
|
|
64
65
|
- Read `## Git Workflow` in CLAUDE.md for project preferences (branching, commits, PRs, auto-push)
|
|
65
66
|
- Respect configured branching strategy, PR preference, and auto-push behavior
|
|
66
|
-
- If no Git Workflow section
|
|
67
|
+
- If no Git Workflow section and no `.claude/config/git-workflow.conf`, default to: feature branches, conventional commits, on-demand PR, auto-push
|
|
68
|
+
- Before creating a feature branch, check if current branch has open PR — if yes, reuse branch and push to update PR; if no or state!=OPEN, create new branch
|
|
69
|
+
- PR detection edge cases: gh CLI not installed (skip detection, proceed with branch creation), detached HEAD (skip detection, create new branch), gh fails (silent fail with warning, continue with branch creation), no remote configured (warn and stop)
|
|
70
|
+
- Silent fail on PR detection errors — log warning to user, continue with normal branch creation flow
|
|
67
71
|
- Update `docs/.context/git-manager.md` with patterns, decisions, and key files discovered
|
package/commands/backlog.md
CHANGED
|
@@ -12,7 +12,7 @@ Activate the **project-manager** agent in backlog mode.
|
|
|
12
12
|
3. Score: impact (H/M/L), effort (H/M/L), feasibility (H/M/L)
|
|
13
13
|
4. Update `docs/backlog.md` — add new items, re-prioritize existing ones
|
|
14
14
|
5. Mark items already implemented as done
|
|
15
|
-
6. Run `bash scripts/refresh-context.sh` to update shared context
|
|
15
|
+
6. Run `bash .claude/scripts/refresh-context.sh` to update shared context
|
|
16
16
|
|
|
17
17
|
Backlog format in `docs/backlog.md`:
|
|
18
18
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cleanup
|
|
3
|
+
description: Delete merged branches and optionally enable auto-cleanup hook
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /cleanup
|
|
7
|
+
|
|
8
|
+
Delete merged local branches and optionally delete remote branches.
|
|
9
|
+
|
|
10
|
+
## Process
|
|
11
|
+
|
|
12
|
+
1. Check if `.claude/.cleanup-configured` exists — if not, prompt for hook opt-in
|
|
13
|
+
2. If marker missing, ask: "Enable auto-cleanup hook after merging PRs? [y/N]"
|
|
14
|
+
3. If user chooses yes:
|
|
15
|
+
- Uncomment post-merge composition in `.claude/hooks/compose.yml` via sed
|
|
16
|
+
- Create marker: `echo 'enabled' > .claude/.cleanup-configured`
|
|
17
|
+
- Notify: "Auto-cleanup hook enabled. Runs automatically after git merge."
|
|
18
|
+
4. If user chooses no:
|
|
19
|
+
- Create marker: `echo 'manual' > .claude/.cleanup-configured`
|
|
20
|
+
5. Run cleanup script: `.claude/scripts/post-merge-cleanup.sh` (interactive mode)
|
|
21
|
+
6. Script detects branching strategy from `.claude/git-workflow.conf` or `CLAUDE.md`
|
|
22
|
+
7. Lists merged branches (excluding main/master/dev/develop/current)
|
|
23
|
+
8. Prompts before deleting each local branch
|
|
24
|
+
9. Prompts before deleting each remote branch (default: no)
|
|
25
|
+
10. If on main branch: pulls latest changes
|
|
26
|
+
11. If `BRANCHING=feature-branches`: optionally creates new feature branch
|
|
27
|
+
|
|
28
|
+
## Rules
|
|
29
|
+
|
|
30
|
+
- First-run hook prompt only shows once (marker file persists preference)
|
|
31
|
+
- Remote deletion requires explicit confirmation (default: no)
|
|
32
|
+
- Never delete main/master/dev/develop or current branch
|
|
33
|
+
- Handle missing config files gracefully (fallback to defaults)
|
|
34
|
+
- Handle git errors without crashing (network, permissions, no remote)
|
|
35
|
+
- If compose.yml missing, warn and skip hook enablement
|
|
36
|
+
- If sed fails, report error but continue cleanup
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: review-pr
|
|
3
|
+
description: Fetch, triage, and address PR review comments in batch — one commit, one notification.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /review-pr
|
|
7
|
+
|
|
8
|
+
Activate the **reviewer** agent using the `pr-review-comments` skill. Target PR: current branch.
|
|
9
|
+
|
|
10
|
+
1. **Auth** — Check MCP GitHub (`mcp__github__list_pull_requests`); fall back to `gh pr view` if unavailable
|
|
11
|
+
2. **Fetch** — Get all open inline threads, review submissions, and conversation comments
|
|
12
|
+
3. **Triage** — Group by author: Copilot / human reviewers / bots; skip resolved and outdated
|
|
13
|
+
4. **Present** — Show numbered threads with `file:line`, author, summary, and proposed fix
|
|
14
|
+
5. **Confirm** — Ask which to address (`1, 3, 5` or `all`) before touching any file
|
|
15
|
+
6. **Fix** (batch, default) — Apply all selected fixes in one pass
|
|
16
|
+
7. **Commit** — `fix: address PR review comments` (single commit)
|
|
17
|
+
8. **Push** — Push to remote
|
|
18
|
+
9. **Reply** — `mcp__github__create_pull_request_review` (batch) or `gh pr comment` — one reply per thread, referencing the commit sha
|
|
19
|
+
|
|
20
|
+
## Flags
|
|
21
|
+
|
|
22
|
+
- `--sequential` — fix → commit → reply per thread; use when comments are complex or interdependent
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Always present threads and wait for user selection before fixing
|
|
27
|
+
- Batch mode: one commit + one review submission = one notification to reviewers
|
|
28
|
+
- Sequential mode: one commit per thread, reply immediately after each fix
|
|
29
|
+
- Never fix resolved or outdated threads unless explicitly requested
|
|
30
|
+
- If no open threads, report and stop
|
|
31
|
+
- If auth fails, prompt `gh auth login` or check `GITHUB_PERSONAL_ACCESS_TOKEN`
|
package/commands/ship.md
CHANGED
|
@@ -8,6 +8,7 @@ description: Commit, create PR, and prepare for deployment.
|
|
|
8
8
|
Activate the **git-manager** agent, then the **reviewer** agent. Ship: `$ARGUMENTS`
|
|
9
9
|
|
|
10
10
|
0. Read `## Git Workflow` from CLAUDE.md if present — respect PR preference and auto-push settings
|
|
11
|
+
0.5. Check if current branch has open PR — run `gh pr view --json state,url 2>/dev/null`; if PR exists and state=OPEN, skip branch creation (update existing PR); if gh unavailable or no PR found, proceed with normal flow
|
|
11
12
|
1. Stage changes and generate conventional commit message
|
|
12
13
|
2. Run quick review pass — reviewer runs Pass 1 (Correctness) and Pass 2 (Security) only, reporting Critical findings exclusively
|
|
13
14
|
3. If review passes, commit and push
|
package/commands/task.md
CHANGED
|
@@ -16,7 +16,7 @@ Activate the **project-manager** agent as orchestrator. Execute backlog item `$A
|
|
|
16
16
|
6. Update `CHANGELOG.md` with a one-line entry under the current version's `### Added` section
|
|
17
17
|
7. If chain completes without critical blockers, execute `/ship`
|
|
18
18
|
8. Mark item as `done` in backlog
|
|
19
|
-
9. Run `bash scripts/refresh-context.sh` to update shared context
|
|
19
|
+
9. Run `bash .claude/scripts/refresh-context.sh` to update shared context
|
|
20
20
|
|
|
21
21
|
## Save Rules
|
|
22
22
|
|
package/cortexhawk
CHANGED
|
@@ -120,8 +120,8 @@ do_validate() {
|
|
|
120
120
|
|
|
121
121
|
# settings.json
|
|
122
122
|
if [ -f "$target_dir/settings.json" ]; then
|
|
123
|
-
if python3 -c "import json; json.load(open(
|
|
124
|
-
node -e "JSON.parse(require('fs').readFileSync(
|
|
123
|
+
if python3 -c "import json,sys; json.load(open(sys.argv[1]))" "$target_dir/settings.json" 2>/dev/null || \
|
|
124
|
+
node -e "JSON.parse(require('fs').readFileSync(process.argv[1]))" "$target_dir/settings.json" 2>/dev/null; then
|
|
125
125
|
check "settings.json valid JSON" "ok"
|
|
126
126
|
else
|
|
127
127
|
check "settings.json invalid JSON" "fail"
|
package/hooks/branch-guard.sh
CHANGED
|
@@ -21,17 +21,25 @@ fi
|
|
|
21
21
|
|
|
22
22
|
PROTECTED_BRANCHES=("main" "master" "production" "release")
|
|
23
23
|
|
|
24
|
-
# Load git workflow config —
|
|
24
|
+
# Load git workflow config — adjust protected branches based on branching strategy
|
|
25
25
|
CONF_FILE="$(git rev-parse --show-toplevel 2>/dev/null)/.claude/git-workflow.conf"
|
|
26
26
|
if [[ -f "$CONF_FILE" ]]; then
|
|
27
27
|
_BRANCHING=$(grep '^BRANCHING=' "$CONF_FILE" | cut -d= -f2)
|
|
28
28
|
if [[ "$_BRANCHING" == "direct-main" ]]; then
|
|
29
29
|
PROTECTED_BRANCHES=("master" "production" "release")
|
|
30
|
+
elif [[ "$_BRANCHING" == "dev-branch" ]]; then
|
|
31
|
+
_WORK_BRANCH=$(grep '^WORK_BRANCH=' "$CONF_FILE" | cut -d= -f2)
|
|
32
|
+
[[ -n "$_WORK_BRANCH" ]] && PROTECTED_BRANCHES+=("$_WORK_BRANCH")
|
|
30
33
|
fi
|
|
31
34
|
fi
|
|
32
35
|
|
|
33
36
|
# Check for git push to protected branches
|
|
34
37
|
if echo "$CMD" | grep -qE 'git\s+push'; then
|
|
38
|
+
# Allow --delete operations (deleting remote branches, not pushing code)
|
|
39
|
+
if echo "$CMD" | grep -qE 'git\s+push\s+.*--delete|git\s+push\s+.*-d\s'; then
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
|
|
35
43
|
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null)
|
|
36
44
|
|
|
37
45
|
for branch in "${PROTECTED_BRANCHES[@]}"; do
|
package/hooks/compose.yml
CHANGED
package/hooks/file-guard.sh
CHANGED
|
@@ -9,12 +9,16 @@ BLOCKED_PATTERNS=(
|
|
|
9
9
|
"*.key"
|
|
10
10
|
"id_rsa"
|
|
11
11
|
"id_ed25519"
|
|
12
|
+
"id_ecdsa"
|
|
12
13
|
"*.p12"
|
|
13
14
|
"*.pfx"
|
|
14
15
|
"*.keystore"
|
|
16
|
+
"*.jks"
|
|
15
17
|
"credentials.json"
|
|
16
18
|
"credentials.yml"
|
|
17
19
|
"credentials.yaml"
|
|
20
|
+
"*secret*"
|
|
21
|
+
"service-account*.json"
|
|
18
22
|
)
|
|
19
23
|
|
|
20
24
|
# Basename patterns that are .env.* but NOT .env.example/.env.sample/.env.template
|
package/hooks/hooks.json
CHANGED
|
@@ -36,6 +36,12 @@
|
|
|
36
36
|
"script": "hooks/commit-guard.sh",
|
|
37
37
|
"description": "Validates commit format and checks for staged secrets"
|
|
38
38
|
},
|
|
39
|
+
{
|
|
40
|
+
"name": "lint-guard",
|
|
41
|
+
"type": "PreToolUse",
|
|
42
|
+
"script": "hooks/lint-guard.sh",
|
|
43
|
+
"description": "Auto-detect and run formatters/linters on staged files before commit"
|
|
44
|
+
},
|
|
39
45
|
{
|
|
40
46
|
"name": "test-reminder",
|
|
41
47
|
"type": "PostToolUse",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# lint-guard — Auto-detect formatters/linters and run before git commit/push
|
|
3
|
+
# Hook type: PreToolUse (Bash)
|
|
4
|
+
# Delegates heavy work to scripts/lint-guard-runner.sh
|
|
5
|
+
|
|
6
|
+
# --- 1. PARSE COMMAND ---
|
|
7
|
+
if [ -n "$CORTEXHAWK_COMMAND" ]; then
|
|
8
|
+
CMD="$CORTEXHAWK_COMMAND"
|
|
9
|
+
else
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
if command -v jq &>/dev/null; then
|
|
12
|
+
CMD=$(printf '%s' "$INPUT" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
|
13
|
+
fi
|
|
14
|
+
[ -z "$CMD" ] && CMD=$(printf '%s' "$INPUT" \
|
|
15
|
+
| grep -o '"command" *: *"[^"]*"' | head -1 | sed 's/.*: *"//;s/"$//')
|
|
16
|
+
fi
|
|
17
|
+
[ -z "$CMD" ] && exit 0
|
|
18
|
+
echo "$CMD" | grep -qE 'git\s+(commit|push)' || exit 0
|
|
19
|
+
|
|
20
|
+
# --- 2. PUSH CHECK — yml takes priority over git-workflow.conf ---
|
|
21
|
+
if echo "$CMD" | grep -qE 'git\s+push'; then
|
|
22
|
+
_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
23
|
+
_YML_PUSH=$(grep -E "^\s+run_on_push:" "$_ROOT/.cortexhawk-lint.yml" 2>/dev/null \
|
|
24
|
+
| sed 's/.*: *//' | tr -d ' \r')
|
|
25
|
+
if [ "$_YML_PUSH" = "true" ]; then
|
|
26
|
+
:
|
|
27
|
+
elif [ "$_YML_PUSH" = "false" ]; then
|
|
28
|
+
exit 0
|
|
29
|
+
else
|
|
30
|
+
LINT_ON_PUSH=$(grep '^LINT_ON_PUSH=' "$_ROOT/.claude/git-workflow.conf" 2>/dev/null | cut -d= -f2)
|
|
31
|
+
[ "$LINT_ON_PUSH" != "true" ] && exit 0
|
|
32
|
+
fi
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# --- 3. DELEGATE ---
|
|
36
|
+
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
37
|
+
[ -z "$REPO_ROOT" ] && exit 0
|
|
38
|
+
|
|
39
|
+
if command -v pre-commit &>/dev/null && [ -f "$REPO_ROOT/.pre-commit-config.yaml" ]; then
|
|
40
|
+
echo "lint-guard: pre-commit detected — delegating to pre-commit framework"
|
|
41
|
+
pre-commit run
|
|
42
|
+
exit $?
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
bash "$REPO_ROOT/.claude/scripts/lint-guard-runner.sh"
|
|
46
|
+
exit $?
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# post-merge — Auto-cleanup merged branches after PR merge
|
|
3
|
+
# Hook type: GitHook
|
|
4
|
+
# Disabled by default — enable via /cleanup first run
|
|
5
|
+
|
|
6
|
+
# Call cleanup script in auto mode (silent, no prompts, skip remote deletion)
|
|
7
|
+
if [ -f ".claude/scripts/post-merge-cleanup.sh" ]; then
|
|
8
|
+
bash ".claude/scripts/post-merge-cleanup.sh" --auto 2>/dev/null || true
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
# Exit silently — don't block merge operation on script failures
|
|
12
|
+
exit 0
|
package/hooks/session-start.sh
CHANGED
|
@@ -132,4 +132,4 @@ if [ -d "$SKILL_DIR" ]; then
|
|
|
132
132
|
fi
|
|
133
133
|
|
|
134
134
|
echo ""
|
|
135
|
-
echo "Commands: /plan /build /test /review /ship /debug /scan /check /refactor /research /doc /bootstrap /tdd /optimize /migrate /monitor /api-gen /changelog /journal /brainstorm /simplify /deploy /export /backlog /pulse /map /learn /chain /task /ci /context"
|
|
135
|
+
echo "Commands: /plan /build /test /review /ship /commit /cleanup /debug /scan /check /refactor /research /doc /bootstrap /tdd /optimize /migrate /monitor /api-gen /changelog /journal /brainstorm /simplify /deploy /export /backlog /pulse /map /learn /chain /task /ci /context /upgrade"
|
package/install.sh
CHANGED
|
@@ -7,6 +7,16 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
7
7
|
if [ -f ".env" ]; then
|
|
8
8
|
while IFS='=' read -r key value; do
|
|
9
9
|
[[ -z "$key" || "$key" == \#* ]] && continue
|
|
10
|
+
# Validate key format: uppercase letters, digits, underscore only (POSIX env var names)
|
|
11
|
+
[[ "$key" =~ ^[A-Z_][A-Z0-9_]{0,63}$ ]] || continue
|
|
12
|
+
# Block dangerous variable names that could hijack script/shell behavior
|
|
13
|
+
case "$key" in
|
|
14
|
+
PATH|LD_PRELOAD|LD_LIBRARY_PATH|LD_AUDIT|LD_DEBUG|BASH_ENV|BASH_FUNC_*|\
|
|
15
|
+
SCRIPT_DIR|HOME|SHELL|IFS|CDPATH|ENV|PYTHONPATH|PYTHONSTARTUP|\
|
|
16
|
+
GIT_SSH|GIT_SSH_COMMAND|GIT_PROXY_COMMAND|GIT_EXEC_PATH|\
|
|
17
|
+
NPM_CONFIG_*|NODE_OPTIONS|RUBYLIB|RUBYOPT|PERL5LIB|PERL5OPT)
|
|
18
|
+
continue ;;
|
|
19
|
+
esac
|
|
10
20
|
# Strip double quotes
|
|
11
21
|
value="${value%\"}" && value="${value#\"}"
|
|
12
22
|
# Strip single quotes
|
|
@@ -161,8 +171,26 @@ STATS_MODE=false
|
|
|
161
171
|
PUBLISH_SKILL_PATH=""
|
|
162
172
|
CHECK_UPDATE_MODE=false
|
|
163
173
|
DEMO_MODE=false
|
|
174
|
+
TRUST_SKILL=false
|
|
164
175
|
MAX_SNAPSHOTS=10
|
|
165
176
|
|
|
177
|
+
# === Component Registry ===
|
|
178
|
+
# Single source of truth for all CortexHawk components.
|
|
179
|
+
# Format: "name:executable"
|
|
180
|
+
# - executable = "yes" runs chmod +x *.sh after copy (hooks, scripts)
|
|
181
|
+
# - executable = "no" for markdown-only components (agents, commands, modes)
|
|
182
|
+
# - skills handled specially via copy_skills()/sync_skills_update() for profile filtering
|
|
183
|
+
# Used by: copy_all_components(), sync_all_components(), count_component_files()
|
|
184
|
+
COMPONENTS=(
|
|
185
|
+
"agents:no"
|
|
186
|
+
"commands:no"
|
|
187
|
+
"skills:no"
|
|
188
|
+
"hooks:yes"
|
|
189
|
+
"modes:no"
|
|
190
|
+
"mcp:no"
|
|
191
|
+
"scripts:yes"
|
|
192
|
+
)
|
|
193
|
+
|
|
166
194
|
while [ $# -gt 0 ]; do
|
|
167
195
|
case "$1" in
|
|
168
196
|
--target)
|
|
@@ -300,6 +328,10 @@ while [ $# -gt 0 ]; do
|
|
|
300
328
|
DEMO_MODE=true
|
|
301
329
|
shift
|
|
302
330
|
;;
|
|
331
|
+
--trust)
|
|
332
|
+
TRUST_SKILL=true
|
|
333
|
+
shift
|
|
334
|
+
;;
|
|
303
335
|
--publish-skill)
|
|
304
336
|
PUBLISH_SKILL_PATH="$2"
|
|
305
337
|
if [ -z "$PUBLISH_SKILL_PATH" ]; then
|
|
@@ -455,7 +487,7 @@ if [ -n "$PACK_NAME" ]; then
|
|
|
455
487
|
exit 1
|
|
456
488
|
fi
|
|
457
489
|
_skills_csv=$(echo "$_row" | awk -F'|' '{print $3}' | sed 's/^ *//;s/ *$//')
|
|
458
|
-
_description=$(echo "$_row" | awk -F'|' '{print $4}' | sed 's/^ *//;s/ *$//')
|
|
490
|
+
_description=$(echo "$_row" | awk -F'|' '{print $4}' | sed 's/^ *//;s/ *$//' | sed 's/"/\\"/g')
|
|
459
491
|
_tmp_profile=$(mktemp /tmp/cortexhawk-pack-XXXXXX.json)
|
|
460
492
|
{
|
|
461
493
|
echo '{'
|
|
@@ -505,11 +537,12 @@ detect_installed_clis() {
|
|
|
505
537
|
copy_skills() {
|
|
506
538
|
local target_dir="$1"
|
|
507
539
|
local profile="$2"
|
|
540
|
+
local source_dir="${3:-$SCRIPT_DIR}"
|
|
508
541
|
if [ -z "$profile" ]; then
|
|
509
|
-
cp -r "$
|
|
542
|
+
cp -r "$source_dir/skills/"* "$target_dir/skills/" 2>/dev/null || true
|
|
510
543
|
else
|
|
511
544
|
grep '"[a-z]' "$PROFILE_FILE" | sed 's/.*"\([a-z][^"]*\)".*/\1/' | grep '/' | while read -r skill; do
|
|
512
|
-
local src="$
|
|
545
|
+
local src="$source_dir/skills/$skill"
|
|
513
546
|
local dest="$target_dir/skills/$skill"
|
|
514
547
|
if [ ! -d "$src" ]; then
|
|
515
548
|
yellow " Warning: skill '$skill' not found in source — skipping"
|
|
@@ -574,6 +607,9 @@ convert_modes_to_skills() {
|
|
|
574
607
|
done
|
|
575
608
|
}
|
|
576
609
|
|
|
610
|
+
# NOTE: profiles/*.json contain an "mcp" field listing recommended servers, but install.sh
|
|
611
|
+
# currently installs all mcp/*.json regardless of profile. Per-profile MCP filtering is planned
|
|
612
|
+
# for a future phase of #137 (install.sh modularization).
|
|
577
613
|
# merge_mcp_json(output_file) — merges mcp/*.json into single JSON file
|
|
578
614
|
merge_mcp_json() {
|
|
579
615
|
local output_file="$1"
|
|
@@ -582,7 +618,7 @@ merge_mcp_json() {
|
|
|
582
618
|
[ -f "$mcp_file" ] || continue
|
|
583
619
|
local server_name command_val args_val entry
|
|
584
620
|
server_name=$(basename "$mcp_file" .json)
|
|
585
|
-
command_val=$(grep '"command"' "$mcp_file" | sed 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/')
|
|
621
|
+
command_val=$(grep '"command"' "$mcp_file" | sed 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/' | sed 's/"/\\"/g')
|
|
586
622
|
args_val=$(grep '"args"' "$mcp_file" | sed 's/.*"args"[[:space:]]*:[[:space:]]*\(\[.*\]\).*/\1/')
|
|
587
623
|
entry=" \"$server_name\": {\n \"command\": \"$command_val\",\n \"args\": $args_val\n }"
|
|
588
624
|
if [ -n "$entries" ]; then
|
|
@@ -658,14 +694,18 @@ $target_dir/" "$gitignore"
|
|
|
658
694
|
if [ -d "$project_root/docs" ] && ! grep -qx "docs/" "$gitignore" 2>/dev/null; then
|
|
659
695
|
echo ""
|
|
660
696
|
echo " docs/ contains agent outputs (brainstorms, plans, research)."
|
|
661
|
-
|
|
697
|
+
if [ -t 0 ]; then
|
|
698
|
+
read -r -p " Track docs/ in git? [Y/n]: " docs_choice
|
|
699
|
+
else
|
|
700
|
+
docs_choice="Y"
|
|
701
|
+
fi
|
|
662
702
|
case "$docs_choice" in
|
|
663
703
|
[nN]*)
|
|
664
704
|
echo "docs/" >> "$gitignore"
|
|
665
705
|
green " Added docs/ to .gitignore"
|
|
666
706
|
;;
|
|
667
707
|
*)
|
|
668
|
-
green " docs/ will be tracked in git"
|
|
708
|
+
green " docs/ will be tracked in git (default)"
|
|
669
709
|
;;
|
|
670
710
|
esac
|
|
671
711
|
fi
|
|
@@ -888,7 +928,8 @@ update_source_git() {
|
|
|
888
928
|
}
|
|
889
929
|
|
|
890
930
|
update_source_release() {
|
|
891
|
-
local tmp_dir
|
|
931
|
+
local tmp_dir
|
|
932
|
+
tmp_dir=$(mktemp -d /tmp/cortexhawk-update-XXXXXX)
|
|
892
933
|
local repo_url
|
|
893
934
|
repo_url=$(get_source_url)
|
|
894
935
|
if [ -z "$repo_url" ] && [ -f "$TARGET/.cortexhawk-manifest" ]; then
|
|
@@ -899,13 +940,19 @@ update_source_release() {
|
|
|
899
940
|
echo "Set CORTEXHAWK_REPO environment variable and retry"
|
|
900
941
|
exit 1
|
|
901
942
|
fi
|
|
902
|
-
mkdir -p "$tmp_dir"
|
|
903
943
|
echo " Downloading from $repo_url..."
|
|
904
|
-
|
|
944
|
+
local tarball="$tmp_dir/update.tar.gz"
|
|
945
|
+
if ! curl -sL "$repo_url/archive/refs/heads/main.tar.gz" -o "$tarball"; then
|
|
905
946
|
echo "Error: failed to download latest release"
|
|
906
947
|
rm -rf "$tmp_dir"
|
|
907
948
|
exit 1
|
|
908
949
|
fi
|
|
950
|
+
if ! tar xzf "$tarball" -C "$tmp_dir" --strip-components=1; then
|
|
951
|
+
echo "Error: failed to extract update archive"
|
|
952
|
+
rm -rf "$tmp_dir"
|
|
953
|
+
exit 1
|
|
954
|
+
fi
|
|
955
|
+
rm -f "$tarball"
|
|
909
956
|
SCRIPT_DIR="$tmp_dir"
|
|
910
957
|
UPDATE_CLEANUP_DIR="$tmp_dir"
|
|
911
958
|
}
|
|
@@ -1097,6 +1144,48 @@ sync_skills_update() {
|
|
|
1097
1144
|
fi
|
|
1098
1145
|
}
|
|
1099
1146
|
|
|
1147
|
+
# === Generic Component Operations ===
|
|
1148
|
+
# These use the COMPONENTS array to avoid hardcoding directory lists.
|
|
1149
|
+
|
|
1150
|
+
# copy_all_components(source_dir, target_dir, profile)
|
|
1151
|
+
copy_all_components() {
|
|
1152
|
+
local src="$1" dst="$2" profile="$3"
|
|
1153
|
+
for entry in "${COMPONENTS[@]}"; do
|
|
1154
|
+
IFS=':' read -r name exec_flag <<< "$entry"
|
|
1155
|
+
[ -d "$src/$name" ] || continue
|
|
1156
|
+
mkdir -p "$dst/$name"
|
|
1157
|
+
if [ "$name" = "skills" ] && [ -n "$profile" ]; then
|
|
1158
|
+
copy_skills "$dst" "$profile" "$src"
|
|
1159
|
+
else
|
|
1160
|
+
cp -r "$src/$name/"* "$dst/$name/" 2>/dev/null || true
|
|
1161
|
+
fi
|
|
1162
|
+
[ "$exec_flag" = "yes" ] && chmod +x "$dst/$name/"*.sh 2>/dev/null || true
|
|
1163
|
+
done
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
# sync_all_components(profile)
|
|
1167
|
+
sync_all_components() {
|
|
1168
|
+
local profile="$1"
|
|
1169
|
+
for entry in "${COMPONENTS[@]}"; do
|
|
1170
|
+
IFS=':' read -r name exec_flag <<< "$entry"
|
|
1171
|
+
if [ "$name" = "skills" ]; then
|
|
1172
|
+
sync_skills_update "$profile"
|
|
1173
|
+
else
|
|
1174
|
+
sync_component "$name"
|
|
1175
|
+
fi
|
|
1176
|
+
done
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
# count_component_files(source_dir)
|
|
1180
|
+
count_component_files() {
|
|
1181
|
+
local src="$1"
|
|
1182
|
+
for entry in "${COMPONENTS[@]}"; do
|
|
1183
|
+
IFS=':' read -r name _ <<< "$entry"
|
|
1184
|
+
local c; c=$(find "$src/$name" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
1185
|
+
printf " %-12s %s files\n" "$name/" "$c"
|
|
1186
|
+
done
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1100
1189
|
do_update() {
|
|
1101
1190
|
# 1. Validate target
|
|
1102
1191
|
if [ "$TARGET_CLI" != "claude" ]; then
|
|
@@ -1245,12 +1334,7 @@ do_update() {
|
|
|
1245
1334
|
SYNC_SKIPPED=0
|
|
1246
1335
|
SYNC_CONFLICTS=0
|
|
1247
1336
|
|
|
1248
|
-
|
|
1249
|
-
sync_component "commands"
|
|
1250
|
-
sync_skills_update "$update_profile"
|
|
1251
|
-
sync_component "hooks"
|
|
1252
|
-
sync_component "modes"
|
|
1253
|
-
sync_component "mcp"
|
|
1337
|
+
sync_all_components "$update_profile"
|
|
1254
1338
|
|
|
1255
1339
|
# 6b. Sync agent personas from project root
|
|
1256
1340
|
local project_root
|
|
@@ -1275,8 +1359,11 @@ do_update() {
|
|
|
1275
1359
|
fi
|
|
1276
1360
|
|
|
1277
1361
|
if [ "$DRY_RUN" != true ]; then
|
|
1278
|
-
# Make
|
|
1279
|
-
|
|
1362
|
+
# Make executable components executable
|
|
1363
|
+
for entry in "${COMPONENTS[@]}"; do
|
|
1364
|
+
IFS=':' read -r name exec_flag <<< "$entry"
|
|
1365
|
+
[ "$exec_flag" = "yes" ] && chmod +x "$TARGET/$name/"*.sh 2>/dev/null || true
|
|
1366
|
+
done
|
|
1280
1367
|
|
|
1281
1368
|
# 7b. Regenerate settings.json hooks + merge new permissions from compose.yml
|
|
1282
1369
|
if [ -f "$SCRIPT_DIR/hooks/compose.yml" ]; then
|
|
@@ -1961,26 +2048,14 @@ do_restore() {
|
|
|
1961
2048
|
|
|
1962
2049
|
# Reinstall using the standard flow
|
|
1963
2050
|
echo "Reinstalling CortexHawk components..."
|
|
1964
|
-
mkdir -p "$TARGET"/{agents,commands,skills,hooks,modes,mcp}
|
|
1965
2051
|
|
|
1966
2052
|
# Use portable archive files if available, otherwise use source repo
|
|
1967
2053
|
if [ -n "$portable_files" ] && [ -d "$portable_files" ]; then
|
|
1968
2054
|
echo " Using files from portable archive..."
|
|
1969
|
-
|
|
1970
|
-
cp -r "$portable_files/commands/"* "$TARGET/commands/" 2>/dev/null || true
|
|
1971
|
-
cp -r "$portable_files/skills/"* "$TARGET/skills/" 2>/dev/null || true
|
|
1972
|
-
cp -r "$portable_files/hooks/"* "$TARGET/hooks/" 2>/dev/null || true
|
|
1973
|
-
cp -r "$portable_files/modes/"* "$TARGET/modes/" 2>/dev/null || true
|
|
1974
|
-
cp -r "$portable_files/mcp/"* "$TARGET/mcp/" 2>/dev/null || true
|
|
2055
|
+
copy_all_components "$portable_files" "$TARGET" ""
|
|
1975
2056
|
else
|
|
1976
|
-
|
|
1977
|
-
cp -r "$SCRIPT_DIR/commands/"* "$TARGET/commands/" 2>/dev/null || true
|
|
1978
|
-
copy_skills "$TARGET" "$PROFILE"
|
|
1979
|
-
cp -r "$SCRIPT_DIR/hooks/"* "$TARGET/hooks/" 2>/dev/null || true
|
|
1980
|
-
cp -r "$SCRIPT_DIR/modes/"* "$TARGET/modes/" 2>/dev/null || true
|
|
1981
|
-
cp -r "$SCRIPT_DIR/mcp/"* "$TARGET/mcp/" 2>/dev/null || true
|
|
2057
|
+
copy_all_components "$SCRIPT_DIR" "$TARGET" "$PROFILE"
|
|
1982
2058
|
fi
|
|
1983
|
-
chmod +x "$TARGET/hooks/"*.sh 2>/dev/null || true
|
|
1984
2059
|
|
|
1985
2060
|
# Restore settings.json from snapshot
|
|
1986
2061
|
if command -v python3 >/dev/null 2>&1; then
|
|
@@ -2707,10 +2782,17 @@ do_add_skill() {
|
|
|
2707
2782
|
exit 1
|
|
2708
2783
|
fi
|
|
2709
2784
|
|
|
2710
|
-
# Security
|
|
2785
|
+
# Security gate: block skills with executable scripts unless --trust is used
|
|
2711
2786
|
if find "$tmp_dir/repo" -name "*.sh" -o -name "*.py" 2>/dev/null | grep -q .; then
|
|
2712
|
-
|
|
2713
|
-
|
|
2787
|
+
if [ "${TRUST_SKILL:-false}" = true ]; then
|
|
2788
|
+
yellow " Warning: this skill contains executable scripts — installing with --trust"
|
|
2789
|
+
else
|
|
2790
|
+
echo " ERROR: This skill contains executable scripts (.sh/.py)."
|
|
2791
|
+
echo " Re-run with --trust to install: install.sh --add-skill $ADD_SKILL_URL --trust"
|
|
2792
|
+
echo " Review the source first: $ADD_SKILL_URL"
|
|
2793
|
+
rm -rf "$tmp_dir"
|
|
2794
|
+
exit 1
|
|
2795
|
+
fi
|
|
2714
2796
|
fi
|
|
2715
2797
|
|
|
2716
2798
|
# Create community directory if needed
|
|
@@ -2831,7 +2913,8 @@ do_team_install() {
|
|
|
2831
2913
|
|
|
2832
2914
|
# Generate temporary profile JSON from skills list
|
|
2833
2915
|
if [ -n "$TEAM_SKILLS" ]; then
|
|
2834
|
-
local tmp_profile
|
|
2916
|
+
local tmp_profile
|
|
2917
|
+
tmp_profile=$(mktemp /tmp/cortexhawk-team-XXXXXX.json)
|
|
2835
2918
|
printf '{\n "name": "team",\n "skills": [\n' > "$tmp_profile"
|
|
2836
2919
|
local first=true
|
|
2837
2920
|
while IFS= read -r skill; do
|
|
@@ -2922,12 +3005,7 @@ install_claude() {
|
|
|
2922
3005
|
echo " Profile: ${PROFILE:-all}"
|
|
2923
3006
|
echo ""
|
|
2924
3007
|
echo "Would install:"
|
|
2925
|
-
|
|
2926
|
-
local c; c=$(find "$SCRIPT_DIR/$comp" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
2927
|
-
printf " %-12s %s files\n" "$comp/" "$c"
|
|
2928
|
-
done
|
|
2929
|
-
local sc; sc=$(find "$SCRIPT_DIR/skills" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
2930
|
-
printf " %-12s %s files\n" "skills/" "$sc"
|
|
3008
|
+
count_component_files "$SCRIPT_DIR"
|
|
2931
3009
|
echo " settings.json"
|
|
2932
3010
|
[ ! -f "$(pwd)/CLAUDE.md" ] && echo " CLAUDE.md"
|
|
2933
3011
|
echo ""
|
|
@@ -2937,9 +3015,8 @@ install_claude() {
|
|
|
2937
3015
|
|
|
2938
3016
|
echo "Installing for Claude Code to project: $TARGET"
|
|
2939
3017
|
|
|
2940
|
-
|
|
3018
|
+
copy_all_components "$SCRIPT_DIR" "$TARGET" "$PROFILE"
|
|
2941
3019
|
|
|
2942
|
-
cp -r "$SCRIPT_DIR/agents/"* "$TARGET/agents/" 2>/dev/null || true
|
|
2943
3020
|
# Copy agent personas from project root if present
|
|
2944
3021
|
local project_root
|
|
2945
3022
|
project_root="$(dirname "$TARGET")"
|
|
@@ -2949,11 +3026,6 @@ install_claude() {
|
|
|
2949
3026
|
persona_count=$(find "$project_root/.cortexhawk-agents" -name "*.md" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
2950
3027
|
[ "$persona_count" -gt 0 ] && echo " Loaded $persona_count agent persona(s) from .cortexhawk-agents/"
|
|
2951
3028
|
fi
|
|
2952
|
-
cp -r "$SCRIPT_DIR/commands/"* "$TARGET/commands/" 2>/dev/null || true
|
|
2953
|
-
copy_skills "$TARGET" "$PROFILE"
|
|
2954
|
-
cp -r "$SCRIPT_DIR/hooks/"* "$TARGET/hooks/" 2>/dev/null || true
|
|
2955
|
-
cp -r "$SCRIPT_DIR/modes/"* "$TARGET/modes/" 2>/dev/null || true
|
|
2956
|
-
cp -r "$SCRIPT_DIR/mcp/"* "$TARGET/mcp/" 2>/dev/null || true
|
|
2957
3029
|
|
|
2958
3030
|
local hooks_json
|
|
2959
3031
|
hooks_json=$(generate_hooks_config "$SCRIPT_DIR/hooks/compose.yml" ".claude/hooks")
|
|
@@ -3330,7 +3402,8 @@ do_test_hooks() {
|
|
|
3330
3402
|
echo ""
|
|
3331
3403
|
|
|
3332
3404
|
local ok=0 fail=0
|
|
3333
|
-
local tmpfile
|
|
3405
|
+
local tmpfile
|
|
3406
|
+
tmpfile=$(mktemp /tmp/.cortexhawk-hooktest-XXXXXX)
|
|
3334
3407
|
echo "test file content" > "$tmpfile"
|
|
3335
3408
|
|
|
3336
3409
|
for hook in "$hooks_dir"/*.sh; do
|
|
@@ -3668,9 +3741,11 @@ do_publish_skill() {
|
|
|
3668
3741
|
# Create GitHub repo
|
|
3669
3742
|
echo ""
|
|
3670
3743
|
echo " Creating repository..."
|
|
3744
|
+
local repo_exists=false
|
|
3671
3745
|
if gh repo view "$gh_user/$repo_name" &>/dev/null; then
|
|
3672
3746
|
echo " Repository already exists: $gh_user/$repo_name"
|
|
3673
3747
|
echo " Pushing latest files..."
|
|
3748
|
+
repo_exists=true
|
|
3674
3749
|
else
|
|
3675
3750
|
gh repo create "$repo_name" --public --description "CortexHawk skill: $skill_desc" --clone=false
|
|
3676
3751
|
fi
|
|
@@ -3694,7 +3769,11 @@ do_publish_skill() {
|
|
|
3694
3769
|
git commit --quiet -m "feat: publish $skill_name skill"
|
|
3695
3770
|
git branch -M main
|
|
3696
3771
|
git remote add origin "https://github.com/$gh_user/$repo_name.git"
|
|
3697
|
-
|
|
3772
|
+
if [ "$repo_exists" = true ]; then
|
|
3773
|
+
git push -u origin main --quiet 2>/dev/null
|
|
3774
|
+
else
|
|
3775
|
+
git push -u origin main --force --quiet 2>/dev/null
|
|
3776
|
+
fi
|
|
3698
3777
|
cd - >/dev/null
|
|
3699
3778
|
|
|
3700
3779
|
# Cleanup
|
|
@@ -3729,7 +3808,8 @@ do_publish_skill() {
|
|
|
3729
3808
|
|
|
3730
3809
|
# --- do_demo() ---
|
|
3731
3810
|
do_demo() {
|
|
3732
|
-
local demo_dir
|
|
3811
|
+
local demo_dir
|
|
3812
|
+
demo_dir=$(mktemp -d /tmp/cortexhawk-demo-XXXXXX)
|
|
3733
3813
|
mkdir -p "$demo_dir"
|
|
3734
3814
|
|
|
3735
3815
|
echo ""
|
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
|
{
|
|
@@ -93,7 +93,7 @@ case "$skill_choice" in
|
|
|
93
93
|
fi
|
|
94
94
|
|
|
95
95
|
# Generate custom profile JSON
|
|
96
|
-
PROFILE_FILE
|
|
96
|
+
PROFILE_FILE=$(mktemp /tmp/cortexhawk-custom-XXXXXX)
|
|
97
97
|
SKILL_LINES=""
|
|
98
98
|
SKILL_COUNT=0
|
|
99
99
|
for category in $SELECTED_CATS; do
|
|
@@ -133,7 +133,8 @@ if [ -n "$SKILLSMP_KEY" ]; then
|
|
|
133
133
|
else
|
|
134
134
|
echo "SKILLSMP_API_KEY=$SKILLSMP_KEY" >> .env
|
|
135
135
|
fi
|
|
136
|
-
|
|
136
|
+
chmod 600 .env
|
|
137
|
+
green " → saved to .env (permissions: owner-only)"
|
|
137
138
|
else
|
|
138
139
|
yellow " → skipped (use --search with local REGISTRY.md only)"
|
|
139
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
|
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": [
|