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.
@@ -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/ — 33 slash 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/ — 9 lifecycle 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
@@ -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, default to: feature branches, conventional commits, on-demand PR, auto-push
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
@@ -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('$target_dir/settings.json'))" 2>/dev/null || \
124
- node -e "JSON.parse(require('fs').readFileSync('$target_dir/settings.json'))" 2>/dev/null; then
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"
@@ -21,17 +21,25 @@ fi
21
21
 
22
22
  PROTECTED_BRANCHES=("main" "master" "production" "release")
23
23
 
24
- # Load git workflow config — allow direct-main push if configured
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
@@ -45,3 +45,9 @@ compositions:
45
45
  matcher: "*"
46
46
  hooks:
47
47
  - session-telemetry
48
+
49
+ # post-merge:
50
+ # event: GitHook
51
+ # matcher: "post-merge"
52
+ # hooks:
53
+ # - post-merge
@@ -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
@@ -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 "$SCRIPT_DIR/skills/"* "$target_dir/skills/" 2>/dev/null || true
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="$SCRIPT_DIR/skills/$skill"
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
- read -r -p " Track docs/ in git? [Y/n]: " docs_choice
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="/tmp/cortexhawk-update-$$"
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
- if ! curl -sL "$repo_url/archive/refs/heads/main.tar.gz" | tar xz -C "$tmp_dir" --strip-components=1; then
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
- sync_component "agents"
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 hooks executable
1279
- chmod +x "$TARGET/hooks/"*.sh 2>/dev/null || true
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
- cp -r "$portable_files/agents/"* "$TARGET/agents/" 2>/dev/null || true
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
- cp -r "$SCRIPT_DIR/agents/"* "$TARGET/agents/" 2>/dev/null || true
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 warning if scripts present
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
- echo " Warning: this skill contains executable scripts"
2713
- echo " Review them before use: $skill_dir/scripts/"
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="/tmp/cortexhawk-team-$$.json"
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
- for comp in agents commands hooks modes mcp; do
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
- mkdir -p "$TARGET"/{agents,commands,skills,hooks,modes,mcp}
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="/tmp/.cortexhawk-hooktest-$$"
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
- git push -u origin main --force --quiet 2>/dev/null
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="/tmp/cortexhawk-demo-$$"
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
+ ```
@@ -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.2.0",
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
  {
@@ -93,7 +93,7 @@ case "$skill_choice" in
93
93
  fi
94
94
 
95
95
  # Generate custom profile JSON
96
- PROFILE_FILE="/tmp/cortexhawk-custom-$(date +%s).json"
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
- green " → saved to .env"
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": [